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 drops a stack's grants and seed rows when its permissions / data is not an array — the composed artifact is silently incomplete, which ruling 5690859601 forbids (follow-up C of #18239) #19784
Filing gate: ① (a product defect with a named site). Reader: the domain:spec seat that dispatches it — the fix site is packages/spec/src/stack.zod.ts step 3 (CONCAT_ARRAY_FIELDS concat pass). ⛔ Filed bare for triage: no domain:*, no priority:*, no type asserted.
Filed by domain:spec seat 4 (session_01VWsFyWDp8Rjb2Ma6a3Cyo8, seat post #18917) because the maintainer's ruling on #18239 routes it here, verbatim (comment 5690859601, batch #139 item 2 · letter B · 「同意」):
permissions / data keep PR #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).
The measurement — answer: YES
Taken by the os-dev round implementing #18239 (PR #19783, head 927ea9bfa6), reported in its os-dev-report on #18239. ⚠️ The seat has ⛔ NOT re-run it; it is the implementing round's reading, relayed as such:
stack A permissions: [pa] + hand-built stack B permissions: { name: 'pb' } (a non-array) ⇒ composed permissions is ['pa'] — B's grant is absent, a console warning only.
same shape for data ⇒ composed data is ['a_item'] — B's seed rows absent.
a non-object ENTRY (permissions: [null, {pb}]) is carried as-is ⇒ content unchanged; only the non-array VALUE loses content.
Ruling 5690859601: 「A composed artifact is complete or it is refused; 「success with a stack's objects silently missing」 is the concealment shape ③ forbids.」 — the same sentence, applied to grants and seed rows, is what this card asks to enforce. PR #19783 enforces it for objects (refusal STACK_SCHEMA_INVALID, 422).
⛔ What this card does not claim
⛔ Not that every CONCAT_ARRAY_FIELDS key should refuse — the measurement covers permissions and data only; whoever takes it re-measures the other keys before widening the refusal to them.
⛔ Not measured whether any authored stack reaches composition with such a shape (the strict defineStack parse already refuses it; the population is hand-built stacks and strict: false).
Dedupe
REST list of the 436 most recently updated issues (open + closed, to 2026-09-20T20:58Z) grepped locally (/search/* is refused by the egress proxy): CONCAT_ARRAY_FIELDS 0 · permissions … non-array 0 · lit control composeStacks 6 (#18239, #14512, #16075, #16348 closed, #18431 closed, the seat post) — none is this defect.
Dedupe words: composeStacks permissions non-array · CONCAT_ARRAY_FIELDS skip content loss · warnMalformedCollectionKey data dropped · loud refusal permissions data
Claim: PM loop round 1
Session: session_01VWsFyWDp8Rjb2Ma6a3Cyo8
Branch: claude/issue-19784-concat-fields-refusal
Worktree: objectstack-issue-19784
Domain: domain:spec
Seat: domain:spec#4
File surface: packages/spec/src/stack.zod.ts — composeStacks step 3, the CONCAT_ARRAY_FIELDS concat pass only — + a new test file + .changeset/ (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 composition input it used to accept with content silently dropped is refused (ruling 5690859601, C)
Thread-read: 5788992982
Serial constraints cleared: serial predecessor #18239 / PR #19783 MERGED (55095cc460); open PR #19666 (#14512, fenced 留守 item, Tier H) edits stack.zod.ts in the composeStacks / preservePackageEntries regions — ordinary concurrency, later lander resolves; no other open PR (18 read this act) touches the file.
Fold-or-serial (#19784 · #19785 share packages/spec/src/stack.zod.ts): ⛔ not folded — gate ① fails (different defect shape and fix: silent content drop in the step-3 concat pass vs a bare TypeError in mergeActionsIntoObjects; triage 5789001253 says 「⛔ 不并」); ⛔ not serialised — the two declared regions (~:2946 vs ~:4515 at origin/main3f9e2eaa1c) are disjoint, so both dispatch now; later lander merges main and resolves.
Claimed 2026-09-23T04:58Z by seat post #18917. The dispatched os-dev runs under this same session id and writes ⛔ no second claim.
{
"issue": 19784,
"status": "done",
"branch": "claude/issue-19784-concat-fields-refusal",
"pr": "https://github.com/objectstack-ai/objectstack/pull/19794",
"session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (subagent of the domain:spec seat 4 PM; same id)",
"premise_still_valid": true,
"summary": "composeStacks step 3 now refuses a non-array value for every concat key (all 37 rows of COMPOSE_KEY_DISPOSITIONS, derived and not transcribed). A value of undefined still means the key is absent. The refusal is StackSchemaInvalidError with code STACK_SCHEMA_INVALID, status 422, one zod invalid_type issue at the key, and a message naming the stack and the key, plus the map-form hint for MAP_SUPPORTED_FIELDS. No new error code and no export change. The per-key measurement at base 3f9e2eaa1c read YES for all 37 keys and ambiguous for none: B's content was absent from the composed top level. Under manifest preserve it survived only inside B's package body for the 33 body keys, and it was absent everywhere for packages, plugins, devPlugins and devLogins. The step-3b collectors' non-array branches now have no reachable caller (the strict parse, or step 3, refuses first), so they are kept as silent type guards with docblocks explaining why and their warn calls are gone. warnMalformedCollectionKey lost its dead value branch and is now warnMalformedCollectionEntry, which only mergeObjects calls; its text is unchanged. The objects refusal shares the new describeNonArrayCollection helper and its message is byte-identical. The assignee was set on arrival (baozhoutao), which is the PM's step 1; I left it alone. I sent the PM probe a status line in the transcript.",
"tests": "At HEAD 0bf2e55646. (1) New packages/spec/src/compose-stacks-concat-shape-refusal.test.ts, 125 cases. (2) Ablation via scripts/ablation-replace.mjs, replacing the step-3 throw with continue: anchor x1 to x0, marker x0 to x1, blob 9b46ea3999bb to ed55f104c3e1. Result 'Tests 84 failed | 41 passed (125)' (37x2 refusal cases plus 10 shape rows red; every control green). Restored: blob == HEAD 9b46ea3999bb and git diff HEAD empty. The test resolves ./stack.zod from src, so there was no dist leg. (3) pnpm --filter @objectstack/spec test: 'Test Files 518 passed (518)', 'Tests 15213 passed | 1 todo'. (4) spec typecheck: exit 0 after gen:test-typecheck-debt, which recorded a debt shrink of 4 to 3 and 3 to 2 in compose-stacks-key-loss.test.ts. (5) spec check:generated: 'All 15 generated artifacts are up to date', against a dist built from this tree. (6) dispatch-gates --repo objectstack-ai/objectstack --ran: '83 derived famil(ies) accounted for — 80 run, 3 NOT-MEASURED'. All 80 exit 0. check:doc-formula-expressions first exited 3 because its prerequisite was not met; I built formula and lint, and the rerun exited 0.",
"gates": {
"derived": 83,
"run_exit_0": 80,
"not_measured": [
"NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (needs full pnpm build), exit 3",
"NOT MEASURED: pnpm check:type-check-debt, reason: PREREQUISITE NOT MET (needs the ./packages/* build closure), exit 3",
"NOT MEASURED: pnpm check:lean-entry-closure, reason: PREREQUISITE NOT MET (needs built @objectstack/objectql); the objectql build hit a lock queue-timeout (exit 99) behind holder pid 10118 (a spec vitest run, held 854s)"
],
"clause2_pair": "node scripts/pm/check-clause2-carriers.mjs --pair 19794 exit 4 (C6: domain:spec contract review of record owed on head 0bf2e55646; seat's act)",
"needs_contract_review_label": "absent on PR #19794 (not touched)"
},
"line_budget": "322 insertions / 102 deletions across 6 files (dispatch-gates: 420 changed lines, under the 5000 human-merge threshold); skills/** not touched",
"files_changed": [
".changeset/19784-concat-fields-refusal.md",
"packages/spec/src/stack.zod.ts",
"packages/spec/src/compose-stacks-concat-shape-refusal.test.ts",
"packages/spec/src/compose-stacks-key-loss.test.ts",
"packages/spec/src/stack-artifact-crossref.test.ts",
"packages/spec/test-typecheck-debt.json"
],
"deviations": [
"File surface: beyond step 3, the diff touches the two step-3b collectors (their non-array branches, as the dispatch asked), the warn helper (its dead value branch removed and the helper renamed), and the objects refusal in mergeObjects. The mergeObjects change only swaps the inline kind/issues computation for the shared describeNonArrayCollection helper; its message is byte-identical. Two existing test files were changed because they pinned the removed skip-and-warn branch. test-typecheck-debt.json was re-recorded because typecheck required it (shrink only). No change in mergeActionsIntoObjects, preservePackageEntries or the options schema.",
"Labels: zero label writes. The dispatch write budget names no label, and skip-changeset does not apply because a changeset is present.",
"Merged origin/main 628e55dfa6 before opening the PR, with a plain merge commit; no conflicts."
],
"mcp_calls": "0",
"api_writes": "2 — (1) pr_create through fleet-write relay (repository_dispatch → POST /repos/objectstack-ai/objectstack/pulls, draft), run 35824375630 success; (2) this os-dev-report comment through scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/19784/comments). Plus git push of the branch (not a REST write). No PR body edit.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · strict defineStack({ manifest, permissions: new Set([{ name: 'rep', label: 'Rep' }]) }) is ACCEPTED with permissions === [] (the same holds for a Map). Repro run at the branch tree with tsx printed 'Set strict ACCEPTED, permissions = []' and 'Map strict ACCEPTED, permissions = []'. Site: packages/spec/src/shared/metadata-collection.zod.ts normalizeMetadataCollection treats any typeof-object as the map form and calls Object.entries, which yields [] for a Set or Map, so the grants vanish before the parse sees them. This hits every MAP_SUPPORTED_FIELDS key. It is upstream of composition, so not fixed here. Dedupe words: normalizeMetadataCollection Set Map · map form Object.entries Set empty · defineStack Set permissions accepted empty · MAP_SUPPORTED_FIELDS non-plain object",
"carrier: none — the seat files it; noted in the PR #19794 Acceptance notes"
]
}
Implemented-by: claude/issue-19784-concat-fields-refusal
Reviewed-by: session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (domain:spec seat 4; at-tier contract review on the PR — PASS)
Shape: Fixes #19784, Clause-②: no (narrowing); 6 files, +322/−102; no governed path ⇒ seat lands it.
Ruling C met, and the card's own boundary honoured: every CONCAT_ARRAY_FIELDS key was measured, not just permissions / data. All 37 read YES (content lost) and none ambiguous, which the reviewer reproduced independently (74 rows). A non-array value is now refused with the fix(spec): composeStacks refuses a non-array objects with an ADR-0112 envelope #19783 envelope (STACK_SCHEMA_INVALID / 422); undefined passes.
Deviations accepted: the step-3b collectors' now-unreachable non-array branches kept as silent type guards with docblocks; the dead warn branch removed and the helper renamed (text byte-identical); mergeObjects routed through a shared describeNonArrayCollection (message byte-identical); two pinned tests re-judged; debt ledger shrink only.
Activity
objectstack-fleet commentedon Sep 23, 2026
定级
pm:queue·priority:p3·domain:spec·area:devpath—— 维护者预先授权的后续卡;缺陷本席在源码上读实分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T04:20Z。立卡门 ①,类 (a)。为什么它不需要再裁一次
#18239 的裁决
5690859601(batch #139 item 2,字母 B,维护者「同意」)逐字写好了这一步:「dev …… 量一行:跳过一个畸形的permissions或data,是否改变组合产物的内容?若是,…… spec 车道立卡把响亮拒绝扩展到它们(C 作为量过的后续)」。实施轮量到是。⇒ 方向已裁,本卡是执行。本席的读数(
origin/main,⛔ 未重跑实施轮的探针,读的是代码)packages/spec/src/stack.zod.ts:4455-4470,组合第 3 步:arrays = declared.filter(Array.isArray)之后只拼arrays;非数组值被过滤掉,只触发warnMalformedCollectionKey(field)。⇒ 一个permissions: { name: 'pb' }的栈,它的授权不进组合产物,只留一条控制台警告 —— 与实施轮的读数一致。该段自己的注释也写明「only reachable viastrict: falseor a hand-built stack object」。p3判据(锚定同族)同族的 #18239(⚠️ 本卡是更糟的那一半 —— 静默少了授权与种子行,而 #18239 是响亮的 TypeError —— 但触达面一样窄,⛔ 不因此抬级。抬级条件:量到任何一个正式书写的栈以这种形状走到组合 ⇒ p2。
objects那一格)定为 p3,可达面相同:严格的defineStack已拒绝这种形状,只有strict: false或手搭的栈对象能到这里。取卡的人要知道
mergeObjects) iteratesstack.objectsunguarded — an unparsed input with a non-arrayobjectsraises a bare TypeError outside the ADR-0112 envelope; same defect family #18212 just closed forpermissions/data, one step earlier, PRE-EXISTING on main #18239 在同一个文件(stack.zod.ts),而 [finding] composeStacks step 2 (mergeObjects) iteratesstack.objectsunguarded — an unparsed input with a non-arrayobjectsraises a bare TypeError outside the ADR-0112 envelope; same defect family #18212 just closed forpermissions/data, one step earlier, PRE-EXISTING on main #18239 在飞(PR fix(spec): composeStacks refuses a non-arrayobjectswith an ADR-0112 envelope #19783,草稿)⇒ 排在 PR fix(spec): composeStacks refuses a non-arrayobjectswith an ADR-0112 envelope #19783 之后串行;⛔ 不并入它(在飞永不并)。permissions与data;把拒绝扩到其余CONCAT_ARRAY_FIELDS之前先各自重量。type字段设不了。Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
Claim: PM loop round 1
Session:
session_01VWsFyWDp8Rjb2Ma6a3Cyo8Branch:
claude/issue-19784-concat-fields-refusalWorktree:
objectstack-issue-19784Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/src/stack.zod.ts— composeStacks step 3, theCONCAT_ARRAY_FIELDSconcat pass only — + a new test file +.changeset/(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 composition input it used to accept with content silently dropped is refused (ruling
5690859601, C)Thread-read: 5788992982
Serial constraints cleared: serial predecessor #18239 / PR #19783 MERGED (
55095cc460); open PR #19666 (#14512, fenced 留守 item, Tier H) editsstack.zod.tsin thecomposeStacks/preservePackageEntriesregions — ordinary concurrency, later lander resolves; no other open PR (18 read this act) touches the file.Fold-or-serial (#19784 · #19785 share
packages/spec/src/stack.zod.ts): ⛔ not folded — gate ① fails (different defect shape and fix: silent content drop in the step-3 concat pass vs a bare TypeError inmergeActionsIntoObjects; triage5789001253says 「⛔ 不并」); ⛔ not serialised — the two declared regions (~:2946vs ~:4515atorigin/main3f9e2eaa1c) are disjoint, so both dispatch now; later lander merges main and resolves.Claimed 2026-09-23T04:58Z by seat post #18917. The dispatched
os-devruns under this same session id and writes ⛔ no second claim.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{ "issue": 19784, "status": "done", "branch": "claude/issue-19784-concat-fields-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/19794", "session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (subagent of the domain:spec seat 4 PM; same id)", "premise_still_valid": true, "summary": "composeStacks step 3 now refuses a non-array value for every concat key (all 37 rows of COMPOSE_KEY_DISPOSITIONS, derived and not transcribed). A value of undefined still means the key is absent. The refusal is StackSchemaInvalidError with code STACK_SCHEMA_INVALID, status 422, one zod invalid_type issue at the key, and a message naming the stack and the key, plus the map-form hint for MAP_SUPPORTED_FIELDS. No new error code and no export change. The per-key measurement at base 3f9e2eaa1c read YES for all 37 keys and ambiguous for none: B's content was absent from the composed top level. Under manifest preserve it survived only inside B's package body for the 33 body keys, and it was absent everywhere for packages, plugins, devPlugins and devLogins. The step-3b collectors' non-array branches now have no reachable caller (the strict parse, or step 3, refuses first), so they are kept as silent type guards with docblocks explaining why and their warn calls are gone. warnMalformedCollectionKey lost its dead value branch and is now warnMalformedCollectionEntry, which only mergeObjects calls; its text is unchanged. The objects refusal shares the new describeNonArrayCollection helper and its message is byte-identical. The assignee was set on arrival (baozhoutao), which is the PM's step 1; I left it alone. I sent the PM probe a status line in the transcript.", "tests": "At HEAD 0bf2e55646. (1) New packages/spec/src/compose-stacks-concat-shape-refusal.test.ts, 125 cases. (2) Ablation via scripts/ablation-replace.mjs, replacing the step-3 throw with continue: anchor x1 to x0, marker x0 to x1, blob 9b46ea3999bb to ed55f104c3e1. Result 'Tests 84 failed | 41 passed (125)' (37x2 refusal cases plus 10 shape rows red; every control green). Restored: blob == HEAD 9b46ea3999bb and git diff HEAD empty. The test resolves ./stack.zod from src, so there was no dist leg. (3) pnpm --filter @objectstack/spec test: 'Test Files 518 passed (518)', 'Tests 15213 passed | 1 todo'. (4) spec typecheck: exit 0 after gen:test-typecheck-debt, which recorded a debt shrink of 4 to 3 and 3 to 2 in compose-stacks-key-loss.test.ts. (5) spec check:generated: 'All 15 generated artifacts are up to date', against a dist built from this tree. (6) dispatch-gates --repo objectstack-ai/objectstack --ran: '83 derived famil(ies) accounted for — 80 run, 3 NOT-MEASURED'. All 80 exit 0. check:doc-formula-expressions first exited 3 because its prerequisite was not met; I built formula and lint, and the rerun exited 0.", "gates": { "derived": 83, "run_exit_0": 80, "not_measured": [ "NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (needs full pnpm build), exit 3", "NOT MEASURED: pnpm check:type-check-debt, reason: PREREQUISITE NOT MET (needs the ./packages/* build closure), exit 3", "NOT MEASURED: pnpm check:lean-entry-closure, reason: PREREQUISITE NOT MET (needs built @objectstack/objectql); the objectql build hit a lock queue-timeout (exit 99) behind holder pid 10118 (a spec vitest run, held 854s)" ], "clause2_pair": "node scripts/pm/check-clause2-carriers.mjs --pair 19794 exit 4 (C6: domain:spec contract review of record owed on head 0bf2e55646; seat's act)", "needs_contract_review_label": "absent on PR #19794 (not touched)" }, "line_budget": "322 insertions / 102 deletions across 6 files (dispatch-gates: 420 changed lines, under the 5000 human-merge threshold); skills/** not touched", "files_changed": [ ".changeset/19784-concat-fields-refusal.md", "packages/spec/src/stack.zod.ts", "packages/spec/src/compose-stacks-concat-shape-refusal.test.ts", "packages/spec/src/compose-stacks-key-loss.test.ts", "packages/spec/src/stack-artifact-crossref.test.ts", "packages/spec/test-typecheck-debt.json" ], "deviations": [ "File surface: beyond step 3, the diff touches the two step-3b collectors (their non-array branches, as the dispatch asked), the warn helper (its dead value branch removed and the helper renamed), and the objects refusal in mergeObjects. The mergeObjects change only swaps the inline kind/issues computation for the shared describeNonArrayCollection helper; its message is byte-identical. Two existing test files were changed because they pinned the removed skip-and-warn branch. test-typecheck-debt.json was re-recorded because typecheck required it (shrink only). No change in mergeActionsIntoObjects, preservePackageEntries or the options schema.", "Labels: zero label writes. The dispatch write budget names no label, and skip-changeset does not apply because a changeset is present.", "Merged origin/main 628e55dfa6 before opening the PR, with a plain merge commit; no conflicts." ], "mcp_calls": "0", "api_writes": "2 — (1) pr_create through fleet-write relay (repository_dispatch → POST /repos/objectstack-ai/objectstack/pulls, draft), run 35824375630 success; (2) this os-dev-report comment through scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/19784/comments). Plus git push of the branch (not a REST write). No PR body edit.", "open_questions": [], "out_of_scope_findings": [ "class: a · strict defineStack({ manifest, permissions: new Set([{ name: 'rep', label: 'Rep' }]) }) is ACCEPTED with permissions === [] (the same holds for a Map). Repro run at the branch tree with tsx printed 'Set strict ACCEPTED, permissions = []' and 'Map strict ACCEPTED, permissions = []'. Site: packages/spec/src/shared/metadata-collection.zod.ts normalizeMetadataCollection treats any typeof-object as the map form and calls Object.entries, which yields [] for a Set or Map, so the grants vanish before the parse sees them. This hits every MAP_SUPPORTED_FIELDS key. It is upstream of composition, so not fixed here. Dedupe words: normalizeMetadataCollection Set Map · map form Object.entries Set empty · defineStack Set permissions accepted empty · MAP_SUPPORTED_FIELDS non-plain object", "carrier: none — the seat files it; noted in the PR #19794 Acceptance notes" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
ACCEPT — PR #19794 (head
0bf2e55646), 2026-09-23T06:17ZImplemented-by:
claude/issue-19784-concat-fields-refusalReviewed-by:
session_01VWsFyWDp8Rjb2Ma6a3Cyo8(domain:specseat 4; at-tier contract review on the PR — PASS)Fixes #19784,Clause-②: no (narrowing); 6 files, +322/−102; no governed path ⇒ seat lands it.CONCAT_ARRAY_FIELDSkey was measured, not justpermissions/data. All 37 read YES (content lost) and none ambiguous, which the reviewer reproduced independently (74 rows). A non-array value is now refused with the fix(spec): composeStacks refuses a non-arrayobjectswith an ADR-0112 envelope #19783 envelope (STACK_SCHEMA_INVALID/ 422);undefinedpasses.mergeObjectsrouted through a shareddescribeNonArrayCollection(message byte-identical); two pinned tests re-judged; debt ledger shrink only.defineStackACCEPTS aSetorMapfor a map-form collection key and stores it as[]—normalizeMetadataCollectionreads any object as the map form, so the authored grants vanish before the parse sees them #19796 (strictdefineStackaccepts aSet/Mapas[]).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, in rework on the same file): it merged cleanly with this PR in a test merge; after both land, its kind/issues block should use this PR's helper.Generated by Claude Code
fix(spec): composeStacks refuses a non-array concatenated collection …
test(spec): the first root-level test file group's titles state each …
test(spec): the second root-level test file group's titles state each…