- Institutional / JVMTM / reserve-provenance / GRU transport + standards JSON - Validation and verify scripts (Blockscout labels, x402, GRU preflight, P1 local path) - Wormhole wiring in AGENTS, MCP_SETUP, MASTER_INDEX, 04-configuration README - Meta docs, integration gaps, live verification log, architecture updates - CI validate-config workflow updates Operator/LAN items, submodule working trees, and public token-aggregation edge routes remain follow-up (see TODOS_CONSOLIDATED P1). Made-with: Cursor
6.1 KiB
Sankofa / Phoenix Phase 4 Migration Runbook
Status: Draft executable runbook for the additive Client / Subscription / Entitlement migration
Last Updated: 2026-03-30
Related: SANKOFA_PHOENIX_COMPLETE_PHASED_EXECUTION_PLAN.md, SANKOFA_PHOENIX_REMAINING_TASKS.md, SANKOFA_PHOENIX_CANONICAL_BOUNDARIES_AND_TAXONOMY.md
Purpose
This runbook describes how to apply, verify, and if needed roll back the additive Phoenix backend migration that introduces:
clientsclient_usersservice_subscriptionsentitlementstenant.client_id- billing-table
client_idandsubscription_idforeign keys
This migration is intentionally additive-first. It does not remove the existing tenant-based reporting shape. It introduces the new commercial boundary while preserving tenant-scoped operations.
Scope of the current tranche
Code already landed for this migration slice in the Sankofa API repo:
- migration
027_client_subscription_entitlements - operating-model types and service
- GraphQL queries for
clients,client,myClient,serviceSubscriptions,mySubscriptions,entitlements, andmyEntitlements - tenant bootstrap hook that creates a default client/subscription/entitlement for new tenants
- identity propagation for
clientIdandsubscriptionId
Preconditions
- Confirm the target database already includes migrations through
026_api_keys. - Confirm a current backup or snapshot exists for the target Postgres instance.
- Confirm application deploy artifacts are available for the Sankofa API and portal.
- Confirm Keycloak tokens or local JWTs can carry
tenant_id, and optionallyclient_id/subscription_id. - Confirm the environment has a rollback window and operator coverage.
Deployment order
- Take a database backup or snapshot.
- Deploy the API code that understands the new fields before applying the migration.
- Apply migration
027_client_subscription_entitlements. - Restart or reload the Sankofa API.
- Deploy the updated portal build so the workspace can display
clientIdandsubscriptionIdcontext. - Run the verification checks below.
Verification checklist
Database verification
Run checks equivalent to the following:
SELECT to_regclass('public.clients');
SELECT to_regclass('public.client_users');
SELECT to_regclass('public.service_subscriptions');
SELECT to_regclass('public.entitlements');
SELECT COUNT(*) AS tenants_without_client
FROM tenants
WHERE client_id IS NULL;
SELECT COUNT(*) AS subscriptions_without_client
FROM service_subscriptions
WHERE client_id IS NULL;
SELECT COUNT(*) AS entitlements_without_subscription
FROM entitlements
WHERE subscription_id IS NULL;
Expected result:
- all four new tables exist
tenants_without_client = 0after backfill completessubscriptions_without_client = 0entitlements_without_subscription = 0
Data-shape verification
Spot-check a migrated tenant:
SELECT
t.id AS tenant_id,
t.name AS tenant_name,
t.client_id,
c.name AS client_name,
s.id AS subscription_id,
s.offer_code,
s.commercial_model,
e.id AS entitlement_id,
e.entitlement_key
FROM tenants t
LEFT JOIN clients c ON c.id = t.client_id
LEFT JOIN service_subscriptions s ON s.tenant_id = t.id
LEFT JOIN entitlements e ON e.subscription_id = s.id
WHERE t.id = '<tenant-id>';
Expected result:
- one linked
client - at least one
tenant-workspacesubscription - at least one
tenant.workspaceentitlement
API verification
Verify GraphQL with an authenticated token that has tenant context:
query VerifyOperatingModel {
myTenant { id clientId name }
myClient { id name status primaryDomain }
mySubscriptions { id offerName commercialModel status fulfillmentMode }
myEntitlements { id entitlementKey status }
}
Expected result:
myTenant.clientIdis populatedmyClientresolves for tenant-scoped usersmySubscriptionsandmyEntitlementsreturn workspace records
Portal verification
- Sign into
portal.sankofa.nexus. - Confirm the dashboard renders:
- client boundary card
- active subscription card
- entitlement summary card
- Confirm the session still works for users with only tenant-scoped claims.
Post-deploy monitoring
Watch for:
- API errors referencing
client_idorsubscription_id - onboarding failures during tenant creation
- billing queries returning null where tenant-linked records should have been backfilled
- portal sessions that include tenant context but fail to resolve
myClient
Rollback strategy
Use rollback only if the migration causes runtime or data-integrity issues that cannot be contained quickly.
Application rollback
- Roll back the API deployment to the previous artifact.
- Roll back the portal deployment if the new workspace cards cause issues.
Database rollback
If the additive schema itself must be removed, run the down path for migration 027_client_subscription_entitlements only after the application is off the new model.
Rollback effects:
- removes
clients,client_users,service_subscriptions, andentitlements - drops
client_idfromtenants - drops
client_id/subscription_idadditions from billing tables
Safer partial rollback option
Prefer this if the issue is application logic rather than schema:
- Keep the new tables in place.
- Disable reads from the new GraphQL queries at the application layer.
- Disable bootstrap of the operating-model service during tenant creation.
- Leave the additive columns in place until a corrected deployment is ready.
This avoids destructive churn on newly backfilled data.
Open follow-up work after this migration
- move billing and invoice views to client/subscription ownership
- add onboarding persistence and workflow states
- add subscription management UI and actions
- add entitlement-aware fulfillment and deployment mapping
- add production smoke tests for hostname plus operating-model GraphQL queries