test(cli): pin the per-package leg's resolution context, both directions - #17724
Conversation
`compile.ts`'s `packageBodyAsStack` hands each package's body the artifact's own `packages[]` as resolution context, so a reference into an object a SIBLING package of the same artifact ships resolves while one no entry provides still errors. Neither half had an in-package pin: the firing direction was held only by `examples/app-multi-package`, and the non-degeneracy direction by a `packages/lint` control whose input is a local three-key replica of `packageBodyAsStack` -- green even if this command stops building that shape. One fixture measures both, because the discriminating fact is WHICH of two lookups on the package's object is reported. The top level carries the sibling object alone, so `objects` is present, `authoringRuleUnionStack` folds nothing (it fills absent keys only), and the union run never sees the referencing object's fields -- whatever the build reports about them comes from the per-package leg and nowhere else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
The case's docblock argued that the union author-time rule run cannot be the
reporter for `probe_order`'s two lookups. That argument is now three readings,
each reddening only this case and leaving the other six in the file green:
1. drop `packages[]` from `packageBodyAsStack` -> the equality receives BOTH
paths, `objects[0].fields.account.reference` first;
2. hand the per-package leg no `objects` -- the "skip the site per package"
option the ruling rejected -- and `os build` exits 0 with `success: true`,
so the union run is not a second reporter for these paths and nothing else
in the command catches the dangling reference either;
3. add `probeOrder` to the TOP-LEVEL `objects` and the union run does report
it, answering `error: 'author-time rules failed'` -- which is why that
string is asserted before the paths are read.
The fold-skip premise the fixture's placement relies on is not re-derived here
either: `src/utils/stack-collections.test.ts` already pins it BY IDENTITY on
this exact shape, so the docblock cites that pin instead of re-arguing it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
📓 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): |
Review of record — ACCEPTHead reviewed: Reviewed-by:
Gates and CI on this head
⭐ The read-only fence is verified by BLOB IDENTITY, not by inspectionThe claim fence made ⇒ byte-identical. No export was needed, ⭐ The pin actually RAN, and non-vacuity is proved by collection countThe file is Collection proved rather than assumed, and re-measured here: ⇒ 7 reported for 7 present. A case that had failed to collect would have shown 6. ⭐ The union-leg claim was TESTED, not repeated — and that was the whole askThe dead run's docblock argued that the union run cannot be the reporter for these paths. The dispatch order said to test that claim rather than restate it. The recovery kept the case's assertions byte-identical and replaced the argument with three measured ablation legs, each red on this one case alone (
⇒ leg B is the test of the claim. With the per-package leg not judging, the command goes green on a dangling reference, so the union run is not a second reporter — and leg C is the converse, which is why the error string is asserted before the paths are read. ⛔ The docblock now cites the in-package pin that already holds the fold-skip premise ( Restore proved by content on every leg: Accepted as reported
VerdictACCEPT. One file, test-only; the read-only fence verified by blob identity; the pin run directly on its own tier with collection proved; the union-leg claim converted from an argument into three measured legs with restores proved by content. Landing armed by this seat. Generated by Claude Code |
Fixes #17169
F1 of #17066's contract-review verdict.
packageBodyAsStack's ruled half had no in-package pin: its firing direction was held only byexamples/app-multi-packageunderBuild Core, and its non-degeneracy direction only bypackages/lint's CONTROL atvalidate-object-references.test.ts:245-254, which cannot reachcompile.tsacross the package boundary. This adds the missing in-package pin.Inside the fence the claim declared: one file,
packages/cli/test/build-multi-package-artifact.e2e.test.ts.packages/cli/src/commands/compile.tsis untouched — the pin needed no export, so the declaredClause-②: nostands unchanged.packages/specis untouched (read only).⛔ Not a defect in what landed. #17066 is correct and merged (
f89dd33cca); this card exists only because a non-blocking finding inside a PASS verdict on a merged PR has no carrier a sweep can reach.What the pin holds
One fixture (
CONFIG_PKG_REFS) and one case, asserting both directions in a single equality over ONE object's two lookups, so the discriminating fact is which of the two is reported:accounttargetsprobe_account, which a sibling package of the same artifact ships — ABSENT from the findings, because the leg resolved it through the artifact'spackages[](the ruled validate / lint / build accept a lookup or master_detail whosereferencenames an object that exists nowhere — the dangling target is found only at runtime #16611 pass-through);ghosttargetsprobe_nothing, which no entry ofpackages[]provides — PRESENT, because the leg still judges.Which leg spoke is asserted rather than assumed: the per-package exit carries
error: 'author-time rules failed for one or more packages', the union exit carrieserror: 'author-time rules failed'. That string is the only thing that tells the two exits apart from outside, so it is asserted before the paths are read.This was a recovery dispatch — what is re-derived, not inherited
The branch's first commit was pushed by a dev the container restart killed mid-flight. It opened no PR, so no CI had ever run on it, and it reported nothing — no gate union, no ablation, no premise confirmation. A pushed branch is not a report. Everything below was measured in this round.
Premise, re-derived on
origin/mainThe control shares the target's failure mode, not merely its channel: same
-c -imulti-alternative form, same pathspec, same ref — only the alternatives differ.The pin, actually run
The file is nightly-tier (
*.e2e.test.ts, selected only byOS_TEST_TIERS=nightly—scripts/nightly-tiers.mjs), so CI green on this PR does not cover it either way. Run directly, exit code captured before any pipe:Collection is proved, not assumed: the file carried 6
it(blocks onorigin/mainand 7 here, and the run reports 7. A "No test files found" run would have exited 1.Ablation — three legs, each reddening THIS case alone
Every leg: mutate, prove the edit landed on disk by occurrence count, run, restore with
git checkout HEAD --naming the ABSOLUTE path, prove the restore by content. Run at commite10a412e; the CLI is exercised throughbin/run-dev.js, which loadssrc/under tsx, so the mutation is live without a rebuild — and each leg's red is itself the proof that it was.packages[]frompackageBodyAsStack(the pre-#16611 shape)AssertionError: expected [ …(2) ] to deeply equal [ 'objects[0].fields.ghost.reference' ], received["objects[0].fields.account.reference", "objects[0].fields.ghost.reference"]objects— the "skip the site per package" option the ruling rejectedexpect(run.code).toBe(1)— received 0, payload{"success":true,...}probeOrderto the fixture's TOP-LEVELobjectsExpected: "author-time rules failed for one or more packages",Received: "author-time rules failed"Which cases fired, per leg:
Tests 1 failed | 6 passed (7)in all three, the failure always the new case, at line 291 (A), 278 (B), 286 (C). The other six tests in the file stayed green in every leg.The union-leg claim — tested, not repeated
The dead run's docblock argued that the union author-time rule run cannot be the reporter for these paths. Leg B is the test of that claim, and it holds: with the per-package leg no longer judging this package's objects,
os buildexits 0 withsuccess: trueon a config that still carries a danglingprobe_nothingreference. So nothing else in the whole command reports these paths — not the union run, not the writer, not the schema. Leg C is the converse: give the union run sight ofprobe_orderand it does report it, under its own error string. Together those two say the discriminator is real and the fixture's placement is what makes the reading a per-package-leg reading.The premise that placement rests on —
authoringRuleUnionStackfills only ABSENT keys, so a top level carryingobjectsis returned unchanged — is already pinned in-package, by identity, on exactly this shape:packages/cli/src/utils/stack-collections.test.ts:181-188. The commit on top of the inherited draft replaces the docblock's argument with these readings plus that citation, so the next editor reads a measurement rather than re-deriving an argument.Restore, proved by content
git checkout HEAD --naming the ABSOLUTE path (⛔ never the bare form, which restores from the index), then, per leg:An empty
hash-objectanswer is treated as FAILURE, not as "nothing to compare". A shelltrapon EXIT, INT and TERM with absolute paths covered the crash path; the hash comparison, not the trap firing, is the proof.Gates
Every reading in this section was taken at
f96e1099, this branch's final commit. The ablation legs above ran ate10a412e, one commit earlier; the only delta is the docblock, so no test verdict can move across it — and the whole union was re-run at the final head regardless.Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-written path list; the tool took its own change set from the merge base98c904a9b), then reconciled with--rancarrying each recorded exit code:NOT MEASURED:
pnpm check:dual-build-cjs-loads, exit 3. Its own printed verdict, which is what counts rather than the number:PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. … Run pnpm build first. ⛔ This is NOT a pass: nothing was measured.Twelve packages in this worktree have nodist(only the CLI's dependency closure was built). The narrowing is provable rather than asserted: the gate reads emitteddistbytes, and this diff puts nothing into anydist— see the publish-surface measurement below. CI checks out fresh and builds the whole tree, so it runs there.A second NOT MEASURED, declared rather than hidden:
packages/cli'stypecheckprogram does not compile the pin file. Measured with--listFilesrather than assumed —tsconfig.jsonlists it 0 times (controlsrc/commands/compile.ts: 1), whiletsconfig.test.jsonlists it 1 time (controltest/vitest-tiers-partition.test.ts: 1). The pin file's type coverage therefore comes fromcheck:test-typecheck(green), whose shrink-onlytest-typecheck-debt.jsonis unchanged: the 34 raw test-layer errors all belong to three pre-existing files (data-model-rules,i18n-extract-action-description,i18n-declared-surface-gate) and zero name this file.Also declared: the queue-tier
integrationproject (32 non-tier files) was not run locally. It excludes this file by construction, and no file imports it, so nothing in that population can read this change; CI runs it.Changeset:
skip-changeset, measured@objectstack/clideclaresfiles: ['dist', 'README.md', 'CHANGELOG.md']. Afterpnpm --filter @objectstack/cli build, grepping those 504 published files:Subjects zero, controls non-zero ⇒ nothing this diff changes publishes from any released package, which is exactly what
skip-changesetis for. Not the default for a bug fix — there is no fix here, only a pin.Acceptance notes
os buildexiting 0 withsuccess: trueon a dangling reference once the per-package leg stops judging — says the per-package leg is the only place this class is caught insideos build. The neighbouring half already has a carrier: os validate and os lint judge an EMPTY stack when a project declares its metadata only in packages[] — the ADR-0130 D4 union fold (authoringRuleUnionStack) is wired into os build alone #17069 (os validate/os lintjudge an empty stack on a packages-only config, so onlyos buildexercises the per-package half at all). Nothing new to file; recorded so the reading is not lost.objectsthat is not the union ofpackages[]— leaves the union run blind to the packages' extra objects, becauseauthoringRuleUnionStackfills only absent keys.resolveStackCollection's own docblock records why that is correct for every shape the platform emits (composeStacksflattens everything, so the top-level array already is the union), and this fixture is deliberately that out-of-contract shape in order to isolate the leg. No defect; carrier: none needed.compile.tsstill has no ADR anchor entry. The card leaves that open and this PR does not decide it — it would be a second file and a scope question for the seat.Generated by Claude Code