test(scripts): pin the prerequisite exit code and advisory in the three i18n gates - #15181
Merged
Merged
Conversation
…rs into pure text functions The three PREREQUISITE NOT MET / COULD NOT MEASURE / POPULATION EMPTY printers built their string inline inside `console.error(...)`, so the exit code each path returns and the advisory that names it had no value a test could read. Same shape `scripts/import-prerequisite.mjs` already uses. Text unchanged: only the wrapper and the trailing comma move. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
… own self-test New battery, 23 cases, registered through the existing `registerCase()` helper: the prerequisite class is 3, distinct from a finding's 1 and from 0; each of the three printers exits through the named constant rather than a literal, prints the pinned text function, interpolates the code rather than spelling one, and renders an advisory that names both numbers with no stale `Exit code 1`. Three negative controls prove each predicate can still fail. Roster: 4 batteries -> 5 (SELF_TEST_BATTERY_FLOOR 4 -> 5); the new battery is floored at its measured 23. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
… its own self-test Extracts the refusal printer into `prerequisiteNotMetText` (message text unchanged, both `scanned` branches move with it) and adds a 14-case battery: the prerequisite class is 3, distinct from a finding's 1 and from 0; the printer exits through the named constant, prints the pinned text function and interpolates the code; both rendered advisories name both numbers with no stale `Exit code 1`. Three negative controls prove each predicate can still fail. Roster: 4 batteries -> 5 (SELF_TEST_BATTERY_FLOOR 4 -> 5); new battery floored at its measured 14. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…s own self-test Extracts the refusal printer into `buildPrerequisiteText(probe)` (message text unchanged — the live refusal on an unbuilt tree is byte-identical before and after, still exit 3) and adds twelve cases in the file's existing self-test shape: the prerequisite class is 3, distinct from a finding's 1 and from 0; the refusal exits through the named constant, prints the pinned text function and interpolates the code; the advisory names both numbers with no stale `Exit code 1` and still names the probe file it was handed. Three negative controls prove each predicate can still fail. No battery roster added here — that is #13799's surface, not this card's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 4, 2026
baozhoutao
marked this pull request as ready for review
September 4, 2026 05:37
baozhoutao
enabled auto-merge
September 4, 2026 05:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14857
The three i18n gates answered
PREREQUISITE NOT METwith an exit code and an advisory that names it, and pinned neither — while the four sibling gates that answer the same words pin both. Each printer built its string inline insideconsole.error(...)withprocess.exit(...)on the next line, so there was no value a test could read: the number PR #14856 moved from 1 to 3 was held in place here by nothing.This extracts each refusal printer into a pure text function (the shape
scripts/import-prerequisite.mjsalready uses) and asserts, from each gate's own--self-test, the value that moved. No gate resisted — in all three the shape change stayed inside the printer and its call site, exactly the fence triage drew.The success lines are updated in the same stroke: all three previously advertised only their classifiers, which is the reading triage kept — a pass line that overstates its own scope stops anyone looking.
Per gate
scripts/check-i18n-coverage.mjsPREREQUISITE NOT MET,COULD NOT MEASURE,POPULATION EMPTYscripts/check-i18n-bundles.mjsscannedrenderingspackages/cli/scripts/check-app-nav-i18n.mjsWhat each gate now asserts, per refusal path:
EXIT_FINDINGSand from0;process.exit(1)written back in, which leaves the constant reading 3 and every consumer green);Exit code 1, and still states that nothing was measured.Each gate also carries negative controls: every predicate is run against a function that does the forbidden thing and must see it, so a typo in either regex cannot pass forever.
⭐
COULD NOT MEASUREandPOPULATION EMPTYsit behind the CLI-build probe — no local run and no other test observes them at all, so for those two these cases are the only observer. They were done first.Rosters (#13799 b2 floors raised)
check-i18n-coverage.mjscheck-i18n-bundles.mjscheck-app-nav-i18n.mjsEach new battery's floor was measured through the gate's own floor machinery (pin the battery at 999, read the count it names back), not counted by hand. Every new assertion registers through the existing
registerCase()helper inside a declared battery; each file's verdict handshake is untouched (SELF_TEST_VERDICTsentinel in both rostered gates; the app-nav gate has none of the three spellings and did not grow one here).Verification
All of it on
2745a1716, with every exit code captured by redirect before any pipe. Exit 3 is read as NOT MEASURED, never as a pass.Self-tests —
check-i18n-coverage✓,check-i18n-bundles✓,check-app-nav-i18n✓ (exit 0 each).Normal mode, live. On the unbuilt worktree all three refused with the real code:
pnpm check:i18n-coverage→ exit 3,pnpm check:i18n→ exit 3,node packages/cli/scripts/check-app-nav-i18n.mjs→ exit 3, each printingExit code 3, distinct from a finding's 1. That is 3 of the 5 paths observed live; the other two are the build-probe pair the pins now cover. The app-nav refusal is byte-identical before and after the extraction (diffof the two captured runs is empty) — the strongest available evidence that the extraction moved no text. Afterpnpm build(under the shared verify lock, 72/72 tasks) all three reach a real verdict: coverage OK (13 configs, 691 baselined), bundles OK (9 packages in sync), app-nav OK (10 contributors, 54 nav ids, 4 locales).Ablation, 7 cases, one per pin class per gate. Each mutation is proven on disk by an exact substring count (anchor → 0, injected → n) and each restore by
git hash-objectequality against the HEAD blob plus an emptygit diff HEAD— never by an editor's or a checkout's exit code; the harness restores under a trap and aborts loudly rather than letting a later measurement run on a mutated tree. These gates are plain.mjsscripts executed directly by node, with nodist/between the edit and the run, so no rebuild leg applies.the refusal exits through the named constant, never a literal; restored → green.Exit code 1, in each of the three gates → self-test red, naming all three advisory pins (interpolates / names both numbers / no stale spelling); restored → green.reportUnmeasuredConfigsstops printingunmeasuredConfigsText→ self-test red, namingthe printer prints the pinned text function; restored → green.An earlier harness attempt is worth recording: its first run reported
MUTATION ON DISK: anchor hits 1629 -> 1629becausegrep -c -Fcounts LINES for a multi-line anchor, so its landing check was meaningless. The green self-test that followed is what caught it, and group A was re-run under exact-substring proof afterwards. Nothing was reported from the run that did not land.Derived family —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(derived from git at2745a1716, 3 paths, three-dot semantics): 42 commands, all run, all green.pnpm check:dual-build-cjs-loadsfirst returned exit 3 (NOT MEASURED, no build); after the build it measures and passes.Always-runs tail —
check:pm-dispatch-gates✓ (1343 cases),check:self-test-wired✓,check-self-test-workflow-commands✓ (no pnpm alias; run asnode scripts/check-self-test-workflow-commands.mjs, plus its--self-test),check:declared-population-live✓,check:ratchet-remedy-authority✓,check:nul-bytes✓. Plusapp-nav-translation-parity.test.ts(the one sibling suite that names a touched gate, in comments) — 24/24.Control bytes —
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over all three edited files: no match. Same scan over every body posted from this task.skip-changeset: the diff publishes nothing.scripts/**ships in no package, andpackages/cli/scripts/**is outside@objectstack/cli'sfiles(dist,README.md,CHANGELOG.md).Written by Claude Code in session
session_012zGPuVVX3deAx9LdjK8jCk, dispatched by thedomain:devxPM seat (claim comment 5535910063).Generated by Claude Code