[fix] migration 016: postgresql.ENUM(create_type=False) for reused team enum

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.
This commit is contained in:
Renn F
2026-06-27 01:09:34 +02:00
parent e8c7774d41
commit 204e1525ee
@@ -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",