Skip to content

A farm-wide detector is the only thing that makes the #8435 ratchet-remedy convention mechanically enforceable — and the farm now has two conflicting ⛔ tokens that must be reconciled first #8540

Description

@qq9340100

Filed by the dev seat that implemented #8519 (PR #8539). Unassigned and unlabeled for triage. Lands in the gate-farm / developer-ergonomics surface.

⚠️No gate is asked to be weakened. Every gate named here is correct. This is about making the #8435 convention enforceable by machine instead of by convention — and about a token collision that must be decided before a detector can be specified at all.

The measured claim

#8435 established the convention: a gate whose second remedy is editing a shrink-only ratchet / ledger / baseline must mark that path maintainer-only in its own output. PR #8517 covered 2 gates; PR #8539 covers 3 more; #8538 records a 6th.

Nothing enforces the convention.#8519 proposed a shared helper module as the route that would. It is not: a module is reachable only from gates that choose to import it, so it standardises the gates that already agreed and is structurally blind to the next gate that offers a ratchet path and imports nothing. #8538 is the direct evidence — the sixth instance was found by sweeping source text, and no shared module could have found it at any quality, because that file imports nothing to be found by.

A second, weaker point against the module route, worth recording so it is not re-proposed: the per-gate predicates are not the same function. Each gate's RATCHET_EXPANSION_OFFER is keyed to its own offer wording, and check-role-word.mjs's carries a constraint the others do not — it must not match its own ratchet-DOWN message, because both directions of that ratchet are spelled --update and marking the improvement path maintainer-only would teach the opposite of the rule. A shared predicate needs per-gate parameterisation regardless, leaving only the 24-character token genuinely shared.

⛔ Decide this first: the farm has TWO ⛔ tokens with different meanings

A detector cannot be specified without deciding which token it enforces, so this is not separable from the rest of the card.

tokenwherewhat it means today
⛔ MAINTAINER-ONLYcheck-engine-double-contract.mjs, check-type-check-coverage.mjs, and the three gates in PR #8539this remedy is a maintainer's to take, not yours
⛔ SHRINK-ONLYcheck-type-source-resolution.mjsthis registry only shrinks — and that gate refuses the weakening remedy outright ("Adding an entry, or widening one, is not how a red build gets fixed") rather than marking it

These are different propositions: one is about authority, the other about ratchet direction, and the second gate resolves the situation more strongly than the first convention asks. A detector that enforces only MAINTAINER-ONLY will report check-type-source-resolution.mjs as a violation when it is arguably the best-behaved gate in the farm. Reconcile deliberately — do not let whichever gate is written next settle it by accident.

Feasibility: measured, three prototypes

Measured against a detached worktree at origin/main = f1da948d8, scored against the hand classification from #8519 + #8538.

Corpus: 79 scripts — 77 .mjs + 2 .mts.#8519's card says 76 .mjs, and that was correct when it was written hours earlier; main has gained a gate since. That decay is itself the argument for a detector over a hand census: a census is accurate only at the instant it is taken, and this one measurably rotted inside one working day.

detectorflaggedtrue posfalse posmissedprecisionrecall
v1 — naive "offers to add an entry"135/67138%83%
v2 — v1 AND the gate calls its registry shrink-only54/61280%67%
v3 — v2 AND the gate does not REFUSE the remedy outright44/602100%67%

At v3 every gate flagged is one hand-classified as a real instance: zero false positives across all 79 scripts, including all three declaration registries #8519 excluded (CROSS_PACKAGE_TEST_INPUTS, INHERIT_JUSTIFIED, FAILURE_PROPAGATION_CALLEES/_SITES), the three further registries found during #8519 (check-error-code-casing.mjsEXEMPT_FILES, check-single-authz-resolver.mjsALLOW, check-skill-compatibility-version.mjsEXEMPT), and both borderline exemption maps recorded on #8538.

The naive detector's 7 false positives are one namable class — declaration registries, where adding the entry is the correct fix — and they are removed by two structural filters rather than a keyword blacklist: require the gate's own testimony that its registry is shrink-only, and exclude gates that refuse the weakening remedy outright.

The two misses have DIFFERENT causes — do not treat them as one bug

  • check-type-check-coverage.mjs — a prototype defect, fixable. It says "shrink-only" seven times and offers add a TEST_DEBT entry / add a DEBT entry. The prototype regex demanded "add … to/in … REGISTRY" while this reads "add … REGISTRY … entry". Pure word order; a better offer grammar reaches it.
  • check-driver-conformance.mjs — a real limit of the v2/v3 anchor. That file contains zero shrink-vocabulary matches. Its authority evidence is worded differently: "a MEASURED, tracked exception the maintainer has agreed to, never the cheaper half of 'enroll the driver'". Both misses are in-script ledgers rather than JSON files, and in-script ledgers do not reliably describe themselves as shrink-only. A detector keyed only on that anchor will keep missing them, however good its offer grammar gets.

The lane rule this measurement implies

The first cut of the prototype spelled its gap class [^.;], which excludes the dot — so every offer naming a registry by path (…baseline.json) silently failed to match, and two known instances read as clean. It was caught only because a hand-classified control set disagreed with the detector.

That is the same failure the convention itself is about, one level up: a measurement that reports a clean result while being structurally blind to a subset. So:

A detector for this convention must be built against a hand-classified control set, and shipping the detector means shipping the control. Zero findings from a detector with no control proves nothing.

The existing gates already carry the right pattern for this (the detector-reaches-its-subject positive-control assertion PR #8517 introduced and PR #8539 copied); a farm-wide detector needs the same idea at corpus scale.

Suggested shape (not decided here)

One gate that sweeps scripts/*.{mjs,mts}, finds author-facing remedy strings offering a ratchet/baseline/ledger-expanding edit, and fails when the agreed authority token is absent — carrying its own hand-classified control corpus as a fixture, and its own positive-control assertion that the sweep still reaches known instances. Whether it also enforces the refusal shape (the stronger check-driver-memory-census.mjs / check-type-source-resolution.mjs precedent) is part of the token decision above.

Related: #8435 (established the convention), PR #8517 (covered 2 gates), #8519 (covered 3 more), PR #8539, #8538 (the 6th instance and the .mts blind spot). None of those is addressed here; #8519 and #8538 remain open on their own terms.


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