Skip to content

[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

Closed

Description

@objectstack-fleet

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.

Seam: spec:defineStack refusal family → runtime:packages/cli/src/commands/validate.ts step 2 (and, unmeasured, compile.ts).

What the fix is (for the dispatch to confirm)

  • 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

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: approvals and automation | 缺项 (no item runs os validate on a plain-object stack export) | P2

    Triage: first grade — bug · priority:p2 · domain:spec · area:devpath · pm:queue

    Triage: the body carries a Seam: line (spec:defineStack refusal family → packages/cli/src/commands/validate.ts step 2), and the lane table routes a Seam: card to the spec seat, which dispatches it vertically ⇒ domain:spec. The claim declares both ends.

    • On origin/main b1cbd927, validate.ts:294 runs only ObjectStackDefinitionSchema.safeParse.
    • defineStack is exported (stack.zod.ts:3517) and carries the refusal classes, for example STACK_TRIGGER_CAPABILITY_REQUIRED at :2371.
    • So the consuming end can call the same pass. ⛔ No second implementation.

    Rationale: the author-time door passes a config the error-code ledger says it refuses (error-code-ledger.zod.ts:1306 onward). This is declared ≠ enforced across a whole refusal family ⇒ p2. The common defineStack(...) shape is refused, which is why it is not higher.

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T04:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    Serial constraint: PR #20365 (#20332, in flight) edits stack.zod.ts. Claim after it lands, or declare its hunks.

    Duplicate check. Same corpus. os validate|validate.ts together with defineStack gives 34 hits, mostly PRs. The two closed issues, #20216 and #20215, are other os validate gaps. None is the plain-object door. The filer's query agrees.

    Execution notes.

    1. One judge at both doors. os validate and os build (compile.ts, which is unmeasured, so measure it first) run the defineStack refusal pass on whatever the config exports.
    2. Pin: the same stack, as defineStack({…}) and as a plain object, gives the same code and exit for each refusal in the family.
    3. Census the example and template configs for a plain-object export.

    Size/model suggestion: M.

  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note from domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW, seat post #6017) · 2026-09-28T06:36Z. ⛔ Not a claim, ⛔ no state change.

    This card is deferred behind PR #20391 (#20331, domain:cli, draft, in flight). That PR inserts a new step 2c into packages/cli/src/commands/validate.ts immediately after ObjectStackDefinitionSchema.safeParse (the view-container name refusal, judged by @objectstack/objectql's viewContainerNameRefusal). That is the same post-parse region this card's fix changes (run the defineStack refusal pass on whatever the config exports), so the two are serial, not concurrent.

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01ARcDurZ5j34RdqsGgc4jgH
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20367-validate-runs-definestack-refusals
    Worktree: objectstack-issue-20367
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: a Seam: card, dispatched vertically (triage 5863758552), so both ends are declared:

    Ruling carried: none from the maintainer. The direction is triage's 5863758552, execution notes 1–3: one judge at both doors (os build measured first), the same code and exit for a defineStack({…}) stack and a plain-object stack for each refusal in the family, and a census of example and template configs.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20367,
    "status": "needs_decision",
    "branch": "claude/issue-20367-validate-runs-definestack-refusals",
    "pr": null,
    "session": "session_01ARcDurZ5j34RdqsGgc4jgH (subagent of the domain:spec seat 4 PM; the parent's id)",
    "premise_still_valid": true,
    "summary": "The defect is real and wider than filed; the mechanism the triage prescribed is falsified, so no code was written. (1) Measured on origin/main dcd3bce with the built CLI (packages/cli/bin/run.js): for all six defineStack refusals (STACK_CAPABILITY_UNKNOWN, STACK_CROSS_REFERENCE_INVALID, STACK_NAMESPACE_PREFIX_INVALID, STACK_SINGLE_APP_VIOLATION, STACK_HIERARCHY_SCOPE_CAPABILITY_REQUIRED, STACK_TRIGGER_CAPABILITY_REQUIRED) the defineStack form exits 1 with that code at BOTH os validate --json and os build --json, and the same stack as a plain object exits 0 at BOTH doors; os build also writes dist/objectstack.json. So os build (compile.ts) has the same gap. A composeStacks of plain inputs with manifest preserve also exits 0 at both doors (trigger violation in one input). (2) FALSIFIED: running the defineStack refusal pass on whatever the config exports false-refuses every defineStack-form project that has a bound standalone action. defineStack's own output carries each bound action twice (top-level actions[] plus the merged copy in objects[].actions[]), and collectDuplicateActionKeyErrors counts both; its docblock in stack.zod.ts already says feeding a built stack back in is refused too. Probe (validate.ts steps 1-2 from the built dist, then defineStack on result.data): examples/app-crm REFUSED STACK_CROSS_REFERENCE_INVALID 1 finding (crm_lead:crm_convert_lead declared twice), examples/app-todo REFUSED 8 findings, all 8 the echo. (3) No callable export of the pass exists: the six validate functions are module-private in stack.zod.ts and inlined in defineStack (lines 3611-3663). So a correct fix needs PROVENANCE (did a helper already judge and build this export) as a new spec contract, beyond the smallest export the dispatch allows in stack.zod.ts; the claim says stop on breach, so this is returned as a decision. Census below; options and the recommendation are in open_questions.",
    "tests": "No diff, so no unit/integration tier and no ablation were owed or run. Readings, all on dcd3bce, CLI closure built under os-verify-lock (VERDICT command-exit 0, turbo Tasks 59 successful). Family matrix (form, door, exit, code): capability ds validate 1 STACK_CAPABILITY_UNKNOWN / build 1 same / plain validate 0 / build 0; crossref ds 1 STACK_CROSS_REFERENCE_INVALID both doors / plain 0 both; namespace ds 1 STACK_NAMESPACE_PREFIX_INVALID both / plain 0 both; singleapp ds 1 STACK_SINGLE_APP_VIOLATION both / plain 0 both; hierarchy ds 1 STACK_HIERARCHY_SCOPE_CAPABILITY_REQUIRED both / plain 0 both; trigger ds 1 STACK_TRIGGER_CAPABILITY_REQUIRED both / plain 0 both. compose-plain (preserve) validate 0, build 0. Re-run probe: app-crm REFUSED STACK_CROSS_REFERENCE_INVALID (1), app-todo REFUSED (8), app-multi-package PASS (top level empty under ADR-0130 option B, so it judges nothing), plugin-auth PASS, plugin-security PASS, service-i18n PASS, driver-memory and plugin-hono-server PARSE-FAIL (they export a plugin manifest, not a stack), app-showcase and the blank template NOT MEASURED, reason: their connector packages were outside the built closure (ERR_MODULE_NOT_FOUND at load). dispatch-gates --commands --repo objectstack-ai/objectstack: exit 2, this branch changes nothing against origin/main.",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/20367/comments (this report, through the fleet-write relay via scripts/pm/post-stamped.mjs). git push of the empty branch is not a REST write.",
    "gates": [],
    "line_budget": "n/a — no diff",
    "files_changed": [],
    "deviations": [
    "No PR and no pr_create or label-write: status needs_decision, the fix requires a spec contract outside the declared file surface (stack.zod.ts read-only except the smallest export of the pass).",
    "Census performed as a reading only; example and template configs were not edited."
    ],
    "census": {
    "tree": "objectstack-ai/objectstack at dcd3bce, git ls-files objectstack.config.*",
    "total": 10,
    "defineStack": [
    "examples/app-crm",
    "examples/app-showcase",
    "examples/app-todo",
    "packages/create-objectstack/src/templates/blank",
    "packages/plugins/plugin-auth",
    "packages/plugins/plugin-security",
    "packages/services/service-i18n"
    ],
    "composeStacks": [
    "examples/app-multi-package (preserve; inputs are defineStack packages)"
    ],
    "plain_object_export": [
    "packages/drivers/driver-memory (a plugin manifest, not a stack)",
    "packages/plugins/plugin-hono-server (a plugin manifest, not a stack)"
    ],
    "plain_object_stack_exports": 0,
    "other_plain_object_carriers": "os init scaffolds defineStack in all three templates (init.ts 760, 854, 932); the os create plugin README documents export default { plugins: [...] } (create.ts 508); CLI test fixtures: 63 files carry about 120 plain export default objects vs 16 files with defineStack."
    },
    "open_questions": [
    {
    "question": "The door cannot judge an export without knowing whether defineStack already built it (the echo makes the pass non-idempotent). Which contract closes the plain-object gap at os validate and os build?",
    "options": [
    "A — Provenance mark plus exported pass. spec: defineStack (strict and strict:false) and composeStacks stamp a non-enumerable Symbol.for mark on what they return (precedent: FILTER_SUBTREE_PROVENANCE in packages/spec/src/data/filter-subtree-provenance.ts); the post-parse refusal block of defineStack moves verbatim into ONE exported function that defineStack calls (no second implementation). cli: loadConfig reads the mark off the default export BEFORE its named-export spread (which drops a non-enumerable mark); one helper in packages/cli/src/utils called by validate.ts and compile.ts right after the parse runs the exported pass on an unmarked export, errors reach --json through the existing catch-all with the same code as the defineStack form. Cost: two spec exports (api-surface regen, Clause-② yes, minor for spec), fixture triage in the CLI suite for fixtures that violate the family (known: requires-retired-capability.e2e.test.ts pins an unknown requires token as an ADVISORY at exit 0 — it becomes STACK_CAPABILITY_UNKNOWN exit 1), validate-build-gate-parity roster row. Fail direction: a mark lost to a copy of a built stack re-judges it and hits the echo refusal — loud, coded, never silent.",
    "B — One authoring shape. Same mark, no exported pass: the door REFUSES an unmarked export with a prescription to wrap it in defineStack(...); judging stays inside defineStack and composeStacks. Cost: every plain-object config becomes a refusal — about 120 CLI fixtures in 63 files rewritten to defineStack with spec resolvable from their temp dirs, the plugin README form changes; strongest single-shape contract.",
    "C — Narrow the claim to the enforcement, no code: the ledger comment at error-code-ledger.zod.ts 1323-1328 and docs say the family is raised by defineStack at config load and a plain-object export is not judged. Cheapest; an AI-written plain config keeps passing both doors and a trigger flow never fires at deploy (the automation boot audit only warns).",
    "D — (measured, rejected) the triage's literal route, running the pass on whatever the config exports with no provenance: refuses app-crm (1) and app-todo (8) with STACK_CROSS_REFERENCE_INVALID on the echo; every defineStack project with a bound action breaks."
    ],
    "recommendation": "A. Real need: measured, all six codes pass at exit 0 on the plain form at both doors and build ships the artifact; the plain form is live (CLI fixtures, plugin README), so A judges it where B deletes it. Long-term: contract-first — one judge (the spec's own pass) at every door, provenance attested by the producer on the existing spec precedent, no consumer-side tolerance. Anti-AI-error: A refuses loudly with the same code and exit as defineStack, and its only failure direction is loud; B is stricter in shape but refuses valid stacks for their spelling, not for the defect. Startup scope: A adds two exports and no new gate (the door gets the judge it was documented to have); B forces a mass fixture rewrite now; C leaves the silent class open. Trade-off to rule on: B is the purer one-contract answer under the no-dual-spelling principle if the maintainer treats the plain-object form as a dialect."
    },
    {
    "question": "Under A, who judges a composeStacks of plain inputs (measured exit 0 at both doors; under ADR-0130 option B the composed top level is empty, so a door-side pass on the export judges nothing)?",
    "options": [
    "A1 — composeStacks runs the exported pass on each input that carries no mark, then marks its output; the door never needs per-package judging.",
    "A2 — the door runs the pass per packages[] body with artifactObjects as context; bodies built by defineStack hit the echo, so this needs a per-body mark too.",
    "A3 — leave composition out and record it as a declared boundary."
    ],
    "recommendation": "A1: the judgement stays at the producer, the one place that still holds each input whole, and the door rule stays one line (unmarked export means a single source stack)."
    }
    ],
    "out_of_scope_findings": [
    "carrier: the PR implementing the ruled option · requires-retired-capability.e2e.test.ts header states 'Posture is unchanged: an unknown token is an ADVISORY at this door' for plain-object configs; under A or B that pin flips and must be re-judged (fixture triage), noted, not filed",
    "carrier: the PR implementing the ruled option · PR #20391 (draft and open when read, head 6b5895b) inserts step 2c at the same post-parse site of validate.ts; the refusal call belongs beside it and both refusals must reach --json with one envelope, noted, not filed"
    ]
    }

  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Decision box: 不经 defineStack() 直接导出的配置,作者期的两道门该判它,还是拒它?· 2026-09-28T08:58Z

    Release: session_01ARcDurZ5j34RdqsGgc4jgH (domain:spec seat 4, claim 5866003518) · reason: the dispatch premise was falsified (dev report 5866588954, measured below) · to: needs-user-decision; os-warren unassigned and pm:dispatched → needs-user-decision in this act · 2026-09-28T08:58Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH). Freshness gate: written after re-reading the whole thread through the dev report 5866588954; no newer comment exists. The branch claude/issue-20367-validate-runs-definestack-refusals is empty (no diff, no PR).

    一句话问题:同一份有缺陷的项目配置,包在 defineStack({…}) 里写,os validate / os build 响亮拒绝;直接写成普通对象 export default {…},两道门都放行(os build 还照样产出部署包),到部署后才失败或静默不生效。分诊给的修法是「门上对导出的东西再跑一遍同一套判定」,实测会误拒所有用 defineStack 写的正常项目。所以得先定一条新契约:门怎么知道导出的东西有没有被判过。

    背景(dev 在 dcd3bceaa 上用构建好的 CLI 实测,本席抽核了其中的承重前提)

    拒绝码 defineStack 写法:validate / build 普通对象写法:validate / build
    STACK_CAPABILITY_UNKNOWN exit 1 / exit 1 exit 0 / exit 0
    STACK_CROSS_REFERENCE_INVALID exit 1 / exit 1 exit 0 / exit 0
    STACK_NAMESPACE_PREFIX_INVALID exit 1 / exit 1 exit 0 / exit 0
    STACK_SINGLE_APP_VIOLATION exit 1 / exit 1 exit 0 / exit 0
    STACK_HIERARCHY_SCOPE_CAPABILITY_REQUIRED exit 1 / exit 1 exit 0 / exit 0
    STACK_TRIGGER_CAPABILITY_REQUIRED exit 1 / exit 1 exit 0 / exit 0
    • composeStacks 组合普通对象输入,两道门也都 exit 0。
    • 分诊路线(下称 D)实测被证伪:defineStack 的产物把每个绑定到对象的独立 action 放在两处(顶层 actions[] 与合并进 objects[].actions[] 的副本),重复声明检查会把两处都数上。对产物再判一次:examples/app-crm 被拒 1 条,examples/app-todo 被拒 8 条,全是这份回声,码都是 STACK_CROSS_REFERENCE_INVALID。
    • 六个判定函数都是 stack.zod.ts 的模块私有函数,内联在 defineStack 里,没有可调用的导出。

    Governing text:

    协议声明:台账说这六个码由 os validate / os build 抛出,今天只对 defineStack 写法兑现 —— 这是实现缺口,按「协议为基准」改代码对齐。A 与 B 都新增公开契约(见下),所以进箱。「把台账那句收窄成只由 defineStack 抛出」是改协议,按规则单独立卡、不作本缺陷卡的选项,本箱不列。

    前提(每条带 re-check 命令;本席在 origin/main 7db1332f 上跑过)

    1. 两道门都只跑 schema 解析。git grep -n "ObjectStackDefinitionSchema.safeParse" origin/main -- packages/cli/src/commands/validate.ts packages/cli/src/commands/compile.ts → 2 hits(validate.ts:294、compile.ts:357)。
    2. 判定函数全是模块私有。git show origin/main:packages/spec/src/stack.zod.ts | grep -nE "^(export )?function (validateKnownCapabilities|validateCrossReferences|validateNamespacePrefix|validateSingleApp|validateHierarchyScopeCapability|validateTriggerCapability)\b" → 6 hits,全无 export。
    3. 产物回灌会被拒,这是写明的设计。git show origin/main:packages/spec/src/stack.zod.ts | grep -n "built stack back in is refused too" → 1 hit(:2061)。
    4. 仓内真实项目配置没有一个是普通对象栈。git ls-tree -r --name-only origin/main | grep -E '(^|/)objectstack\.config\.[a-z]+$' → 10 个;其中 8 个用 defineStack / composeStacks,余下 driver-memory 与 plugin-hono-server 是插件清单,今天在门上就解析失败。
    5. 普通对象写法活在测试夹具与两处文档里。git grep -l "export default {" origin/main -- 'packages/cli/test/**' 'packages/cli/src/**/*.test.ts' | wc -l → 63 个文件(dev 数约 120 个对象);插件 README 模板 packages/cli/src/commands/create.ts:508 写 export default { plugins: [...] };content/docs/api/environment-routing.mdx:76 写「Older stacks that instead spread it onto the exported config object, e.g. export default { ...stack, api: {...} }, still work」。
    6. 普通对象写法没有功能上的必要。严格的 defineStack 拒绝内联函数的 action target(action.zod.ts:1022 是 z.string()),这是 CLI 给普通对象留门的历史理由(cli CHANGELOG「inline function handlers … cannot pass strict defineStack」)。git grep -nE "target: *(async *)?\(" origin/main -- 'content/docs/**' 'skills/**' 'examples/**' 'packages/create-objectstack/**' → 0 hits;只有 CLI 测试里 2 处。

    选项 × 真实代价

    选项 做什么 客户可感知的后果
    A 打标 + 门上补判 defineStack 与 composeStacks 在产物上打一个不进部署包的来源标记。把 defineStack 里那段拒绝判定原样搬进一个导出函数,defineStack 自己调用它,不存在第二份实现。门上遇到没标记的导出,就用这个函数判。composeStacks 对没标记的输入先判再组合(dev 的 A1)。 普通对象配置照写,缺陷被同码同退出码拒绝。两种写法永久并存。spec 新增 2 个导出;测试夹具里只有真违规的那几个翻红(已知 requires-retired-capability.e2e.test.ts)。约 M 号。标记丢失时(例如产物被 JSON 复制,或被展开成新对象),门会重判产物,报出「action 重复声明,删掉一个」这条错误的拒绝。
    B 只认一种写法 同一个来源标记,但门上不判:没标记的导出直接拒绝,提示「用 defineStack(...) 包起来」。判定只留在 defineStack / composeStacks 里;composeStacks 同样拒绝没标记的输入。台账新增 1 个拒绝码。 项目配置只剩一种合法写法,门上一条规则。约 63 个 CLI 测试文件改写成 defineStack;插件 README 模板与 { ...stack, api } 那句文档一并退役。约 L 号。标记丢失时报的是「不是 defineStack 造的」,诊断正确。仓外已有的普通对象配置升级后会被拒(数量未测)。

    D(分诊原路线,门上直接判导出、不看来源)已实测否决:误拒 app-crm 与 app-todo,所有带绑定 action 的 defineStack 项目都会坏。

    业务含义直译

    • A:像 Vite 的 defineConfig —— 包不包都行,工具在门口统一检查。但 Vite 的包装函数什么都不做,我们的 defineStack 会改写数据(合并 action),所以门口看到的其实可能是两种不同的东西。
    • B:像 Terraform 只执行 terraform plan 生成的计划文件、不接受手拼的计划 —— 产物只能出自一个生产者,门口只查出身。

    四轴

    • 项目长远合理性:两年后,一个 ObjectStack 项目的 objectstack.config.ts 应当只有一种合法写法:导出 defineStack / composeStacks 的产物。作者期判定只住在这一个生产者里,每道门只验出身。defineStack 不是 Vite 那种什么都不做的类型包装,它会规范化、解析、拒绝、合并 action;它的输出是产物,普通对象是另一种没被判过的源码方言,stack.zod.ts 的注释已经写明「author the source shape, not the artifact」。A 让两道门永久兼容源码与产物两种形态,判定多一个调用点,组合还要另立规则。B 一条规则收口,不留特例。⇒ B。
    • 实际业务需求:仓内 8/8 个真实栈配置、3/3 个 os init 模板、AGENTS.md 都用 defineStack。普通对象写法只活在 63 个测试文件与两处文档里;它当初的功能理由(内联函数 target)在文档、技能、示例、模板里零使用。仓外用户未测。⇒ 零实测拉动,倾向 B(退役)而不是 A(维护)。
    • 防 AI 犯错:A 与 B 对 AI 写的普通对象都是响亮拒绝。差别在标记丢失时:B 报「不是 defineStack 造的,包起来」,诊断正确。A 重判产物,报「action 重复声明,删掉一个」;AI 照做就会删掉一个正确的 action。B 还把作者写法收成闭合的一种。⇒ B。
    • 创业阶段不扩散:B 立即退役一种写法,不留双拼写宽限,代价是一次性的机械改写;新增 1 个导出、1 个拒绝码。A 新增 2 个导出,并把两种写法都变成永久义务。⇒ B。

    os-decision-facets
    ① 项目长远合理性:B 收成一种写法、判定只在生产者一处;A 让门永久兼容源码与产物两形态,并多出组合特例。
    ② 实际业务拉动:仓内 8/8 真实栈配置与 3/3 模板用 defineStack;普通对象只在 63 个测试文件与两处文档;仓外未测。
    ③ 防 AI 犯错:A、B 对普通对象都响亮拒绝;标记丢失时 B 诊断正确,A 给出「删掉重复 action」的误导处方。
    ④ 创业阶段不扩散:B 立即退役一种写法(1 导出 + 1 码);A 保留两种写法为永久义务(2 导出)。
    Prior rulings read: defineStack,plain,provenance,authoring → 64 hits; ADR-0109 Decision §5, ADR-0018 Decision §4, ADR-0018 Decision §5, ADR-0019 D3 — read, each shares a term only, none rules on the author-time door or the config's authoring shape; thread: none; repo: objectstack-ai/objectstack
    推荐:B(回退:A)。只看①选 B;②③④ 是否翻转:否(②④ 与①同向;③ 两者都响亮,标记丢失时 B 更准)。
    置信缺口:仓外普通对象配置的数量未测;宿主形导出(plugins 里是已实例化的插件对象)是否经过 os validate / os build 未测;os serve / os migrate 两道宿主门(台账同样点名)未测;app-showcase 与 blank 模板未能端到端跑(连接器包不在构建闭包里),只按源码读为 defineStack。

    本席的推荐与 dev 报告不同(dev 荐 A):本箱按上面的四轴独立得出,dev 的 A 已完整列为回退项。

    裁后执行

    • B(带前提的裁决):本卡回 pm:queue 由本席重认领,Clause-② yes,约 L 号。
      • spec:defineStack(两种模式)与 composeStacks 给产物打一个不可枚举的 Symbol.for 标记,先例 packages/spec/src/data/filter-subtree-provenance.ts。导出一个判定谓词;composeStacks 拒绝没标记的输入;台账登记新拒绝码,码名在 PR 里定,过达档契约复核。
      • cli:loadConfig 在具名导出展开之前读默认导出上的标记;validate.ts 与 compile.ts 加载后先拒没标记的导出;--json 信封形状不变。
      • 夹具与文档:63 个测试文件改写;requires-retired-capability.e2e.test.ts 的钉子重判;插件 README 模板、environment-routing.mdx:76 那句、generate.ts:815 注释里「plain-object config export」那扇门同 PR 退役。
      • 前提:宿主形导出不经过这两道作者期门。dev 先测,不成立就报 fork,⛔ 不硬做,⛔ 不悄悄退回 A。
      • 验收钉子:同一份缺陷配置,defineStack 写法两门报原码 exit 1;普通对象写法两门报新码 exit 1;examples/app-crm 与 examples/app-todo 两门都通过(D 的回声不复现)。
    • A:本卡回 pm:queue 由本席重认领,Clause-② yes,约 M 号。
      • 同一标记;defineStack 那段拒绝判定原样搬进一个导出函数并由 defineStack 调用;门上对没标记的导出调用它;composeStacks 对没标记的输入先判再组合(A1)。
      • 验收钉子:六个码各一对,两种写法在两门同码同退出码;app-crm 与 app-todo 两门都通过。
    • 两者共同的串行约束:domain:cli 席 1 的 PR fix(objectql,cli): os validate refuses a view container whose name disagrees with its object, as boot does (#20331) #20391([finding] os validate passes a view container whose own name disagrees with the object key it binds to, and os serve then refuses that stack at boot #20331)在 validate.ts 同一位置插入 step 2c。实现 PR 开之前先合 main,两条拒绝共用一个 --json 信封。

    相关:分诊 5863758552 · 认领 5866003518 · dev 报告 5866588954 · PR #20365(本卡来源)· PR #20391 · ADR-0130(组合后顶层为空,决定了组合要在生产者那里判)。

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH


    Generated by Claude Code

  6. 9 remaining items

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    维护者确认(裁决 B 的前提实测结果)· PR #20460 · domain:spec 2 号座位(session_014EJ1ED8X4MMrT18BhVx4tx)· 2026-09-28T16:28Z

    ⛔ 不是新决策卡,也不重开裁决;这是裁决 5869334748 要求「前提不成立就报 fork」的那一报,等你一个字。PR #20460 其余全部已过达档复核(PASS 5874193973,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 (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」(无形状例外)。

    实测(dev 在 PR 头实测,复核抽核):仓内裸宿主配置只出现在 serve / migrate / doctor 的测试夹具里,它们都不走这两道门;docs、README、skills、模板、示例里的宿主写法全部已是 defineStack;defineStack strict 模式接受插件实例。

    选项 做什么 客户看得见的后果
    A · 照裁执行,不开例外(推荐) 合入 PR #20460 现状 手写裸宿主配置的作者在 os validate / os build 被拒,报错给出一行修法(包进 defineStack);os serve / os migrate 不变
    B · 给宿主型开例外 另加「看起来像宿主配置就放行」的判定 又多一种合法写法;每道门都要维护这条例外;AI 写配置时两种形状都「能过」,错的那种不会被响亮拒绝

    业务含义: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 · defineStack on #20367 → ruling 5869334748, decision box 5866732842; thread: 12 comments

    推荐 A,回退无(B 与裁决的「一种写法」本意相反)。只看①选 A;②③④ 是否翻转:否。置信缺口:仓外(hotcrm、cloud)的宿主配置未实测;若它们有裸宿主配置走 os build,升级后会收到同一条带处方的拒绝。

    裁后执行:回「A」⇒ 本席摘 PR 上的 needs-user-decision,过落地前检后转 ready 入队;回「B」⇒ 本席立新卡定例外判据,PR 留 draft。


    Generated by Claude Code

  11. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    Execution parameters (ruled in the same stroke):

    • PR feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 at head 5355f47597 (PASS 5874193973, CI green, merge-tree clean) proceeds to landing by its seat; not Tier H. needs-user-decision leaves the PR in this stroke; a pointer to this ruling goes on the PR.
    • 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.

  12. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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.
      • Closing keywords: Fixes #20367 only.
    • Findings and follow-ups:
    • Landing: this seat flips the PR ready and arms auto-merge in the next act.

    Generated by Claude Code

  13. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record — PR #20460 MERGED · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T01:54Z

    • 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.
    • Closing: Fixes #20367 closed this card as completed; pm:dispatched is removed in this act. Its parked follow-up finding(spec+cli): the ADR-0087 conversions defineStack applies never reach os validate / os build --json conversions or the --strict gate — a retiring conversion passes --strict at exit 0 #20476 (OQ1: the conversions defineStack applies never reach --json / --strict) returns to pm:queue in the next act, since its Blocked-by: #20367 is met.

    Generated by Claude Code

  14. added 4 commits that reference this issue on Sep 29, 2026
    ba5927f
    6154165
    487a784
    7a1faf1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions