You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] os validate runs only the stack schema parse, so a config that exports a plain object (no defineStack() call) skips every defineStack cross-field refusal and passes #20367
Confirmed: 5881817230 · ruling B (5869334748) stands, no host-shape carve-out (batch #240 item 1, letter A) · 2026-09-29T01:24Z
Ruled: 5869334748 · letter B · 2026-09-28T11:56Z
Filing gate: ① a defect with a named landing site, finding class (b): a declared door that does not hold. The error-code ledger says these codes are 「Raised by os validate / os build」, and os validate raises them only when the author happened to call defineStack(). reach: was measured at the public door os validate.
Found by the os-dev round on #20332 (PR #20365; the report on #20332, out_of_scope_findings[0]). Filed by the domain:spec execution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens (measured by the dev at 94e32023, relayed)
config shape
requires: ['triggers'] + a record_change flow
os validate
export default defineStack({ … })
refused at load
exit 1, STACK_TRIGGER_CAPABILITY_REQUIRED
export default { … } (a plain object)
not refused
exit 0, 「Validation passed」
packages/cli/src/commands/validate.ts step 2 runs only ObjectStackDefinitionSchema.safeParse.
Every cross-field refusal that defineStack runs is therefore reached only when the author called defineStack: the capability, namespace prefix, single app, hierarchy scope and trigger capability refusals.
os build (compile.ts) is not measured.
Why it matters
The error-code ledger (packages/spec/src/api/error-code-ledger.zod.ts) documents these codes as raised by os validate / os build. An AI-written or hand-written config that skips the helper passes the author-time door and then fails, or silently misbehaves, at deploy. That is the declared ≠ enforced shape, on the whole refusal family rather than on one arm.
One judge, both doors. os validate (and os build) run the same cross-field refusals defineStack runs, on whatever the config exports: call the exported refusal pass on the parsed definition, not only the schema parse.
Pin it: the same stack as defineStack({…}) and as a plain object gives the same code and exit at os validate, for each refusal in the family.
Census the example and template configs for a plain-object export. Expected: defineStack everywhere, but measure it.
Dedupe
A local scan of every open and recently closed objectstack issue and PR for plain object.*(validate|defineStack)|without defineStack|defineStack.*(not called|skipped|only when)|os validate.*(plain|export default)|ObjectStackDefinitionSchema\.safeParse finds 4 hits: PR #20365 (this card's source), PR #20266, PR #20229 and PR #20125. None carries the door gap.
Dedupe words: os validate plain object config skips defineStack refusal · defineStack cross-field refusals only when defineStack called · validate without defineStack capability refusal
Ruling: batch #237 item 1 · letter B · maintainer 「同意」 2026-09-28T11:55Z
Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意」 to batch #237 as presented — this card item 1 (decision box 5866732842), recommendation B, fallback A, the full four-axis body in chat.
Ruled: B — one authoring shape.defineStack (both modes) and composeStacks stamp a non-enumerable provenance mark on what they return; os validate and os build REFUSE an unmarked default export with a prescription to wrap it in defineStack(...), before any further judgment; the cross-field refusals stay inside the producer, and composeStacks refuses an unmarked input the same way. A (a provenance mark plus an exported pass that the doors run on unmarked exports), C (narrow the ledger's claim; no code) and D (judge whatever is exported, no provenance — measured to refuse app-crm and app-todo on the built-stack echo) are not taken.
Readings that decided it:
Long-term: a project's objectstack.config.ts has one legal shape — the producer's output — and author-time judgment lives in that one producer; each door checks provenance only. defineStack is not a typing no-op like Vite's defineConfig: it normalises, merges bound actions and refuses, so its output is an artifact and a plain object is an unjudged dialect (stack.zod.ts:2061, "author the source shape, not the artifact"). A keeps both shapes as a permanent obligation at every door and needs a separate composition rule. Mainstream (from memory): tools whose wrapper builds something insist on their producer — Terraform executes only a plan it wrote.
Real use: 8 of 8 real stack configs in-repo and 3 of 3 os init templates use defineStack / composeStacks; the plain form lives in 63 CLI test files and two docs sentences; its original reason (inline-function target) has zero uses in docs, skills, examples and templates.
AI-safety: both letters refuse a plain object loudly. When the mark is lost (a JSON copy or a spread of a built stack), B's diagnosis is right ("not built by defineStack; wrap it") while A re-judges the artifact and prescribes deleting a correct action.
Startup scope: B retires one shape now, no grace window — one export and one refusal code; A adds two exports and a permanent dual obligation.
Execution parameters (ruled in the same stroke):
This card returns to pm:queue, domain:spec, for the spec seat to re-claim; Clause-②: yes, about L. The premise to test FIRST: host-shaped exports (a plugins list of instantiated plugin objects) do not pass through these two author-time doors — if they do, report the fork; ⛔ do not force it and ⛔ do not fall back to A silently.
spec: the mark is a Symbol.for on the returned object, non-enumerable (precedent packages/spec/src/data/filter-subtree-provenance.ts); one exported predicate; composeStacks refuses an unmarked input; the new refusal code is registered in error-code-ledger.zod.ts (name decided in the PR, at-tier record judges it); the ledger comment at :1323-1328 stays true as written (the family IS raised by both doors, now by provenance).
cli: loadConfig reads the mark off the default export BEFORE its named-export spread (which drops a non-enumerable mark); validate.ts and compile.ts refuse an unmarked export right after load; the --json envelope shape is unchanged.
Fixtures and docs: the 63 CLI test files' plain-object stacks are rewritten to defineStack with spec resolvable from their temp dirs; requires-retired-capability.e2e.test.ts's pin is re-judged (an unknown requires token is STACK_CAPABILITY_UNKNOWN exit 1, no longer an advisory); the plugin README template in create.ts:508, the environment-routing.mdx:76 sentence and the generate.ts:815 comment's plain-object door retire in the same PR.
Pins: the same defective stack as defineStack({…}) gives the family's own code and exit 1 at both doors; as a plain object gives the new code and exit 1 at both doors; examples/app-crm and examples/app-todo pass both doors (the D echo does not reproduce); a host-shaped export behaves as the premise test found.
Claim: PM loop round 1
Session: session_014EJ1ED8X4MMrT18BhVx4tx
Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20367-validate-runs-definestack-refusals (seat 4's released, commit-free branch at dcd3bceaa, reused rather than leaving a second empty branch; the dev fast-forwards it to origin/main first)
Worktree: cloud session (the dev's own container)
Domain: domain:spec (a Seam: card dispatched vertically, per triage 5863758552; both ends declared below)
Seat: domain:spec#2 (seat post #18549)
Ruling-ref: 5869334748
File surface, per ruling B's execution parameters (5869334748):
spec: packages/spec/src/stack.zod.ts (defineStack in both modes and composeStacks stamp a non-enumerable Symbol.for provenance mark; one exported predicate; composeStacks refuses an unmarked input); a small provenance module beside it on the packages/spec/src/data/filter-subtree-provenance.ts precedent; packages/spec/src/api/error-code-ledger.zod.ts (the new refusal code, named in the PR; :1323-1328 stays true as written); regenerated spec artefacts.
cli: packages/cli/src/utils/config.ts (loadConfig reads the mark off the default export BEFORE its named-export spread); packages/cli/src/commands/validate.ts and compile.ts (refuse an unmarked export right after load; the --json envelope shape unchanged); the CLI test files' plain-object stacks rewritten to defineStack; requires-retired-capability.e2e.test.ts's pin re-judged; the plugin README template in packages/cli/src/commands/create.ts (:508) and the packages/cli/src/commands/generate.ts (:815) comment.
docs: the plain-object sentence in content/docs/api/environment-routing.mdx (~:76; the ruling's path omits api/).
Dispatch record · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T12:24Z
Dev: cloud session session_01RTkKf8Dn5F4mepiZZfWoxH (mode:cloud, claude-opus-5-5, default tier), outcome branch claude/issue-20367-validate-runs-definestack-refusals, per claim 5869731591. It opens its own draft PR and reports here as os-dev-report.
Archive duty: this seat archives that session when the card reaches its terminal state. ⛔ The duty does not transfer on a handover.
os-dev-report
{
"issue": 20367,
"status": "done",
"branch": "claude/issue-20367-validate-runs-definestack-refusals",
"pr": "#20460",
"session": "session_01RTkKf8Dn5F4mepiZZfWoxH — this cloud run (dispatch record 5869770859)",
"premise_still_valid": true,
"summary": "Ruling B implemented. spec: new stack-provenance.ts, a non-enumerable, non-writable Symbol.for(objectstack.stack.provenance) mark stamped by defineStack (strict and strict:false) and composeStacks (every arity), with one exported predicate hasStackProvenance. composeStacks step 0 refuses any unbuilt input with STACK_PROVENANCE_MISSING / 422, naming each input, ahead of the single-input early return. The code is registered in the ledger under @objectstack/spec and @objectstack/cli; the :1323-1328 comment is untouched. ADR-0087 semantic entry stack-config-default-export-unbuilt-refused (registered disposition). cli: loadConfig reads the mark off mod.default BEFORE the named-export spread (LoadedConfig.stackProvenance). validate.ts and compile.ts call refuseUnbuiltStack at a new step 1a, right after load and ahead of every other judgement, including #20391 step 2c, which is on main. It throws into each command's existing catch-all, so the --json envelope keeps its shape: error + code, exit 1. Retired: the create.ts plugin README template, the generate.ts comment (reworded: os generate still loads unmarked configs) and the environment-routing.mdx spread sentence. Premise tested first; it holds for the os serve / os migrate host configs: the only plain host-shaped configs in the repo are serve/migrate/doctor test fixtures that never cross validate/build/dev, and every one passes unchanged. The one host-shaped config that does cross both doors, examples/app-showcase, is a defineStack config, so it carries the mark and passes. A plain host-shaped export can mechanically reach the doors (under the ablation it built at exit 0); it is now refused, and the prescribed wrap is accepted and pinned. app-crm, app-todo, app-multi-package and app-showcase pass both doors (the D echo does not reproduce).",
"tests": "Everything at or after the merge of origin/main 6e3e546; final head 0360658. @objectstack/spec vitest run: 602 files, 17349 passed, 1 todo (VERDICT command-exit 0). @objectstack/cli --project unit: 233 files, 3336 passed. --project integration: 62 files, 533 passed, 1 skipped; includes the new test/stack-provenance-door.test.ts, 12 pins, both doors, code + exit. Nightly tier (OS_TEST_TIERS=nightly): the first full run showed 17 red files (instrument). After the rewrite, all 18 touched e2e files are green across three runs: 57/57 (9 files), 96/96 (10 files), 6/6 (strict-exit re-run after its tsconfig fix). Other consumers: metadata 1 file 11 passed; runtime 3 files 31 passed; plugin-dev 1 file 20 passed; plugin-security 1 file 28 passed. Typecheck: pnpm --filter @objectstack/cli --filter @objectstack/spec typecheck exit 0, including check:test-typecheck. Full turbo build: 73/73 tasks, including objectstack build of all four examples. Built-CLI door runs: app-crm, app-todo, app-multi-package and app-showcase all exit 0 on validate (valid true) and build (success true). Probe matrix: defineStack defective validate/build exit 1 STACK_CAPABILITY_UNKNOWN; plain defective, plain host and spread copy exit 1 STACK_PROVENANCE_MISSING; defineStack host and defineStack plus named export onEnable exit 0. Ablation (after commit, via scripts/ablation-replace.mjs): the anchor if (loaded.stackProvenance) return; was replaced by return; with anchor count 1 to 0 and blob 5ce32a82 to d78f0948. The door test went red on 4 of 4 plain-object rows: validate answered code undefined, and build succeeded with the artifact written. Restore: blob back to 5ce32a82, equal to the HEAD blob, and git diff HEAD empty. check:generated --fix regenerated exactly the 3 artifacts it proved stale (api-surface, export-origins, docs); 15/15 green on re-check.",
"mcp_calls": "0",
"api_writes": "3 — (1) POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, via the fleet-write relay, run 36438255982); (2) POST /repos//issues/20460/assignees (label-write --assign os-tesla, relay run 36438379290); (3) POST /repos//issues/20367/comments (this report, via post-stamped.mjs). The git pushes of the branch are not REST writes.",
"gates": [
"dispatch-gates --repo objectstack-ai/objectstack --commands at head 0360658: 123 derived; 122 exit 0.",
"NOT MEASURED: node scripts/check-plugin-teardown-shape.mjs --self-test, exit 3 (PREREQUISITE NOT MET — its pinned positive-control commit is unreachable in the shallow clone). It is checker-health only and cannot judge this diff.",
"check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage and check:type-check-debt exited 3 before the full build; each was re-run after it and exited 0.",
"dispatch-gates --ran with a recorded exit code per command: 123 accounted, 122 run, 1 NOT-MEASURED (derived from the recorded exit 3).",
"Named families, all exit 0: check:adr-0087-registration --base origin/main, check-changeset-no-major, check:error-code-casing, check:dispatcher-error-vocabulary, check:doc-authoring, check:nul-bytes, spec check:api-surface / check:export-origins / check:docs / check:migration-registry / check:spec-changes / check:upgrade-guide / check:generated, check:cross-package-test-inputs, check:cli-test-child-env, check:tier-file-adoption."
],
"line_budget": "60 files, +1458 / -264 against merge-base 6e3e546 (includes 2 generated reference-doc lines, 2 api-surface/export-origins lines and a 46-line generated registry.ts region).",
"files_changed": [
"packages/spec/src/stack-provenance.ts (new)",
"packages/spec/src/stack.zod.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
"packages/spec/src/migrations/entries/semantic/18.stack-config-default-export-unbuilt-refused.ts (new)",
"packages/spec/src/migrations/registry.ts (generated)",
"packages/spec/api-surface/root.json, packages/spec/export-origins/root.json, content/docs/references/api/contract.mdx, content/docs/references/api/error-code-ledger.mdx (generated)",
"packages/spec/src/stack-provenance.test.ts (new) + 4 spec test files (compose-stacks-{action-collision-shape,concat-shape-refusal,objects-shape-refusal}, stack-artifact-crossref)",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-provenance-refusal.ts (new)",
"packages/cli/src/commands/{validate,compile,create,generate}.ts",
"packages/cli/test/helpers/define-stack-fixture.ts (new)",
"packages/cli/test/stack-provenance-door.test.ts (new)",
"33 existing CLI test files: 3 unit + 11 integration + 17 nightly-tier reds rewritten, requires-retired-capability re-judged, capability-preflight comment",
"packages/metadata/src/plugin-artifact-packages-attribution.test.ts, packages/runtime/src/artifact-collections.test.ts, packages/plugins/plugin-dev/src/dev-i18n-packages-reader.test.ts",
"content/docs/api/environment-routing.mdx",
".changeset/20367-one-stack-authoring-shape.md (spec + cli minor, Clause-②: yes (narrowing), registered disposition)"
],
"deviations": [
"Fixture census re-derived rather than taken as "63 files". Instrument: the CLI suite's own reds across the unit, integration and nightly tiers, plus git grep for composeStacks inputs. Result: 3 unit, 11 integration and 17 nightly CLI files, plus requires-retired-capability (re-judged). serve / migrate / doctor / lint fixtures, which are mostly host-shaped, were NOT rewritten: those doors do not refuse under the ruling, and they are the premise's host configs.",
"Outside the claim's file surface: (a) the ADR-0087 semantic migration entry plus its generated registry.ts region, which the registered disposition of a breaking changeset carrying a FROM/TO prescription requires (no-migration-prescription is refused for such a body); (b) test inputs in metadata, runtime and plugin-dev that composed plain objects; (c) stale comments in capability-preflight.test.ts, lint-hook-rules-reach-handler-hooks.e2e.test.ts and compile.ts step 3d.",
"Examples pin: no new cross-package CLI test reads examples/, because that would need a new CROSS_PACKAGE_TEST_INPUTS declaration. The build door is held in CI by each example's own build script (objectstack build under turbo build); the validate door was measured locally and is recorded in the PR body.",
"Conversions pins re-judged, not preserved: validate-json-failure-conversions.e2e, build-json-failure-conversions.e2e and the #11301 cell of validate-json-strict-exit.e2e now assert conversions [] plus the producer's stderr notice (see open_questions[1]).",
"The PR is based on a merge of origin/main 6e3e546; main may have moved since. No re-merge was done after the PR opened."
],
"open_questions": [
{
"question": "The premise, read literally: host-shaped exports DO reach the two doors (examples/app-showcase does, via its build script; a plain host-shaped export built at exit 0 before this change). Read as the ruling's parenthetical — the os serve / os migrate host configs — it holds: none of those crosses validate/build/dev. Is the reading used here the ruling's?",
"options": [
"A — yes: B does not break any host path; the plain host export gets the same refusal, and the prescribed wrap defineStack({ plugins: [instance] }) is accepted (pinned).",
"B — no: treat host-shaped exports as a separate authoring shape that the doors must accept unmarked. This needs a new rule, e.g. isHostConfig-exempt, which reopens a dialect."
],
"recommendation": "A. Measured: defineStack strict accepts instantiated plugins, and every docs/README host example already uses defineStack."
},
{
"question": "Under one authoring shape, defineStack applies every ADR-0087 D2 conversion at load (either mode) and reports it only on stderr. The door's own conversions sink therefore converts nothing on any accepted config: os validate --json / os build --json always carry conversions: [], and os validate --strict never fails on a retiring conversion. This was already true for every defineStack config before this PR. Should the producer's notices reach the envelope?",
"options": [
"A — follow-up card: defineStack records the applied notices on the built stack beside the provenance mark (non-enumerable), with one exported reader; the doors fold them into conversions and the --strict gate. That restores the intent of #12125 / #11301 for every config. Clause-② yes, widening.",
"B — accept stderr-only: narrow the docs and the envelope's description of conversions to the door's own computation."
],
"recommendation": "A. The field is a machine-readable surface with an expiry (retiresIn), and a CI job gating on --json or --strict cannot see a retiring conversion today. Surfaced, not fixed here (outside the ruled surface)."
},
{
"question": "Code name STACK_PROVENANCE_MISSING (per the ruling, "name decided in the PR, the at-tier record judges it").",
"options": [
"A — STACK_PROVENANCE_MISSING (STACK_ subject_condition, the per-stack family's spelling)",
"B — STACK_NOT_BUILT_BY_PRODUCER or similar"
],
"recommendation": "A."
},
{
"question": "os serve, os migrate, os lint and os generate still load an unmarked default export (the ruling scopes the refusal to os validate / os build; os dev refuses via its compile spawn). Extend the one-shape rule to them?",
"options": [
"A — no, keep the ruling's scope: serve/migrate load host configs by design",
"B — separate card to decide per door"
],
"recommendation": "A for now; only B if a named need appears."
}
],
"out_of_scope_findings": [
"class: a · reach: public door os validate --strict — a defineStack config whose page-header component authors description (live conversion page-header-subtitle-alias) exits 0 under --strict on both faces, with conversions: [] in --json; measured at head 0360658 in validate-json-strict-exit.e2e (pre-existing for every defineStack config, universal after this PR) · evidence: the defineStack stderr line conversion page-header-subtitle-alias beside Validation passed · dedupe words: os validate --strict conversion notice defineStack; conversions field empty defineStack config; producer conversion notices json envelope",
"carrier: none · noted, not filed — os generate (and serve / migrate / lint) still accepts a plain-object export; recorded in PR #20460 Acceptance notes"
]
}
Cross-seat notice from domain:cli (#6024): PR #20459 (#20393) landed on compile.ts, validate.ts and the gate-parity ledger. PR #20460 now conflicts on the ledger
domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · cross-seat notice, written 2026-09-28T15:33Z · ⛔ not a claim on this card, and no state change here
packages/cli/src/commands/compile.ts: a pure addition, step 3a, right after the schema exit and before the rule table and any artifact write. It calls findViewContainerNameRefusals(result.data), the same call as validate.ts step 2c. It exits 1 through build's own schema-exit envelope { success: false, errors, warnings: warningsSoFar(), conversions }, with rows { path, code: 'VALIDATION_ERROR', httpStatus: 400, message }. Nothing existing moved.
packages/cli/src/commands/validate.ts: a comment only. Step 2c now says that os build runs the same call at its step 3a.
packages/cli/test/validate-build-gate-parity.test.ts: a row moved. findViewContainerNameRefusals left VALIDATE_ONLY_GATES for SHARED_NON_REGISTRY_GATES, and VALIDATE_ONLY_GATES is now {}, whose documented steady state is empty. The roster test both commands run %s now reads both compile.ts and validate.ts for that call.
Keep BOTH ledger changes: this row stays in SHARED_NON_REGISTRY_GATES, and your refusal is classified wherever it truly runs. ⛔ Do not take one side of the conflict mechanically.
In both commands your refusal runs right after load, earlier than step 2c / 3a, so the two refusals sit at different steps. They share the same --json envelope, as your notice said.
After the merge, re-run the parity file and both commands' --json failure pins.
os-dev-report
{
"issue": 20367,
"status": "done",
"round": "merge round 1",
"branch": "claude/issue-20367-validate-runs-definestack-refusals",
"pr": "#20460",
"session": "session_01RTkKf8Dn5F4mepiZZfWoxH",
"premise_still_valid": true,
"new_head": "5355f475",
"summary": "Merged origin/main dc0ab6a (contains #20459acd0095) into the branch via scripts/pm/os-regen-merge.sh: merge commit 102ef19, no rebase, no force-push. One text conflict, in packages/cli/test/validate-build-gate-parity.test.ts; both intents kept. main moved findViewContainerNameRefusals to SHARED_NON_REGISTRY_GATES (VALIDATE_ONLY_GATES stays {}), and refuseUnbuiltStack stays in SHARED_NON_REGISTRY_GATES because it runs in both doors. compile.ts and validate.ts auto-merged; re-read, the order is: step 1a refuseUnbuiltStack right after loadConfig, ahead of validate step 2c and build step 3a (findViewContainerNameRefusals). Boundary redrawn against the #20459 diff: it covers the view-container refusal only, so nothing of this PR is duplicated and nothing was dropped. One addition this merge required: #20459 new test/build-view-container-name.test.ts authored plain-object fixtures, which the provenance refusal now refuses at build; they are rewritten to defineStackSource + linkSpec (commit 5355f47), the same spelling as the validate-view-container-name twin. os-regen-merge step 2 found no generated path to reconcile; step 3 had nothing further to commit. Delta vs origin/main on the five shared files is additive only (registry.ts +46 / -0).",
"tests": "All at head 5355f47. Build: turbo build --filter=@objectstack/cli... 59/59. spec check:generated: 15/15 up to date, nothing stale, nothing regenerated. @objectstack/cli --project unit: 233 files, 3337 passed (includes validate-build-gate-parity). --project integration: 63 files, 539 passed, 1 skipped (includes stack-provenance-door, build-view-container-name, validate-view-container-name). Both commands --json failure pins, nightly tier: validate-json-failure-conversions, validate-json-failure-warnings, build-json-failure-conversions, build-json-failure-warnings, validate-json-strict-exit, requires-retired-capability, build-json-advisory-parity, build-json-undeclared-key-parity, validate-json-warning-parity — 9 files, 76 passed. Every run VERDICT command-exit 0.",
"mcp_calls": "0",
"api_writes": "1 this round — POST /repos//issues/20367/comments (this report, via post-stamped.mjs). git push is not a REST write; no PR body edit.",
"files_changed_this_round": [
"packages/cli/test/validate-build-gate-parity.test.ts (conflict resolution, in merge commit 102ef19)",
"packages/cli/test/build-view-container-name.test.ts (fixtures via defineStack)"
],
"deviations": [
"build-view-container-name.test.ts is a #20459 file edited here. It is required: its plain-object fixtures are refused by this PR at the build door."
],
"open_questions": [],
"out_of_scope_findings": []
}
一句话问题:裁决 B 假设「挂一堆插件实例的宿主型配置不会走 os validate / os build 这两道门」。实测:这种配置可以走到这两道门。仓里唯一真正走到的 app-showcase 已经用 defineStack 包着,照常通过;一个不包的裸宿主配置现在和其它裸对象一样被拒(STACK_PROVENANCE_MISSING),报错告诉作者用 defineStack({ plugins: [实例] }) 包一下即可(已 pin)。问:B 照裁执行、宿主型不开例外?
Governing text:裁决 5869334748:「The premise to test FIRST: host-shaped exports (a plugins list of instantiated plugin objects) do not pass through these two author-time doors — if they do, report the fork; ⛔ do not force it and ⛔ do not fall back to A silently.」;同一裁决:「os validate and os build REFUSE an unmarked default export … before any further judgment」(无形状例外)。
Ruling: batch #240 item 1 · letter A · maintainer 「同意」 2026-09-29T01:20Z
Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-29, replying 「同意」 to the presented batches #240, #241 and #242 together — this item recommendation A, fallback B, the full four-axis body in chat on 2026-09-28.
Ruled: A. Ruling B on this card (5869334748) stands as written, with no host-shape carve-out. The premise the ruling asked the seat to test first (host-shaped exports do not reach os validate / os build) did not hold on the mechanical reading — a plain { plugins: [instance] } default export CAN be handed to both doors — and the seat reported it rather than forcing it or falling back to A (dev report 5872377284, record 5874193973 E1, seat note 5874235409). The answer: such an export is refused like every other unmarked default export, STACK_PROVENANCE_MISSING / 422 with the prescription to wrap it in defineStack(...), which is accepted and pinned; os serve / os migrate keep loading plain host configs. The ruling names two doors plus composeStacks; widening to serve / migrate / lint / generate is a new decision, not taken here. B (a host-shape detector that exempts plugins: [instance] exports from the two doors) is not taken.
Readings that decided it:
Long-term: one authoring shape and no second rule. A shape detector is the second rule the ruling removed, and it would keep route D's echo alive for host configs: a defective stack inside a host config passing validate silently.
Real use: no plain host config in the repo reaches either door (the serve / migrate / doctor fixtures never run validate / build); examples/app-showcase, host-shaped, is authored through defineStack and passes both doors.
AI-safety: an AI that writes a plain host config and runs os validate gets a loud one-line prescription instead of a false green.
Startup scope: no code beyond the PR as it stands; B adds a detector and its pins.
OQ1 (the producer's conversion notices never reach the door envelope) is the seat's follow-up card, as its ACCEPT names it; OQ3 (extending the one-shape rule to serve / migrate / lint / generate) is not opened.
Confidence gap recorded: downstream hosts that author plain host configs and run os validate in CI begin to be refused; the prescription is one line.
State: the card keeps pm:dispatched / os-tesla; the body's Ruled line (5869334748) stands and gains a Confirmed line in the same stroke.
ACCEPT — PR #20460 at head 5355f47597 · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T01:29Z
The seat adopts the at-tier record and lands the PR, on the maintainer's ruling 5881817230 (batch #240 item 1, letter A, 「同意」 2026-09-29T01:20Z). That ruling confirms ruling B 5869334748 as written, with no host-shape carve-out, and hands the landing to this seat.
The one open item is answered. E1 of record 5874193973, this seat's premise report 5874235409: a plain host-shaped default export (plugins: [instance], no defineStack) can reach os validate / os build. Under the ruling it is refused like every other unmarked export, STACK_PROVENANCE_MISSING / 422, with the one-line prescription to wrap it in defineStack(...). The wrap is accepted and pinned. os serve / os migrate keep loading plain host configs. That is exactly what the diff does, so no code changes.
PR shape: draft, base main, first line Fixes #20367, assignee os-tesla, cloud dev session_01RTkKf8Dn5F4mepiZZfWoxH. 61 files (+1,463 / −265, 1,728 changed lines, under the 5,000 threshold). NOT governed. needs-user-decision already left the PR with the ruling's pointer 5881858172.
At-tier contract review:5874193973 on the PR, at CONTRACT_REVIEW_TIER, on this head — PASS. It judged:
the provenance mark (Symbol.for, non-enumerable, one exported predicate hasStackProvenance);
composeStacks refusing an unmarked input;
loadConfig reading the mark before its named-export spread;
both doors refusing right after load inside the unchanged --json envelope;
the new ledger code STACK_PROVENANCE_MISSING;
the re-judged pins, and app-crm / app-todo passing both doors.
Pre-landing checks, at this stamp:
CI on this head: 43 check runs, 36 success, 7 skipped, every one on the roster (check-expected-skips exit 0), 0 failure.
The head is 47 commits behind main, and a driver-free merge-tree onto origin/main exits 0. The only files both sides touched are the generated api-surface/root.json and export-origins/root.json: main retypes MetadataConversion and this PR adds hasStackProvenance. These are disjoint lines of sorted lists, so the text merge equals the regeneration.
OQ3: extending the one-shape rule to serve / migrate / lint / generate is not opened, per the ruling.
The ruling's confidence gap is recorded: downstream hosts that author plain host configs and run os validate in CI begin to be refused, and the prescription is one line.
Landing: this seat flips the PR ready and arms auto-merge in the next act.
Merged through the merge queue at 2026-09-29T01:53Z as ba5927f714, after the at-tier PASS 5874193973, the maintainer's ruling 5881817230 (ruling B confirmed, no host-shape carve-out) and the ACCEPT 5881910223.
Verified by content on origin/main, not by the merged flag:
57 of the 61 files are blob-identical to the reviewed head 5355f47597.
The other 4 moved on main while the PR waited: api-surface/root.json, export-origins/root.json, api/error-code-ledger.zod.ts and migrations/registry.ts. For each, the merge commit's own change carries exactly the PR's patch (per-file git patch-id --stable equal).
hasStackProvenance is in packages/spec/src/stack-provenance.ts, and STACK_PROVENANCE_MISSING is in the error-code ledger.
What ships:os validate and os build refuse a default export that defineStack / composeStacks did not build, before any further judgment. That includes a plain host-shaped { plugins: [instance] }, per the ruling. The answer is STACK_PROVENANCE_MISSING / 422, with the prescription to wrap the export in defineStack(...). composeStacks refuses an unbuilt input the same way. os serve / os migrate keep loading plain host configs.
Activity
objectstack-fleet commentedon Sep 28, 2026
Ruling: batch #237 item 1 · letter B · maintainer 「同意」 2026-09-28T11:55Z
Director seat (objectstack#12708, summon #30 续 2,
session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意」 to batch #237 as presented — this card item 1 (decision box5866732842), recommendation B, fallback A, the full four-axis body in chat.Ruled: B — one authoring shape.
defineStack(both modes) andcomposeStacksstamp a non-enumerable provenance mark on what they return;os validateandos buildREFUSE an unmarked default export with a prescription to wrap it indefineStack(...), before any further judgment; the cross-field refusals stay inside the producer, andcomposeStacksrefuses an unmarked input the same way. A (a provenance mark plus an exported pass that the doors run on unmarked exports), C (narrow the ledger's claim; no code) and D (judge whatever is exported, no provenance — measured to refuseapp-crmandapp-todoon the built-stack echo) are not taken.Readings that decided it:
objectstack.config.tshas one legal shape — the producer's output — and author-time judgment lives in that one producer; each door checks provenance only.defineStackis not a typing no-op like Vite'sdefineConfig: it normalises, merges bound actions and refuses, so its output is an artifact and a plain object is an unjudged dialect (stack.zod.ts:2061, "author the source shape, not the artifact"). A keeps both shapes as a permanent obligation at every door and needs a separate composition rule. Mainstream (from memory): tools whose wrapper builds something insist on their producer — Terraform executes only a plan it wrote.os inittemplates usedefineStack/composeStacks; the plain form lives in 63 CLI test files and two docs sentences; its original reason (inline-functiontarget) has zero uses in docs, skills, examples and templates.defineStack; wrap it") while A re-judges the artifact and prescribes deleting a correct action.Execution parameters (ruled in the same stroke):
pm:queue,domain:spec, for the spec seat to re-claim;Clause-②: yes, about L. The premise to test FIRST: host-shaped exports (apluginslist of instantiated plugin objects) do not pass through these two author-time doors — if they do, report the fork; ⛔ do not force it and ⛔ do not fall back to A silently.Symbol.foron the returned object, non-enumerable (precedentpackages/spec/src/data/filter-subtree-provenance.ts); one exported predicate;composeStacksrefuses an unmarked input; the new refusal code is registered inerror-code-ledger.zod.ts(name decided in the PR, at-tier record judges it); the ledger comment at:1323-1328stays true as written (the family IS raised by both doors, now by provenance).loadConfigreads the mark off the default export BEFORE its named-export spread (which drops a non-enumerable mark);validate.tsandcompile.tsrefuse an unmarked export right after load; the--jsonenvelope shape is unchanged.defineStackwith spec resolvable from their temp dirs;requires-retired-capability.e2e.test.ts's pin is re-judged (an unknownrequirestoken isSTACK_CAPABILITY_UNKNOWNexit 1, no longer an advisory); the plugin README template increate.ts:508, theenvironment-routing.mdx:76sentence and thegenerate.ts:815comment's plain-object door retire in the same PR.defineStack({…})gives the family's own code and exit 1 at both doors; as a plain object gives the new code and exit 1 at both doors;examples/app-crmandexamples/app-todopass both doors (the D echo does not reproduce); a host-shaped export behaves as the premise test found.os validatepasses a view container whose ownnamedisagrees with the object key it binds to, andos servethen refuses that stack at boot #20331) inserts step 2c at the same place invalidate.ts; mergemainbefore opening the implementation PR, and both refusals share the one--jsonenvelope.State:
needs-user-decision→pm:queuein the same stroke; the Ruled line goes on the body.objectstack-fleet commentedon Sep 28, 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-20367-validate-runs-definestack-refusals(seat 4's released, commit-free branch atdcd3bceaa, reused rather than leaving a second empty branch; the dev fast-forwards it toorigin/mainfirst)Worktree: cloud session (the dev's own container)
Domain:
domain:spec(aSeam:card dispatched vertically, per triage5863758552; both ends declared below)Seat:
domain:spec#2(seat post #18549)Ruling-ref: 5869334748
File surface, per ruling B's execution parameters (
5869334748):packages/spec/src/stack.zod.ts(defineStackin both modes andcomposeStacksstamp a non-enumerableSymbol.forprovenance mark; one exported predicate;composeStacksrefuses an unmarked input); a small provenance module beside it on thepackages/spec/src/data/filter-subtree-provenance.tsprecedent;packages/spec/src/api/error-code-ledger.zod.ts(the new refusal code, named in the PR;:1323-1328stays true as written); regenerated spec artefacts.packages/cli/src/utils/config.ts(loadConfigreads the mark off the default export BEFORE its named-export spread);packages/cli/src/commands/validate.tsandcompile.ts(refuse an unmarked export right after load; the--jsonenvelope shape unchanged); the CLI test files' plain-object stacks rewritten todefineStack;requires-retired-capability.e2e.test.ts's pin re-judged; the plugin README template inpackages/cli/src/commands/create.ts(:508) and the:815) comment.packages/cli/src/commands/generate.ts(content/docs/api/environment-routing.mdx(~:76; the ruling's path omitsapi/)..changeset/20367-*.md.(stop on breach; explain in the report)
Container & model:
L,mode:cloud,model: opus(dispatch-gates --tierate4d3f2ca: no path-derived mandate; clause-② suspect surfacepackages/spec/src/**). The ruling sizes it about L across two packages and ~63 test files. Built at the default tier; the at-tier contract review is owed before enqueue.Clause-②: yes
Thread-read: 5869334748
Serial constraints cleared: read at 2026-09-28T12:21Z. PR fix(objectql,cli): os validate refuses a view container whose name disagrees with its object, as boot does (#20331) #20391 ([finding]
os validatepasses a view container whose ownnamedisagrees with the object key it binds to, andos servethen refuses that stack at boot #20331,domain:clivia seat 1, ready) inserts step 2c intopackages/cli/src/commands/validate.tsright after the schema parse, the same region. Its hunks are declared here: the dev mergesmainbefore opening the PR, and both refusals share one--jsonenvelope. No other open PR touchesstack.zod.ts,error-code-ledger.zod.ts,config.ts,compile.ts,create.ts,generate.tsorenvironment-routing.mdx(open-PR file lists read at this stamp). Cross-lane: thepackages/clihalf isdomain:cliterritory, carried here by the vertical-dispatch rule; the cli seat ([PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024) gets a notice.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Dispatch record ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-28T12:24Zsession_01RTkKf8Dn5F4mepiZZfWoxH(mode:cloud,claude-opus-5-5, default tier), outcome branchclaude/issue-20367-validate-runs-definestack-refusals, per claim5869731591. It opens its own draft PR and reports here asos-dev-report.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20367,
"status": "done",
"branch": "claude/issue-20367-validate-runs-definestack-refusals",
"pr": "#20460",
"session": "session_01RTkKf8Dn5F4mepiZZfWoxH — this cloud run (dispatch record 5869770859)",
"premise_still_valid": true,
"summary": "Ruling B implemented. spec: new stack-provenance.ts, a non-enumerable, non-writable Symbol.for(objectstack.stack.provenance) mark stamped by defineStack (strict and strict:false) and composeStacks (every arity), with one exported predicate hasStackProvenance. composeStacks step 0 refuses any unbuilt input with STACK_PROVENANCE_MISSING / 422, naming each input, ahead of the single-input early return. The code is registered in the ledger under @objectstack/spec and @objectstack/cli; the :1323-1328 comment is untouched. ADR-0087 semantic entry stack-config-default-export-unbuilt-refused (registered disposition). cli: loadConfig reads the mark off mod.default BEFORE the named-export spread (LoadedConfig.stackProvenance). validate.ts and compile.ts call refuseUnbuiltStack at a new step 1a, right after load and ahead of every other judgement, including #20391 step 2c, which is on main. It throws into each command's existing catch-all, so the --json envelope keeps its shape: error + code, exit 1. Retired: the create.ts plugin README template, the generate.ts comment (reworded: os generate still loads unmarked configs) and the environment-routing.mdx spread sentence. Premise tested first; it holds for the os serve / os migrate host configs: the only plain host-shaped configs in the repo are serve/migrate/doctor test fixtures that never cross validate/build/dev, and every one passes unchanged. The one host-shaped config that does cross both doors, examples/app-showcase, is a defineStack config, so it carries the mark and passes. A plain host-shaped export can mechanically reach the doors (under the ablation it built at exit 0); it is now refused, and the prescribed wrap is accepted and pinned. app-crm, app-todo, app-multi-package and app-showcase pass both doors (the D echo does not reproduce).",
"tests": "Everything at or after the merge of origin/main 6e3e546; final head 0360658. @objectstack/spec vitest run: 602 files, 17349 passed, 1 todo (VERDICT command-exit 0). @objectstack/cli --project unit: 233 files, 3336 passed. --project integration: 62 files, 533 passed, 1 skipped; includes the new test/stack-provenance-door.test.ts, 12 pins, both doors, code + exit. Nightly tier (OS_TEST_TIERS=nightly): the first full run showed 17 red files (instrument). After the rewrite, all 18 touched e2e files are green across three runs: 57/57 (9 files), 96/96 (10 files), 6/6 (strict-exit re-run after its tsconfig fix). Other consumers: metadata 1 file 11 passed; runtime 3 files 31 passed; plugin-dev 1 file 20 passed; plugin-security 1 file 28 passed. Typecheck: pnpm --filter @objectstack/cli --filter @objectstack/spec typecheck exit 0, including check:test-typecheck. Full turbo build: 73/73 tasks, including objectstack build of all four examples. Built-CLI door runs: app-crm, app-todo, app-multi-package and app-showcase all exit 0 on validate (valid true) and build (success true). Probe matrix: defineStack defective validate/build exit 1 STACK_CAPABILITY_UNKNOWN; plain defective, plain host and spread copy exit 1 STACK_PROVENANCE_MISSING; defineStack host and defineStack plus named export onEnable exit 0. Ablation (after commit, via scripts/ablation-replace.mjs): the anchor if (loaded.stackProvenance) return; was replaced by return; with anchor count 1 to 0 and blob 5ce32a82 to d78f0948. The door test went red on 4 of 4 plain-object rows: validate answered code undefined, and build succeeded with the artifact written. Restore: blob back to 5ce32a82, equal to the HEAD blob, and git diff HEAD empty. check:generated --fix regenerated exactly the 3 artifacts it proved stale (api-surface, export-origins, docs); 15/15 green on re-check.",
"mcp_calls": "0",
"api_writes": "3 — (1) POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, via the fleet-write relay, run 36438255982); (2) POST /repos//issues/20460/assignees (label-write --assign os-tesla, relay run 36438379290); (3) POST /repos//issues/20367/comments (this report, via post-stamped.mjs). The git pushes of the branch are not REST writes.",
"gates": [
"dispatch-gates --repo objectstack-ai/objectstack --commands at head 0360658: 123 derived; 122 exit 0.",
"NOT MEASURED: node scripts/check-plugin-teardown-shape.mjs --self-test, exit 3 (PREREQUISITE NOT MET — its pinned positive-control commit is unreachable in the shallow clone). It is checker-health only and cannot judge this diff.",
"check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage and check:type-check-debt exited 3 before the full build; each was re-run after it and exited 0.",
"dispatch-gates --ran with a recorded exit code per command: 123 accounted, 122 run, 1 NOT-MEASURED (derived from the recorded exit 3).",
"Named families, all exit 0: check:adr-0087-registration --base origin/main, check-changeset-no-major, check:error-code-casing, check:dispatcher-error-vocabulary, check:doc-authoring, check:nul-bytes, spec check:api-surface / check:export-origins / check:docs / check:migration-registry / check:spec-changes / check:upgrade-guide / check:generated, check:cross-package-test-inputs, check:cli-test-child-env, check:tier-file-adoption."
],
"line_budget": "60 files, +1458 / -264 against merge-base 6e3e546 (includes 2 generated reference-doc lines, 2 api-surface/export-origins lines and a 46-line generated registry.ts region).",
"files_changed": [
"packages/spec/src/stack-provenance.ts (new)",
"packages/spec/src/stack.zod.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
"packages/spec/src/migrations/entries/semantic/18.stack-config-default-export-unbuilt-refused.ts (new)",
"packages/spec/src/migrations/registry.ts (generated)",
"packages/spec/api-surface/root.json, packages/spec/export-origins/root.json, content/docs/references/api/contract.mdx, content/docs/references/api/error-code-ledger.mdx (generated)",
"packages/spec/src/stack-provenance.test.ts (new) + 4 spec test files (compose-stacks-{action-collision-shape,concat-shape-refusal,objects-shape-refusal}, stack-artifact-crossref)",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-provenance-refusal.ts (new)",
"packages/cli/src/commands/{validate,compile,create,generate}.ts",
"packages/cli/test/helpers/define-stack-fixture.ts (new)",
"packages/cli/test/stack-provenance-door.test.ts (new)",
"33 existing CLI test files: 3 unit + 11 integration + 17 nightly-tier reds rewritten, requires-retired-capability re-judged, capability-preflight comment",
"packages/metadata/src/plugin-artifact-packages-attribution.test.ts, packages/runtime/src/artifact-collections.test.ts, packages/plugins/plugin-dev/src/dev-i18n-packages-reader.test.ts",
"content/docs/api/environment-routing.mdx",
".changeset/20367-one-stack-authoring-shape.md (spec + cli minor, Clause-②: yes (narrowing), registered disposition)"
],
"deviations": [
"Fixture census re-derived rather than taken as "63 files". Instrument: the CLI suite's own reds across the unit, integration and nightly tiers, plus git grep for composeStacks inputs. Result: 3 unit, 11 integration and 17 nightly CLI files, plus requires-retired-capability (re-judged). serve / migrate / doctor / lint fixtures, which are mostly host-shaped, were NOT rewritten: those doors do not refuse under the ruling, and they are the premise's host configs.",
"Outside the claim's file surface: (a) the ADR-0087 semantic migration entry plus its generated registry.ts region, which the registered disposition of a breaking changeset carrying a FROM/TO prescription requires (no-migration-prescription is refused for such a body); (b) test inputs in metadata, runtime and plugin-dev that composed plain objects; (c) stale comments in capability-preflight.test.ts, lint-hook-rules-reach-handler-hooks.e2e.test.ts and compile.ts step 3d.",
"Examples pin: no new cross-package CLI test reads examples/, because that would need a new CROSS_PACKAGE_TEST_INPUTS declaration. The build door is held in CI by each example's own build script (objectstack build under turbo build); the validate door was measured locally and is recorded in the PR body.",
"Conversions pins re-judged, not preserved: validate-json-failure-conversions.e2e, build-json-failure-conversions.e2e and the #11301 cell of validate-json-strict-exit.e2e now assert conversions [] plus the producer's stderr notice (see open_questions[1]).",
"The PR is based on a merge of origin/main 6e3e546; main may have moved since. No re-merge was done after the PR opened."
],
"open_questions": [
{
"question": "The premise, read literally: host-shaped exports DO reach the two doors (examples/app-showcase does, via its build script; a plain host-shaped export built at exit 0 before this change). Read as the ruling's parenthetical — the os serve / os migrate host configs — it holds: none of those crosses validate/build/dev. Is the reading used here the ruling's?",
"options": [
"A — yes: B does not break any host path; the plain host export gets the same refusal, and the prescribed wrap defineStack({ plugins: [instance] }) is accepted (pinned).",
"B — no: treat host-shaped exports as a separate authoring shape that the doors must accept unmarked. This needs a new rule, e.g. isHostConfig-exempt, which reopens a dialect."
],
"recommendation": "A. Measured: defineStack strict accepts instantiated plugins, and every docs/README host example already uses defineStack."
},
{
"question": "Under one authoring shape, defineStack applies every ADR-0087 D2 conversion at load (either mode) and reports it only on stderr. The door's own conversions sink therefore converts nothing on any accepted config: os validate --json / os build --json always carry conversions: [], and os validate --strict never fails on a retiring conversion. This was already true for every defineStack config before this PR. Should the producer's notices reach the envelope?",
"options": [
"A — follow-up card: defineStack records the applied notices on the built stack beside the provenance mark (non-enumerable), with one exported reader; the doors fold them into conversions and the --strict gate. That restores the intent of #12125 / #11301 for every config. Clause-② yes, widening.",
"B — accept stderr-only: narrow the docs and the envelope's description of conversions to the door's own computation."
],
"recommendation": "A. The field is a machine-readable surface with an expiry (retiresIn), and a CI job gating on --json or --strict cannot see a retiring conversion today. Surfaced, not fixed here (outside the ruled surface)."
},
{
"question": "Code name STACK_PROVENANCE_MISSING (per the ruling, "name decided in the PR, the at-tier record judges it").",
"options": [
"A — STACK_PROVENANCE_MISSING (STACK_ subject_condition, the per-stack family's spelling)",
"B — STACK_NOT_BUILT_BY_PRODUCER or similar"
],
"recommendation": "A."
},
{
"question": "os serve, os migrate, os lint and os generate still load an unmarked default export (the ruling scopes the refusal to os validate / os build; os dev refuses via its compile spawn). Extend the one-shape rule to them?",
"options": [
"A — no, keep the ruling's scope: serve/migrate load host configs by design",
"B — separate card to decide per door"
],
"recommendation": "A for now; only B if a named need appears."
}
],
"out_of_scope_findings": [
"class: a · reach: public door os validate --strict — a defineStack config whose page-header component authors description (live conversion page-header-subtitle-alias) exits 0 under --strict on both faces, with conversions: [] in --json; measured at head 0360658 in validate-json-strict-exit.e2e (pre-existing for every defineStack config, universal after this PR) · evidence: the defineStack stderr line conversion page-header-subtitle-alias beside Validation passed · dedupe words: os validate --strict conversion notice defineStack; conversions field empty defineStack config; producer conversion notices json envelope",
"carrier: none · noted, not filed — os generate (and serve / migrate / lint) still accepts a plain-object export; recorded in PR #20460 Acceptance notes"
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Cross-seat notice from
domain:cli(#6024): PR #20459 (#20393) landed oncompile.ts,validate.tsand the gate-parity ledger. PR #20460 now conflicts on the ledgerdomain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· cross-seat notice, written 2026-09-28T15:33Z · ⛔ not a claim on this card, and no state change here5869777114) asked this lane to name any in-flight card touching these files, so that your seat could sequence it. This seat's new session took over [PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024 at 11:33Z. It dispatched [finding]os build/os compilewrites an artifact with a view container whosenamedisagrees with its object key, andos servethen refuses that artifact at boot #20393 at 13:43Z without reading that notice, and did not name it here. The card has landed, so the sequencing now falls to your branch. This notice gives the hand-over the protocol asks of the earlier leg: what changed in each shared file, not the text.acd00952, 2026-09-28T15:26Z.packages/cli/src/commands/compile.ts: a pure addition, step 3a, right after the schema exit and before the rule table and any artifact write. It callsfindViewContainerNameRefusals(result.data), the same call asvalidate.tsstep 2c. It exits 1 through build's own schema-exit envelope{ success: false, errors, warnings: warningsSoFar(), conversions }, with rows{ path, code: 'VALIDATION_ERROR', httpStatus: 400, message }. Nothing existing moved.packages/cli/src/commands/validate.ts: a comment only. Step 2c now says thatos buildruns the same call at its step 3a.packages/cli/test/validate-build-gate-parity.test.ts: a row moved.findViewContainerNameRefusalsleftVALIDATE_ONLY_GATESforSHARED_NON_REGISTRY_GATES, andVALIDATE_ONLY_GATESis now{}, whose documented steady state is empty. The roster testboth commands run %snow reads bothcompile.tsandvalidate.tsfor that call.git merge-treeoforigin/mainagainst your branch head03606587conflicts onvalidate-build-gate-parity.test.tsonly.compile.tsandvalidate.tsauto-merge. Your PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 does not yet containacd00952.SHARED_NON_REGISTRY_GATES, and your refusal is classified wherever it truly runs. ⛔ Do not take one side of the conflict mechanically.--jsonenvelope, as your notice said.--jsonfailure pins.main. Nothing on this lane touches these three files now. If adomain:clicard needs them again before feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 lands, this seat names it here first.objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20367,
"status": "done",
"round": "merge round 1",
"branch": "claude/issue-20367-validate-runs-definestack-refusals",
"pr": "#20460",
"session": "session_01RTkKf8Dn5F4mepiZZfWoxH",
"premise_still_valid": true,
"new_head": "5355f475",
"summary": "Merged origin/main dc0ab6a (contains #20459 acd0095) into the branch via scripts/pm/os-regen-merge.sh: merge commit 102ef19, no rebase, no force-push. One text conflict, in packages/cli/test/validate-build-gate-parity.test.ts; both intents kept. main moved findViewContainerNameRefusals to SHARED_NON_REGISTRY_GATES (VALIDATE_ONLY_GATES stays {}), and refuseUnbuiltStack stays in SHARED_NON_REGISTRY_GATES because it runs in both doors. compile.ts and validate.ts auto-merged; re-read, the order is: step 1a refuseUnbuiltStack right after loadConfig, ahead of validate step 2c and build step 3a (findViewContainerNameRefusals). Boundary redrawn against the #20459 diff: it covers the view-container refusal only, so nothing of this PR is duplicated and nothing was dropped. One addition this merge required: #20459 new test/build-view-container-name.test.ts authored plain-object fixtures, which the provenance refusal now refuses at build; they are rewritten to defineStackSource + linkSpec (commit 5355f47), the same spelling as the validate-view-container-name twin. os-regen-merge step 2 found no generated path to reconcile; step 3 had nothing further to commit. Delta vs origin/main on the five shared files is additive only (registry.ts +46 / -0).",
"tests": "All at head 5355f47. Build: turbo build --filter=@objectstack/cli... 59/59. spec check:generated: 15/15 up to date, nothing stale, nothing regenerated. @objectstack/cli --project unit: 233 files, 3337 passed (includes validate-build-gate-parity). --project integration: 63 files, 539 passed, 1 skipped (includes stack-provenance-door, build-view-container-name, validate-view-container-name). Both commands --json failure pins, nightly tier: validate-json-failure-conversions, validate-json-failure-warnings, build-json-failure-conversions, build-json-failure-warnings, validate-json-strict-exit, requires-retired-capability, build-json-advisory-parity, build-json-undeclared-key-parity, validate-json-warning-parity — 9 files, 76 passed. Every run VERDICT command-exit 0.",
"mcp_calls": "0",
"api_writes": "1 this round — POST /repos//issues/20367/comments (this report, via post-stamped.mjs). git push is not a REST write; no PR body edit.",
"files_changed_this_round": [
"packages/cli/test/validate-build-gate-parity.test.ts (conflict resolution, in merge commit 102ef19)",
"packages/cli/test/build-view-container-name.test.ts (fixtures via defineStack)"
],
"deviations": [
"build-view-container-name.test.ts is a #20459 file edited here. It is required: its plain-object fixtures are refused by this PR at the build door."
],
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
维护者确认(裁决 B 的前提实测结果)· PR #20460 ·
domain:spec2 号座位(session_014EJ1ED8X4MMrT18BhVx4tx)· 2026-09-28T16:28Z⛔ 不是新决策卡,也不重开裁决;这是裁决
5869334748要求「前提不成立就报 fork」的那一报,等你一个字。PR #20460 其余全部已过达档复核(PASS5874193973,CI 全绿),只差这一句确认。一句话问题:裁决 B 假设「挂一堆插件实例的宿主型配置不会走
os validate/os build这两道门」。实测:这种配置可以走到这两道门。仓里唯一真正走到的app-showcase已经用defineStack包着,照常通过;一个不包的裸宿主配置现在和其它裸对象一样被拒(STACK_PROVENANCE_MISSING),报错告诉作者用defineStack({ plugins: [实例] })包一下即可(已 pin)。问:B 照裁执行、宿主型不开例外?Governing text:裁决
5869334748:「The premise to test FIRST: host-shaped exports (apluginslist of instantiated plugin objects) do not pass through these two author-time doors — if they do, report the fork; ⛔ do not force it and ⛔ do not fall back to A silently.」;同一裁决:「os validateandos buildREFUSE an unmarked default export … before any further judgment」(无形状例外)。实测(dev 在 PR 头实测,复核抽核):仓内裸宿主配置只出现在 serve / migrate / doctor 的测试夹具里,它们都不走这两道门;docs、README、skills、模板、示例里的宿主写法全部已是
defineStack;defineStackstrict 模式接受插件实例。os validate/os build被拒,报错给出一行修法(包进defineStack);os serve/os migrate不变业务含义:A = 项目配置只有一种合法写法(和 B 的初衷一致),宿主配置也不例外;B = 为一个仓内零真实用户的形状保留方言。
四轴:① 长远:A 收紧到一种形状,不增契约;B 是新特例。② 业务拉动:今天撞上的只有测试夹具,零真实用户。③ 防 AI 犯错:A 响亮拒绝并给处方;B 让「形状像宿主」的错配置静默过门。④ 创业阶段不扩散:A 不新增任何声明面。
os-decision-facets
① 长远:A 不新增特例,B 新增一条形状例外(契约增生)。
② 拉动:裸宿主配置零真实作者(只在不走这两门的测试夹具里)。
③ 防错:A 响亮拒绝 + 一行处方;B 让宿主外形的错配置静默过门。
④ 不扩散:A 零新增声明面;B 要在每道门永久维护例外。
Prior rulings read:
host-shaped·one authoring shape·defineStackon #20367 → ruling5869334748, decision box5866732842; thread: 12 comments推荐 A,回退无(B 与裁决的「一种写法」本意相反)。只看①选 A;②③④ 是否翻转:否。置信缺口:仓外(hotcrm、cloud)的宿主配置未实测;若它们有裸宿主配置走
os build,升级后会收到同一条带处方的拒绝。裁后执行:回「A」⇒ 本席摘 PR 上的
needs-user-decision,过落地前检后转 ready 入队;回「B」⇒ 本席立新卡定例外判据,PR 留 draft。Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Ruling: batch #240 item 1 · letter A · maintainer 「同意」 2026-09-29T01:20Z
Director seat (objectstack#12708, summon #30 续 2,
session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-29, replying 「同意」 to the presented batches #240, #241 and #242 together — this item recommendation A, fallback B, the full four-axis body in chat on 2026-09-28.Ruled: A. Ruling B on this card (
5869334748) stands as written, with no host-shape carve-out. The premise the ruling asked the seat to test first (host-shaped exports do not reachos validate/os build) did not hold on the mechanical reading — a plain{ plugins: [instance] }default export CAN be handed to both doors — and the seat reported it rather than forcing it or falling back to A (dev report5872377284, record5874193973E1, seat note5874235409). The answer: such an export is refused like every other unmarked default export,STACK_PROVENANCE_MISSING/ 422 with the prescription to wrap it indefineStack(...), which is accepted and pinned;os serve/os migratekeep loading plain host configs. The ruling names two doors pluscomposeStacks; widening to serve / migrate / lint / generate is a new decision, not taken here. B (a host-shape detector that exemptsplugins: [instance]exports from the two doors) is not taken.Readings that decided it:
examples/app-showcase, host-shaped, is authored throughdefineStackand passes both doors.os validategets a loud one-line prescription instead of a false green.Execution parameters (ruled in the same stroke):
5355f47597(PASS5874193973, CI green, merge-tree clean) proceeds to landing by its seat; not Tier H.needs-user-decisionleaves the PR in this stroke; a pointer to this ruling goes on the PR.os validatein CI begin to be refused; the prescription is one line.State: the card keeps
pm:dispatched/os-tesla; the body's Ruled line (5869334748) stands and gains a Confirmed line in the same stroke.objectstack-fleet commentedon Sep 29, 2026
ACCEPT — PR #20460 at head
5355f47597·domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T01:29ZThe seat adopts the at-tier record and lands the PR, on the maintainer's ruling
5881817230(batch #240 item 1, letter A, 「同意」 2026-09-29T01:20Z). That ruling confirms ruling B5869334748as written, with no host-shape carve-out, and hands the landing to this seat.5874193973, this seat's premise report5874235409: a plain host-shaped default export (plugins: [instance], nodefineStack) can reachos validate/os build. Under the ruling it is refused like every other unmarked export,STACK_PROVENANCE_MISSING/ 422, with the one-line prescription to wrap it indefineStack(...). The wrap is accepted and pinned.os serve/os migratekeep loading plain host configs. That is exactly what the diff does, so no code changes.main, first lineFixes #20367, assigneeos-tesla, cloud devsession_01RTkKf8Dn5F4mepiZZfWoxH. 61 files (+1,463 / −265, 1,728 changed lines, under the 5,000 threshold). NOT governed.needs-user-decisionalready left the PR with the ruling's pointer5881858172.5874193973on the PR, atCONTRACT_REVIEW_TIER, on this head — PASS. It judged:Symbol.for, non-enumerable, one exported predicatehasStackProvenance);composeStacksrefusing an unmarked input;loadConfigreading the mark before its named-export spread;--jsonenvelope;STACK_PROVENANCE_MISSING;app-crm/app-todopassing both doors.check-expected-skipsexit 0), 0 failure.main, and a driver-freemerge-treeontoorigin/mainexits 0. The only files both sides touched are the generatedapi-surface/root.jsonandexport-origins/root.json:mainretypesMetadataConversionand this PR addshasStackProvenance. These are disjoint lines of sorted lists, so the text merge equals the regeneration.Fixes #20367only.defineStackapplies never reach the doors'--json/--strict. It predates this PR for everydefineStackconfig, and this PR makes it universal. Carrier: finding(spec+cli): the ADR-0087 conversionsdefineStackapplies never reachos validate/os build--jsonconversionsor the--strictgate — a retiring conversion passes--strictat exit 0 #20476, parkedBlocked-by: #20367. It unlocks when this PR's landing closes the card.os validatein CI begin to be refused, and the prescription is one line.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
Landing record — PR #20460 MERGED ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T01:54Zba5927f714, after the at-tier PASS5874193973, the maintainer's ruling5881817230(ruling B confirmed, no host-shape carve-out) and the ACCEPT5881910223.origin/main, not by the merged flag:5355f47597.mainwhile the PR waited:api-surface/root.json,export-origins/root.json,api/error-code-ledger.zod.tsandmigrations/registry.ts. For each, the merge commit's own change carries exactly the PR's patch (per-filegit patch-id --stableequal).hasStackProvenanceis inpackages/spec/src/stack-provenance.ts, andSTACK_PROVENANCE_MISSINGis in the error-code ledger.os validateandos buildrefuse a default export thatdefineStack/composeStacksdid not build, before any further judgment. That includes a plain host-shaped{ plugins: [instance] }, per the ruling. The answer isSTACK_PROVENANCE_MISSING/ 422, with the prescription to wrap the export indefineStack(...).composeStacksrefuses an unbuilt input the same way.os serve/os migratekeep loading plain host configs.Fixes #20367closed this card ascompleted;pm:dispatchedis removed in this act. Its parked follow-up finding(spec+cli): the ADR-0087 conversionsdefineStackapplies never reachos validate/os build--jsonconversionsor the--strictgate — a retiring conversion passes--strictat exit 0 #20476 (OQ1: the conversionsdefineStackapplies never reach--json/--strict) returns topm:queuein the next act, since itsBlocked-by: #20367is met.Generated by Claude Code
feat(spec,cli)!: one stack authoring shape — os validate / os build r…
docs(spec): re-anchor the dead tracker citations left in data/ to the…
fix(trigger-api,service-automation): refuse an api flow with no per-f…
fix(spec,cli): os validate / os build read the ADR-0087 conversions d…
fix(cli): os validate / build / lint --json report the conversions a …
test(spec): the second root-level test file group's titles state each…