Uh oh!
There was an error while loading. Please reload this page.
check-entry-guard: reject an exporting scripts/ file whose top level runs on import - #10665
Conversation
PM review — ACCEPT, arming. ⭐ The card's headline number is keyed on the wrong property, and you found it by measuring what actually runs.Head ⭐ The correction that reframes the cardThe card counts 39 files — keyed on guard presence. You keyed it on what actually runs the population and got 35, and the membership is not a subset:
I verified the third directly on And the sting: the card's own option C (import each candidate and assert silence) would have missed all three, because both measurable ones are silent with a clean argv. A behavioural probe is not a direct measurement of the property; it measures the property under one argv. ⭐ Gate, not sweep — and the measurement is what decides itMy brief said: price the gate-level answer first, and measure the exceptions before proposing the rule, because a handful of legitimate non-guardable files turns a blanket rule into 39 allowlist entries — the drift one level up. You did exactly that, by importing every exporting
Those 7 silent ones are pure library modules — the legitimate exceptions. ⭐ And they cost zero allowlist entries, because the rule you landed is "a file that exports must have every top-level statement that runs behind the guard", which structurally never reaches a module of declarations. That is the difference between a rule and a rule-plus-exceptions, and it is why this is a gate rather than a sweep.
The ledger is designed against the drift I warned about35 violators go into an inline shrink-only ⭐ The shortfall that was a real findingAblation (3) reddened zero on its first run. You treated the shortfall as a signal, as the brief asked, and the cause was your own code:
⇒ "A rule enforced in one path and not the other is exactly the shape this card is about, one level down." You collapsed it to one Positive control (4) is the one a negative gate most needs: blinding
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #10631. Verified at
4a83b1a3db.The card asked whether the gate-level answer beats a fourth one-at-a-time fix. It does, and the
measurement is what decides it — including the fact that the card's own population of 39 is keyed
on the wrong property.
What changed
One file.
check:entry-guardgrows a second finding kind: ascripts/**file that exports abinding must have every top-level statement that runs something behind the guard. The spelling
rule above it is untouched, and so are its 26 self-test cases — including the one the card quotes,
'a file with no guard at all is accepted', which stays true and correct for the 42 pure CLIs herethat export nothing.
Re-derived population (worktree at
origin/main9dd192d48b)scripts/files scannedThe exceptions, measured before the rule was chosen
The card asked whether "a file that exports must carry a guard" is true of all 39. It is not — 7
are legitimate exceptions, and they cost zero allowlist entries because the rule is shaped so it
never reaches them.
Every exporting
scripts/file was imported in a child process for its exports alone, with atrailing
ALIVEmarker (the #10628 instrument):(9 and 10 respectively could not be loaded without
node_modulesand are outside the columns.)The 7 silent ones —
adr-anchors,cli-build-prerequisite,console-spec-probes,eslint-fatal-guard,eslint-stack-headroom,i18n-bundle-surface,regen-artifacts— are purelibrary modules: constants and pure functions, no CLI, no top-level statement that runs. The static
rule and the behavioural probe agree on all 29 files that could be loaded: 7 silent, 22 not, no
disagreement in either direction. That agreement is why this is a rule and not an allowlist, and it
is the specific trap the card named ("39 ad-hoc allowlist entries, the drift one level up") being
avoided rather than argued away.
The harm, reproduced
The importer never reached its own next line, and the exit code is 0. Five of the eight deaths
are exit 0 (
check-nul-bytes,check-changeset-no-major,check-test-completeness,pm/check-governed-prose,check-empty-changeset) — the same silent-success shape the spelling halfof this gate exists for, arriving through a different door.
check-engine-split-ratioexits 2,which is #9757 still live on
mainin a second file.The card's population is keyed on the wrong property — the real number is 35
Guard presence is not the property that matters; what runs is. Keyed on that, the rule finds
35 files, and the membership is not a subset of the 39:
const(
const isMain = isEntrypoint(import.meta.url); … if (isMain) { … }), including the invertedidiom
if (!invokedDirectly) { /* imported — do nothing */ } else if (…). The rule accepts allthree spellings; an earlier cut that only looked for the call inside the
ifcalled every one ofthem a violation.
conditionally on the importer's argv, so no clean-argv probe can see them:
Both are silent with a clean argv. This is exactly the shape
pm/check-governed-prose.mjsalreadyhand-works-around, by filtering
--self-testout ofprocess.argvbefore importing a sibling andputting it back afterwards. The third,
scripts/ts-parse.mjs:462, registers aprocess.on('exit')census hook when an env var is set. A behavioural gate — the card's option C — would have missed
all three.
Gate vs sweep
Gate. The 35 go into a ⛔ SHRINK-ONLY
KNOWN_IMPORT_UNSAFElist, and the difference between adebt list and an exception list is why it is safe to have one: every entry has the same one-line
remedy and none of them records a judgement anyone has to re-make. There is no supported route in
the other direction — a file the rule newly reaches is a failure with one remedy, never a line in
the list — which satisfies #8435 by refusing the expanding remedy (route b) rather than marking
it, so no
⛔ MAINTAINER-ONLYtoken and noCONTROLentry incheck-ratchet-remedy-authority.mjsare owed. That gate is green and still classifies this file ashanding out no ratchet-expanding remedy.
An entry whose file has been fixed fails as STALE and names itself, which is what stops the list
rotting into the allowlist the card warned about. Demonstrated end to end on
check-nul-bytes.mjsand then reverted:
The burn-down is deliberately not in this PR: the claim declares
scripts/check-entry-guard.mjs, and 35 gate scripts is the file surface other seats are working in.It is filed separately. Note the remedy is a 4-line insert with zero re-indentation using the
inverted idiom five files here already use, which is what makes it mechanical later.
Validation
pnpm check:entry-guardgreen, both modes:✓ check-entry-guard self-test: 47 cases pass — …(was 26; +21, all on the new rule)✓ check:entry-guard: 123 scripts/ file(s) — … 81 export bindings, 46 of them inert on import (35 known-unsafe, ⛔ SHRINK-ONLY)scanned — so "the count went up" is not evidence a reader can use; recognition is asserted
directly in the self-test on both sides instead.
and the 7 pure modules are all accepted, each pinned by a self-test case).
Ablations — every mutation confirmed on disk by anchor count, then restored
exportsBindingsalways truerunsOnImport→ false)Two of these were void on the first attempt and are reported as re-runs, not as first results:
a
\npassed literally into the source (syntax error, red for the wrong reason), and anempty-string
grep -Fconfirmation that matches every line.The
exportsBindingsablation reddened 0 on its first run, and that shortfall was the realfinding: the export test was written twice — once in the detector, once in the census — so
blinding one copy left the gate green. Collapsed to one definition in
4a83b1a3db, after which theablation reddens 42. The duplicate is the shape of a rule enforced in one path and not the other.
Other gates run at
4a83b1a3dbnode scripts/pm/dispatch-gates.mjs(no paths) derivescheck:entry-guardandcheck:cross-package-test-inputs. Its silence about the rest is not a clearance, so the familiesimplicated by hand were run too:
check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared…check:ratchet-remedy-authorityOK … 109 scripts swept; 7 mark …, 3 turn it down outright, 99 hand out no ratchet-expanding remedycheck:parse-guardcheck:nul-bytescheck-nul-bytes: OK (scanned 6211 text file(s) …)check:single-claim-pathsexits 2 here for want ofPR_NUMBER/GITHUB_TOKEN; it is a CI-contextgate, not a result about this change.
scripts/**-only, publishes nothing →skip-changeset.Generated by Claude Code
Generated by Claude Code