Skip to main content

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

  1. Author new policy bundle.
  2. CI: policy linter + two-tenant isolation test.
  3. Sign with KMS.
  4. Publish with version = prev + 1.
  5. Consumers poll (60s); verify signature; pin new version.
  6. Roll back: revert to prior signed bundle; consumers fetch on next poll.

5. Event Evolution

EventVersionEvolution
tenant.org.provisioned.v1v1Additive fields only
tenant.role.created.v1v1Permissions array additive
tenant.membership.activated.v1v1orgUnit fields additive
tenant.dynamic_group.evaluated.v1v1Member-count sketch additive
tenant.data_residency.changed.v1v1New 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:

  1. Freeze writes at source region (tenant.settings.writeFrozen = true; gateway rejects writes).
  2. Snapshot each service in scope (identity, authoring, content, progress, etc.) — verify checksums.
  3. Copy to target region — per-service; some replicate via CDC, some bulk export/import.
  4. Rebuild indices in target (search, vector).
  5. Verify checksums + sample read parity.
  6. Unfreeze writes at target; traffic DNS/routing switch.
  7. Emit tenant.data_residency.changed.v1.
  8. 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

  • ltree rename/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

SurfaceWindow
URL major API≥ 1 milestone (typically 3 months)
Event major bump≥ 1 milestone dual-publish
Policy bundleImmediate (consumers refresh 60s)
Role rename (internal ULID stable)N/A
Data residency region addN/A
Data residency region removal≥ 6 months notice