Skip to content

A green gate union proves nothing about the head it is reported against — nothing ties the run to a commit, so a mid-round union goes stale silently #8550

Description

@os-zhuang

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:

#8545this
what went wrongthe derivation emitted check:type-check-coverage where the ratchet is check:type-check-debtthe invocation was correct
when it was runcorrect time, wrong commandcorrect command, stale tree
remedymake dispatch-gates.mjs emit the invocation CI runstie 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)

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions