Filed by the dev seat on #8470 (PR #8537), where this cost a red CI lap. Unassigned and unlabeled for triage. Surface: dev-seat contract / PM dispatch tooling.
⚠️No gate is wrong and none is asked to be weakened.check:engine-double-contract behaved correctly — it caught a real second unguarded engine double. This is about the fact that a PR body can truthfully report a green union over a tree that is no longer the head, and nothing anywhere notices.
What happened
The lane's contract says: derive the gate families from lint.ymland re-derive with scripts/pm/dispatch-gates.mjs, run the union, report. I did. Every gate exited 0 and the PR body listed them.
Then review feedback landed and I pushed two more commits, both adding test code. One of them grew a second unguarded engine double in a file whose shrink-only baseline records one. The gate that catches exactly that had been run — before the code that broke it existed. CI went red on a PR whose body accurately reported a green union.
Why this is not #8545
#8545 is the same lane, one lap earlier, and it is worth stating the difference because the remedies do not overlap:
| #8545 | this |
|---|
| what went wrong | the derivation emitted check:type-check-coverage where the ratchet is check:type-check-debt | the invocation was correct |
| when it was run | correct time, wrong command | correct command, stale tree |
| remedy | make dispatch-gates.mjs emit the invocation CI runs | tie a union run to a commit, or require re-running at the final head |
Fixing #8545's emitted name would not have prevented this, and vice versa. They are neighbours, not duplicates.
Why the existing defences do not catch it
The contract is written as a checklist of which gates to run. It says nothing about when, and the natural workflow — implement, verify, then respond to review — puts the verification before the last commits by construction. The failure is therefore most likely on exactly the PRs that get the most review attention, which is the wrong way round.
It is also invisible after the fact: a gate log carries no commit sha, so neither the dev seat re-reading its own transcript nor the PM reading the PR body can tell whether a reported green run covers the head. The report and the tree can disagree with nothing in between to notice.
Note the ratchet gates make this sharper than ordinary lint. A stale ordinary-lint run is usually still true. A stale ratchet run is a claim about a ledger measured against different code, and the ratchets are precisely the gates whose whole purpose is to notice code that was just added.
Suggested shapes (not decided here)
- Cheapest, contract-only: the dev-seat rule becomes "run the union after the final commit, and quote
git rev-parse --short HEAD from that run". Costs one re-run per review round; catches this whole class. - Mechanical: have the gate runner (or a thin wrapper) stamp the sha it ran against, so a union report carries
HEAD=<sha> and a reviewer — or a bot — can compare it to the PR head. This is the version that does not rely on anyone remembering. - Cheapest useful subset, if 1 and 2 are both too much: require re-running only the ratchet family after the final commit (
check:engine-double-contract, check:type-check-debt, check:query-options-erasure, and the other shrink-only ledgers), since those are the ones a late commit actually moves.
Worth noting one thing observed while fixing this: the gate's own message says "pin the new one rather than raising it", but a third option was better here — build the fixture by overriding the file's existing double instead of declaring a second one, so there is no new double to pin or ledger. Not a defect in the message; just a remedy the message does not mention and that a seat under ratchet pressure may not think of.
Related
#8470 / PR #8537 (where it surfaced) · #8545 (sibling failure mode, same lane, different cause) · #5278 (shrink-only ratchet rule)
Generated by Claude Code
Filed by the dev seat on #8470 (PR #8537), where this cost a red CI lap. Unassigned and unlabeled for triage. Surface: dev-seat contract / PM dispatch tooling.
check:engine-double-contractbehaved correctly — it caught a real second unguarded engine double. This is about the fact that a PR body can truthfully report a green union over a tree that is no longer the head, and nothing anywhere notices.What happened
The lane's contract says: derive the gate families from
lint.ymland re-derive withscripts/pm/dispatch-gates.mjs, run the union, report. I did. Every gate exited 0 and the PR body listed them.Then review feedback landed and I pushed two more commits, both adding test code. One of them grew a second unguarded engine double in a file whose shrink-only baseline records one. The gate that catches exactly that had been run — before the code that broke it existed. CI went red on a PR whose body accurately reported a green union.
Why this is not #8545
#8545 is the same lane, one lap earlier, and it is worth stating the difference because the remedies do not overlap:
check:type-check-coveragewhere the ratchet ischeck:type-check-debtdispatch-gates.mjsemit the invocation CI runsFixing #8545's emitted name would not have prevented this, and vice versa. They are neighbours, not duplicates.
Why the existing defences do not catch it
The contract is written as a checklist of which gates to run. It says nothing about when, and the natural workflow — implement, verify, then respond to review — puts the verification before the last commits by construction. The failure is therefore most likely on exactly the PRs that get the most review attention, which is the wrong way round.
It is also invisible after the fact: a gate log carries no commit sha, so neither the dev seat re-reading its own transcript nor the PM reading the PR body can tell whether a reported green run covers the head. The report and the tree can disagree with nothing in between to notice.
Note the ratchet gates make this sharper than ordinary lint. A stale ordinary-lint run is usually still true. A stale ratchet run is a claim about a ledger measured against different code, and the ratchets are precisely the gates whose whole purpose is to notice code that was just added.
Suggested shapes (not decided here)
git rev-parse --short HEADfrom that run". Costs one re-run per review round; catches this whole class.HEAD=<sha>and a reviewer — or a bot — can compare it to the PR head. This is the version that does not rely on anyone remembering.check:engine-double-contract,check:type-check-debt,check:query-options-erasure, and the other shrink-only ledgers), since those are the ones a late commit actually moves.Worth noting one thing observed while fixing this: the gate's own message says "pin the new one rather than raising it", but a third option was better here — build the fixture by overriding the file's existing double instead of declaring a second one, so there is no new double to pin or ledger. Not a defect in the message; just a remedy the message does not mention and that a seat under ratchet pressure may not think of.
Related
#8470 / PR #8537 (where it surfaced) · #8545 (sibling failure mode, same lane, different cause) · #5278 (shrink-only ratchet rule)
Generated by Claude Code