`--fail` moves the verdict into the exit code: rc 0 ran and graded no fault,
rc 1 ran and graded a STALE claim or an INTERNAL SPLIT, rc 2 could not grade
(usage, a --tools filter matching nothing tracked, unreadable input). Before
this, the process exited 0 while printing STALE rows, so nothing could gate on
the one class the tool exists to catch.
Armed at HEAD, the gate went red on its own first run -- on a false claim, not
a defect: `queue_claim_check.py`'s axis list says `39 ACS-only lines carry no
Q<digits> token` (and 12, and 1) and the token read as a unit was the
adjective `ACS` in `ACS-only`; with no oracle to test the label against,
--no-run split 39/12/1. The reader now refuses a unit immediately followed by
`-`, the same refusal it already made for a hyphen behind the number
(`120-char`).
Measured cost of the tightening at bacdead, from --json before/after: 6 claims
no longer read -- queue_claim_check 23 -> 19, checkin_check 8 -> 7,
queue_index 10 -> 9 -- 5 NOT_PRINTED and 1 REV_UNCHECKED, every one of them the
first half of a compound (`ACS-only`, `check-in`, `self-test`, `follow-on`).
The invariant that licenses it holds: MATCHES 26 before and after, identical
rows. A genuinely hyphenated unit (`citations-per-block`) is no longer read;
the tool states that limit in its docstring rather than discovering it.
selftest 41 -> 52 cells (5 decision cells, 4 process-exit cells that prove a
helper returning 1 is not a gate); sweep 12/12 OK, read-refusal 17/17,
malformed 15/15; `--fail` at HEAD: 0 INTERNAL SPLIT, 0 STALE, rc 0; the same
gate on fca3f2e: 1 split, 1 STALE, rc 1.