fix: the migration's DROP CONSTRAINT names doubled their own prefix (4295)
CI and images / lint (push) Failing after 3s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 19s
CI and images / backend-lint-and-test (push) Failing after 30s
CI and images / integration (push) Failing after 2m15s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
CI and images / lint (push) Failing after 3s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 19s
CI and images / backend-lint-and-test (push) Failing after 30s
CI and images / integration (push) Failing after 2m15s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
@@ -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")
|
||||
|
||||
+17
-27
@@ -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}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user