feat(migrate): label addition — evolve Postgres enum types under migrate_updates, with Alembic parity (#329) - #335
Merged
Merged
Conversation
…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
force-pushed
the
feat/enum-label-addition
branch
from
August 5, 2026 14:38
900eb41 to
1b37b1b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #329. Closes #330. Closes #331. Closes #332. Closes #333. Closes #334. Fixes #328.
What this does
A
StrEnumthat grows a member now works against existing databases. Undermigrate_updates=True, the reconciliation pass performs label addition (ADR-0011): it diffs each model-referenced, ferro-owned Postgres enum type againstpg_enumand appends missing labels withALTER 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=Truealone. 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 tomigrate_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
feat(migrate)Label addition tracer: missing labels appended under migrate_updates, new member round-trips #330 — tracer: introspection, shared diff, autocommit pre-pass, live round-tripfeat(migrate)Label addition warnings: extra live labels warn-never-act; create pass stays silent #331 — warn-never-act for extra labels (pinned, single-sourced text); create-pass silence pinnedtest(migrate)Label addition edges: shared types, default-in-same-run, append ordering, idempotence #332 — edges: shared type reconciles once, new-label-as-default-in-same-run (the Prisma #8424 trap), append ordering, idempotent second boot, new-table-over-stale-typefeat(alembic)Alembic comparator: autogenerate emits label additions from the shared diff, with parity pins #333 — comparator + cross-language statement parity pin in the canonical parity seamdocs(migrate)Label addition docs: the auto_migrate trap, the migrate_updates contract, the ordering caveat #334 — the trap, the contract, theORDER BYcaveat; both declaration styles, lambda predicatesdocs(adr)— ADR-0011 + glossary (enum label, label addition, derivation-based ferro-ownership) + AGENTS.md I-1 item 11Verification
cargo test --workspace --no-default-features --features testing)test_cross_emitter_parity.py, AGENTS.md I-1 registry entry added, docs lambda naming fixed. One review finding refuted with evidence: freshCREATE TABLEnever renders server-side defaults, so the create pass cannot reference a new label — pinned by test rather than restructured.