Files
LineWalker cc70a33086 Fix F006 Step 4 case sensitivity + add end-to-end verification script
Seeding the live 114 MySQL with realistic mock data and running the full
F006 migration surfaced a second latent bug: SCM_ROLE_MAPPING and
SCM_TYPE_MAPPING use lowercase keys (creator/admin/member, space/channel),
but production MySQL stores the columns as ENUM('SPACE','CHANNEL') and
ENUM('CREATOR','ADMIN','MEMBER') — uppercase. Step 4 was silently dropping
every real space/channel member row (19 rows on 114) because .get(role)
returned None and the code fell into the "skip unknown role" branch.

Test fixtures had masked the issue: the SQLite schema uses VARCHAR(16)
with lowercase test data, so .get(role.lower_value) happened to work in
tests but never in production.

Fix:
- migrate_rbac_to_rebac.py step4_space_channel_members: normalize role
  and biz_type to lower() before mapping lookup
- migrate_rbac_to_rebac.py verify_all: same normalization on scm_set
  (so old-system membership comparison stays case-insensitive)
- test_f006_permission_migration.py: new TestStep4.test_uppercase_enum_values
  guarding against regressions by inserting the exact values production's
  enum would store

Also adds scripts/verify_f006_migration.py — a reusable end-to-end
verification harness that seeds all 9 legacy tables with diverse mock
data (all 10 AccessType values, 6 SCM role/status combinations, 3-level
folder hierarchy, skip-boundary cases for type=99 / role_id=1 /
PENDING / REJECTED / is_delete=1 / flow_type=15 / non-numeric paths),
runs the migration, invokes --verify mode, reconciles 47 must_have and
17 must_not_have tuples against OpenFGA, and cleans up.

Validation on 114 after the fixes:
  Step 1 super_admin:  2   Step 4 SCM:      25  (mock 6 + prod 19)
  Step 2 user_group:  13   Step 5 owners:   96
  Step 3 role_access: 17   Step 6 folder:   14
  Total tuples: 167     --verify regression: 0
  must_have 47/47     must_not_have 17/17     OVERALL: PASS
2026-04-18 00:06:42 +08:00
..