fix(tooling): check:i18n-walk-parity refuses with the prerequisite code, not a finding's - #16970
Conversation
…de, not a finding's
`reportPrerequisiteNotMet` said "NOTHING was measured" and then returned exit 1
— the code a real finding uses. On one worktree sweep three gates refused for
the same reason and returned 3, 1, 3: two are legible as NOT MEASURED from the
number alone, and this one read as a false RED against whatever landed most
recently. The number, not the stderr, is what a sweep, a runner script or a CI
step condition reads.
Route the refusal through the repo's one answer: import EXIT_PREREQUISITE_NOT_MET
and EXIT_FINDINGS from `scripts/import-prerequisite.mjs` rather than re-spelling
either, the way `check-dts-closure.mjs` does and the two sibling gates in this
same i18n family already do. The refusal's words are unchanged byte for byte;
it gains the shared "capture the code BEFORE any pipe" advisory, which names
both numbers by interpolating them.
Pinned by a second self-test battery that DRIVES all three verdicts through the
exit code a shell actually sees — a throwaway root holding this gate, the modules
it imports and whichever built inputs the case wants it to see:
* the card's repro (no built inputs) -> 3, with the same headline
* FIRING CONTROL: a declared group nothing
emits, both sides readable -> 1, reached by measuring
* NONSENSE CONTROL: nothing to find -> 0
A pin that only checked the prerequisite path would pass on a file that returned
3 for everything, which is a worse defect than the one being fixed. Structural
cases pin the same thing over the function bodies — no literal exit call, no
spelled-out code in the advisory — each with a negative control that must SEE a
function doing the forbidden thing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
ACCEPT in substance — PR #16970, reviewed against the tree, ⛔ not against the reportFlip + arm withheld until the four running legs report. ⭐ The exit-site map, old head → new head, read by me
⇒ Exactly one exit site moved, and it is the one the card is about. Nothing else in the file changed number.
⭐ The
|
Part of #16558
Clause-②: no
The defect
scripts/check-i18n-walk-parity.mjsrefused correctly when its inputs were not built, printed the right words —NOTHING was measured— and then returned exit 1, the code a real finding uses. The card measured the cost directly: on one worktree sweep three gates refused for the same reason and returned3,1,3. Two are legible as NOT MEASURED from the number alone; this one read as a false RED against whatever landed most recently. The number, not the stderr, is what a sweep, a runner script or a CI step condition reads.What changed
One file:
scripts/check-i18n-walk-parity.mjs.EXIT_PREREQUISITE_NOT_METandEXIT_FINDINGSfromscripts/import-prerequisite.mjs— the repo's one home for these two numbers, whose header states the reason in as many words. Four siblings already import it, two of them in this same i18n family (check-i18n-bundles.mjs,check-i18n-coverage.mjs). Neither number is re-spelled here.reportPrerequisiteNotMetexitsEXIT_PREREQUISITE_NOT_MET, and its text is split out into a pureprerequisiteNotMetTextso the self-test can assert on the advisory as a VALUE rather than on a comment about it. That split is the shapecheck-i18n-coverage.mjsandcheck-i18n-bundles.mjsalready carry.main()are spelledEXIT_FINDINGS— same value,check-dts-closure.mjs's shape.scripts/import-prerequisite.mjswas read, never written.Judgement on the second bare
process.exit(1)— it stays 1The claim brief flagged a second bare
process.exit(1)that does not go through the helper (line 725 before this change, 766 after). It is a real finding and exit 1 is correct. Left alone, deliberately.It is the
--self-testdispatch's completion guard:Three reasons it is not a prerequisite:
--self-test"needs neither" built side — it drives the pure classifiers against recorded samples. Nothing about the environment can make that path refuse.returnadded above the verdict line.pnpm installandturbo buildclear nothing here; the remedy is to fix the source. That is the definition of a finding.Corroborating precedent, not inference:
check-i18n-coverage.mjscarries the identical guard with the identicalprocess.exit(1), and the PR that moved that file's refusal numbers from 1 to 3 left it at 1.The five
reportPrerequisiteNotMetcall sites were re-read and all five are genuine "this gate cannot read its input" refusals — inputs not built, inputs unloadable, the built schema exposing no shape, the built walker not exporting the walker, and an empty population. Their classification is unchanged by this PR.Callers: nothing changes behaviour, checked
.github/workflows/lint.yml:5902—run: pnpm check:i18n-walk-parity, a bare run step. Any non-zero fails it; 3 is non-zero.package.json— the script itself.scripts/check-ratchet-remedy-authority.mjs— reads this file's remedy TEXT, not its exit code. Unchanged, and the gate is green below.half-state-patrol.ymlon thepm/gates, unrelated.pnpm check:i18n-walk-paritypropagates the new code — pnpm reportsCommand failed with exit code 3and exits 3 — so what the CI step sees is 3, and its verdict (fail) is unchanged.The pin, and why it is two-sided
A test that only checked the prerequisite path would pass on a file that returned 3 for everything — a worse defect than the one being fixed. So a second self-test battery drives all three verdicts through the exit code a shell actually sees, in a throwaway root holding this gate, the four modules it imports, and whichever built inputs the case wants it to see. The child runs in production mode, so it can never spawn a child of its own, and the spawn is bounded by a timeout.
Plus structural cases over the FUNCTION BODIES, because the regression that costs something is not a mistyped constant — it is a
process.exit(1)written back in by an author who never thought about exit codes, or a number typed into the advisory instead of interpolated. Each of those predicates has a NEGATIVE CONTROL that must SEE a function doing the forbidden thing, so a typo in either regex cannot pass forever.Verification
Exit codes captured BEFORE any pipe,
cmd > file 2>&1; EXIT=$?.Before / after, the card's repro (worktree installed, dependency closure not built):
On the built tree (
turbo run build --filter=@objectstack/cli --filter=@objectstack/spec, 57/57 tasks successful):Ablations — each mutation proved to have reached disk by grep count in both directions, each restored from
HEADand verified by blob hash,git diff HEADempty at the end. These were run from the committed implementation, so the restore leg had something to restore to.process.exit(EXIT_PREREQUISITE_NOT_MET)back toprocess.exit(1)(injected count 3, removed count 0). Self-test EXIT=1 with exactly the two cases that should see it:KNOWN_NO_EXTRACTOR_FACE. Production path, built tree, EXIT=1, printing the ledger-ratchet finding. Restored; the unmutated tree exits 0 in the same harness.Gate family — derived from the merge base rather than hand-listed, then reconciled:
All 31 exited 0, including
check:ratchet-remedy-authority,check:pm-dispatch-gates,check:self-test-wired,check:scripts-symbol-anchors,check:declaration-mirrors,check:nul-bytesandcheck:parse-guard.Lint —
eslint --no-inline-configover the changed file: 1 file, 0 errors, 0 warnings. The narrowing is a measurement, not a skip: this repo runs oneeslint.config.mjswhich, in its own words, "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file", so a one-file diff cannot move the verdict on any untouched file. The repo-widepnpm lintrun is CI's.Control characters —
grep -naPfor the C0/DEL class over the changed file: no matches.Changeset
skip-changeset, measured rather than asserted. The changed file is a repo-root gate script; the root package is private. Searching every published package's declaredfiles[]roots — 76 roots across 23 published packages — forcheck-i18n-walk-parityreturns 0 hits, with a positive control (TranslationDataSchema) returning 32 in the same sweep. Nothing published moves.The neighbour sweep the card asked for — it is NOT a class
The card named
check-doc-frontmatter.mjsas an unverified neighbour and the triage asked for the branch to be measured before assuming anything, with the instruction that a third or fourth site would make it a class to handle in one pass. Measured: there is no second site.Every file in
scripts/andpackages/lint/scripts/that printsPREREQUISITE NOT METwas enumerated and each one's refusal branch read:check-doc-frontmatter.mjs,check-dts-closure.mjs,check-i18n-bundles.mjs,check-i18n-coverage.mjs,check-published-readme-exports.mjs,check-regen-pending.mjs,check-release-section-coverage.mjs,check-sourcemap-no-sources-content.mjs,check-test-completeness.mjs,check-type-check-coverage.mjs,check-vendor-export-contract.mjs— all importEXIT_PREREQUISITE_NOT_METand already exit 3.check-dual-build-cjs-loads.mjs,check-manifest-repository-directory.mjs,docs-audit/check-docs-transcript-drift.mjs— refuse with a locally namedEXIT_PREREQ = 3. Correct number; the shared module's own header lists that spelling as prior art rather than as a violation.objectui-changeset-digest.mjsrefuses with a documented exit 2 of its own, pinned by its own self-test. A different declared contract, not this one.Every bare
process.exit(1)sitting near one of those refusal texts — eight of them,check-doc-frontmatter.mjs:1805,check-dts-closure.mjs:581,check-dual-build-cjs-loads.mjs:1599,check-manifest-repository-directory.mjs:1258,objectui-changeset-digest.mjs:1193among them — is the same self-test completion guard judged above, and 1 is correct in every one. So the answer the card wanted is:check-i18n-walk-parity.mjsreally was the only site, and this PR closes it.验收备注
Noted while reading, not filed:
EXIT_PREREQ = 3locally rather than importing the shared constant. They return the right number and each pins it in its own self-test, so this is neither a reproducible defect nor a contract breach — the shared module's header names that spelling as the prior art the constant was extracted from. Whoever next edits one of those three files is the one who would touch it; no PR is heading there today, so: no taker.Generated by Claude Code