Skip to content

feat(migrate): label addition — evolve Postgres enum types under migrate_updates, with Alembic parity (#329) - #335

Merged
0x054 merged 6 commits into
mainfrom
feat/enum-label-addition
Aug 5, 2026
Merged

feat(migrate): label addition — evolve Postgres enum types under migrate_updates, with Alembic parity (#329)#335
0x054 merged 6 commits into
mainfrom
feat/enum-label-addition

Conversation

@0x054

@0x054 0x054 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #329. Closes #330. Closes #331. Closes #332. Closes #333. Closes #334. Fixes #328.

What this does

A StrEnum that grows a member now works against existing databases. Under migrate_updates=True, the reconciliation pass performs label addition (ADR-0011): it diffs each model-referenced, ferro-owned Postgres enum type against pg_enum and appends missing labels with ALTER TYPE ... ADD VALUE IF NOT EXISTS — executed as an autocommit pre-pass before the per-table transactions, so it is legal on every supported Postgres version and each label is committed before any table plan can reference it. Labels the database has but the model lacks warn loudly and are never removed.

The Alembic bridge gains an autogenerate comparator consuming the same Rust decision table over FFI: a generated revision carries the byte-identical statements inside autocommit_block() (ahead of table ops) and a comment for extra labels; in-sync models generate nothing.

Why the issue's repro stays failing on purpose

#328 reconnects with auto_migrate=True alone. Per ADR-0011 (the ADR-0010 line extended to enum types), an existing type is an existing schema object: the create pass stays introspection-free and silent, and drift handling of every kind — enum drift included — belongs to migrate_updates. The docs now carry this trap prominently, including why it is structurally invisible to tests (fresh schemas always get the complete label set).

Commits map 1:1 to the tickets

Verification

0x054 added 6 commits August 5, 2026 10:38
…g enum labels (#330)

An existing ferro-owned Postgres enum type now learns model-declared labels
it is missing: the reconciliation pass diffs declared labels against pg_enum
(one decision table in ferro-ddl-lowering) and executes ALTER TYPE ... ADD
VALUE IF NOT EXISTS per missing label as an autocommit pre-pass before the
per-table transactions. Ownership is by derivation; append-only (ADR-0011).
…s silent (#331)

Live labels the model no longer declares warn loudly — naming the type, the
labels, and the reviewed-migration exit — and are never removed (rows may
still hold them; old code may still run against the schema mid-deploy).
Exactly one warning per drifted type. Plain auto_migrate remains inert and
warning-free with drift in either direction, pinned by test (ADR-0011).
…un, ordering, idempotence (#332)

Pins the production edges of label addition: a shared StrEnum reconciles
once (one warning per drifted type, both tables usable); a new label used as
a new column's literal-backfill default works in one run because the
autocommit pre-pass commits the label before any table plan (the Prisma
#8424 trap); appended labels sort last regardless of Python declaration
order (documented ORDER BY caveat); a second boot replans to nothing.
… shared diff (#333)

The bridge registers a schema comparator: named sa.Enum types in the target
metadata diff against pg_enum on the connection, through the same Rust
decision table auto-migrate consumes (_plan_enum_label_addition over FFI) —
the generated revision executes byte-identical statements. Additions render
inside op.get_context().autocommit_block(), inserted ahead of table ops so
the label is committed before anything references it; extra live labels
render as a warn-never-act comment naming the reviewed-migration exit.
In-sync models generate nothing (no phantom diffs, AGENTS.md I-1); a
cross-language pin holds the rendered statement byte-for-byte on both sides.
…dates contract, the ordering caveat (#334)

Documents enum evolution end to end: the test-invisible trap (fresh schemas
always get the complete label set, so only existing databases fail), the
migrate_updates contract (append-only, warn-never-act, labels commit before
table plans, shared types reconcile once), the ORDER BY caveat for appended
labels, SQLite non-applicability, and the bridge's autogenerate comparator.
Examples in both declaration styles with lambda predicates.
…l addition)

Records the grilling decisions behind #329: update-gated (the ADR-0010 line
extended to enum types), derived-name ownership, append-only with
warn-never-act, one decision table for both migration doors, and the
rejected alternatives. Glossary gains *enum label* and *label addition*;
*Ferro-owned artifact* extends to derivation-based ownership for types.
@0x054
0x054 force-pushed the feat/enum-label-addition branch from 900eb41 to 1b37b1b Compare August 5, 2026 14:38
@0x054
0x054 merged commit 449616f into main Aug 5, 2026
7 checks passed
@0x054
0x054 deleted the feat/enum-label-addition branch August 5, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment