zanith

migrate / recover

01 — The safety net

A drop is not a delete until you say it is.

Destructive ops rename aside to _zanith_shadow and write a row to _zanith_migration_artifacts with a checksum. Restore refuses if the archive was tampered with — proven in the proof suite on real Postgres.

zanith recover
archive → checksum
AAA-001pending
BBB-002pending
CCC-003pending
DDD-004pending
EEE-005pending
5 artifact kinds7 recover verbs + cleanupchecksum tamper-refuse
02Five strategies

Two strategies, three shapes, one forensic.

soft-drop renames in place — cheap, instant, no data movement. archive copies data out before dropping. Each comes in two flavours (column / table). The fifth captures schema for forensic use only.
soft_drop_columnRenames the column to _zanith_dropped_<col>_<id>. Restore = rename back. Instant.
soft_drop_tableRenames the table aside. Restore = rename back. Instant, zero copy.
archive_columnCopies the table into _zanith_shadow, then drops the column. Stores row count + checksum.
archive_tableMoves the whole table into _zanith_shadow and renames it. Instant — no row copy.
rebuild_tableAfter a copy-swap rebuild, keeps the old table as _zanith_dropped_<table>_<id>.
recovery shape
1 / 5 · data movement

Renames the column to _zanith_dropped_<col>_<id>. Restore = rename back. Instant.

03The bookkeeping

One row remembers everything.

Every reversible op writes a row to _zanith_migration_artifacts. The row knows what was dropped, where the data parked, how to verify it's untouched, and when the auto-cleanup window closes.

Thirteen columns capture source, physical name, recovery SQL, row count, checksum, and expiry. Restore reads this row before it writes anything back.

Restore verifies before it writes. If parked data was edited out-of-band, the checksum won't match and recover restore refuses: Refusing to restore: archive checksum changed since archive time (recorded 9a15e1d…)

_zanith_migration_artifacts
recover list · 5 kinds
soft_drop_columnsoft_drop_tablearchive_columnarchive_tablerebuild_table
{
  "id": "20260402::archive_column::p5_legacy.legacy_code",
  "artifact_type": "archive_column",
  "row_count": 5,
  "checksum": "9a15e1d…"
}

proof rows: AAA-001 · BBB-002 · CCC-003 · DDD-004 · EEE-005

04Seven commands

3 read. 4 write.

The same zanith binary that applied the migration knows how to walk it back. Read verbs inspect and export; write verbs restore, reseed, or purge.

read-only

  • recover listEvery recoverable artifact, newest first.
  • recover inspect <t[.col]>Metadata, checksum, and the exact recovery SQL.
  • recover export <t[.col]>Read archived rows out as csv / json / jsonl.

write

  • recover restore-column <t.col>Rename back, or re-add + backfill from the archive.
  • recover restore-table <t>Move a soft-dropped or archived table back to public.
  • recover reseed <t[.col]>Insert archived rows into a reshaped table (expand/contract).
  • recover purge <id>Drop the archive data and delete the artifact row.

zanith cleanup --older-than 30d is not a recover verb — the top-level maintenance command for bulk-purging artifacts past their window.

zanith recover
read-only · 3
$ recover list
$ recover inspect <t[.col]>
$ recover export <t[.col]>
write · 4
$ recover restore-column <t.col>
$ recover restore-table <t>
$ recover reseed <t[.col]>
$ recover purge <id>
$ zanith cleanup --older-than 30d · bulk-purge past window