From 204e1525ee1f73a8ae876920ad4c6e58e09141c5 Mon Sep 17 00:00:00 2001 From: Renn F Date: Sat, 27 Jun 2026 01:09:34 +0200 Subject: [PATCH] [fix] migration 016: postgresql.ENUM(create_type=False) for reused team enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 016_add_products_and_task_product_id used `sa.Enum(..., create_type=False)` for the reused Postgres "team" enum — the same latent defect that crashed 052 on a real orchestrator boot. On the generic `sa.Enum` the `create_type` kwarg is silently dropped, so `_check_for_name_in_memos` never sees it and `op.create_table` (checkfirst=False) emits a redundant `CREATE TYPE team` that fails with "type 'team' already exists" against a DB where the enum pre-exists. Switch to the postgres-native `postgresql.ENUM(..., create_type=False)` — its `create_type` is a real attribute the guard reads, so the CREATE TYPE is suppressed (and DROP TYPE on downgrade too). The member list is inert under create_type=False (it never creates/alters the type), so it stays at 016's original six, reflecting the enum as it stood then, not the later-widened set. This never crashed in prod because 016 is never re-run (alembic_version is past it), but it's the same defect class. Verified on the real boot path: upgrade to 015 in process A (team enum created by 001), then `upgrade head` in a fresh process B — 016 applied clean, no DuplicateObjectError; downgrade 016->015 clean, shared team enum preserved. See project_migration_enum_create_type_gotcha. --- .../versions/016_add_products_and_task_product_id.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/alembic/versions/016_add_products_and_task_product_id.py b/alembic/versions/016_add_products_and_task_product_id.py index 25bbfe45..3919b18e 100644 --- a/alembic/versions/016_add_products_and_task_product_id.py +++ b/alembic/versions/016_add_products_and_task_product_id.py @@ -25,7 +25,17 @@ down_revision = "015_drop_task_execution_outputs" branch_labels = None depends_on = None -_TEAM_ENUM = sa.Enum( +# Reuse the existing Postgres "team" enum in place (created in 001_initial_schema, +# widened since by later migrations). ``create_type=False`` MUST be set on the +# postgres-native ``postgresql.ENUM``: it's that class's ``create_type`` attribute +# that ``_check_for_name_in_memos`` reads to suppress the redundant ``CREATE TYPE`` +# on ``op.create_table`` (checkfirst=False, so the has_type probe is skipped). On +# the generic ``sa.Enum`` the kwarg is silently dropped, so the CREATE TYPE would +# fire and crash a boot against a DB where the enum pre-exists ("type 'team' +# already exists"). The member list is inert under create_type=False (it never +# creates/alters the type), so it reflects the enum as it stood at 016's time, +# not the later-widened set. See project_migration_enum_create_type_gotcha. +_TEAM_ENUM = postgresql.ENUM( "backend", "frontend", "ux_ui",