From 830394ed5ea6947686e16489855e308051c065c9 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 23 Sep 2026 12:51:27 -0400 Subject: [PATCH] fix: the migration's DROP CONSTRAINT names doubled their own prefix (4295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run 7365, integration lane: (psycopg.errors.UndefinedObject) constraint "ck_worker_lane_ck_worker_lane_slots_within_cap" of relation "worker_lane" does not exist That is #3275 exactly, from the other direction. alembic 0088 had to RENAME four constraints CREATED with a doubled prefix; this one tried to DROP two with the same doubling. `op.drop_constraint` runs its name through Base.metadata's naming convention, which prepends `ck_worker_lane_` to a string that already carries it — `op.f()` is what marks a name as final, and 0103 used it on the way in. The model test also went red, correctly: `test_worker_lane_check_constraints` was parametrised over (slots, cap) pairs and asserted all three constraints, and two of them went with the `slots` column. It is one unparametrised test now, asserting the whole remaining set rather than a membership — a constraint left behind naming a dropped column is not a harmless leftover, it is a table the migration cannot have produced. Worth recording: **the gate worked.** Run 7365 skipped `sign-extension`, `build-web`, `smoke-web`, `promote` and `build-agent`, and `:dev` still names the previous digest. That is the red-direction verification #4339 owed, and it arrived by accident rather than by a forced failure — which is the better evidence. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR --- alembic/versions/0105_worker_lane_one_cap.py | 18 ++++++-- tests/test_worker_lanes.py | 44 ++++++++------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/alembic/versions/0105_worker_lane_one_cap.py b/alembic/versions/0105_worker_lane_one_cap.py index 01b4813..d36458b 100644 --- a/alembic/versions/0105_worker_lane_one_cap.py +++ b/alembic/versions/0105_worker_lane_one_cap.py @@ -70,11 +70,21 @@ _CAPS = ( def upgrade() -> None: - # The constraint goes first: it names `slots`, so dropping the column out - # from under it fails on Postgres. - op.drop_constraint("ck_worker_lane_slots_within_cap", "worker_lane", type_="check") + # The constraints go first: they name `slots`, so dropping the column out + # from under them fails on Postgres. + # + # `op.f()` around each name, and it is load-bearing. Without it alembic + # runs the name through Base.metadata's naming convention, which prepends + # `ck_worker_lane_` to a string that already carries it — and the DROP + # goes looking for `ck_worker_lane_ck_worker_lane_slots_within_cap`, which + # no database has. That is #3275 exactly, from the other direction: + # alembic 0088 had to RENAME four constraints created with the same + # doubling. Caught here by the integration lane, run 7365. op.drop_constraint( - "ck_worker_lane_slots_non_negative", "worker_lane", type_="check", + op.f("ck_worker_lane_slots_within_cap"), "worker_lane", type_="check", + ) + op.drop_constraint( + op.f("ck_worker_lane_slots_non_negative"), "worker_lane", type_="check", ) op.drop_column("worker_lane", "slots") op.drop_column("worker_lane", "enabled") diff --git a/tests/test_worker_lanes.py b/tests/test_worker_lanes.py index b3ba4fc..14be621 100644 --- a/tests/test_worker_lanes.py +++ b/tests/test_worker_lanes.py @@ -246,22 +246,16 @@ def test_an_unrecognised_queue_set_still_gets_a_true_label(): # --- the model's invariant --------------------------------------------------- -@pytest.mark.parametrize( - "slots,cap,ok", - [ - (0, 0, True), - (1, 1, True), - (1, 4, True), - (5, 4, False), # slots above its own cap - (-1, 1, False), # negative slots - (1, -1, False), # negative cap - ], -) -def test_worker_lane_check_constraints(slots, cap, ok): - """The constraints live in the database, not only in the service, because - a row violating `slots <= slots_cap` is not a rejected request — it is a - lane the reconcile (step 3) will drive UP to a number the operator - capped.""" +def test_the_row_carries_exactly_one_constraint_now(): + """`slots >= 0` and `slots <= slots_cap` went with the `slots` column on + 2026-09-23 — there is one number left, and the only thing that can be + wrong with it is being negative. + + Asserted as the WHOLE set rather than as a membership check: a constraint + left behind naming a dropped column is not a harmless leftover, it is a + table the migration cannot have produced, and the model would then + describe a schema no database has. + """ from backend.app.models import WorkerLane constraints = { @@ -269,17 +263,13 @@ def test_worker_lane_check_constraints(slots, cap, ok): if hasattr(c, "sqltext") } # The names carry the convention's `ck_worker_lane_` prefix ALREADY — the - # model declares them bare and Base.metadata's naming_convention applies it. - # Asserting the prefixed form is what pins the thing that actually went - # wrong once: alembic 0088 had to rename four constraints that shipped as - # `ck_x_ck_x_name`, because the migration pre-prefixed a name the - # convention then prefixed again (#3275). A bare-name assertion here would - # pass just as happily against a doubled one. - assert constraints == { - "ck_worker_lane_slots_non_negative": "slots >= 0", - "ck_worker_lane_cap_non_negative": "slots_cap >= 0", - "ck_worker_lane_slots_within_cap": "slots <= slots_cap", - } + # model declares them bare and Base.metadata's naming_convention applies + # it. Asserting the prefixed form is what pins the thing that actually + # went wrong once: alembic 0088 had to rename four constraints that + # shipped as `ck_x_ck_x_name`, because the migration pre-prefixed a name + # the convention then prefixed again (#3275). A bare-name assertion here + # would pass just as happily against a doubled one. + assert constraints == {"ck_worker_lane_cap_non_negative": "slots_cap >= 0"} for name in constraints: assert not name.startswith("ck_worker_lane_ck_"), f"doubled prefix: {name}"