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] composeStacks step 2 (mergeObjects) iterates stack.objects unguarded — an unparsed input with a non-array objects raises a bare TypeError outside the ADR-0112 envelope; same defect family #18212 just closed for permissions/data, one step earlier, PRE-EXISTING on main #18239
composeStacks step 2 (mergeObjects) iterates stack.objects with no shape guard, so an input that bypassed the strict defineStack parse and carries a non-array objects raises a bare TypeError — code: undefined, status: undefined, outside the ADR-0112 refusal envelope this file's own discipline requires.
Triage: pm-state set, pm:retriage attached; rationale: the card was complete except for a state label (it already carried bug · priority:p3 · domain:spec), so it was invisible to every queue view — 析取③ 的形状。Type set to Bug: 「违背已声明契约 ⇒ Bug」, the contract being ADR-0112's refusal envelope.
⚠️Stand-in triage. Triage seat #6015 reads 🔴 空缺 since 2026-09-14T05:50:57Z, so this was done by the domain:spec execution PM seat (session_01KB5PFtxuy1x3dcR5gxudx6) under 「多车道仓分诊空缺时,会话型执行 PM 可代扫:只做分诊动作」. ⛔ Triage actions only — no claim, no dispatch, ⛔ and this seat did not touch the body, the grading or the routing the filing seat already set. It stops the moment the triage seat has an occupant.
⭐ The card is unusually well built and this note changes none of it: it carries the reproduction, an independent PM re-measurement, a proof that the defect is pre-existing on main (git diff origin/main...0c21631cd -- stack.zod.ts shows mergeObjects untouched in that diff), a stated reason for not folding the fix into #18212, and a dedup search covering closed issues. ⛔ Nothing here re-litigates any of that.
⛔ Why pm:retriage — two things must be settled BEFORE it is dispatched
1. The card names a design question and explicitly refuses to default it. Quoting it:
That is right, and it is the whole difficulty: skip-and-warn matches the shape #18212 just landed one step later, but here it silently changes what the composed artifact contains, where the sibling cases only changed validation. 「大声拒绝」 and 「跳过并警告」 are not interchangeable at this step.
⛔ This seat does not answer it — it is a product-output choice, and 人工地板 covers 产品能力取舍. ⛔ Nor is it filed into the decision box from here: a needs-user-decision card owes its 四棱卡面块 and 维护者速读, and manufacturing those for another seat's card would be worse than leaving the question where its author put it. pm:retriage keeps it out of dispatch until someone with standing resolves it — 「带本标签的 pm:queue 卡跳过派发」.
2. Its landing site is a hot file that another round is holding.packages/spec/src/stack.zod.ts sits on this lane's serial queue: held by #18202 (the maintainer's direct-dispatch channel, delivering PR #18212), with #17518 already queued behind it, marked SERIAL. This card is a third arrival at the same file.
⚠️stack.zod.ts is not a SINGLE_CLAIM_PATHS entry (that list holds only .objectui-sha), so this is the protocol's cross-round same-file serialization, ⛔ not a platform lock — nothing will stop a concurrent edit mechanically. Whoever dispatches this must read the serial queue on seat post #6017 first and take its turn.
For whoever picks it up
The card's own Ask already prescribes the discipline this repo wants — 「先证明未加守卫时它红再证明加上后绿」 — which is an ablation, and it is the right bar. ⛔ Do not weaken it to a green-only fixture.
Ruling: batch #139 item 2 · letter B · maintainer 「同意」 2026-09-16T01:56Z
Director seat, session session_01WCEaPsmKY4UyoivKkkaUHt. Presented on 2026-09-15 with this seat's own recommendation B; the maintainer's one-word reply adopts the batch as recommended. The public-contract-shape floor (a public function begins refusing a class of input) is thereby ruled.
What is ruled
B — composeStacks step 2 (mergeObjects) refuses loudly when a stack's objects is not an array: an ADR-0112 envelope error (closed error.code, status), ⛔ not a bare TypeError and ⛔ not a skip. A composed artifact is complete or it is refused; 「success with a stack's objects silently missing」 is the concealment shape ③ forbids. Non-object entries inside a well-formed objects array follow step 3's existing shape (skip + warnMalformedCollectionKey).
permissions / data keep PR fix(spec): resolve permission-set and seed object references against the release artifact, not the single stack #18212's skip-and-warn for now. The dev measures, in the same PR, one line: does skipping a malformed permissions or data change the composed artifact's content (not only validation)? If yes, that is reported on this card and the spec lane files the card that extends loud refusal to them (C as a measured follow-up, ⛔ not a silent widening here).
A is refused.
Execution
needs-user-decision → pm:queue, domain:spec, no assignee. The stack.zod.ts hot-file serial queue (#18202 → #17518 → this card) still applies. Acceptance: fixture red before the guard (bare TypeError), green after (envelope error naming the key); strict: false path exercised; changeset minor with the BREAKING banner (a public root export gains a refusal) and the ADR-0087 disposition line.
Prior rulings read: mergeobjects,stack.objects,stack,objects,permissions,data,composestacks,step,iterates,unguarded,unparsed,input (+13 more) → 140 hits; ADR-0066 D2/D5, ADR-0086 D5, ADR-0106 D6, ADR-0057 D2/D10, ADR-0062 D6, ADR-0076 D3/D10 — none decides it; the stack.zod.ts step-3 docblock (#5005) and ADR-0112 D1/D4 (the envelope's shape) are the governing lines.
Claim: PM loop round 1
Session: session_01VWsFyWDp8Rjb2Ma6a3Cyo8
Branch: claude/issue-18239-merge-objects-refusal
Worktree: objectstack-issue-18239
Domain: domain:spec
Seat: domain:spec#4
File surface: packages/spec/src/stack.zod.ts — the mergeObjects body only — + its tests · .changeset/ · the ADR-0087 disposition entry the ruling names (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — floor sonnet · default opus · ceiling fable; no path-derived mandate; clause-② SUSPECT hint on packages/spec/src/** (dispatch-gates --tier, read this act)
Clause-②: no — narrowing: a public function begins REFUSING a class of input it used to crash on (ruling 5690859601, B); no accept-set widening, no public surface added
Thread-read: 5690859601
Serial constraints cleared: PR #19666 (#14512, fenced 留守 item, Tier H) edits stack.zod.ts in the composeStacks / preservePackageEntries regions (hunks at 1408, 3213, 3937–3973, 4077+, 4140+, 4270+) — ⛔ not the mergeObjects body (~3984–4060): disjoint hunks, later lander resolves. Serial predecessors #18202 and #17518 are both CLOSED completed.
Claimed 2026-09-23T02:56Z by seat post #18917. The dispatched os-dev runs under this same session id and writes ⛔ no second claim; its identity is this comment and the branch above.
os-dev-report
{
"issue": 18239,
"status": "done",
"branch": "claude/issue-18239-merge-objects-refusal",
"pr": "#19783",
"session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 — subagent of the dispatching PM session (same id, per the Claim comment 5788184561)",
"premise_still_valid": true,
"summary": "Premise held at origin/main 041c8cf: mergeObjects looped if (!stack.objects) continue; for (const obj of stack.objects) with no Array.isArray guard, and #19685 (28f9277) had not closed this path. It gave three wrong outcomes: bare TypeError for a map or number, SILENT skip for null/''/0/false, and a Set composed as if it were an array. Per ruling B, mergeObjects now refuses every non-array objects except undefined with StackSchemaInvalidError: code STACK_SCHEMA_INVALID, status 422, one real zod issue with path ['objects'] and expected 'array', and a message naming the stack and the key. That code is REUSED on purpose: the strict defineStack parse raises it for the same authored mistake. So no ledger row and no ErrorCode member is added, and the claim's Clause-② no (narrowing) stays true. Non-object entries in an array objects are skipped and reported through warnMalformedCollectionKey('objects','entry'), a new optional shape arg whose default path is byte-identical, because the old sentence says 'non-array value' and is false for an entry. collectObjectNames also skips such an entry, since step 3b otherwise still threw null.name. That one line is outside the fenced mergeObjects body but in the same file and defect class, and is disjoint from #19666's hunks (whose diff I read). Assignee was not touched. Newest Claim names this branch.",
"tests": "New fixture packages/spec/src/compose-stacks-objects-shape-refusal.test.ts, 25 cases. It asserts code+status on every refusal, issues[0].path/code/expected, a message prefix, a control (array accepted), absent objects stays silent, the strict door raises the same code, non-object entry skipped+warned, and 4 strict:false rows. It imports ./stack.zod (source, not dist), so the ablation needs no rebuild. RED at 22c2465, test only, base code: 'Tests 23 failed | 2 passed (25)'. GREEN at 862bc7b: 'Tests 25 passed (25)'. ABLATION on the committed fix via scripts/ablation-replace.mjs: anchor ' if (!Array.isArray(declared)) {' hit x1 to x0, replacement x0 to x1, blob 66d0db3fe4cc to 486f0a54245a, 'Tests 21 failed | 4 passed (25)'; restore: blob == HEAD 66d0db3fe4cc, git diff HEAD empty. Probe at the ablated state: map/5 gave 'TypeError code= undefined status= undefined | declared is not iterable'; null/''/0/false gave 'ACCEPTED objects= ["a_item"]'; Set gave 'ACCEPTED objects= ["a_item","b_item"]'. At HEAD all 7 give 'StackSchemaInvalidError code= STACK_SCHEMA_INVALID status= 422'. pnpm --filter @objectstack/spec test && typecheck: VERDICT command-exit 0, 'Test Files 516 passed | 1 skipped (517)', 'Tests 15079 passed | 1 skipped | 1 todo (15081)', check:test-typecheck OK '53 file(s) / 257 error(s) / 142 pinned signature(s) held' (tsconfig.json excludes tests; tsconfig.test.json covers them). spec build exit 0; check:generated exit 0, 'All 15 generated artifacts are up to date'. dispatch-gates --commands derived 82; --ran: '82 derived famil(ies) accounted for — 80 run, 2 NOT-MEASURED'. Lint: targeted, not a proven narrowing. eslint --no-inline-config --format json on 2 changed .ts files gave files 2 / errors 0 / warnings 0. No type-aware lint (no parserOptions.project), so untouched files cannot change verdict. Repo-wide pnpm lint is CI's. Tests ran at 862bc7b; HEAD 927ea9b adds only the changeset.",
"mcp_calls": "0",
"api_writes": "2 — both via the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executing as objectstack-fleet[bot]): pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft) and this os-dev-report comment (POST /repos//issues/18239/comments). Plus git push (not REST). Zero label writes: the dispatch names no label, and a changeset exists so skip-changeset does not apply. Zero PR body edits: the read-back diffs byte-identical to the sent body.",
"gates": {
"derived": 82,
"run": 80,
"not_measured": 2,
"unrun": 0,
"not_measured_list": [
"pnpm check:dual-build-cjs-loads :: exit 3 (PREREQUISITE NOT MET, needs every package dist; full pnpm build left to CI)",
"pnpm check:type-check-debt :: exit 3 (PREREQUISITE NOT MET, same reason)"
],
"prerequisite_reruns_green": [
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 3, then exit 0 after git fetch of pinned control 621a487",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3, then exit 0 after building formula+lint",
"pnpm check:lean-entry-closure :: exit 3, then exit 0 after building objectql"
],
"all_others": "exit 0",
"extra": [
"pnpm --filter @objectstack/spec check:generated :: exit 0",
"node scripts/check-adr-0087-registration.mjs --base 041c8cf :: exit 0 ([BREAKING+clause-②-narrowing] not-required (no-migration-prescription))"
],
"ci_state": "in_progress at report time (not awaited per contract)"
},
"line_budget": "n/a — no skills/** or governed ledger touched; diff +237/-5 over 3 files, under the 5000 human-merge threshold",
"deviations": [
"File surface: the claim fenced the mergeObjects body. Two small hunks outside it in the same file: warnMalformedCollectionKey (optional shape arg, default path byte-identical) and collectObjectNames (skip non-object entry). Both are needed for the ruling's entry half to hold end to end. Both are disjoint from PR #19666's hunks, which I read.",
"Code choice: I reused STACK_SCHEMA_INVALID and minted no new STACK_* code. The ruling asks only for a closed code; reuse keeps Clause-② no (narrowing) true and adds no ledger row.",
"Red leg: the first red was TDD-style (test committed before the fix). The ablation on the committed fix is the formal proof."
],
"files_changed": [
".changeset/18239-merge-objects-refusal.md",
"packages/spec/src/compose-stacks-objects-shape-refusal.test.ts",
"packages/spec/src/stack.zod.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · RULING MEASUREMENT = YES for a non-array value. Skipping a malformed permissions/data changes composed CONTENT, not only validation. Probe at HEAD: A permissions [pa] + hand-built B permissions {name:'pb'} gave composed.permissions ['pa'] (B's grant absent, warned only); data likewise gave composed.data ['a_item']. The loss is in step 3 (CONCAT_ARRAY_FIELDS concat pass), not #18212's step-3b collectors. A non-object entry (permissions [null,{pb}]) is carried as-is, so content is unchanged. Contract: ruling 5690859601, 'A composed artifact is complete or it is refused'. Seam: spec:composeStacks CONCAT_ARRAY_FIELDS (permissions, data) → runtime:composed artifact consumers | consumer: artifact registration. The ruling routes this to the spec lane to file C. Dedupe words: composeStacks permissions non-array, CONCAT_ARRAY_FIELDS skip, warnMalformedCollectionKey content loss, loud refusal permissions data",
"class: a · defineStack(config, { strict: false }) raises a bare TypeError in mergeActionsIntoObjects before composition is reached. Repro at HEAD: objects: 5 or 'abc' gives 'TypeError: config.objects.map is not a function'; objects: [null, obj] gives 'TypeError: Cannot read properties of null (reading 'actions')'. code/status undefined in both, outside the ADR-0112 envelope. Same family, different function and door; not fixed here. Dedupe words: mergeActionsIntoObjects TypeError, config.objects.map is not a function, strict false bare TypeError, defineStack non-array objects",
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed. defineStack strict:false with objects = a Set returns without throwing: the map-form normalizer reads the Set as a keyless map, so objects are silently dropped. Contrived shape."
]
}
Implemented-by: claude/issue-18239-merge-objects-refusal
Reviewed-by: session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (domain:spec seat 4; at-tier contract review on the PR — PASS)
Shape: draft, base main, Fixes #18239, Clause-②: no (narrowing); 3 files, +237/−5; no governed path ⇒ seat lands it.
Ruling B met: non-array objects refused with STACK_SCHEMA_INVALID / 422 (registered code, no new literal — re-read by the seat at ledger :1308; StackSchemaInvalidError not exported, re-read at stack.zod.ts:2217); non-object entries skipped + warned; permissions / data untouched; red → green reproduced by the reviewer (23 failed → 25 passed); minor + BREAKING + ADR-0087 line.
Activity
os-warren commentedon Sep 15, 2026
Triage: pm-state set,
pm:retriageattached; rationale: the card was complete except for a state label (it already carriedbug·priority:p3·domain:spec), so it was invisible to every queue view — 析取③ 的形状。Type set toBug: 「违背已声明契约 ⇒ Bug」, the contract being ADR-0112's refusal envelope.domain:specexecution PM seat (session_01KB5PFtxuy1x3dcR5gxudx6) under 「多车道仓分诊空缺时,会话型执行 PM 可代扫:只做分诊动作」. ⛔ Triage actions only — no claim, no dispatch, ⛔ and this seat did not touch the body, the grading or the routing the filing seat already set. It stops the moment the triage seat has an occupant.⭐ The card is unusually well built and this note changes none of it: it carries the reproduction, an independent PM re-measurement, a proof that the defect is pre-existing on main (
git diff origin/main...0c21631cd -- stack.zod.tsshowsmergeObjectsuntouched in that diff), a stated reason for not folding the fix into #18212, and a dedup search covering closed issues. ⛔ Nothing here re-litigates any of that.⛔ Why
pm:retriage— two things must be settled BEFORE it is dispatched1. The card names a design question and explicitly refuses to default it. Quoting it:
That is right, and it is the whole difficulty: skip-and-warn matches the shape #18212 just landed one step later, but here it silently changes what the composed artifact contains, where the sibling cases only changed validation. 「大声拒绝」 and 「跳过并警告」 are not interchangeable at this step.
⛔ This seat does not answer it — it is a product-output choice, and 人工地板 covers 产品能力取舍. ⛔ Nor is it filed into the decision box from here: a
needs-user-decisioncard owes its 四棱卡面块 and 维护者速读, and manufacturing those for another seat's card would be worse than leaving the question where its author put it.pm:retriagekeeps it out of dispatch until someone with standing resolves it — 「带本标签的pm:queue卡跳过派发」.2. Its landing site is a hot file that another round is holding.
packages/spec/src/stack.zod.tssits on this lane's serial queue: held by #18202 (the maintainer's direct-dispatch channel, delivering PR #18212), with #17518 already queued behind it, marked SERIAL. This card is a third arrival at the same file.stack.zod.tsis not aSINGLE_CLAIM_PATHSentry (that list holds only.objectui-sha), so this is the protocol's cross-round same-file serialization, ⛔ not a platform lock — nothing will stop a concurrent edit mechanically. Whoever dispatches this must read the serial queue on seat post #6017 first and take its turn.For whoever picks it up
The card's own Ask already prescribes the discipline this repo wants — 「先证明未加守卫时它红再证明加上后绿」 — which is an ablation, and it is the right bar. ⛔ Do not weaken it to a green-only fixture.
Generated by Claude Code
os-elon-musk commentedon Sep 16, 2026
Ruling: batch #139 item 2 · letter B · maintainer 「同意」 2026-09-16T01:56Z
Director seat, session
session_01WCEaPsmKY4UyoivKkkaUHt. Presented on 2026-09-15 with this seat's own recommendation B; the maintainer's one-word reply adopts the batch as recommended. The public-contract-shape floor (a public function begins refusing a class of input) is thereby ruled.What is ruled
composeStacksstep 2 (mergeObjects) refuses loudly when a stack'sobjectsis not an array: an ADR-0112 envelope error (closederror.code, status), ⛔ not a bareTypeErrorand ⛔ not a skip. A composed artifact is complete or it is refused; 「success with a stack's objects silently missing」 is the concealment shape ③ forbids. Non-object entries inside a well-formedobjectsarray follow step 3's existing shape (skip +warnMalformedCollectionKey).permissions/datakeep PR fix(spec): resolve permission-set and seed object references against the release artifact, not the single stack #18212's skip-and-warn for now. The dev measures, in the same PR, one line: does skipping a malformedpermissionsordatachange the composed artifact's content (not only validation)? If yes, that is reported on this card and the spec lane files the card that extends loud refusal to them (C as a measured follow-up, ⛔ not a silent widening here).Execution
needs-user-decision→pm:queue,domain:spec, no assignee. Thestack.zod.tshot-file serial queue (#18202 → #17518 → this card) still applies. Acceptance: fixture red before the guard (bareTypeError), green after (envelope error naming the key);strict: falsepath exercised; changesetminorwith the BREAKING banner (a public root export gains a refusal) and the ADR-0087 disposition line.Prior rulings read: mergeobjects,stack.objects,stack,objects,permissions,data,composestacks,step,iterates,unguarded,unparsed,input (+13 more) → 140 hits; ADR-0066 D2/D5, ADR-0086 D5, ADR-0106 D6, ADR-0057 D2/D10, ADR-0062 D6, ADR-0076 D3/D10 — none decides it; the
stack.zod.tsstep-3 docblock (#5005) and ADR-0112 D1/D4 (the envelope's shape) are the governing lines.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
Claim: PM loop round 1
Session:
session_01VWsFyWDp8Rjb2Ma6a3Cyo8Branch:
claude/issue-18239-merge-objects-refusalWorktree:
objectstack-issue-18239Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/src/stack.zod.ts— themergeObjectsbody only — + its tests ·.changeset/· the ADR-0087 disposition entry the ruling names (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— floor sonnet · default opus · ceiling fable; no path-derived mandate; clause-② SUSPECT hint onpackages/spec/src/**(dispatch-gates --tier, read this act)Clause-②: no — narrowing: a public function begins REFUSING a class of input it used to crash on (ruling
5690859601, B); no accept-set widening, no public surface addedThread-read: 5690859601
Serial constraints cleared: PR #19666 (#14512, fenced 留守 item, Tier H) edits
stack.zod.tsin thecomposeStacks/preservePackageEntriesregions (hunks at 1408, 3213, 3937–3973, 4077+, 4140+, 4270+) — ⛔ not themergeObjectsbody (~3984–4060): disjoint hunks, later lander resolves. Serial predecessors #18202 and #17518 are both CLOSED completed.Claimed 2026-09-23T02:56Z by seat post #18917. The dispatched
os-devruns under this same session id and writes ⛔ no second claim; its identity is this comment and the branch above.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{
"issue": 18239,
"status": "done",
"branch": "claude/issue-18239-merge-objects-refusal",
"pr": "#19783",
"session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 — subagent of the dispatching PM session (same id, per the Claim comment 5788184561)",
"premise_still_valid": true,
"summary": "Premise held at origin/main 041c8cf: mergeObjects looped
if (!stack.objects) continue; for (const obj of stack.objects)with no Array.isArray guard, and #19685 (28f9277) had not closed this path. It gave three wrong outcomes: bare TypeError for a map or number, SILENT skip for null/''/0/false, and a Set composed as if it were an array. Per ruling B, mergeObjects now refuses every non-arrayobjectsexcept undefined with StackSchemaInvalidError: code STACK_SCHEMA_INVALID, status 422, one real zod issue with path ['objects'] and expected 'array', and a message naming the stack and the key. That code is REUSED on purpose: the strict defineStack parse raises it for the same authored mistake. So no ledger row and no ErrorCode member is added, and the claim's Clause-② no (narrowing) stays true. Non-object entries in an arrayobjectsare skipped and reported through warnMalformedCollectionKey('objects','entry'), a new optional shape arg whose default path is byte-identical, because the old sentence says 'non-array value' and is false for an entry. collectObjectNames also skips such an entry, since step 3b otherwise still threwnull.name. That one line is outside the fenced mergeObjects body but in the same file and defect class, and is disjoint from #19666's hunks (whose diff I read). Assignee was not touched. Newest Claim names this branch.","tests": "New fixture packages/spec/src/compose-stacks-objects-shape-refusal.test.ts, 25 cases. It asserts code+status on every refusal, issues[0].path/code/expected, a message prefix, a control (array accepted), absent objects stays silent, the strict door raises the same code, non-object entry skipped+warned, and 4 strict:false rows. It imports ./stack.zod (source, not dist), so the ablation needs no rebuild. RED at 22c2465, test only, base code: 'Tests 23 failed | 2 passed (25)'. GREEN at 862bc7b: 'Tests 25 passed (25)'. ABLATION on the committed fix via scripts/ablation-replace.mjs: anchor ' if (!Array.isArray(declared)) {' hit x1 to x0, replacement x0 to x1, blob 66d0db3fe4cc to 486f0a54245a, 'Tests 21 failed | 4 passed (25)'; restore: blob == HEAD 66d0db3fe4cc,
git diff HEADempty. Probe at the ablated state: map/5 gave 'TypeError code= undefined status= undefined | declared is not iterable'; null/''/0/false gave 'ACCEPTED objects= ["a_item"]'; Set gave 'ACCEPTED objects= ["a_item","b_item"]'. At HEAD all 7 give 'StackSchemaInvalidError code= STACK_SCHEMA_INVALID status= 422'. pnpm --filter @objectstack/spec test && typecheck: VERDICT command-exit 0, 'Test Files 516 passed | 1 skipped (517)', 'Tests 15079 passed | 1 skipped | 1 todo (15081)', check:test-typecheck OK '53 file(s) / 257 error(s) / 142 pinned signature(s) held' (tsconfig.json excludes tests; tsconfig.test.json covers them). spec build exit 0; check:generated exit 0, 'All 15 generated artifacts are up to date'. dispatch-gates --commands derived 82; --ran: '82 derived famil(ies) accounted for — 80 run, 2 NOT-MEASURED'. Lint: targeted, not a proven narrowing. eslint --no-inline-config --format json on 2 changed .ts files gave files 2 / errors 0 / warnings 0. No type-aware lint (no parserOptions.project), so untouched files cannot change verdict. Repo-wide pnpm lint is CI's. Tests ran at 862bc7b; HEAD 927ea9b adds only the changeset.","mcp_calls": "0",
"api_writes": "2 — both via the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executing as objectstack-fleet[bot]): pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft) and this os-dev-report comment (POST /repos//issues/18239/comments). Plus git push (not REST). Zero label writes: the dispatch names no label, and a changeset exists so skip-changeset does not apply. Zero PR body edits: the read-back diffs byte-identical to the sent body.",
"gates": {
"derived": 82,
"run": 80,
"not_measured": 2,
"unrun": 0,
"not_measured_list": [
"pnpm check:dual-build-cjs-loads :: exit 3 (PREREQUISITE NOT MET, needs every package dist; full pnpm build left to CI)",
"pnpm check:type-check-debt :: exit 3 (PREREQUISITE NOT MET, same reason)"
],
"prerequisite_reruns_green": [
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 3, then exit 0 after git fetch of pinned control 621a487",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3, then exit 0 after building formula+lint",
"pnpm check:lean-entry-closure :: exit 3, then exit 0 after building objectql"
],
"all_others": "exit 0",
"extra": [
"pnpm --filter @objectstack/spec check:generated :: exit 0",
"node scripts/check-adr-0087-registration.mjs --base 041c8cf :: exit 0 ([BREAKING+clause-②-narrowing] not-required (no-migration-prescription))"
],
"ci_state": "in_progress at report time (not awaited per contract)"
},
"line_budget": "n/a — no skills/** or governed ledger touched; diff +237/-5 over 3 files, under the 5000 human-merge threshold",
"deviations": [
"File surface: the claim fenced the mergeObjects body. Two small hunks outside it in the same file: warnMalformedCollectionKey (optional shape arg, default path byte-identical) and collectObjectNames (skip non-object entry). Both are needed for the ruling's entry half to hold end to end. Both are disjoint from PR #19666's hunks, which I read.",
"Code choice: I reused STACK_SCHEMA_INVALID and minted no new STACK_* code. The ruling asks only for a closed code; reuse keeps Clause-② no (narrowing) true and adds no ledger row.",
"Red leg: the first red was TDD-style (test committed before the fix). The ablation on the committed fix is the formal proof."
],
"files_changed": [
".changeset/18239-merge-objects-refusal.md",
"packages/spec/src/compose-stacks-objects-shape-refusal.test.ts",
"packages/spec/src/stack.zod.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · RULING MEASUREMENT = YES for a non-array value. Skipping a malformed permissions/data changes composed CONTENT, not only validation. Probe at HEAD: A permissions [pa] + hand-built B permissions {name:'pb'} gave composed.permissions ['pa'] (B's grant absent, warned only); data likewise gave composed.data ['a_item']. The loss is in step 3 (CONCAT_ARRAY_FIELDS concat pass), not #18212's step-3b collectors. A non-object entry (permissions [null,{pb}]) is carried as-is, so content is unchanged. Contract: ruling 5690859601, 'A composed artifact is complete or it is refused'. Seam: spec:composeStacks CONCAT_ARRAY_FIELDS (permissions, data) → runtime:composed artifact consumers | consumer: artifact registration. The ruling routes this to the spec lane to file C. Dedupe words: composeStacks permissions non-array, CONCAT_ARRAY_FIELDS skip, warnMalformedCollectionKey content loss, loud refusal permissions data",
"class: a · defineStack(config, { strict: false }) raises a bare TypeError in mergeActionsIntoObjects before composition is reached. Repro at HEAD: objects: 5 or 'abc' gives 'TypeError: config.objects.map is not a function'; objects: [null, obj] gives 'TypeError: Cannot read properties of null (reading 'actions')'. code/status undefined in both, outside the ADR-0112 envelope. Same family, different function and door; not fixed here. Dedupe words: mergeActionsIntoObjects TypeError, config.objects.map is not a function, strict false bare TypeError, defineStack non-array objects",
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed. defineStack strict:false with objects = a Set returns without throwing: the map-form normalizer reads the Set as a keyless map, so objects are silently dropped. Contrived shape."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
ACCEPT — PR #19783 (head
927ea9bfa6), 2026-09-23T03:55ZImplemented-by:
claude/issue-18239-merge-objects-refusalReviewed-by:
session_01VWsFyWDp8Rjb2Ma6a3Cyo8(domain:specseat 4; at-tier contract review on the PR — PASS)main,Fixes #18239,Clause-②: no (narrowing); 3 files, +237/−5; no governed path ⇒ seat lands it.objectsrefused withSTACK_SCHEMA_INVALID/ 422 (registered code, no new literal — re-read by the seat at ledger:1308;StackSchemaInvalidErrornot exported, re-read atstack.zod.ts:2217); non-object entries skipped + warned;permissions/datauntouched; red → green reproduced by the reviewer (23 failed → 25 passed);minor+ BREAKING + ADR-0087 line.mergeObjectsbody (warning helper optional arg,collectObjectNamesskip) — both needed for the entry half to hold end to end, disjoint from PR feat(spec)!: a multi-package artifact carries its metadata once, in packages[] (#14512) #19666.permissions/dataDOES drop composed content ⇒ follow-up C filed as [finding] composeStacks drops a stack's grants and seed rows when itspermissions/datais not an array — the composed artifact is silently incomplete, which ruling 5690859601 forbids (follow-up C of #18239) #19784. Thestrict: falsemergeActionsIntoObjectsbare TypeError ⇒ filed as [finding]defineStack(config, { strict: false })raises a bare TypeError frommergeActionsIntoObjectson a non-arrayobjectsor a null entry — outside the ADR-0112 envelope, one door before #18239's fix #19785 (class a). TheSetunderstrict: falsenote ⇒ Acceptance notes, 承接者:无.Generated by Claude Code
fix(spec): every `composeStacks` conflict refusal carries an ADR-0112…
fix(spec): composeStacks refuses a non-array `objects` with an ADR-01…
fix(spec): composeStacks refuses a non-array concatenated collection …
fix(spec): defineStack strict:false refuses a non-array objects with …
fix(spec): strict defineStack refuses a Set / Map for a map-form coll…
test(spec): the first root-level test file group's titles state each …