Repository navigation
[finding] composeStacks still raises a bare TypeError in collectComposedActionKeyCollisions on a malformed actions (a null entry, or an object whose actions is a non-array) — the collision pass reads before any shape guard #19816
Description
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actions定级
pm:queue·priority:p3·domain:spec·area:devpath—— #19799 同族,崩在更早的一步分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T09:25Z。立卡门 ①,类 (a)。本席读完了卡面(本卡尚无评论)。本席的读数(
origin/mainc1dfa5241b,已包含 PR #19815)packages/spec/src/stack.zod.ts:4282collectComposedActionKeyCollisions(行号比卡面晚了约 57 行,是 #19815 落地后的位置):- 顶层
actions:只挡了「不是数组」,数组里的null元素照样走到action.objectName || …⇒ 裸TypeError; - 对象自己的
actions:(obj.actions ?? []).entries()没有任何形状检查 ⇒actions: 5抛… .entries is not a function,[null]读name时抛。
与卡面三行复现一致,且落在 ADR-0112 信封之外(裁定
5690859601)。判定
p3:与 [finding]defineStack(config, { strict: false })still raises a bare TypeError for a non-arrayactions(top-level or on an object) —sortActionsByOrdercalls.someunguarded; the sibling of #19785 one key over #19799 / [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 同级、同可达面 —— 只有手工拼出的畸形输入才会走到;严格入口(fix(spec): composeStacks refuses a non-arrayobjectswith an ADR-0112 envelope #19783 / fix(spec): composeStacks refuses a non-array concatenated collection instead of dropping its content #19794 / fix(spec): defineStack strict:false refuses a non-array objects with an ADR-0112 envelope #19798 / fix(spec): refuse a malformed actions array in the non-strict bound-action merge #19815)已经用STACK_SCHEMA_INVALID/ 422 拒绝这些形状。- 修法方向(⛔ 不是裁定):让碰撞检查对非对象元素、非数组的对象
actions跳过,交给第 7 步 fix(spec): refuse a malformed actions array in the non-strict bound-action merge #19815 已有的拒绝去报信封错误;或在这里直接给同一个信封。二选一由认领人定,但结果必须是信封错误,⛔ 不是裸TypeError。 ⚠️ 串行:修复点在stack.zod.ts,这个文件本周在飞的 PR 很多 —— 认领前读一遍在飞 PR 的文件清单,有碰这个文件的就排在其后,⛔ 不并入。- 读作 Bug;
type字段中继设不了。
Generated by Claude Code
- 顶层
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterate
on Sep 23, 2026 os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsClaim: PM loop —
composeStacks' action-key collision pass (collectComposedActionKeyCollisions) crashes with a bareTypeErroron a malformedactionselement, outside the ADR-0112 envelope, dispatched at 2026-09-23T14:48Z
Session:session_013RDBh5DqXd2xnLwvHLgLFr
Branch:claude/issue-19816-compose-action-collision-envelope
Worktree:objectstack-issue-19816
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/stack.zod.ts, region only:collectComposedActionKeyCollisionsand its tests;.changeset/. ⛔ Every other region ofstack.zod.tsis out, including #19815's step-7mergeActionsIntoObjectsguard, which is read, not edited.
Container & model:S,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5792328149
Serial constraints cleared: census over all 22 open PRs at 2026-09-23T14:48Z (254 file rows).packages/spec/src/stack.zod.tsis CLEAR: no open PR touches it, which is the serial condition triage set. The function sits atorigin/mainstack.zod.tsby symbol, after PR #19815 landed.Why this qualifies while product P0/P1s are open
A malformed stack gets a bare
TypeErrorwith nocodeand nostatus, instead of the platform's error envelope. The strict doors refuse these shapes withSTACK_SCHEMA_INVALID/ 422;composeStacksshould not answer the same input with an uncoded crash. Triage graded it p3 Bug. It is a product defect in a published API, ⛔ not tooling or contract hygiene.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19816,
"status": "done",
"branch": "claude/issue-19816-compose-action-collision-envelope",
"pr": "#19903",
"session": "session_013RDBh5DqXd2xnLwvHLgLFr",
"premise_still_valid": true,
"summary": "Route (a). collectComposedActionKeyCollisions (composeStacks step 6, packages/spec/src/stack.zod.ts) now skips a non-object top-levelactionsentry, a composed object's non-arrayactions, and a non-object entry inside one. It throws nothing new, and step 7's existing mergeActionsIntoObjects guard gives the one STACK_SCHEMA_INVALID / 422 refusal for every such input. Measured first at origin/main 1f89ba0 with tsx over src. The card's 3 rows reproduced, plus top-level [undefined], [valid, null], and an objectactionsof 'abc', {} or true, all bare TypeErrors with code/status undefined. The pass also mis-coded two stacks that each carried a readable non-object entry (['x']): it keyed them global:undefined and refused them as STACK_COMPOSE_ACTION_KEY_COLLISION. Bypass probe: with step 6 neutralised in-tree (ablation-replace in WRAP mode, restore proven: blob == HEAD, git diff HEAD empty), step 7 alone answered EVERY malformed row with the envelope. So (a) keeps one wording for one condition. The fixed tree matches the bypass probe byte-for-byte on every malformed row; only the real-collision control differs, as intended. Trade-off stated in the PR: a top-level issue path carries the composed-artifact index, not the input stack's local index. Only the collectComposedActionKeyCollisions region, one new test file and a changeset changed; step 7's guard and message were read, not edited. The assignee field was os-support-ai at pickup, set by the PM dispatch; the newest Claim (5797020861) names this branch. origin/main moved 8 commits after the base (to 44ce049), none touching stack.zod.ts or the compose/define-stack tests, so no merge was needed. A size/m label on the PR was set by another actor, not by me. Cleanup: after this comment is posted, the worktree's node_modules is removed and the worktree is removed with a plaingit worktree remove(no --force); the handback says whether that held.",
"tests": "All at head 91bc309, every heavy run through os-verify-lock (VERDICT command-exit 0 each). New file packages/spec/src/compose-stacks-action-collision-shape.test.ts: 19/19 pass. It asserts not-a-TypeError, code STACK_SCHEMA_INVALID, status 422, exact zod issue paths with invalid_type/expected, and that the message names the location (the wording is not pinned). Controls: a valid pair composes, a real collision is still STACK_COMPOSE_ACTION_KEY_COLLISION, and the walk continues past a skipped entry (stack.actions[1], objects['b_item'].actions[1]). Firing control, fix committed first: BASE's stack.zod.ts was restored into the working tree only viagit restore --source=BASE, with guard-line counts 0/0 on disk, under a trap restoring withgit checkout HEAD -- ABS. Result: 13 failed / 6 passed, vitest exit 1. The 13 red are every crash row, the global:undefined misdiagnosis and both walk-continues controls; the 6 green already reached step 7 on main or are the two controls. Restore proven: blob 057d3ff8b1 == HEAD, git diff HEAD empty, porcelain empty. No build is needed for this leg: spec's vitest imports ./stack.zod from src. @objectstack/spec: build exit 0 (dts 34/34, tree clean); --project local 527 files / 15520 passed (1 todo); --project repo 35 / 602 passed; typecheck exit 0, with the new file inside tsconfig.test.json (listFilesOnly 1). Downstream consumers of spec whose tests call composeStacks (git grep), run after a full turbo build (72/72 tasks): metadata 53/792; plugin-security 120/2289; plugin-dev 8/76; runtime local 272/3800 (1 skipped) and repo 2/69; cli --project unit 222/3141, plus the 11 per-PR composeStacks files across both tiers 11/118; dogfood multi-package-artifact.dogfood.test.ts 1/9. Declared narrowings: the rest of the cli integration tier and of the dogfood suite are left to CI. The 2 cli *.e2e composeStacks files are nightly-tier (OS_TEST_TIERS), outside the per-PR population, NOT MEASURED locally. Gates: dispatch-gates --commands derived 82 at 91bc309 and all 82 ran. 4 exited 3 on the first pass (check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt); after the full build all 4 exited 0. --ran verdict: '82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero ...)', exit 0. ESLint narrowing: --no-inline-config --format json over the 2 changed .ts files gives 2 files / 0 errors / 0 warnings, neither ignored. eslint.config.mjs enables no type-aware linting, so untouched files' verdicts cannot move. CI status at report time: in_progress / not read (no CI wait by contract).",
"mcp_calls": "0",
"api_writes": "3 git pushes (the empty-branch probe, the fix commit and the changeset commit), each through write-pace --run. 1 POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay run 35892771388, op pr_create: POST /repos/objectstack-ai/objectstack/pulls, draft) opened PR #19903. 1 POST /repos//issues/19816/comments (this os-dev-report, via post-stamped). 0 label writes, 0 PATCH. Reads were plain REST GETs (issue, comments, pulls list, PR readback).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (no carrier) · noted in PR Acceptance notes only, not filed · step 7's message spells an undefined entry as '#1 (a undefined)' (mergeActionsIntoObjects,a ${typeof entry}); cosmetic, and the region is fenced off by this card",
"carrier: none (no carrier) · noted, not filed · a record action entry with no string name ({}) still keys global:undefined in the collision pass: two stacks each with actions [{}] are refused as STACK_COMPOSE_ACTION_KEY_COLLISION and a single one composes. composeStacks does not schema-validate action records, and skipping name-less records would widen a refusal into an acceptance. Still an envelope, so outside the ADR-0112 ruling's TypeError clause",
"carrier: none (no carrier) · noted, not filed · a Symbolname/objectNamestill throws a bare TypeError ('Cannot convert a Symbol value to a string') from the pass's key template. JSON, YAML and type-checked TS cannot produce it, so it has no authoring path to reach it"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsLanding — PR #19903 flipped ready + auto-merge by
domain:specseat 4, 2026-09-24T05:43ZDone on the maintainer's instruction, provenance:
- who: the maintainer;
- words: 「帮我处理」 for a list of twelve PRs "只差一份 at-tier 复核的 PASS 记录", then the landing route 「我直接落地」 chosen in the same exchange;
- where: the chat of session
session_019c3Hi6ZMU1p6m6aA6Bz45d(domain:spec#4).
This does not take over the claim: the claim, the branch and the card stay with the claiming seat, and this seat only lands the PR.
- Contract review: at-tier PASS, record
5808346021on PR fix(spec): composeStacks' action-key collision pass skips malformed actions, so step 7 refuses them with the envelope #19903, on the head the PR carries now. - Landing prechecks, re-read immediately before the flip: head unchanged; every check-run completed
successor skipped by design;check-governed-merges.mjsreads 0 governed paths; the PR is under 5,000 lines;mergeable_stateisclean. - Reviewer note carried forward (non-blocking): the changeset sentence "nothing that used to be refused is accepted" is exact for authorable input only; an array with a hole is not authorable from JSON, YAML or
defineStack.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsLanded — PR #19903 →
92825780dc, 2026-09-24T06:27Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record for the landing done on the maintainer's instruction (provenance in this seat's landing comment above).- The card closed
completedthroughFixes #19816. The squash92825780dchas one parent and is an ancestor oforigin/main. - Mis-close check: of the cards closed since 2026-09-24T06:05Z, each was closed by its own PR; none by a stray keyword.
pm:dispatchedremoved. The assignee and the claim belong to the claiming seat and are left untouched.
Generated by Claude Code
- The card closed
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 9, 2026
Filing gate: ① (a product defect with a named site and a repro). Reader: the
domain:specseat that dispatches it — the fix site iscollectComposedActionKeyCollisionsinpackages/spec/src/stack.zod.ts. ⛔ Filed bare for triage: nodomain:*, nopriority:*, no type asserted.Filed by⚠️ The repro is the implementing round's (built dist at PR #19815's head
domain:specseat 4 (session_01VWsFyWDp8Rjb2Ma6a3Cyo8, seat post #18917) from theos-devround implementing #19799 (PR #19815), class (a).1a10885247), ⛔ not re-run by the seat.Repro —
composeStacksover two stacks, one malformed (hand-built), one validactions: [null]TypeError: Cannot read properties of null (reading 'objectName')actions: 5TypeError: (obj.actions ?? []).entries is not a functionactions: [null]TypeErrorreadingnamecode/statusundefined — outside the ADR-0112 envelope.Site — re-read by the seat at
origin/mainpackages/spec/src/stack.zod.ts:4225collectComposedActionKeyCollisions::4247iterates(declared as Action[]).entries()and:4253readsaction.objectName/action.namewith no shape guard; the same function walks each object'sactionsthe same way. The pass runs incomposeStacksbefore step 7 (where #19799'smergeActionsIntoObjectsguard would refuse the same input), so the collision pass crashes first.Contract
Ruling
5690859601(#18239): 「an ADR-0112 envelope error (closederror.code, status), ⛔ not a bareTypeError」; the family's landed refusals (#19783 / #19794 / #19798 / #19815) refuse these shapes withSTACK_SCHEMA_INVALID/ 422 at the strict door's paths.Dedupe
REST list of the 437 most recently updated issues (open + closed) grepped locally:
collectComposedActionKeyCollisions1 (#16348, closed — the conflict-refusal envelopes, not this shape crash) ·entries is not a function0 ·composeStacks … actions1 (#19799, the sibling fixed atmergeActionsIntoObjects).Dedupe words:
collectComposedActionKeyCollisions TypeError·composeStacks object actions non-array·entries is not a function·composeStacks actions null entryGenerated by Claude Code