Add scripts/migrate_mysql_to_dm.py + .sh wrapper + example config: a single
data-only migration tool covering all three databases. Schema is owned by each
app's own DM path (create_all/init_dm.sql/openfga migrate); the script copies
rows with IDENTITY_INSERT, trigger/FK disabling, identity reseed, JSON->CLOB
conversion, --verify and --resume-from.
Remove src/backend/scripts/mysql_to_dm_migrate.py: bisheng-only and superseded
by the unified script (which also handles IDENTITY_INSERT and update_time
trigger disabling that the old script lacked).
find_membership defaulted to ACTIVE-only for channels, so the approval
activation flow could not locate the applicant's PENDING membership and
on_approved silently returned missing_membership — outbox/instance reported
success while the membership stayed PENDING and no ReBAC grant was written,
so the channel never appeared in the user's subscription list.
- find_membership: add include_inactive flag (default keeps ACTIVE-only)
- approval activation paths pass include_inactive=True
- on_approved now raises on missing membership instead of faking success
- add regression tests + repair script for stuck instances 248/250/251/252
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