Skip to main content

Migration Plan

:::info Source Sourced from services/identity-service/MIGRATION_PLAN.md in the documentation repo. :::

1. Overarching Rules

Identity-service participates in the platform-wide backward-compatibility policy (../../docs/roadmap/migration-backward-compatibility.md):

  1. No breaking changes without a migration window in which both old and new coexist.
  2. Additive changes (new fields, events, endpoints) are safe by default.
  3. Breaking changes require a new major version (v2), dual-publish window ≥ 1 milestone.
  4. JWT claims follow strict CF (Conformist) contract — claim rename requires coordinated rollout with every consumer.
  5. Offline bundles depend on device public key shape — key format changes require re-registration.

2. Schema Evolution

2.1 Database

OperationAllowed Between MinorsBetween MajorsHow
Add column (nullable)Single migration
Add column (NOT NULL)✅ with defaultTwo-step: add nullable → backfill → alter NOT NULL
Drop column✅ (after deprecation)Rename to_be_dropped_* → deploy new code → drop in next minor
Rename columnAdd new, dual-write, migrate readers, drop old
Change column typeAdd new, backfill, switch, drop old
Add index✅ (CONCURRENTLY)CREATE INDEX CONCURRENTLY
Drop indexDROP INDEX CONCURRENTLY
Add table
Drop tableStop writes → retain read-only ≥ 1 milestone → drop

All migrations must:

  • Apply and roll back on ephemeral Postgres in CI.
  • Have a companion rollback_<name>.sql or Liquibase <rollback> block.
  • Be tested against a production-size snapshot in staging before prod deploy.

2.2 RLS Policy Changes

RLS policies are part of the schema contract. Changes require:

  • Two-tenant isolation test updated.
  • Code review by Security Lead.
  • Staged rollout: staging → 10% prod → full prod.

3. API Versioning

3.1 Endpoint Versioning

  • URL-major: /api/v1/…/api/v2/… (major bump).
  • Response header X-API-Version: 1.42 reflects minor.
  • Deprecation: true, Sunset: <date>, Link: <doc>; rel="deprecation" on deprecated endpoints.
  • Dual-serve for ≥ 1 release cycle (typically ≥ 1 milestone).

3.2 Request/Response Evolution

ChangeVersioning
Add optional request fieldMinor; existing clients unaffected
Add required request fieldMajor; dual-accept window ≥ 1 milestone
Add response fieldMinor; clients tolerate unknown fields
Remove response fieldMajor; 6-month deprecation in docs + Sunset
Narrow enum / typeMajor
Widen enum / typeMinor — consumers must tolerate unknown enums

3.3 Error Code Stability

  • Error codes in /openapi/errors.json are a frozen contract.
  • New codes added; existing codes never renamed.
  • HTTP status never changed for an existing code.

4. JWT Claim Evolution

JWT claims are a cross-service contract (CF pattern). Changes require platform-wide coordination.

ClaimShapeEvolution Policy
subUserId (ULID)Never change format
tidTenantId (ULID)Never change format
amrarray of stringsAdditive (new auth methods)
scopespace-separated stringAdditive (new scopes); deprecation dual-issue
didDeviceIdAdded in v2; backward-compatible (consumers tolerate absence)
audarrayAdditive
issstringNever change without platform-wide rotation
exp, iat, nbfRFC 7519Never change
kid (header)opaqueRotation window ≥ 2 days

Adding a new claim: ship behind a feature flag; consumers opt-in; flip default after all consumers confirm.

5. Event Versioning

Events follow ../../docs/04-event-driven-architecture.md rules.

ChangeVersioning
Add optional payload fieldSame v1, minor schema bump
Add required payload fieldNew v2, dual-publish ≥ 1 milestone
Remove or rename fieldNew v2, dual-publish; CI blocks v1 removal until zero consumers
New event subjectSafe — add and document
Retire event subjectDeprecation event → stop production → zero-consumer → retire

5.1 Current Identity Events

SubjectVersionStatus
identity.user.registered.v1v1Active
identity.user.email_verified.v1v1Active
identity.user.logged_in.v1v1Active
identity.user.locked.v1v1Active
identity.session.revoked.v1v1Active
identity.device.bound_for_offline.v1v1Active
identity.api_key.issued.v1v1Active
identity.api_key.revoked.v1v1Active
identity.password.reset_requested.v1v1Active

6. Password Hash Migration

  • Argon2id parameters may need to be hardened (m, t) as hardware advances.
  • Rehash on successful login pattern:
    • On login, if stored hash uses older params, rehash with current params and update the row.
    • Users who haven't logged in for > 180 days are notified to reset password.
  • Old hashes remain verifiable until all users migrate or password reset is forced (tenant policy).

7. MFA Factor Migration

  • SMS MFA is deprecated for sensitive scopes (NIST 800-63B). Existing SMS factors kept for compatibility but cannot be added for new high-trust users.
  • Users with only SMS prompted to add WebAuthn / TOTP on next login.
  • Sunset planned: M5+1 (remove SMS entirely except for legacy regulated flows).

8. SAML / OIDC IdP Configuration Migration

  • IdP metadata URL stored per-tenant; refreshed daily.
  • Cert rotation from IdP: auto-detected via fresh metadata; old cert remains valid during overlap window.
  • Tenant-initiated IdP change: new config validated with test SSO flow before activation; old config stays active until admin flips switch.

9. Offline Bundle / Device Key Migration

  • Device public keys are registered at binding.
  • If platform migrates from Ed25519 to a new curve:
    • New device bindings use new curve.
    • Existing bindings remain valid until re-binding.
    • Bundles encrypted with key derived from existing public key — backward-compatible.

10. Tenant Migration Runbook

When breaking changes affect multiple tenants:

  1. Pre-flight: identify affected tenants via analytics (identity.user.logged_in.v1 consumers).
  2. Communication: in-app banner + email 14 days before.
  3. Opt-in window: tenants activate on schedule.
  4. Auto-migration: remaining tenants migrated at low-traffic slot.
  5. Verification: per-tenant smoke test post-migration (login with seeded user in staging replica of prod).
  6. Rollback plan: documented per change; tested in staging within last 7 days.
  7. Audit: migration log retained per tenant.

11. Identity backend (provider) migration

Moving between in-house, Keycloak, and vendor OIDC backends:

  1. Claim stability: UserId and JWT claims must remain stable; migration is implemented by re-linking ExternalIdentity rows and preserving users.id (no user ID churn for in-house users).
  2. Dual-write window: Not required for read-only IdP switch; required if both native passwords and Keycloak must work during cutover — enable dual authentication policies per tenant for a defined window.
  3. Keycloak import: Bulk export from in-house is optional; preferred path for new enterprise tenants is SSO-only with JIT from Keycloak; existing users get ExternalIdentity links on first successful OIDC login.
  4. Rollback: Revert deployment config to previous IdentityBackendKind; sessions may be invalidated (documented in runbook).

See SERVICE_OVERVIEW §11–14 for provider taxonomy.

12. Breaking-Change Changelog

Maintained in CHANGELOG.md:

  • Every breaking change lists: what broke, migration path, sunset date, consumer coordination status.
  • Tagged with semver and GitHub release.