Skip to content

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 into
mainfrom
claude/issue-18894-build-json-failure-derived-counts
Sep 18, 2026
Merged

os-support-ai merged 1 commit into
mainfrom
claude/issue-18894-build-json-failure-derived-counts

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes #18894

Clause-②: no

What this changes

packages/cli/test/build-json-failure-warnings.e2e.test.ts and
packages/cli/test/build-json-failure-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, and that is the defect: the shape, not a stale
number. 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:p1 pre-staged on compile.ts.

Ported from the shape PR #18880 landed on the sibling validate-json-failure-* pair:

  • the count is derivedtoHaveLength(callSites.length) against a word-boundary
    emitJson call-site match over the same masked source the payload extractor reads;
  • the old integer is demoted to a floor, toBeGreaterThanOrEqual(10), which rots only in
    the exits-are-REMOVED direction;
  • one partition assertion (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.ts is card #18431's carrier (branch ccdc9f5389) and is left alone.

Premises, re-measured on origin/main before a line was written

Re-derived with the tests' OWN extractor against the masked compile.ts at
f112a740bd (origin/main at branch point), ⛔ not inherited from the card:

payload literals          11      the pins' 11 was correct
success: false            10      correct
success: true              1      correct
/\bemitJson\s*\(/g        11      symmetric with the extractor

Collection, re-measured in this worktree (vitest list --filesOnly, packages/cli):
OS_TEST_TIERS unset ⇒ 267 files, 0 of these two among them, lit control
artifact-packages.test.ts present; =nightly ⇒ 68 files, both present, lit control
absent. Both files sit in the integration project (--project unit collects 0 of them).

⚠️ The trap this card names — the verdict key, MEASURED not copied

compile.ts spells its verdict key success:. validate.ts spells it valid:. Measured
over the 11 masked payload literals of compile.ts:

needle count
success: false 10
success: true 1
valid: false 0
valid: true 0

A 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 comment
beside them so the next author does not have to re-derive it.

The symmetry the derived form needs — every emitJson occurrence enumerated

The looser /\bemitJson\s*\(/g can only be trusted equal to the extractor's
await emitJson( needle if nothing else in the file matches it. Enumerated with left context
on compile.ts, 13 raw occurrences:

where reading
:43 emitJson, — an import from ../utils/format.js. No paren ⇒ matches neither side.
:104, :694 prose inside comments — the shared maskComments blanks both (#18520, applied to these two files by #18924)
the other 10 await emitJson( call sites, plus the multi-line ones ⇒ 11 payload literals

emitJson is IMPORTED here, never declared — there is no function emitJson( in
compile.ts (grep: zero hits for function emitJson / const emitJson), so the looser
pattern 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:

file ruling landed by count pinned that day
build-json-failure-warnings.e2e.test.ts maintainer, 2026-08-25, option 1 (#11772) d114d5e140 (#12079) toHaveLength(10) — 9 failure + 1 success
build-json-failure-conversions.e2e.test.ts the same 2026-08-25 ruling applied to conversions (#12125) 79cf692b0e (#12285) toHaveLength(10) — "all nine of its failure exits" + 1

Control for the anchor rule: the sibling validate-json-failure-warnings.e2e.test.ts was
created at 33e81a5fe0 (#12130) pinning toHaveLength(6), and #18880's floor is 6. Same
rule, 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 bare git checkout --), verified by comparing
git hash-object against the HEAD blob hash and by git diff HEAD being empty. Each leg
prints 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 captured
before any pipe. No rebuild is needed or done: both files read compile.ts from disk and the
CLI is loaded from src/ through bin/run-dev.js, as each file's own header states.

leg mutation to compile.ts measured on disk after THIS branch the frozen 11/10/1
0 control none literals 11 · sites 11 GREEN 7/7
A honest 12th exit carrying both keys literals 12 · sites 12 GREEN 7/7 REDexpected [ …(12) ] to have a length of 11 (both files)
B 12th exit missing both keys literals 12 · sites 12 REDan os build --json exit publishes no warnings / no conversions
C 12th call site spelled without await literals 11 · sites 12 REDan emitJson( call site the payload extractor could not read: expected [ …(11) ] to have a length of 12 GREEN 7/7
D two exits deleted (truncation) literals 9 · sites 9 REDpublishes 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 main at 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 ways

packages/cli files[] is ["dist","README.md","CHANGELOG.md"]; tsconfig.build.json has
rootDir: "src", include: ["src"] and an exclude that drops every *.test.ts. Measured
against the freshly built packages/cli/dist:

probe direction hits in dist/
an emitJson( call site the payload extractor could not read new, this diff 0
publishes fewer exits than the ruling above was made over new, this diff 0
ABLATION new, this diff 0
access matrix drift positive control, lives in src/commands/compile.ts 1
warningsSoFar positive control 2
runtime bundle failed positive control 1
build-json-failure ⚠️ 1 hit — dist/commands/compile.js:621, a comment compiled from src/commands/compile.ts:698 that names the test file. Not this diff's content; compile.ts is untouched. 1

The positive controls prove the grep was reading dist. Nothing this diff adds reaches any
published path from any released package ⇒ skip-changeset, label applied on this PR.

⚠️ The adjacent axis — a POSITION, not a silent widening

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.indexOf across all four files), which finds MORE than the
seat's word probe did — including one in validate-json-failure-conversions.e2e.test.ts, where
the seat's probe scored 0 and recorded it as 未判:

file line what it binds byte-exactly
build-json-failure-warnings.e2e.test.ts :536 :537 const warningsSoFar = () => [ , and the 4-member spread ORDER
build-json-failure-conversions.e2e.test.ts :450 sink declaration + blank line + try { adjacency
validate-json-failure-warnings.e2e.test.ts :571 :572 the same two, 5 members
validate-json-failure-conversions.e2e.test.ts :486 the same adjacency ⇐ the seat's 0 was 未判, not absent

Judgement: 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):

  • Rung 1 — 「⛔ nightly-only 的 pin 永不绑一个逐 PR 兄弟已经绑着的字节精确拼写。」 Its
    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-tier
    and compares with new Set(...) — order-blind by construction. No per-PR sibling binds this
    order behaviourally or textually. ⇒ rung 1 does not authorise deleting it.
  • Rung 2 — 「⭐ nightly-only 的 pin 绑那条性质本身,从源码推导 —— 读出来的计数、分区、下限
    —— ⛔ 永不绑一个写死的数。」 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.
  • Rung 3 — 「⭐ 一个读原始源码文本的读者,要么能把代码和散文分开,要么就不该对位置/顺序下
    断言。」 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 maskComments in 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 this
PR to answer it would convert half a family under a rule nobody has made yet. Handed to the
domain:cli seat as a finding with dedupe words.

Verification

Measured on git rev-parse --short HEAD = 0eb90c0a91, in worktree
/home/user/objectstack-issue-18894, branched from origin/main at f112a740bd.

what how result
the two files, BEFORE the edit OS_TEST_TIERS=nightly … --project integration on both files 27 passed (27) — the pins were correct, as the card said
the two files, AFTER the edit same 27 passed (27)
packages/cli WHOLE, default tier pnpm --filter @objectstack/cli test (bare vitest run, ⛔ no --project) 267 files / 3485 tests passed
packages/cli WHOLE, nightly tier OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli test 68 files / 695 tests passed — the tier that actually collects these two files
typecheck pnpm --filter @objectstack/cli typecheck exit 0 (incl. check:test-typecheck)
dependency closure + package build pnpm --filter '@objectstack/cli^...' build then pnpm --filter @objectstack/cli build exit 0 both — required, several pins refuse at load without it
gate families node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, then --ran with exit codes 47 derived, 46 run green, 1 NOT MEASURED
whole-repo lint pnpm lint (eslint . --no-inline-config), NOT narrowed exit 0

NOT MEASURED (1): pnpm check:dual-build-cjs-loads exits 3PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. It needs a whole-repo pnpm build; this worktree built only the @objectstack/cli closure. ⛔ Recorded as NOT MEASURED,
not as a pass and not as a red. CI's Build Core job builds the tree and measures it there.
dispatch-gates --ran classified it from the recorded exit code rather than from a claim.

Two derivation caveats stated rather than hidden: dispatch-gates printed a STALE TREE
warning (origin/main moved 2 commits under this branch, touching
scripts/check-adr-0087-registration.mjs and scripts/check-single-claim-paths.mjs); the
command list was re-derived after git fetch origin main and came back byte-identical, so
the 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:

  • The describe blocks in both files are still titled with their originating card numbers
    (#11772, #12125) while the assertions inside them now answer [finding] two more nightly-only emitJson exit-count pins freeze 11/10/1 over compile.ts — green today, one honest exit from rerunning the #18848 p1 #18894 too. Cosmetic;
    renaming a describe moves every -t filter that names it. Carrier: whoever next opens
    these files for a behavioural change.
  • packages/cli/test/build-json-advisory-parity.e2e.test.ts is nightly-tier and is the only
    other reader of the four advisory-list identifiers; its new Set(...) comparisons are
    deliberately 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:cli seat, together with the rung-1 extension question.

Generated by Claude Code

… 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>
@os-support-ai os-support-ai added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 89c6ec52b56a24d94c5bdea57fb8a7a8db4273dapackageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] two more nightly-only emitJson exit-count pins freeze 11/10/1 over compile.ts — green today, one honest exit from rerunning the #18848 p1

2 participants