test(cli): derive the os build --json exit count from compile.ts, so an honest new exit cannot red main at night - #18966
Merged
os-support-ai merged 1 commit intoSep 18, 2026
Conversation
… so an honest new exit cannot red `main` at night
`build-json-failure-{warnings,conversions}.e2e.test.ts` each froze three
integers read off `packages/cli/src/commands/compile.ts` — 11 payload
literals, 10 `success: false`, 1 `success: true`. All six were correct; the
defect is the shape. Both files are nightly-tier by name, so the pull request
that adds an honest twelfth exit cannot collect them (measured: `OS_TEST_TIERS`
unset collects 267 files in this package with zero of these two among them,
`=nightly` collects 68 with both), merges green, and leaves `main` red until a
cron says so — the #18848 incident, pre-staged.
Ported from the shape PR #18880 landed on the `validate-json-failure-*` pair:
the count is derived (`toHaveLength(callSites.length)` against a word-boundary
`emitJson` call-site match over the same masked source the extractor reads),
the old integer is demoted to a `toBeGreaterThanOrEqual(10)` floor that rots
only in the exits-are-removed direction, and one partition assertion
(`success: true` ×1, `success: false` = `literals.length - 1`) replaces the two
frozen counts.
The verdict key was re-derived rather than copied: `compile.ts` spells it
`success:`, not the `valid:` of `validate.ts` — measured over the 11 masked
payload literals, `success: false` 10 / `success: true` 1 / `valid:` 0.
Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
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): |
os-support-ai
marked this pull request as ready for review
September 18, 2026 08:56
os-support-ai
enabled auto-merge
September 18, 2026 08:57
os-support-ai
deleted the
claude/issue-18894-build-json-failure-derived-counts
branch
September 18, 2026 09:21
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 #18894
Clause-②: no
What this changes
packages/cli/test/build-json-failure-warnings.e2e.test.tsandpackages/cli/test/build-json-failure-conversions.e2e.test.tseach froze three integers readoff
packages/cli/src/commands/compile.ts— 11 payload literals, 10success: false,1
success: true. All six were correct, and that is the defect: the shape, not a stalenumber. One honest new exit — the very thing these two files exist to enforce — turns all six
red, and because both files are nightly-tier by NAME they turn red only at night, hours after
the PR that did it has merged. That is #18848's
priority:p1pre-staged oncompile.ts.Ported from the shape PR #18880 landed on the sibling
validate-json-failure-*pair:toHaveLength(callSites.length)against a word-boundaryemitJsoncall-site match over the same masked source the payload extractor reads;toBeGreaterThanOrEqual(10), which rots only inthe exits-are-REMOVED direction;
success: true×1,success: false=literals.length - 1)replaces the two frozen counts.
⛔ No production file is touched. The diff is those two test files and nothing else —
compile.tsis card #18431's carrier (branchccdc9f5389) and is left alone.Premises, re-measured on
origin/mainbefore a line was writtenRe-derived with the tests' OWN extractor against the masked
compile.tsatf112a740bd(origin/mainat branch point), ⛔ not inherited from the card:Collection, re-measured in this worktree (
vitest list --filesOnly,packages/cli):OS_TEST_TIERSunset ⇒ 267 files, 0 of these two among them, lit controlartifact-packages.test.tspresent;=nightly⇒ 68 files, both present, lit controlabsent. Both files sit in the
integrationproject (--project unitcollects 0 of them).compile.tsspells its verdict keysuccess:.validate.tsspells itvalid:. Measuredover the 11 masked payload literals of
compile.ts:success: falsesuccess: truevalid: falsevalid: trueA verbatim port of #18880's two partition lines would have asserted over an empty
population. The lines here read
success:, and the reading is recorded in the code commentbeside them so the next author does not have to re-derive it.
The symmetry the derived form needs — every
emitJsonoccurrence enumeratedThe looser
/\bemitJson\s*\(/gcan only be trusted equal to the extractor'sawait emitJson(needle if nothing else in the file matches it. Enumerated with left contexton
compile.ts, 13 raw occurrences::43emitJson,— an import from../utils/format.js. No paren ⇒ matches neither side.:104,:694maskCommentsblanks both (#18520, applied to these two files by #18924)await emitJson(call sites, plus the multi-line ones ⇒ 11 payload literals⭐
emitJsonis IMPORTED here, never declared — there is nofunction emitJson(incompile.ts(grep: zero hits forfunction emitJson/const emitJson), so the looserpattern cannot over-count and red the pin by construction. Raw and masked agree: 11 literals,
11 call sites, both ways. That agreement is a reading, not a law, and the derived equality
is exactly where a future file that broke it would report.
The floor's anchor — 10, and why not 11
#18880 chose "the population the governing ruling was made over", not today's count. The
equivalent anchor here is the commit that landed each file's ruling, read out of git:
build-json-failure-warnings.e2e.test.tsd114d5e140(#12079)toHaveLength(10)— 9 failure + 1 successbuild-json-failure-conversions.e2e.test.tsconversions(#12125)79cf692b0e(#12285)toHaveLength(10)— "all nine of its failure exits" + 1Control for the anchor rule: the sibling
validate-json-failure-warnings.e2e.test.tswascreated at
33e81a5fe0(#12130) pinningtoHaveLength(6), and #18880's floor is6. Samerule, same arithmetic. ⇒ the floor here is 10. Today's eleventh exit is ADR-0130 D4's
per-package author-time rule leg (#14439), which arrived after the ruling — exactly what a
derived count absorbs and a frozen one trips over.
Ablation battery — every leg's mutation proven on disk, every restore proven by hash
Harness: a bash trap on EXIT, INT and TERM whose handler is the restore function, absolute paths, restore is
git checkout HEAD -- PATH(never a baregit checkout --), verified by comparinggit hash-objectagainst the HEAD blob hash and bygit diff HEADbeing empty. Each legprints the extractor's counts BEFORE and AFTER the mutation and a marker grep, so a no-op leg
is refused rather than read as green. All runs
OS_TEST_TIERS=nightly, exit codes capturedbefore any pipe. No rebuild is needed or done: both files read
compile.tsfrom disk and theCLI is loaded from
src/throughbin/run-dev.js, as each file's own header states.compile.ts11/10/1expected [ …(12) ] to have a length of 11(both files)an os build --json exit publishes no warnings/no conversionsawaitan emitJson( call site the payload extractor could not read: expected [ …(11) ] to have a length of 12publishes fewer exits than the ruling above was made over: expected 9 to be greater than or equal to 10⭐ Leg C is the one that decides it. A twelfth call site the payload extractor cannot read
leaves the frozen integer green and reddens the derived equality. The frozen form is not a
parallel option — it is strictly weaker. Leg A is the mirror: the honest exit this contract
exists to welcome now stays green instead of reddening
mainat 20:44Z.The "old" arms of A and C ran the pre-change files restored worktree-only with
git restore --source=f112a740bd -- PATH, hashes printed and matched against the BASE blobs,then reset with
git checkout HEAD -- PATH.skip-changeset, by measurement with controls both wayspackages/clifiles[]is["dist","README.md","CHANGELOG.md"];tsconfig.build.jsonhasrootDir: "src",include: ["src"]and anexcludethat drops every*.test.ts. Measuredagainst the freshly built
packages/cli/dist:dist/an emitJson( call site the payload extractor could not readpublishes fewer exits than the ruling above was made overABLATIONaccess matrix driftsrc/commands/compile.tswarningsSoFarruntime bundle failedbuild-json-failuredist/commands/compile.js:621, a comment compiled fromsrc/commands/compile.ts:698that names the test file. Not this diff's content;compile.tsis untouched.The positive controls prove the grep was reading
dist. Nothing this diff adds reaches anypublished path from any released package ⇒
skip-changeset, label applied on this PR.Comment 5726117707 on the card asked for a judgement on the byte-exact multi-line spread ORDER
pin, and warned its own probe was narrow. Re-derived here with a different predicate
(
expect(SRC)/SRC.match/SRC.indexOfacross all four files), which finds MORE than theseat's word probe did — including one in
validate-json-failure-conversions.e2e.test.ts, wherethe seat's probe scored 0 and recorded it as 未判:
build-json-failure-warnings.e2e.test.ts:536:537const warningsSoFar = () => [, and the 4-member spread ORDERbuild-json-failure-conversions.e2e.test.ts:450try {adjacencyvalidate-json-failure-warnings.e2e.test.ts:571:572validate-json-failure-conversions.e2e.test.ts:486Judgement: named, measured, and left to the seat — deliberately NOT done in this PR.
Against the lane's standing three-rung ruling (claim 5725386686 on #18520):
precondition is measured FALSE. Repo-wide, the only other reader of those four list
identifiers is
build-json-advisory-parity.e2e.test.ts, which is itself nightly-tierand compares with
new Set(...)— order-blind by construction. No per-PR sibling binds thisorder behaviourally or textually. ⇒ rung 1 does not authorise deleting it.
—— ⛔ 永不绑一个写死的数。」 It governs frozen numbers. The order pin binds a spelling.
⇒ rung 2 does not reach it; this PR discharges rung 2 for the counts, which is this card.
断言。」 Already satisfied: PR test(cli): mask comments in the nightly-tier readers of this package's own source, and derive the pins that bound a spelling #18924 put the shared
maskCommentsin place on both files. ⛔Not re-added here.
⇒ Under the ruling as written and as measured, the order pin in these two files is
currently compliant and needs no change. What is not settled is a rung-1 extension: may a
nightly-only pin bind a byte-exact spelling when no per-PR sibling binds the property at
all? That is a new question, not an application of the existing three rungs, and it sits
identically on the
validate-json-failure-*pair, which has no carrier card. Widening thisPR to answer it would convert half a family under a rule nobody has made yet. Handed to the
domain:cliseat as a finding with dedupe words.Verification
Measured on
git rev-parse --short HEAD=0eb90c0a91, in worktree/home/user/objectstack-issue-18894, branched fromorigin/mainatf112a740bd.OS_TEST_TIERS=nightly … --project integrationon both filespackages/cliWHOLE, default tierpnpm --filter @objectstack/cli test(barevitest run, ⛔ no--project)packages/cliWHOLE, nightly tierOS_TEST_TIERS=nightly pnpm --filter @objectstack/cli testpnpm --filter @objectstack/cli typecheckcheck:test-typecheck)pnpm --filter '@objectstack/cli^...' buildthenpnpm --filter @objectstack/cli buildnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, then--ranwith exit codespnpm lint(eslint . --no-inline-config), NOT narrowedNOT MEASURED (1):
pnpm check:dual-build-cjs-loadsexits 3 —PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. It needs a whole-repopnpm build; this worktree built only the@objectstack/cliclosure. ⛔ Recorded as NOT MEASURED,not as a pass and not as a red. CI's
Build Corejob builds the tree and measures it there.dispatch-gates --ranclassified it from the recorded exit code rather than from a claim.Two derivation caveats stated rather than hidden:
dispatch-gatesprinted a STALE TREEwarning (
origin/mainmoved 2 commits under this branch, touchingscripts/check-adr-0087-registration.mjsandscripts/check-single-claim-paths.mjs); thecommand list was re-derived after
git fetch origin mainand came back byte-identical, sothe 47 stands. And the tool's own footer is quoted rather than paraphrased: the 47 is not a
complete account of CI — 51 artifact-roster families, 11 wide-population families, 13
pending-changeset families and 5 path-scheduled CI jobs are outside it by construction.
Acceptance notes
Noted, not filed — observations from the two files, none of them a reproducible defect, a
contract violation, or a metadata-authoring trap:
describeblocks in both files are still titled with their originating card numbers(
#11772,#12125) while the assertions inside them now answer [finding] two more nightly-onlyemitJsonexit-count pins freeze 11/10/1 overcompile.ts— green today, one honest exit from rerunning the #18848 p1 #18894 too. Cosmetic;renaming a
describemoves every-tfilter that names it. Carrier: whoever next opensthese files for a behavioural change.
packages/cli/test/build-json-advisory-parity.e2e.test.tsis nightly-tier and is the onlyother reader of the four advisory-list identifiers; its
new Set(...)comparisons aredeliberately order-blind, so the family has no order coverage in the per-PR tier at all.
Recorded as the measurement behind the adjacent-axis position above, not as a defect.
Carrier: the
domain:cliseat, together with the rung-1 extension question.Generated by Claude Code