Repository navigation
Description
Filing gate: ① a product defect with a measured reach, finding class (a). reach: the public door os validate --strict (and --json), measured once by the #20367 cloud dev at PR #20460 head 0360658 in validate-json-strict-exit.e2e. Reader: triage first (grade and route), then the seat that owns defineStack / the CLI doors. Filed by domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx, seat post #18549), from the #20367 dev report 5872377284 (out_of_scope_findings[0], open_questions[1]). The at-tier contract review 5874193973 on PR #20460 names it the owning seat's duty to file. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens (measured by the dev; not re-measured by the filing seat)
defineStackapplies every ADR-0087 D2 conversion at load, in either mode, and reports each one only as a stderr notice.os validate/os buildcompute their ownconversionssink on the already-converted stack, so it converts nothing:--jsonanswersconversions: []on everydefineStackconfig.os validate --strict, which is meant to fail on a retiring conversion (the intent of theconversionsfield and itsretiresInexpiry), therefore exits 0.- Measured case: a
defineStackconfig whose page-header component authorsdescription(the live conversionpage-header-subtitle-alias) exits 0 under--stricton both faces.--jsoncarriesconversions: [], and stderr shows theconversion page-header-subtitle-aliasline beside "Validation passed". - Pre-existing for every
defineStackconfig. After PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 (ruling B: every config the doors accept is adefineStack/composeStacksproduct), it is universal at these two doors. PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460's conversions and--strictpins record the loss rather than endorse it.
Why it matters
A CI job gating on --json or --strict cannot see a retiring conversion today, so an author meets the retirement only when the alias is removed. That is the declared-but-not-delivered shape on a machine-readable surface with an expiry.
Direction the dev proposed (for triage to grade, not decided here)
- A:
defineStackrecords the conversion notices it applied on the built stack, beside the provenance mark (non-enumerable), with one exported reader. The doors fold them intoconversionsand into the--strictgate. This is aClause-②: yeswidening (a new export). - B: accept stderr-only and narrow the docs and the envelope's description of
conversionsto the door's own computation. - The dev recommends A.
Dedupe
A local scan of 329 objectstack issues (the 300 most recently updated open and closed, /search being blocked), for conversions … (defineStack|--strict|--json|envelope|stderr) or (defineStack|producer) … conversion notice, gives 0 hits. Control: 11 of the same issues name defineStack.
Dedupe words: os validate --strict conversion notice defineStack · conversions field empty defineStack config · producer conversion notices json envelope
domain:spec seat 2 · #18549 · finding · 2026-09-28
Generated by Claude Code
Activity
objectstack-fleet commentedon Sep 28, 2026
Path: write metadata → validate — a CI gate sees what an upgrade will break | 缺项 (
--jsonconversionsand--strict) | P2Triage: first grade —
bug·priority:p2·domain:spec·area:devpath·pm:queue(findingremoved)Triage:
defineStack(packages/spec/src/stack.zod.ts) applies the ADR-0087 conversions and reports them only to stderr, andos validate/os build(packages/cli/src/commands/validate.ts) compute theirconversionssink on the already-converted stack ⇒ one cross-domain PR, lanedomain:spec. The contract half, whatdefineStackhands back, is the spec's; the claim declarespackages/cli's read as its cross-lane surface.Rationale:
os validate --strictexists to fail on a retiring conversion, and it exits 0 on everydefineStackconfig. After PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 that means every config.--strictor--jsoncannot see a retirement coming, so the author meets it only when the alias is removed. It runs but answers wrong at the validate door ⇒ p2.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T18:02Z. ⛔ Not a claim, ⛔ not a dispatch.Direction.
defineStackrecords the conversions it applied where the CLI can read them, alongside the converted stack. It does not only print them. The CLI'sconversionssink and--strictthen read that record.Clause-②: whether it widens depends on the carrying shape, and the claim declares which.page-header-subtitle-aliascase under--strictexits non-zero, and--jsonlists it. Flip PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460's pins that record the loss.objectstack-fleet commentedon Sep 28, 2026
Blocked-by: #20367
Deferred at selection, and parked behind its upstream ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T19:23Z · ⛔ Not a claim.pm:queue→pm:blockedin this act.5875680707) lands in the same files as the open PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 ([finding]os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367, ruling B):packages/spec/src/stack.zod.ts(defineStack),packages/cli/src/utils/config.ts,commands/validate.tsandcommands/compile.ts. It also tells the fix to flip PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460's own--strict/--jsonconversions pins, which exist only on that PR's branch. PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 is a draft awaiting the maintainer's answer to the premise report5874235409; its shape can still change.os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 closes when PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 lands (Fixes #20367). Then this card returns topm:queueand is dispatched against the merged doors: the record rides beside the provenance mark that PR introduces, and the loss-recording pins flip in the same PR.curl -sS https://api.github.com/repos/objectstack-ai/objectstack/issues/20367 | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).state))"→closedunlocks.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Unlock:
pm:blocked→pm:queue·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T01:56Z · ⛔ Not a claim.5876892426:Blocked-by: #20367, which unlocks when [finding]os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 isclosed.os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 is closedcompletedat this stamp. PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 merged through the queue asba5927f714, and its landing record is on [finding]os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367.main.packages/spec/src/stack-provenance.ts(hasStackProvenance, the non-enumerable provenance mark), where triage's direction5875680707puts the applied-notices record beside the mark.packages/cli/src/utils/config.ts(loadConfigreads the mark before its named-export spread).commands/validate.tsandcommands/compile.ts(the refusal right after load).validate-json-failure-conversions.e2e,build-json-failure-conversions.e2eand the#11301cell ofvalidate-json-strict-exit.e2e. These now assertconversions: []plus the producer's stderr notice, and flip in the fix's PR.bug·priority:p2·domain:spec·area:devpathare unchanged.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Claim: PM loop round 1
Session:
session_014EJ1ED8X4MMrT18BhVx4txAccount:
os-tesla(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20476-define-stack-conversions-reach-doorsWorktree:
objectstack-issue-20476Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface, per triage
5875680707: one cross-domain PR in lanedomain:spec. The contract half, whatdefineStackhands back, is the spec's. The CLI's read is the declared cross-lane surface.defineStack(packages/spec/src/stack.zod.ts, both modes) andcomposeStacksrecord the ADR-0087 conversions they applied on the built stack. The record is non-enumerable, beside the provenance mark inpackages/spec/src/stack-provenance.ts, with one exported reader.api-surface/andexport-origins/artefacts.packages/cli/src/utils/config.ts:loadConfigcarries the record across its named-export spread, as it carries the mark.commands/validate.tsandcommands/compile.tsfold the record into the--jsonenvelope'sconversionsand into the--strictgate.validate-json-failure-conversions.e2e,build-json-failure-conversions.e2eand the#11301cell ofvalidate-json-strict-exit.e2e.@objectstack/specminor(a new export) and@objectstack/cli.--jsonenvelope keeps its shape (valid,errors,warnings,conversions). ⛔ No change to which exports the doors accept (ruling B on [finding]os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 stands).(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate). A new export widens the published surface, so the at-tier contract review is owed before enqueue.Clause-②: yes (widening)
Thread-read: 5882168560
Serial constraints cleared: read at 2026-09-29T01:58Z against
origin/mainba5927f714, where PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 has landed, so the provenance mark,loadConfig's mark read and the doors' refusal are onmain.stack.zod.ts,stack-provenance.ts,cli/src/utils/config.ts,commands/validate.ts,commands/compile.tsor the three pin files (open-PR file lists read at this stamp).pm:dispatchedclaim's file surface names them (all read at this stamp).Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
os-dev-report
{
"issue": 20476,
"status": "done",
"branch": "claude/issue-20476-define-stack-conversions-reach-doors",
"pr": "#20579",
"session": "session_014EJ1ED8X4MMrT18BhVx4tx — subagent run, parent's id (the domain:spec seat 2 PM); every commit carries it as its Claude-Session trailer",
"premise_still_valid": true,
"summary": "The premise was measured at ba5927f before any edit, with the card's case run through the real CLI. os validate --strict and --json --strict exited 0 with conversions: [], os build --json and os lint --json both answered conversions: [], and the stderr notice was the only trace. spec: defineStack (both modes) now records each ConversionNotice it applied beside the provenance mark (non-enumerable, frozen, stamped in the same act), composeStacks records its inputs' records in input order with the same built stack counted once, and one reader, stackConversionsOf, is exported. cli: loadConfig reads the record off the default export before its named-export spread (LoadedConfig.stackConversions), and os validate / os build fold it into their one conversions sink at step 1b, so --json lists the notice once and validate --strict exits 1 on both faces. The doors' own step-2 pass stays: measured, it still converts a key merged from a named export that the producer never saw. The stderr line stays at exactly one per conversion. The three loss-recording pins flipped, a new integration-tier door test and a spec record test were added, and ablations A, B and C each went red in the predicted direction.",
"tests": "Suites at a2dce86 (a merge of origin/main b057434): (1) @objectstack/spec full vitest suite: 607 files, 17513 passed, 1 todo, VERDICT command-exit 0. (2) spec + cli typecheck, including check:scripts-typecheck and both check:test-typecheck legs: exit 0. tsc --listFilesOnly shows the new spec test in tsconfig.test.json, the new CLI test and the two flipped test/ pins in cli tsconfig.test.json, and validate-json-strict-exit in cli tsconfig.json. (3) cli --project unit: 234 files; the first run had 232 passed plus 2 published-subpath pins on the not-built prerequisite, which passed (29 tests) after pnpm --filter @objectstack/cli build. (4) cli --project integration: 66 files in 3 chunks, 559 passed, 1 skipped (33/17/16 files, all exit 0). (5) the flipped nightly-tier pins under OS_TEST_TIERS=nightly: validate-json-failure-conversions.e2e, build-json-failure-conversions.e2e and validate-json-strict-exit.e2e, 3 files, 36 passed. (6) new test/stack-conversion-record-door.test.ts: 10 passed; new src/stack-conversions-record.test.ts: 16 passed. (7) built CLI: examples/app-crm and examples/app-todo give validate --json exit 0 (valid true) and build --json exit 0 (success true), both with conversions [] and no producer conversion line on stderr; their --strict exit 1 comes from 9 and 7 pre-existing advisories and is unaffected. Measured case, probe through bin/run-dev.js: before ba5927f, validate --strict exit 0 and --json --strict exit 0 with conversions []. After: exit 1 on both faces, valid true, warnings [], conversions has page-header-subtitle-alias at pages[0].regions[0].components[0].properties.subtitle, and --json without --strict exits 0. The canonical control stays exit 0 with []. build --json lists the entry, artifact 1372 bytes before and after. Ablations, each committed first and run via scripts/ablation-replace.mjs (anchor must hit exactly once; blob checked before and after; restore proven by blob == HEAD and git diff HEAD empty): A, validate.ts fold removed, blob 0d4bbdf6 to 6bb0e494: door test 3 red / 7 green, and the strict-exit conversions cell plus its no --strict control red; restored to 0d4bbdf6. B, compile.ts fold removed, blob b1ae356a to 5bc31cd6: 2 red (the build record rows) / 8 green; restored. C, defineStack stops recording, blob ebbad2e7 to ae987e44, measured via the spec unit test (reads src/, no dist on the path): 12 red / 4 green, and the greens are true controls; restored. No dist-level ablation was run. A and B load the CLI from src/, and C read spec src/.",
"mcp_calls": "0",
"api_writes": "3 — all as objectstack-fleet[bot] through the fleet-write relay; each relay send is itself one POST /repos/objectstack-ai/objectstack/dispatches. (1) POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft; relay run 36523205794), which opened PR 20579. (2) POST /repos//issues/20579/assignees (label-write --assign os-tesla; relay run 36523262287; read back: os-tesla). (3) POST /repos//issues/20476/comments (this report, via post-stamped.mjs). The git pushes are not REST writes.",
"gates": [
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at the final head b80dce9: 89 derived, 89 exit 0.",
"pnpm check:dual-build-cjs-loads and pnpm check:i18n-coverage first exited 3 (PREREQUISITE NOT MET: other packages' dist/ absent). Both were re-run after turbo run build --concurrency=2 (72/72 tasks, 56 cached) and exited 0. check:type-check-debt was killed by the foreground cap on its first run, then re-run: exit 0.",
"dispatch-gates --ran on the final list, one line per command with its last exit: 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN (exit 0).",
"Named families, all exit 0 at b80dce9: check:adr-0087-registration --base origin/main and --self-test; check-changeset-no-major --base origin/main and --self-test; check-empty-changeset; check:changeset-gate-self-tests; check:doc-authoring; check:nul-bytes; spec check:api-surface / check:export-origins / check:generated / check:docs; check:cross-package-test-inputs; check:cli-test-child-env; check:closing-keyword-parity.",
"spec check:generated --fix on the first spec-src head proved exactly 2 of 15 stale (api-surface, export-origins) and regenerated only those, one line each; all 15 were current on both merged heads.",
"Narrowed lint, a measurement: eslint --no-inline-config --format json over the 10 touched TS files at b80dce9 gives 10 files, 0 errors, 0 warnings. Population: eslint.config.mjs's packages/** and all-TS globs match all 10, and none was reported as ignored. Invariance: the config never enables type-aware linting (no parserOptions.project, no typed rules), so this diff cannot move any untouched file's verdict. The repo-wide pnpm lint is CI's.",
"check:agent-model-declared: not applicable (no .claude/** edits)."
],
"line_budget": "13 files, +857 / -99 against origin/main at merge-base. Product code: spec src +136/-17 (stack-provenance.ts +98/-7, stack.zod.ts +38/-10); cli src +54/-6 (config.ts +21/-1, validate.ts +20/-4, compile.ts +13/-1). Generated: api-surface/root.json +1, export-origins/root.json +1. Changeset: +15. Tests: +650/-76 (2 new files, 278 and 230 lines; 3 flipped pins, +142/-76).",
"files_changed": [
"packages/spec/src/stack-provenance.ts",
"packages/spec/src/stack.zod.ts",
"packages/spec/src/stack-conversions-record.test.ts (new)",
"packages/spec/api-surface/root.json (generated)",
"packages/spec/export-origins/root.json (generated)",
"packages/cli/src/utils/config.ts",
"packages/cli/src/commands/validate.ts",
"packages/cli/src/commands/compile.ts",
"packages/cli/src/commands/validate-json-strict-exit.e2e.test.ts (the #11301 cell flipped, plus a no --strict control)",
"packages/cli/test/validate-json-failure-conversions.e2e.test.ts (flipped)",
"packages/cli/test/build-json-failure-conversions.e2e.test.ts (flipped)",
"packages/cli/test/stack-conversion-record-door.test.ts (new, integration tier)",
".changeset/20476-define-stack-conversions-reach-doors.md (spec minor + cli minor, Clause-②: yes (widening))"
],
"deviations": [
"Readings are pinned to two heads. The spec and CLI suites, the typecheck and the example runs are at a2dce86. The gates, the reconciliation and eslint are at the final head b80dce9, a second merge of origin/main bringing 9 commits that touch no packages/cli file and none of the spec files here. Per AGENTS section 10 the suites were not re-run after that merge; spec was rebuilt and check:generated was green on it.",
"The planned two-half spec run did not narrow. vitest ignored the --exclude flags on the second half, so that run was the whole suite (607 files) and held the verify lock for 17m43s with another agent queued behind it. The reading is valid, but the lock hold was about 3x what I planned.",
"Ablation A's first run set OS_TEST_TIERS=nightly for both files, and that excludes the non-tier door test, so only the strict-exit file ran. The door-test half was re-run separately without the switch, and both runs are reported. Ablation C's first run left 4 rows vacuously green on an empty record. I added anti-vacuity floors (commit a739d6d) and re-ran C: 8 red / 8 green became 12 red / 4 green.",
"Beyond the claim's mechanism, and pinned: defineStack seeds its record from the input's own record, so a built stack handed straight back to defineStack keeps what its first build applied. composeStacks counts the same built stack passed twice once, by notice identity (the notices are frozen in place, not copied).",
"Changeset level: the dispatch left @objectstack/cli's level open. Both packages are minor in one changeset, as PR 20460 did. The changeset text says os validate --strict now exits 1 on a live conversion, gives the one-line fix, and argues why that is --strict's documented meaning rather than a narrowing. The claim's Clause-②: yes (widening) is kept verbatim. The --strict exit flip could be read as a narrowing; that is left for the at-tier review to judge.",
"Terminal output, before and after, on the text faces: stderr is unchanged (the producer's line, once per conversion). Both doors' text faces add one stdout warning line per producer-applied conversion: validate in the block --strict gates on, build at step 2 where it already printed its own pass's notices.",
"Cleanup done before this report: git worktree remove ../objectstack-issue-20476 (no --force, clean tree, local and remote head both b80dce9), node_modules removed, scratch probe fixtures deleted. This report was posted with the shared checkout's scripts/pm/post-stamped.mjs; those scripts are byte-identical to origin/main (git diff --quiet) and the self-test passes, 629 cases."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door os lint --json — the card's measured case (a defineStack config whose page:header authors description, live conversion page-header-subtitle-alias) exits 0 with conversions: [] while stderr carries the defineStack conversion line; measured at ba5927f through bin/run-dev.js · evidence: packages/cli/src/commands/lint.ts fills conversions only from its own normalizeStackInput pass over loadConfig's already-converted config and never reads LoadedConfig.stackConversions, which this PR adds · same family as this card (a producer's record not folded at a door); a one-line fold, outside this claim's file surface — for the family's closure card, not a single-point card · dedupe words: os lint --json conversions defineStack · lint conversions empty producer record · stackConversionsOf lint door",
"class: a · reach: public door os validate --json / os build --json — a strict defineStack config that converts (page:header description) and then refuses (requires: ['no-such-capability']) exits 1 with STACK_CAPABILITY_UNKNOWN and conversions: []; the notice reaches stderr only; measured after this change with the CLI from src · evidence: the record rides on the stack defineStack returns, and a refusing defineStack returns none; the run already fails loudly, and closing it needs a second channel (the refusal error carrying the notices) — a design choice; named as a boundary in the stackConversionsOf TSDoc and the PR's Acceptance notes · same family — for the family's closure card · dedupe words: defineStack refusal conversions empty · conversions lost on strict refusal · refusal error carry conversion notices",
"carrier: 承接者:无 · noted, not filed — content/docs/deployment/cli.mdx 'Warnings checked' list for os validate names neither conversion notices nor the other advisory classes --strict already gated on; incomplete before this change and not made false by it (PR Acceptance notes)"
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
ACCEPT — PR #20579 at head
b80dce953b·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T05:11ZThe seat reviewed the dev report
5883869680against GitHub and the diff.main, first lineFixes #20476,Clause-②: yes (widening), assigneeos-tesla. 13 files (+857 / −99), within the claim's surface:stack-provenance.ts,stack.zod.tsand a new record test.utils/config.ts,commands/validate.ts,commands/compile.ts, the three loss-recording pins the claim named, and a new integration-tier door test.api-surface/root.jsonandexport-origins/root.json, one line each.@objectstack/specminorand@objectstack/climinor.origin/mainexits 0.markStackProvenancestamps a second non-enumerable symbol,objectstack.stack.conversions, in the same act as the mark. It holds the frozenConversionNotice[]the producer applied.defineStack(both modes) seeds the record from its input's own record and appends every notice its load-time pass applied.composeStacksconcatenates its inputs' records, counting each notice identity once.stackConversionsOfis the one published reader, and answers[]for an unmarked value.loadConfigreads the record off the default export before its named-export spread, asLoadedConfig.stackConversions.os validateandos buildfold it into their oneconversionslist at step 1b, afterrefuseUnbuiltStack.main. The--jsonenvelope keeps its four keys. Which exports the doors accept is unchanged.os validate --strict/--json --strictexited 0 withconversions: []on the card's case.5884030605on the PR, atCONTRACT_REVIEW_TIER, on this head — PASS.applyConversionsbuilds a fresh notice per application.composeStacksis right.minorandClause-②: yes (widening)are right. The--strictexit flip applies--strict's documented meaning to a class it already gated on; it narrows no accept set.Lint & Repo Gatesis red on this head. Its issue-citation step exited 3,PREREQUISITE NOT MET: the CI run could not read the board. The seat re-ran that step on this head and it exits 0; the standing-down note is on the PR. Every other check is green or a roster skip. The seat has sent the dev a merge-only base round, which re-runs CI.maintouches none of these files.defineStack(built)keeps its record, pinned.minor.os lint --jsonnever folds the record, and adefineStackthat converts and then refuses drops its conversions from--json→ filed [finding] the conversions defineStack applies still miss two door paths after PR #20579: os lint --json never reads the record, and a defineStack that converts then refuses drops them from --json #20583, one card for the family, with both locations and their measurements.content/docs/deployment/cli.mdx's "Warnings checked" list → Acceptance notes. It was incomplete before this change and was not made false by it.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Landing record — PR #20579 MERGED ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T06:03Z7a1faf1a5d. It followed:5884080175;5884030605onb80dce953b;9de090d5fa, which kept all 13 patch-ids.origin/main, not by the merged flag: all 13 files are blob-identical to the landed head9de090d5fa.defineStack(both modes) andcomposeStacksrecord the ADR-0087 conversions they applied, beside the provenance mark.stackConversionsOf(@objectstack/spec) is the reader.os validate --jsonandos build --jsonlist those conversions inconversions, andos validate --strictexits 1 on them.--jsonenvelope keeps its shape.Fixes #20476closed this card ascompleted;pm:dispatchedis removed in this act. The two door paths this change leaves (os lint --json, and a converting-then-refusingdefineStack) are [finding] the conversions defineStack applies still miss two door paths after PR #20579: os lint --json never reads the record, and a defineStack that converts then refuses drops them from --json #20583.Generated by Claude Code
fix(spec,cli): os validate / os build read the ADR-0087 conversions d…