zanith

migrate / risk

01 — The classifier

Six levels. Twenty-one reasons. Seven gates.

Every op Zanith emits carries a published level, score, and reason code from risk.ts. CI wires --max-risk against the numbers — not opinions. blocked is reserved for future row-count-aware refusals; the classifier does not assign it today.

risk classifier
safe · score 5
CreateIndex & constraintAlterDropSecurity (RLS)
31 op kinds · 6 levels · 21 codes7 gates3 safety modes
02Ops, by family

31 kinds. 7 families.

Verbatim from types.ts. Each op maps to a level + score in risk.ts — no hand-maintained marketing table.

Create

4 ops

adding things rarely breaks production

createExtensioncreateTableaddColumnaddGeneratedColumn

Index & constraint

6 ops

constraints can fail on real data

addIndexdropIndexaddUniquedropUniqueaddForeignKeydropForeignKey

Alter

5 ops

type and nullability changes need preflight

alterColumnTypealterColumnNullablealterColumnDefaultrenameTablerenameColumn

Drop

3 ops

removing data is expensive — most need consent

dropColumndropTabledropExtension

Security (RLS)

4 ops

RLS changes affect every read and write

enableRlsdisableRlscreatePolicydropPolicy

Recovery & advanced

8 ops

restorable until cleanup or purge

softDropColumnsoftDropTablearchiveColumnarchiveTablebackfillsplitColumnmergeColumnsrebuildTable

Escape hatch

1 ops

the planner can't reason about it — review by hand

rawSql
types.ts → risk.ts
Create4 ops
createExtensioncreateTableaddColumnaddGeneratedColumn
Index & constraint6 ops
addIndexdropIndexaddUniquedropUniqueaddForeignKeydropForeignKey
Alter5 ops
alterColumnTypealterColumnNullablealterColumnDefaultrenameTablerenameColumn
Drop3 ops
dropColumndropTabledropExtension
Security (RLS)4 ops
enableRlsdisableRlscreatePolicydropPolicy
Recovery & advanced8 ops
softDropColumnsoftDropTablearchiveColumnarchiveTablebackfillsplitColumnmergeColumnsrebuildTable
Escape hatch1 ops
rawSql
adding things rarely breaks production
03Reason codes

21 codes, four families.

Every classified op carries one or more reason tags. The codes are stable — grep your audit tables by reason, not by hand-maintained labels.

Additions

  • adds_nullable_column
  • adds_required_column_without_default
  • adds_index
  • adds_unique_constraint
  • adds_foreign_key
  • adds_extension

Drops

  • drops_column_with_data
  • drops_table_with_data
  • drops_index
  • drops_unique_constraint
  • drops_foreign_key
  • drops_extension

Alters

  • narrows_column_type
  • changes_column_type
  • changes_column_nullable_to_not_null
  • rebuilds_table
  • requires_backfill

Structural & raw

  • rls_change
  • raw_sql
  • rename_table
  • rename_column
risk.ts reason codes
Additions · chip wall
adds_nullable_columnadds_required_column_without_defaultadds_indexadds_unique_constraintadds_foreign_keyadds_extension
04Gates & modes

7 gates. All must pass.

An op moves from parsed to committed only after clearing seven checkpoints. They layer rather than replace each other — and a safety mode just chooses sensible defaults for all seven at once.
01
Risk budget--max-risk <n>

Aborts if any op's score exceeds the ceiling. Production caps at 60, staging at 80.

02
Destructive opt-in--allow-destructive

DROP COLUMN / DROP TABLE refuse without it. No implicit consent for data loss.

03
Preflight probes

Four data probes run before apply: UNIQUE dupes, FK orphans, NULLs before NOT NULL, populated tables before NOT NULL adds.

04
Advisory lock

pg_advisory_xact_lock around the apply loop — two deploys can't race the same database.

05
Per-migration transaction

Each migration's ops run in one transaction. A failed step rolls back and is recorded as failed.

06
Shadow-verified--shadow-verified

In production mode, up refuses unless verify ran on a shadow DB (override with --skip-shadow-check).

07
Dry run--dry-run

Prints the plan, risks, and preflight results. Touches nothing in production.

Set migrationSafety.mode and the gates inherit a posture. The default when unset is staging — safe by default.

safety.ts + runner.ts
migrationSafety.mode presets
modemax-riskdestructiveadvisory lockpreflightshadow
dev100allowedoffoffoff
staging80needs flagononrecommended
production60blockedononrequired
gate sequence
01
Risk budget--max-risk <n>
Aborts if any op's score exceeds the ceiling. Production caps at 60, staging at 80.
02
Destructive opt-in--allow-destructive
DROP COLUMN / DROP TABLE refuse without it. No implicit consent for data loss.
03
Preflight probes
Four data probes run before apply: UNIQUE dupes, FK orphans, NULLs before NOT NULL, populated tables before NOT NULL adds.
04
Advisory lock
pg_advisory_xact_lock around the apply loop — two deploys can't race the same database.
05
Per-migration transaction
Each migration's ops run in one transaction. A failed step rolls back and is recorded as failed.
06
Shadow-verified--shadow-verified
In production mode, up refuses unless verify ran on a shadow DB (override with --skip-shadow-check).
07
Dry run--dry-run
Prints the plan, risks, and preflight results. Touches nothing in production.
default when unset: staging — per-field overrides and CLI flags still win