zanith

Lifecycle

Five stages — generate, plan, verify, apply, audit. Each is a CLI command (and a function on the programmatic API).

Stage 1 — generate

SHELL
zanith migrate generate --name add_orders_sku_index
# → migrations/20260504_120000_add_orders_sku_index.json

Diffs 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.

FlagPurpose
--name Slug for the generated file (timestamp is auto-prefixed).
--no-snapshotSkip 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-runPrint the plan to stdout, don't write a file.

Stage 2 — plan

SHELL
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.

FlagPurpose
--jsonMachine-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

SHELL
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)

SHELL
zanith migrate up # apply every pending migration
zanith migrate up --steps 1 # apply just the next one
zanith 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)

SHELL
zanith migrate down # reverse the most recent migration
zanith 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 failsWhat happens
Schema diff is emptygenerate writes nothing and exits 0.
Plan exceeds --max-riskplan exits non-zero. CI gate fires.
Shadow verify finds driftverify exits non-zero. up refuses to start.
Preflight finds duplicates / orphans / NULLsup aborts before any DDL. The finding lists sample offending rows.
A DDL op throws mid-migrationTransaction rolls back. The audit row is marked failed with the error.
A non-transactional op throwsMigration is partially-applied. The next up resumes from the next op.