fix(gates): answer PREREQUISITE NOT MET with the frame's exit code in the last three i18n gates - #14856
Conversation
… the last three i18n gates `#13983` moved `scripts/import-prerequisite.mjs` -- the frame 45 gates inherit -- onto `EXIT_PREREQUISITE_NOT_MET` (3), joining the five sibling sites that already meant 3 by those two words. Three gates still answered them with 1: - `scripts/check-i18n-coverage.mjs` 3 refusal paths - `scripts/check-i18n-bundles.mjs` 1 - `packages/cli/scripts/check-app-nav-i18n.mjs` 1 All five now import `EXIT_PREREQUISITE_NOT_MET` / `EXIT_FINDINGS` from that frame rather than re-picking them, so there is no new literal 3 in the diff, and each printed advisory announces the code it actually exits with. The real-verdict and self-test exits in these files are untouched; only the refusals that already say "nothing was measured" moved. `packages/cli/scripts/check-app-nav-i18n.mjs` also takes the converged pipe-shape paragraph (`#13429`'s, verbatim apart from this gate's own command). Its old sentence prescribed `echo "EXIT=$?"` without saying WHERE, so a reader who did the natural thing read `tail`'s status -- the exact false green the prescription exists to prevent. The census `git grep -l "no pipe shape repairs it"` goes 7 files to 8, and the census-negative string now has zero hits under `scripts/**` and `packages/**`. The lane doc `.claude/skills/pm-dispatch/references/lanes/services.md:22` keyed on the old number, so it moves in the same stroke rather than starting to lie. Governed surface: this lands as a draft for a human merge. Cards: `#14008` (the exit code) and `#13825` (the banner wording) -- declared once in the PR body, since this branch squashes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAwHpn4uVuf4N1geBcD5i3
📓 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): |
|
PM status ( Generated by Claude Code |
|
PM note on the red ( Generated by Claude Code |
Two conflicts, both against main commits that landed after this branch's merge base 5ff5f95: - .claude/skills/pm-dispatch/references/lanes/services.md: main's 365141f (#14856) edited the very bullet this branch deletes (the bullet moved to references/platform-readings.md). Settled to OUR side: the bullet stays deleted and the move stands. The FACT main corrected in that bullet (the i18n gates answer PREREQUISITE NOT MET with exit 3, not 1) is carried into the moved text by the follow-up commit, so this merge commit's non-conflict content stays a pure merge of the two parents. - scripts/check-ratchet-remedy-authority.mjs: main's a610775 (#14896) added the self-test battery roster and floor at the spot where this branch's section (21) sits. Settled by keeping BOTH: section (21) opens with battery('(21) ...'), is declared in SELF_TEST_BATTERIES at its case count (2), and sits before the floor-evaluation block; the roster's own size pin SELF_TEST_BATTERY_FLOOR moves 20 -> 21 by the header's own convention (the roster size is pinned at the declared count). The exception phrase stays assembled, never spelled, in author-facing text, and the gate's run line is byte-identical to main's. scripts/pm/check-skill-line-ratchet.mjs auto-merged with main's c47a5ac (#14853) verdict handshake; the two run-path verdict lines this branch adds are outside that handshake, which covers selfTest()'s sentinel return only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…8n reading references/platform-readings.md line 301 -- the bullet this branch moved out of lanes/services.md -- still said the i18n gates answer PREREQUISITE NOT MET with exit 1. main's 365141f (#14856) moved the last three i18n gates (scripts/check-i18n-coverage.mjs, scripts/check-i18n-bundles.mjs, packages/cli/scripts/check-app-nav-i18n.mjs) onto EXIT_PREREQUISITE_NOT_MET (3, from scripts/import-prerequisite.mjs) and corrected the source bullet to "退 3" in the same commit; the merge took our deletion of that bullet, so the corrected fact is carried into the moved text here rather than re-landing the claim main already corrected. Same line count (324, ceiling unchanged); no other edit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…e first one (#14860) * tooling(pm): admit a same-PR cross-file move in the skill line ratchet The per-file line ratchet is shrink-only, which prices a FACT MOVING TO ITS SINGLE-SOURCE FILE as though it were growth: the destination sits at headroom 0 like every other entry, so consolidating a misplaced per-repo reading needed a per-instance maintainer ruling even though the corpus shrank on net. Adds the declared cross-file move as the one raise an author may take alone, under the three ruled conditions: the destination's raise is at most the net decrease of the sources it names, the map-wide total does not increase, and the declaration cites the ruling that authorises it. The declaration is in-tree data carrying each participant's pre-move ceiling, so the arithmetic is computable from the working tree alone — no baseline ref a shallow CI checkout can fail to provide, and no answer that evaporates once the move has landed. Verdict lines and exit codes are unchanged; the map is empty in this commit. Part of #14685 Part of #13597 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * tooling: record the authorised cross-file move in the remedy-authority gate The #8435 convention asks who may take a registry-growing remedy on a shrink-only ratchet, and the answer has always been "a maintainer, per instance". One class of act is now authorised generally: a cross-file move in the per-file line ratchet, while the destination's raise is covered by the named sources' net decrease, the map-wide total does not increase, and the raised entry cites the ruling. The exception is written in comment space, because author-facing text describing a registry-growing act would make this gate an instance of the convention it enforces. Two self-test assertions hold it there: one that the phrase never reaches an author-facing message, one that the amended ratchet is still outside this non-recursive walk — so the exception stays honestly documentary, and a wider walk reds here instead of admitting a remedy class in silence. No new verdict class: a fifth class with a population of zero is enforcement nobody has. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * docs(pm-dispatch): consolidate three misplaced per-repo readings A per-repo gate reading written into one lane charter is read by that lane and missed by the other six. Their single source is references/platform-readings.md, so the three the Phase-1 tables measured move there, under the cross-file-move rule this branch adds: lanes/cli.md -4 the objectstack required-check set, `in_progress` is not a pass, advisory red into main is shared damage lanes/services.md -2 the two aggregate-reading gate boundaries rest-channel.md -5 the merge_group count-is-not-a-mechanism tombstone, moved byte-identically platform-readings.md +10 against a net source decrease of 11, so the corpus falls by one line: the per-job-conclusion rule cli.md carried is already stated in the destination's queue section, so the moved bullet points at it rather than paying for a second copy. Ceilings move with the text (314→324, 35→31, 30→28, 93→88) and the move is declared, so the gate re-derives the arithmetic on every run instead of trusting the comment beside it. No pointer is left at any source: nothing at the three sites becomes unreadable without the moved sentence, and a pointer per move is how a corpus grows while its ledger says it shrank. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * tooling(pm): singularise the cross-file move verdict's line counts Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * docs(pm-dispatch): carry main's corrected exit code into the moved i18n reading references/platform-readings.md line 301 -- the bullet this branch moved out of lanes/services.md -- still said the i18n gates answer PREREQUISITE NOT MET with exit 1. main's 365141f (#14856) moved the last three i18n gates (scripts/check-i18n-coverage.mjs, scripts/check-i18n-bundles.mjs, packages/cli/scripts/check-app-nav-i18n.mjs) onto EXIT_PREREQUISITE_NOT_MET (3, from scripts/import-prerequisite.mjs) and corrected the source bullet to "退 3" in the same commit; the merge took our deletion of that bullet, so the corrected fact is carried into the moved text here rather than re-landing the claim main already corrected. Same line count (324, ceiling unchanged); no other edit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #14008
Fixes #13825
Two cards, one PR, because triage bound them: they change the same line of
packages/cli/scripts/check-app-nav-i18n.mjs— #14008 the exit code that bannerannounces, #13825 the banner's wording — and landing them apart would only make
them conflict (#14008 triage, R+81 and R+83: 「与 #13825 同笔落地」).
What moved
scripts/import-prerequisite.mjsis the frame 45 gates inherit, and since #13983it answers
PREREQUISITE NOT METwithEXIT_PREREQUISITE_NOT_MET= 3 — the codefive sibling sites already meant by those two words. Three gates still answered
them with 1. They no longer do:
scripts/check-i18n-coverage.mjs— CLI-not-built refusalEXIT=1EXIT=3scripts/check-i18n-coverage.mjs—COULD NOT MEASURE(partial round)process.exit(1)process.exit(EXIT_PREREQUISITE_NOT_MET)scripts/check-i18n-coverage.mjs—POPULATION EMPTYprocess.exit(1)process.exit(EXIT_PREREQUISITE_NOT_MET)scripts/check-i18n-bundles.mjs— prerequisite refusalEXIT=1EXIT=3packages/cli/scripts/check-app-nav-i18n.mjs— prerequisite refusalEXIT=1EXIT=3Three of the five were exercised live in an unbuilt worktree, exit code captured
BEFORE any pipe, both before and after the edit. The other two sit behind the
CLI-build probe, which fires first, so they are unreachable without a build and
are reported here as source-level readings only — stated rather than glossed.
⛔ No new literal 3. All three files import
EXIT_PREREQUISITE_NOT_METandEXIT_FINDINGSfromscripts/import-prerequisite.mjsand print them into theadvisory, so the number a reader sees and the number the process returns cannot
drift apart.
scripts/cli-build-prerequisite.mjsstayed untouched: it did notneed to re-export the constant, because importing the frame directly works from
both roots —
packages/lint/scripts/*already reach repo-root gate infrastructurethat way, which is the precedent the
packages/cliimport follows.⛔ Only the refusals moved. Each of these paths already printed "nothing was
measured / nothing was compared". Every real-verdict exit and every self-test exit
in the three files is still 1:
check-i18n-coverage.mjskeeps 3 of its 6, andcheck-i18n-bundles.mjs4 of its 5, andcheck-app-nav-i18n.mjs3 of its 4.The doc comments that argued for the old number ("inventing a second failure code
would be a new contract nobody asked for") were the strongest remaining statement
of a position #13983 already settled, so they are replaced rather than left to
contradict the code beside them.
The #13825 half — the wording, and its machine-checkable acceptance
The old sentence was true but prescribed
echo "EXIT=$?"without saying WHERE, soa reader doing the natural thing —
... | tail -4; echo "EXIT=$?"— readtail'sstatus, which is the exact false green the prescription exists to prevent. Per
triage (「抄现成的,⛔ 不要另写一版」) the file adopts #13429's converged paragraph
verbatim apart from this gate's own command. ⛔ No eleventh wording, and ⛔ nothing
appended to the old sentence: the old sentence is deleted.
Acceptance is the census, not the reading:
git grep -l "no pipe shape repairs it"— 7 files before, 8 after, the newone being
packages/cli/scripts/check-app-nav-i18n.mjs. That grep now reallydoes equal the whole repo; before this PR every "all unified" judgement made
with it was short by one.
git grep -n "the PIPE's status" -- 'scripts/**' 'packages/**'— 1 hit before,0 after (exit 1 from grep, captured before any pipe).
first explained the defect by quoting the old sentence, which silently put the
census-negative string back and would have left the acceptance criterion failing
while the code was correct. It is reworded, and the comment now says out loud that
the string must not be reintroduced even as a quotation.
Governed rider
.claude/skills/pm-dispatch/references/lanes/services.md:22read, verbatim:That sentence becomes false the moment these gates move, so it moves in the same
stroke — the minimal factual edit triage specified, 退 1 → 退 3, Chinese text kept
Chinese, nothing else in that file, one character of diff. It is the only place in
the repo keying on the old number (
git grep -n "退 1"returns exactly this line).Because the diff carries a
.claude/**edit this is a governed surface PR(AGENTS.md Prime Directive 14): opened as a draft, never flipped ready, and
auto-merge is never armed by this seat. The PM requests the human reviewers.
Changeset:
skip-changeset, and the reading behind itpackages/cli'spackage.jsondeclares"files": ["dist", "README.md", "CHANGELOG.md"].scripts/is not in it, sopackages/cli/scripts/check-app-nav-i18n.mjsis an unshipped dev gate rather thana shipped file whose advisory text reaches consumers — the npm tarball is
unchanged by this PR. The other three paths are repo-root
scripts/**and.claude/**, neither of which publishes from any released package. ⇒ the diffpublishes nothing, which is exactly what the label is for (AGENTS.md: the label is
for a diff that publishes nothing, never a substitute for a fix's patch
changeset). Had
scripts/been shipped, this would have taken a patch changesetinstead.
Verification
All 38 gate families this card owes, derived AFTER the edits from the real change
set (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack— 36 by path + 2 by change kind; the
--repoassertion checked against thischeckout's
origin), run on the final commitfcdbc6d2, over a workspacebuilt through the shared verify lock (
turbo run build, 67/67 then 70/70 tasks,both exit 0). Every exit code was captured BEFORE any pipe — redirect to a file,
read
$?, then look at the file.37 of 38 green (exit 0). The two gates this PR edits are among them, and they
answer their real questions rather than refusing:
check:i18n-coverage's own--self-testis part of that pass and it exercises themissing-CLI-build classifier directly ("the missing-CLI-build, i18n-rule and
per-config-failure classifiers all go red, stay distinct…"), so the refusal path
this PR moved is covered by the gate's own pins, not only by the live runs above.
Also green, and named because they are the families that read what this diff
touched:
check:self-test-wired,check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-merges,check:skill-frame-sync,check:doc-authoring,check:published-files,check:undeclared-dep-imports,check:entry-guard,check:parse-guard,check:watch-hint-literal,check:ratchet-remedy-authority,check:pnpm-filter-targets,check:cross-package-test-inputs,check:dual-build-cjs-loads,check:pm-dispatch-gates,node scripts/pm/bare-root-worklist.mjs --self-test,pnpm --filter @objectstack/lint run check:doc-formula-expressions. Pluspnpm check:nul-bytes(8070 files, clean) and a direct control-byte sweep of thefour changed files (
grep -naPover the C0 range, zero hits).1 of 38 NOT MEASURED, declared rather than counted green:
node scripts/check-test-completeness.mjsreturns 3 —PREREQUISITE NOT MET — this gate grades a saved 'turbo run test' log, and no log was named. It does notrun tests and cannot produce one; CI tees the log and passes the path on every
invocation. That is a standing property of running it outside CI, identical for
every card, and it is NOT a red and NOT a pass.
pnpm lint(whole-repo ESLint) is CI's run and is not claimed here.🤖 Generated with Claude Code
https://claude.ai/code/session_01LAwHpn4uVuf4N1geBcD5i3
Generated by Claude Code