Uh oh!
There was an error while loading. Please reload this page.
fix(ci): run the four self-tests that ran in no workflow, including the two the scripts/ routing gates delegate to - #10649
Conversation
…o, and the sink gate that never ran `check:entry-guard` and `check:parse-guard` are spelling gates whose headers each answer "why is spelling enough?" by delegating to a module's own `--self-test` — `invoked-as.mjs` and `ts-parse.mjs`. Neither self-test ran in any workflow, so CI enforced "everybody routes through the module" and never checked that the module still refuses. `js-comment-mask.mjs`, which both gates use to tell code from prose, was unrun for the same reason. `check:optional-error-sink` landed in #10555 with a root alias and no workflow invoking it, so it has enforced nothing since it merged. Wired as `lint.yml` steps in the `Lint & Repo Gates` job (the required status-check context), no `if:`, no `paths:` filter. No new root `package.json` alias — that file is #9465 fence territory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
commented
Aug 21, 2026
PM review — ACCEPT, arming. ⭐ You corrected the card's table in both columns, and the four opt-outs are the better half of the work.Head ⭐ The predicate correctionThe card said 87 scripts ship a The gap is the predicate, and it is a good one:
Counting only real
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10608
Four
--self-test/ gate invocations that ran in no workflow are now steps inlint.yml'sLint & Repo Gatesjob. No script changed; the whole diff is 61 lines ofworkflow, two steps and their comments.
The measurement, re-derived on today's tree
The card's table was produced at
d5e7b9f5a1. Re-derived here by resolvingpnpm check:*aliases (transitively, including nested
pnpm run) and matching them plus directnode scripts/…invocations against all 26 workflows, with YAML comment lines stripped so acommented-out invocation does not read as wiring.
The card under-counts, in both columns. It reports 87 scripts shipping a
--self-testand 6 unreached. Running the same derivation against the card's own commit gives
82 shipping / 9 unreached; against
origin/maintoday (6b0be02209), 83 / 8.The gap is the predicate.
--self-testappearing in a file is not the same as a fileshipping one:
adr-anchors.mjs,eslint-fatal-guard.mjsandregen-artifacts.mjsonlymention the flag in prose and have no
process.argvbranch for it, so counting mentionsinflates the denominator. Counting only files that actually branch on the flag deflates it —
and surfaces two unreached scripts the card does not list:
scripts/ts-parse.mjsscripts/js-comment-mask.mjsscripts/check-optional-error-sink-contract.mjscheck:optional-error-sink)scripts/invoked-as.mjs← not in the cardscripts/checklist-select.mjs← not in the cardcheck:platform-checklist)scripts/setup-git-hooks.mjsprepare)scripts/ablation-dist-preflight.mjsscripts/measure-test-shard-timings.mjsAfter this change the same derivation reports 4 unreached, and they are exactly the four
left deliberately.
One row of the card is confirmed by the derivation rather than asserted:
check-parse-guard.mjsitself scored unreached at
d5e7b9f5a1and scores reached today, which is #10612 landing —a known before/after the detector gets right.
The four wired, and why each is not bookkeeping
ts-parse.mjs+invoked-as.mjs— the two modules whose self-tests are load-bearing forgates that already run.
check:entry-guardandcheck:parse-guardare spelling gates bydeliberate design, and each header answers "why is spelling enough?" the same way: the
behaviour is pinned once, at the module, by that module's own
--self-test.lint.ymlsaysso in its own words, one step apart —
Neither self-test ran anywhere. So CI enforced "everybody routes through the module" and
never once checked that the module still refuses.
invoked-as.mjsis the addition the card does not list. It is the same defect, in the samefile, one step earlier, found by re-deriving rather than by adopting the table — wiring it is
one line in a step this PR was already adding.
js-comment-mask.mjs— the masker both those gates use to tell code from prose. Itsfailure directions are asymmetric: a mask that stops masking makes both gates fabricate
findings out of their own documentation (loud), while a mask that starts over-masking makes
both go quietly green over text they never read.
check:optional-error-sink— the gate landed with PR #10555 carrying its root alias, andno workflow ever invoked it, so it has enforced nothing since the hour it merged while reading
in
package.jsonexactly like the gates that do. Its alias already exists, so this is a steprunning
pnpm check:optional-error-sinkand nothing else.The four left, with reasons
setup-git-hooks.mjs— its--self-testasserts a property of the current clone(
core.hooksPathand two merge-driver settings in.git/config), not of the committedtree. Its own header states the registrar warns and exits 0 on every failure path precisely
so "CI images that install with
--ignore-scripts" are not broken by it. Wiring it wouldmake a required context red for an environment fact no PR changed. ⛔ Not a candidate.
checklist-select.mjs— reachable only throughcheck:platform-checklist, whichlint.ymlalready documents as excluded by maintainer decision: "it is NOT wired intoCI — it runs on a periodic manual cadence … The checklist is a QA ledger, not a code gate."
Wiring the selector's self-test would partly re-open a lane a ruling closed. Left to the
maintainer, not decided here.
measure-test-shard-timings.mjs— a generator, run by hand against.turbo/runs/artifacts, whose product (
scripts/test-shard-timings.json) is committed. The consumer sideis already pinned in CI:
partition-test-shards.mjs --self-testruns at line 1367 andloadTimings()refuses a missing or malformed dataset rather than falling back. The failuremode a CI run could catch is already caught downstream.
ablation-dist-preflight.mjs— the closest call of the four. Pure fixture self-test,~0.07 s, so cost is not the argument. It is left because no CI gate consumes it and no gate's
design argument delegates to it — its consumers are agents, not workflows — which is the
distinction that puts the other four in and this one out. Worth revisiting if that changes.
Wiring proof — parsed, not grepped
.github/workflows/lint.ymlparsed withyaml.safe_load:Lint & Repo Gatesis the required context registered inscripts/check-required-contexts.mjsforlint.ymljoblint. Nopaths:filter on anytrigger, no
if:on the job or either step — an exemption is what a self-test must not have,or the gap simply moves.
Each newly wired step provably catches something
Four ablations. Every mutation was confirmed on disk by anchor count before the run — an
editor's exit code proves nothing,
sed -iandperl -0piboth exit 0 on zero matches. Eachrestore is proved by
git diff --exit-codeagainst the commit. No build is involved:noderesolves these modules straight from
scripts/*.mjssource, so there is nodist/that cango stale between the mutation and the measurement.
ts-parse.mjsparseDiagnosticsread —?.parseDiagnostics1 → 0, injected 1x ts-parse self-test: 10 of 28 case(s) failed.invoked-as.mjs✗ invoked-as self-test: 2 of 11 case(s) failed.js-comment-mask.mjsif (c === '/' && next === '/')→if (c === '/'), the over-mask direction — anchor 1 → 0, injected 1✗ js-comment-mask self-test: 1 of 15 case(s) failed.check-optional-error-sink-contract.mjsFALLBACK_CHANNEL = 'warn'→'info', the answer its header rejects — anchor 1 → 0, injected 1✗ optional-error-sink-contract self-test: 3 case(s) failed, step exit 1Two of them fail in exactly the shape their gate exists to distrust, which is the part worth
reading:
a caller's try/catch cannot downgrade the refusal into a skip — {"status":0,"out":"NOT REACHED","err":""}: with the diagnostics read gone the parse silently succeeds.a probe reached through a SYMLINK runs -- {"out":"","status":0}— the inert-child, exit-0 shape.regex literal containing an escaped // -- mask: BLINDS (real code removed).js-comment-mask.mjshas #10427 in flight against it (PR #10632). Nothing here edits thatfile: it was mutated transiently for ablation 3 and restored,
sha2565bd5ff8bb852bd1fa336ed67f392808c63045358c9999d4b0277a9e57fa74676before and after.Positive control and CI cost
Unmodified tree, both steps exactly as written:
Added CI wall-clock: ~14 s per PR, of which ~10 s is
ts-parseand the spawns are thepoint — it starts ~20 real node children to pin that a caller's
try/catchcannot swallowthe refusal, which is not testable in-process. The card estimates
ts-parseat ~4 s; measuredhere it is ~10 s, so the true cost is about 2.5x what the card budgeted. Still small against
the ~79 steps already in
Lint & Repo Gates.Scope
No script changed, and no second line in root
package.json— the #9465 fence grantedthis lane one line, in PR #10612. Both new steps follow the precedent already in this file
(
Shallow-history guard self-tests,Hand-written declaration mirrors,Published skills token ratchet), including its stated reason for thenode scripts/…spelling.The durable version of this — one gate asserting that every
scripts/**tool shipping a--self-testis reachable, with a reasoned opt-out list — is not built here; it stays withthe parent, #10196. This PR is the measurement plus the instances it found. The ~30-line
alias-resolving detector used for the table above is reproducible from the description here.
Gates
Union derived with
node scripts/pm/dispatch-gates.mjs(no paths passed — it takes the changeset from the merge base itself), at
2fd31188fd:All 10 families it names, run on this tree at
2fd31188fd(all seven distinct commands; thethree
node scripts/…rows are the same scripts as their aliases):Quoting each gate's own verdict line rather than a shell status:
check:type-check-debtgenuinely belongs to this diff rather than matching by accident:observed()reads.github/workflows/lint.ymland derivesciInvokesTask/ciInvokesRootfrom it, andevaluate()asserts on both. It needed the workspace closurebuilt first (
turbo run build --filter='./packages/*' --filter='./packages/*/*', 70 tasks,4m07s) — the gate refuses loudly rather than measuring a different world, which is the
correct behaviour and not a defect of this change.
check:type-check-debtalso reports a standing, pre-existing surplus unrelated to this PR:11 raw errors across 1 entry sit below their recorded ceiling, closable with
pnpm check:type-check-debt --lower. Not touched here — it is not this card's surface.Generated by Claude Code
Generated by Claude Code