zanith

migrate / audit · 01 — Four tables

History as data, not log lines.

Migration audit lives in four real Postgres tables, each with a clear shape and a clear question. Wire monitoring against them, replay history, build post-mortems. No log scraper required.

_zanith_migrationsapplied

Answers which migrations have run, when.

1 row per applied migration
_zanith_migration_stepsper-step audit

Answers every op's status, risk, error, SQL.

N rows per migration
_zanith_schema_snapshotsbefore/after

Answers the runtime graph at each side of the apply.

2 rows per migration
_zanith_migration_artifactsrecovery

Answers which destructive ops are still reversible.

1 row per Phase-2 op
4 tables · 0 sidecar servicessame DB as your appidempotent re-creationv0.2 · shipped

02 — Four shapes

Each table has its own shape.

Narrow tables for the high-volume rows, wide tables for the ones that capture detail. All four created idempotently when the runner first connects.

_zanith_migrationsapplied set

Which migrations have run, in what order.

shape
30-day fill14 rows
cardinality
1 row per migration
storage
negligible
_zanith_migration_stepsper-step audit

Every op's status, risk, error, and SQL.

shape
30-day fill86 rows
cardinality
N rows per migration
storage
small (~200B per row)
_zanith_schema_snapshotsbefore / after

The schema graph at each side of the apply.

shape
30-day fill28 rows
cardinality
2 rows per migration
storage
moderate (10–50KB each)
_zanith_migration_artifactsrecovery

Which destructive ops are still reversible.

shape
30-day fill5 rows
cardinality
1 row per Phase-2 op
storage
small (archive data is separate)

03 — History + replay

Reconstruct any past state.

History is a join across the four tables. Every applied migration leaves a row, a per-step audit, and two snapshots (before + after). Reconstruct the schema from any point in time without restoring a backup.

applied · most recent first3 migrations
  • add_last_logindestructive · 804 stepsMay 01, 11:30
    20260501_113022_add_last_login
  • add_user_indexeslow · 152 stepsApr 28, 14:18
    20260428_141755_add_user_indexes
  • initialsafe · 57 stepsApr 20, 09:20
    20260420_092011_initial

snapshot · before

14 models · 86 fields

captured 2026-05-01T11:30:21Z

snapshot · after

14 models · 87 fields

captured 2026-05-01T11:30:23Z

Snapshots are stored as JSON in _zanith_schema_snapshots. Diff any two to see exactly what changed between releases.

04 — Questions answered

Six questions your audit table answers.

Each one is a one-page Grafana panel, a Slack alert, or a monthly engineering review row away. The data lives in the same Postgres your app uses — no log scraper, no sidecar.

Who changed what, when?

20 most recent migrations as a clean list with their risk pills.

Where do migrations spend their time?

Average step duration, grouped by op kind. addIndex on big tables dominates.

What broke this week?

Failed steps in the last 7 days, with the error captured at fail time.

Which migrations were spicy?

Per-migration risk profile — worst score and total score, ranked.

What's still recoverable?

Live artifacts by age and size — what to purge, what to restore.

What changed between releases?

Diff two schema snapshots. See exactly which models or fields moved.