Lifecycle
Five stages — generate, plan, verify, apply, audit. Each is a CLI command (and a function on the programmatic API).
Stage 1 — generate
zanith migrate generate --name add_orders_sku_index# → migrations/20260504_120000_add_orders_sku_index.jsonDiffs the declared schema (TypeScript or .zanith) against the live database, emits a MigrationOp[] plan, writes a JSON file. Deterministic — the same diff always produces the same plan.
| Flag | Purpose |
|---|---|
--name | Slug for the generated file (timestamp is auto-prefixed). |
--no-snapshot | Skip writing a baseline snapshot before the change. Default is to write one — it powers recover if anything goes wrong. |
--from | Diff against a specific past migration, not the live DB. |
--dry-run | Print the plan to stdout, don't write a file. |
Stage 2 — plan
zanith migrate plan# Reads pending migrations + risk-scores them.# → table of ops with risk level, reasons, and the SQL each one will emit.plandoesn't touch the database. It loads the pending files, classifies each op via risk model, and prints a summary you can read on a PR.
| Flag | Purpose |
|---|---|
--json | Machine-readable output for CI. |
--max-risk <0–5> | Exit non-zero if any op exceeds this level. |
--fail-on | Exit non-zero on a specific reason code (destructive, lock, non-reversible, …). |
Stage 3 — verify
zanith migrate verify# Spins up a shadow database, applies pending plan, introspects, diffs.See Shadow-DB verify for the full mechanism. The short version: apply the plan on a disposable copy of production, introspect the result, diff against the declared schema. If the diff is non-empty, up refuses to run.
Stage 4 — up (apply)
zanith migrate up # apply every pending migrationzanith migrate up --steps 1 # apply just the next onezanith migrate up --to <id> # apply up to and including <id>Runs preflight checks against live data, then applies the plan inside a transaction per migration. Each op writes to the per-step audit table — see Audit + history.
Stage 5 — down (rollback)
zanith migrate down # reverse the most recent migrationzanith migrate down --to <id> # roll back to <id>Each op type has a known reversal. Operations that aren't reversible (dropColumn, dropTable) live via recovery instead — they soft-drop or archive, never destroy.
The failure paths
| What fails | What happens |
|---|---|
| Schema diff is empty | generate writes nothing and exits 0. |
Plan exceeds --max-risk | plan exits non-zero. CI gate fires. |
| Shadow verify finds drift | verify exits non-zero. up refuses to start. |
| Preflight finds duplicates / orphans / NULLs | up aborts before any DDL. The finding lists sample offending rows. |
| A DDL op throws mid-migration | Transaction rolls back. The audit row is marked failed with the error. |
| A non-transactional op throws | Migration is partially-applied. The next up resumes from the next op. |