Uh oh!
There was an error while loading. Please reload this page.
feat(cli): carry the computed advisory lists on every os validate --json failure exit - #12130
Conversation
…` failure exit All five failure exits of `os validate --json` published strictly less than the run had already computed: two carried `ruleAdvisories` alone, three carried no advisory list at all. The text face points at `--json` for the full list, so a tree failing a later gate made that remedy unreachable. The strongest instance is the parse-failure exit, which dropped the undeclared-authoring-key findings computed PRE-parse specifically so they would survive an unrelated schema error. The success payload's five-list spread moves to one `warningsSoFar()` site that every exit reads, so the member order cannot drift between exits. Lists are CARRIED, never recomputed: each stays computed at the step that owns it, and `structuralWarnings` — computed below all five failure exits — therefore rides each of them empty. Ref #12047 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-trump
commented
Aug 25, 2026
Contract-review verdict: PASS (#12047)Reviewed by the contract-review chain at Reviewed: the full diff (all 3 files), the changeset, and the ruling lineage on #12047. Conformance to the family ruling (maintainer 2026-08-25 on #11772, option 1, inherited here under the same-family rule — the inheritance was recorded on #12047 before dispatch, so this review's job was shape-conformance, and the shape conforms):
Clearing Generated by Claude Code |
os-trump
commented
Aug 25, 2026
Landing completed by the contract-review chain — ready + auto-merge armed (merge queue lands it when CI is green). This supersedes the "landing stays with the owning seat" line in the PASS verdict above, on maintainer instruction (2026-08-25, live PM chat, verbatim and untranslated): 「审核通过你应该直接负责合并吧,还要等原始的项目经理吗」. Standing practice from here: a contract-review PASS on a non-governed code PR completes the landing in the same stroke. Generated by Claude Code |
Fixes#12047
os validate --jsonpublished strictly less than the run had already computedon all five of its failure exits. The text face prints its advisory blocks
ending
— re-run with --json for the full list, so on a tree that fails alater gate the remedy the notice names returned a payload without the list in
it — the "the remedy named is unreachable" shape of #11643 and #11391.
Route: the maintainer's 2026-08-25 ruling on #11772 (option 1 — every failure
exit carries the lists the run has ALREADY COMPUTED, so
warningsmeans thesame thing on every exit), inherited here under the same-family rule.
Re-derived at
2ba4329, not inherited from the cardThe card's line table was read at
c804f0ca5; the claim comment namedorigin/mainasa933ed7. Both were re-measured against the actual base ofthis branch,
2ba4329— which is itself two commits past the claim's reading:All eleven offsets in the card's table hold unchanged at
2ba4329(86 / 94 / 126 / 133 / 169 / 175 / 203 / 206 / 238 / 306 / 391), and the count
is confirmed: six
emitJsonexits,valid: falseon five of them andvalid: trueon one. The card's enumeration was accurate — reported asmeasured, and no figure was reconciled to it.
The parse-failure exit is confirmed as the strongest instance:
unknownKeyWarningsis computed at 86,safeParseruns at 90, and the exitthat dropped the list is at 94 — the hoist exists precisely so the finding
survives a schema error, and that payload discarded it four lines later.
The fork clause:
structuralWarningsdoes NOT change the answerTriage required this measured before implementing. Result: it is not a
semantic difference, and here is the measurement rather than the argument.
structuralWarningsis computed at line 238 — below every one of the fivefailure exits (94 / 133 / 175 / 206) and reachable to the catch-all only via
a throw later than the last computation. So under "carry what the run has
already computed" it contributes to zero failure payloads; it is a
non-participant, not a member whose meaning differs. It is a list of the same
class as the other four — a non-blocking advisory about the stack, gated by
--strict, already inside the success payload'swarnings— differing only inWHEN it becomes available, which is the axis
docWarningsandcapProviderWarningsalready differ on.Measured live on the fixture shape used by the pins (identical stack, no
appsand no
plugins, so a structural advisory genuinely fires):The same shape shows the structural advisory on the success exit and never on a
failure exit. That is an availability difference, which the ruling already
covers, so the ruling's reason holds for
validate.tsand no fork was raised.⛔ The tempting "fix" for that emptiness is to hoist the structural computation
above the exits so early payloads look fuller. That is option 2 wearing option
1's clothes — it would change what the command costs on its failure paths, and
it is pinned against.
What changed
The five failure payloads and the success payload all read one
warningsSoFar()site, hoisted out of thetrywith the four (now five)advisory bindings, in the success payload's existing member order:
warningsbeforeerrorserrorsruleAdvisorieserrorserrorsruleAdvisorieserrorThe success payload is unchanged in content; what changed is that a seventh
exit cannot be added with a different member order, and the two exits that
carried
ruleAdvisorieskeep those entries in the same leading position.Exit codes are untouched — every failure exit still exits 1, and
--strictstill reads the text face's own list, so
--json --strictreaches the verdictit did before.
⛔ No shared
emitJsonhelper was extracted. PR #12079 (#11772) is open andunmerged against
compile.ts, the other caller such a helper would serve;fold-or-serial was answered SERIAL at claim time. This change is local to
validate.ts.truncation-remainder-notices.test.ts— the shared file #12079also edits — needed no change here: its two
validate.tsanchors(
errors: ruleErrors,,errors: docErrors,) both survive verbatim.Verification
packages/cli/test/validate-json-failure-warnings.e2e.test.ts(new) drives allsix exits through the real CLI and asserts the classes each payload carries —
no fewer and NO MORE, which is what tells "carry what was computed" apart
from "compute everything at every exit". Plus an exhaustiveness pass over the
source: all six
emitJsonpayload literals carrywarnings, so a seventh exitcannot silently reopen the hole, with a positive control proving the extractor
can see a missing key before its negative is trusted.
Measured, on the final commit
473bcee2The mutation was confirmed on disk with anchored greps in both directions
before either run — the injected spelling counted to 0 and the two reverted
spellings counted back to 2 and 1 — so neither reading came from a no-op edit.
LEG C proves the green tree LEG A measured is byte-identical to the one that
ships, so the green is not a claim about a different tree. The ablation script
carries a
trap … EXIT INT TERMrestore, so a cap SIGTERM mid-ablation couldnot leave a mutated tree behind for the next measurement.
No rebuild sits between the legs, and that is a property of the measured path,
not an omission:
bin/run-dev.jsruns the CLI fromsrc/through tsx, sovalidate.tsis loaded from source by the child. Demonstrated rather thanassumed — a single CLI invocation against the patched source returned the new
payload with no build of
packages/clianywhere in between.⭐ 2 of the 10 stayed green in the ablated state, and they are named as
REGRESSION GUARDS, not as red-before evidence:
structural controlassertsthe SUCCESS payload, which this change leaves unchanged in content, and
the extractor produces a POSITIVE before its negative is trustedruns on syntheticinput. Both are anti-vacuity controls and are green in both states by
construction — that is what they are for.
Gates
Re-derived from the ACTUAL change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(3 paths, merge base2ba4329e4), whichnamed more families than the dispatch list did — the extras are run and
named here rather than reconciled away:
Green:
check:cli-test-child-env(not in the dispatch list) ·check:cross-package-test-inputs·check:test-source-alias·check:type-source-resolution·check:published-files·check:slot-lookup·check:type-check-coverage·check:engine-double-contract·check:where-matcher·check:query-options-erasure·check:changeset-gate-self-tests·check:objectui-changeset·check-adr-0087-registration·check-changeset-no-major·check-empty-changeset·check-ci-filter-parity·check-plugin-teardown-shape·docs-audit/check-affected-docs·release-rehearsal-clone --self-test·check:i18n·pnpm --filter @objectstack/cli typecheck·pnpm lint(FULL REPO, not narrowed)Every verdict above is read from the gate's own printed line with the exit code
captured before any pipe.
NOT MEASURED, declared rather than counted green:
check:type-check-debt— refused, not failed. Its--re-measurelegaborts because a workspace dependency of the ledgered packages
(
@objectstack/service-knowledge) has no built type entry point on disk, andthe script refuses outright rather than measure a different world. Nothing
was measured; CI runs it after building the full closure. Its structural half,
check:type-check-coverage, is green above.check:i18n-coverage— refused, not failed:COULD NOT MEASURE — 1 of 12 config(s) failed to lint, becauseexamples/app-showcaseimports@objectstack/connector-mcp, which "is installed but has no build output inthis worktree". 11 configs linted; nothing was compared, and the gate states
outright that its result "says NOTHING about whether any declared label went
untranslated". Unrelated to this change and clears with a full
pnpm build,which CI does. Its sibling
check:i18nis green above, after the@objectstack/clibuild that both of them had first REFUSED for("PREREQUISITE NOT MET — the workspace CLI is not built … Nothing was
checked"). ⛔ A refusal is not a pass, in either direction.
Declared deviation: the last four checks ran WITHOUT the shared verify lock
check:i18n,check:i18n-coverage, the@objectstack/clibuild+typecheck andpnpm lintwere run in the foreground without holdingscripts/pm/os-verify-lock.sh. Declared rather than quietly done, because thelock discipline is the repo's, not mine to relax silently.
Cause: three consecutive tickets returned
VERDICT queue-timeout (exit 99) · never acquired— nothing ran on any of them, ~27 minutes of queue for oneacquisition, against a container saturated by other agents (at the last
timeout, holder pid 10928 on
issue-11614/verify.shhad held 583s withissue-11666/ablation.shqueued ahead of me). The one ticket that DID acquireis the ablation pair above, which is the measurement that actually needed
serialising.
What was NOT relaxed is the checks' population:
pnpm lintran full-repo(
eslint . --no-inline-config, exit 0), not narrowed to the diff, so this greencovers the same surface CI's does. ⛔ Each of the three exit-99 tickets was
reported by the task harness as "exit code 0"; the lock's own VERDICT line is
the authority, and none of them is counted as evidence anywhere above.
Out of scope, filed
os validate --jsonandos build --jsondrop theconversionsfield on every failure exit, the same waywarningswas dropped #12125 —os validate --jsonandos build --jsondrop theconversionsfield on every failure exit, the same shape one FIELD over. Measured on two
runs differing by one character; filed unlabelled and unassigned, not fixed
here, because widening a second public payload field is its own
machine-contract decision rather than an extension of this ruling.
Generated by Claude Code