Migration Plan
:::info Source
Sourced from services/tenant-service/MIGRATION_PLAN.md in the documentation repo.
:::
1. Core Rules
- TenantId VO is shared-kernel; shape is frozen (F03, M0 end). Never change format.
- Role IDs are ULIDs; names are display-only — renaming does not break consumers.
- Policy bundles are versioned; consumers pin version.
- Data-residency migrations are multi-step sagas, not simple schema changes.
2. Database Evolution
- Add column (nullable): ✅ same minor.
- Add column (NOT NULL): ✅ two-step.
- Drop column: ❌ without a major + deprecation window.
- Rename: via dual-write, never direct rename.
- Indexes:
CONCURRENTLY.
3. API Evolution
- URL major:
/api/v1/…→/api/v2/…on breaking. - New tenant setting fields: additive; default applied to existing tenants.
- Removing a setting: 6-month deprecation +
Sunset. - Response envelope + error codes: contract (§ API_CONTRACTS).
4. RBAC / ABAC Policy Evolution
- New permission: additive; default on system roles reviewed by Security.
- Permission rename: never — assign ULIDs internally; human-readable name is label only.
- ABAC predicate DSL: versioned; consumer libraries pinned. Deprecated operators callable for ≥ 1 milestone.
- Policy bundle version: monotonic; consumers reject older versions.
4.1 Policy Bundle Upgrade Procedure
- Author new policy bundle.
- CI: policy linter + two-tenant isolation test.
- Sign with KMS.
- Publish with
version = prev + 1. - Consumers poll (60s); verify signature; pin new version.
- Roll back: revert to prior signed bundle; consumers fetch on next poll.
5. Event Evolution
| Event | Version | Evolution |
|---|---|---|
tenant.org.provisioned.v1 | v1 | Additive fields only |
tenant.role.created.v1 | v1 | Permissions array additive |
tenant.membership.activated.v1 | v1 | orgUnit fields additive |
tenant.dynamic_group.evaluated.v1 | v1 | Member-count sketch additive |
tenant.data_residency.changed.v1 | v1 | New regions additive in enum |
Dual-publish rules apply per 04-event-driven-architecture.md.
6. Data Residency Migration Procedure
Owned by tenant-service via Data Residency Migration Saga:
- Freeze writes at source region (tenant.settings.writeFrozen = true; gateway rejects writes).
- Snapshot each service in scope (identity, authoring, content, progress, etc.) — verify checksums.
- Copy to target region — per-service; some replicate via CDC, some bulk export/import.
- Rebuild indices in target (search, vector).
- Verify checksums + sample read parity.
- Unfreeze writes at target; traffic DNS/routing switch.
- Emit
tenant.data_residency.changed.v1. - Retain source data 30 days post-migration, then purge.
Compensation: unfreeze at source; discard target copies; emit tenant.data_residency.migration_failed.v1.
Requires CTO sign-off per migration.
7. OrgUnit Tree Migration
ltreerename/move is expensive on deep trees. Use batched updates within a transaction.- For major restructuring, require tenant admin consent + offline window.
8. System Role Seed Evolution
- System roles (
tenantId: null) seeded on migration. - New system roles: add in migration; existing tenants receive via
tenant.role.created.v1. - Existing tenant memberships that reference deprecated system roles auto-migrate to replacement role (one-time job).
9. Backward Compatibility Windows
| Surface | Window |
|---|---|
| URL major API | ≥ 1 milestone (typically 3 months) |
| Event major bump | ≥ 1 milestone dual-publish |
| Policy bundle | Immediate (consumers refresh 60s) |
| Role rename (internal ULID stable) | N/A |
| Data residency region add | N/A |
| Data residency region removal | ≥ 6 months notice |