migrate / lifecycle
01 — The spine
Five stages, three failure paths, one audit terminator.
Every migration walks the same spine: generate from drift, plan with risk scores, verify on shadow, apply with per-step audit. When something fails — budget exceeded, shadow drift, mid-flight error — the engine branches to a documented recovery path instead of leaving you in limbo.
The diff becomes the migration.
renameColumn, not a destructive dropColumn, before you apply.- addColumn, addIndex, softDropColumn, and backfill — ordered safely with reversal logic written automatically.
- Four operations from one diff. Reversal logic written automatically. Ready to plan.
A verdict before anything writes.
Step 03 (dropColumn) scores 80 — over a budget of 35. The planner refuses to run anything that crosses the line.
Tighten the budget in CI. Loosen it for emergency hotfixes. The alternative is finding out at apply time, with prod half-changed.
Next: shadow verify →A throwaway database takes the hit first.
- connect + resetConnect to the configured shadowAdapter and run the optional resetShadow hook (usually DROP SCHEMA public CASCADE).
- apply on shadowRun every pending migration against the shadow DB. Destructive ops allowed here; this copy is disposable.
- introspectRead the shadow's live structure back into a SchemaGraph — the actual result of your migrations.
- diff vs declaredDiff the introspected graph against your declared schema. Empty diff = ok; anything else is drift.
Local docker-compose, CI service container, or same-cluster ephemeral schema — the shadow can be cheap.
Next: audited apply →Every step writes its own row.
Happy path: all four steps land, the migration row gets written, ledger tells the whole story. Failure path: step 3 errors, step 4 never runs — the failure is recorded in _zanith_migration_steps; the migration is not marked applied. Re-run picks up where it stopped.
History as data, not log lines.
_zanith_migrationsApplied migration IDs and applied_at. The ledger of what ran. · 2 columns
_zanith_migration_stepsPer-op audit: status, SQL, risk level + score, error. The black box. · 10 columns
_zanith_schema_snapshotsFull SchemaGraph JSON after each migration (`after` phase). Replay the shape at any point. · 4 columns
_zanith_migration_artifactsRecovery artifacts: type, source, physical name, checksum, recovery SQL, expiry. · 13 columns
_zanith_migration_checkpointsResumable backfill cursor + processed rows + status. Crash-safe progress. · 7 columns
Written inside the same transaction as the migration step.
Post-mortem queries, risk profiles →The commands for after it's shipped.
expandContractRename() — add the new column, backfill, dual-write window, then soft-drop the old one. No client ever reads a name that isn't there.
expandContractTypeChange() — new typed column, batched backfill with a cast, swap reads, retire the original. Two generators, zero downtime.
Batched backfills write a cursor to _zanith_migration_checkpoints after every batch (default batch 500). Kill the process mid-run and the next up resumes exactly where it left off — proven in the suite, not promised.
Keep going down the stack.
The lifecycle touches every other surface. Here's where each one goes deeper.
Risk model
6 levels, 21 reason codes, 7 gates, 31 op kinds. The full classifier.
/migrate/riskShadow-DB verify
The 4 internal stages of verifyOnShadow, deployment topologies, 4 verdicts.
/migrate/verifyRecovery
5 artifact kinds, the bookkeeping table, the 7-verb recover CLI.
/migrate/recoverAudit + history
5 Postgres tables track every migration, step, snapshot, and artifact.
/migrate/auditelsewhere