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
service-automation: a parent-id resume of a map whose parked item run was cancelled still advances past that item #27971
Ruled: 6523833308 · letter A · 2026-10-30T13:22Z
Ruling-ref: 6523833308
Filing gate: ① read beside a ruled card by the #27877 dev (PR #27951, os-dev-report 6515519800, out-of-scope finding) and escalated for filing by its contract review 6515887943. Filed by domain:server seat 4. ⛔ Not a claim. No grade: triage sets the lane and grade, and whether it is the accepted shape.
What the code does (read, not measured)
After PR #27951 a parent-id resume of a map whose item child is still suspended is refused 409 RESUME_CHILD_PENDING. The ruling (triage 6513867475) covers a still-suspended item only. When the item run is gone because it was cancelled through cancelRun, which does not touch the parent, a parent-id resume of the map still advances past that item: the map moves on as if the item had run.
Wanted (route is triage's)
Decide whether advancing past a cancelled item is the accepted shape (cancellation means skip), or whether the parent should record the item as cancelled or fail. Measure first through engine.resume.
Also from the same review (docs, no behaviour)
The new 409 is missing from the code lists in packages/client/src/index.ts (~L5947) and packages/services/service-automation/README.md (~L310), and docs/qa/platform-checklist/areas/automation.json has no 409 RESUME_CHILD_PENDING row. These can ride whichever PR takes this card.
Dedupe
Read the open titles for map + cancel, parked item, RESUME_CHILD_PENDING: only #27877. New.
The docs half (the new 409 in the packages/client code list, the service-automation README and the checklist) rides along. The checklist edit is declared in the claim.
Rider for whichever PR takes this card · 2026-10-30T06:55Z
The contract review of PR #27951 (6515887943) left one should-fix on the same walk this card touches: resolveSuspensionChain in service-automation/src/engine.ts never seeds seen with the root run id, so a corrupt A→B→A correlation cycle yields the chain [A, B, A] and the refusal names the caller's own run as the one to resume. Seeding seen with the root before the loop fixes it. It rides here with the docs half already listed (client and README code lists, QA checklist row for 409 RESUME_CHILD_PENDING).
Claim: PM loop round 2 · 2026-10-30T11:53Z · unblocked: PR #27951 merged, so Blocked-by: #27951 is cleared
Session: session_016WV4d1oZadHHFdEZXANxiG (card thread; seat session_016WV4czVnUdQQCVKSW5J2uk)
Account: os-elon-musk
Branch: claude/project-thread-2p9rtc (the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread opened)
Worktree: objectstack-issue-27971
Domain: domain:server
Seat: domain:server#1
File surface: packages/services/service-automation/src/engine.ts and run-resume.ts (the map parent-id resume past a cancelled item; resolveSuspensionChain's seen seeding — rider 6516883756), the map node's item-result recording under packages/services/service-automation/src/, tests there; docs half per triage: the 409 RESUME_CHILD_PENDING entry in packages/client/src/index.ts's code list and packages/services/service-automation/README.md, and the docs/qa/platform-checklist/areas/automation.json row; .changeset/*. Re-graded by the seat 2026-10-30 ~14:15Z under ruling A (6523833308): plus packages/spec/src/automation/builtin-node-config.zod.ts (the MapConfigSchema.outputVariable describe text only, with its regenerated spec artifacts) and content/docs/automation/flows.mdx (the batch paragraph): an element can be the closed $-keyed cancelled marker. Amended by the seat ~15:50Z from the contract review of PR #28176 (should-fix S1): one sentence each naming RESUME_CHILD_PENDING's second arm (the item row is gone and its history is not cancelled) and its remedy, in packages/spec/src/contracts/automation-service.ts (docblock), packages/spec/src/api/error-code-ledger.zod.ts (the code's row text, with regenerated artifacts), packages/client/src/index.ts (the resume() docblock), the service-automation README code list and docs/qa/platform-checklist/areas/automation.json; and the remedy in that arm's refusal message. Stop and report on any other breach.
Container & model: M, mode:thread, model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: yes (widening) — ruling A, Ruling-ref: 6523833308; contract review at CONTRACT_REVIEW_TIER on the final head
Metadata-protocol: added (a readable output shape; the seat confirms the arm from the diff)
Responsibility: service-automation's map resume path | none (the cancel path does not touch the parent) | any flow whose map item is cancelled by an operator, today
Thread-read: 6516883756
Premise: holds (triage 6516093830 measured-first rule; PR #27951 merged 2026-10-30T11:48:11Z)
Serial constraints cleared: open PR — none touches service-automation/src/engine.ts or run-resume.ts at this reading. Seat 3's in-flight #28016 (branch claude/project-thread-0vmpzv) declares the connector-delegate region of engine.ts only, disjoint from the resume paths; whichever lands second merges main.
{
"issue": 27971,
"status": "needs_decision",
"branch": "claude/project-thread-2p9rtc",
"pr": null,
"session": "session_016WV4d1oZadHHFdEZXANxiG",
"premise_still_valid": true,
"summary": "Measured on main f6adf480 through engine.resume: a 2-item map whose item 1 parks, cancelRun(item1) (true; getRun(item1).status = cancelled), then resume(parentId) answers success/paused and the map parks on item 2 with $mapState {started:2, results:[]}; after item 2 completes the parent row is completed and a downstream node sees results = [item 2's output] and signoffs.count = 1, so item 1 is silently skipped and results[0] is no longer item 0. The graded rule needs the cancelled item recorded where downstream nodes read it, and the map's per-item result has no place for a status: outputVariable (and the node output signoffs.results) is a bare array of child outputs, spec-described as \"Each item's subflow output, collected in order\" (MapConfigSchema.outputVariable). Every way to record cancelled adds an author-readable shape, so per the dispatch order this is Clause-②: yes and I stopped before the main fix. Shipped on the branch (pushed, no PR yet): rider 6516883756 (seen seeded with the root run id, A→B→A cycle pinned) and the docs half (client resume docblock, service README code list, automation checklist acceptance[4] + variant + history revision 9).",
"tests": "@ 24787eeb: pnpm --filter @objectstack/service-automation test → Test Files 226 passed (226), Tests 2863 passed (2863); typecheck (check:test-typecheck) OK. New pin map-parent-resume-refusal.test.ts 'names the item run, never the caller's own, when a corrupt correlation cycles back to the caller': 6/6 green. Ablation (committed fix first; test imports ./engine.js from src, no dist on the path): scripts/ablation-replace.mjs anchor 'const seen = new Set(TYPE)([runId]) (the seeded constructor; TYPE = string)' x1→x0, blob c3ff057c→43ab85a7 → the new pin red (1 failed | 5 passed), restored blob == HEAD c3ff057c, git diff HEAD empty. Repro of the cancelled-item skip is a scratch test, not committed.",
"gates": "@ 24787eeb, local subset: dispatch-gates --commands derived 58 families; --ran reconciliation: 58 accounted, 55 run exit 0, 3 NOT MEASURED (exit 3, prerequisite): node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone cannot reach its pinned positive control), pnpm --filter @objectstack/spec run check:skill-examples (client-react/client dist not built), pnpm check:dual-build-cjs-loads (adapter/app/client dist not built) — all three re-run on the final head once the main fix lands. node scripts/check-platform-checklist.mjs --changed-from origin/main :: exit 0 (14 anchors judged, 0 lost). pnpm lint: not run (repo-wide scan, CI-owned). No PR, so no CI check-runs yet.",
"dogfood_battery": "n/a — no `!` in a PR title and no (narrowing) arm; no PR opened",
"mcp_calls": "5 — read only: issue_read get + get_comments (#27971), ToolSearch loads; no MCP write tool",
"api_writes": "1 — this os-dev-report comment via post-stamped (relay)",
"open_questions": [
{
"question": "Where does a map record an item whose run was cancelled, so the map may advance (triage 6516093830) and downstream nodes can read it?",
"options": [
"A — a marker element at the item's index in results / outputVariable (e.g. a `$`-keyed object naming status cancelled and the item run id; a child flow's output keys can never start with `$`, so it cannot collide), count = items that ran. Keeps results[i] = item i. Needs the MapConfigSchema.outputVariable describe text and the flows.mdx batch-approval paragraph to say an element can be that marker: packages/spec + docs, outside this claim's surface; additive Clause-② yes. Cost: M, contract review owed.",
"B — results unchanged (outputs of the items that ran); the node output gains a cancelled list of {index, runId}, read downstream as `NODE.cancelled`; count = items that ran. No spec text change (the map node output is undeclared in spec), but it is a new author-readable key, and once an item is cancelled results[i] is no longer item i — a silent misalignment for any downstream that pairs results with the collection. Cost: S-M.",
"C — refuse: a parent-id resume of a map whose item run was cancelled answers 409 RESUME_CHILD_PENDING (the triage's own fallback arm, no new code), the message naming the cancelled item run and telling the operator to cancel the parent run. No new shape, but the code's spec docblock says the item 'is still suspended', so its meaning widens (a docblock sentence in packages/spec), and an operator can never continue a batch past a cancelled item. Cost: S."
],
"recommendation": "A, because it is the triage's graded reading ('the item's result records cancelled', not counted as a success, readable downstream), keeps the ordered-collection invariant results[i] = item i that B silently breaks, and is the per-item status record the deferred concurrent fan-out (ADR-0037) would need anyway; its cost is an additive spec describe edit under a re-graded claim and a contract review. C is the zero-surface fallback if the maintainer prefers that a cancelled item stops the batch."
}
],
"out_of_scope_findings": [
"class: a · reach: named producer — plugin-approvals' revise-window recall calls cancelRun on the run parked at the approval (packages/plugins/plugin-approvals/src/approval-service.ts, per run-resume.ts cancelRun's docblock); on a batch sign-off (showcase_release_signoff → one_task_signoff) that run is a map item, so the batch run stays parked with nothing to wake it and a raw resume of it is the skip this card measures. Read, not measured. carrier: #27971 (this card's ruling decides it) · noted, not filed"
]
}
pm:dispatched → needs-user-decision. The claim (6522041026) stays; the card thread holds the rider and the docs half on claude/project-thread-2p9rtc @ 24787eeb and opens them as a Part of #27971 PR now (no behaviour question in either). The main fix waits for the ruling. ⛔ The seat does not answer this.
One-line problem. When an operator cancels one item of a batch (a map node) and the batch is resumed, the batch finishes as if that item never existed: downstream steps get one result fewer, and every result after the gap sits at the wrong position.
Background (measured by the dev on mainf6adf480, through engine.resume). A two-item map parks on item 1; cancelRun(item1) answers true; resume(parentId) advances, the map parks on item 2, and when item 2 completes the parent is completed and a downstream node reads results = [item 2's output], count = 1. Triage 6516093830 graded the rule: the map may advance, but the item's result records cancelled, is never a success, and is readable downstream; a map that cannot record it refuses, as #27877 does.
Governing text.MapConfigSchema.outputVariable (packages/spec): "Each item's subflow output, collected in order." The result is a bare array of outputs, so there is no place for a status. RESUME_CHILD_PENDING's docblock: the item "is still suspended". No ADR decides a cancelled map item (check-prior-rulings --card 27971: 13 hits on broad terms, none on this question).
Protocol. Every way to record cancelled downstream adds an author-readable shape → Clause-②: yes for A and B. C changes no shape but widens a refusal code's meaning (one docblock sentence).
option
what it does
customer-visible result
effort
A marker at the item's index
results[i] for a cancelled item is a $-keyed marker (status cancelled, the item run id); count = items that ran; spec describe + flows.mdx say an element can be that marker
the batch continues; position i is still item i; a downstream step can see which item was cancelled
M · packages/spec describe + service-automation + docs · one PR · contract review owed · no precedent
B side list
results unchanged; the node output gains cancelled: [{index, runId}]
the batch continues; results[i] silently stops matching item i once anything is cancelled
S–M · no spec text (node output undeclared) but a new key
C refuse
a parent-id resume of a map with a cancelled item answers 409 RESUME_CHILD_PENDING, naming the cancelled run and telling the operator to cancel the parent
a batch can never continue past a cancelled item; the operator must cancel the whole batch
S · one docblock sentence in packages/spec
Business reading. A = a payroll run where one employee's payslip was voided: the run finishes and the register shows that line as "voided". B = the register drops the line and everyone below moves up one row. C = voiding one payslip forces voiding the whole run.
Named producer (read, not measured — dev's out-of-scope finding).plugin-approvals' revise-window recall calls cancelRun on the run parked at the approval. On a batch sign-off (showcase_release_signoff → one_task_signoff) that run is a map item, so today the batch is left parked and a raw resume is exactly this skip. Under C every such recall strands the batch until the operator cancels it.
① long-term: A grows the contract by one closed marker shape and keeps the ordered-collection invariant; B adds a key and breaks the invariant; C adds nothing but leaves "continue past a cancelled item" impossible.
② business pull: real today — any operator cancel on a batch item, and the approval recall path on batch sign-offs (named producer above).
③ AI-error: C is the loudest (a refusal with a prescription); A is a closed marker an author can test for; B fails silently (misaligned indexes).
④ no-spread: C declares nothing; A declares one marker forever; B declares one key forever.
Prior rulings read: map,cancelled,resume_child_pending,outputvariable → 13 hits; none on a cancelled map item (ADR hits are broad-term: ADR-0014 §4, ADR-0019 D4, ADR-0020 D1/D2, ADR-0032 §3, ADR-0062 D7, ADR-0065 §2, ADR-0080 §2, ADR-0081 §4, ADR-0099 D2); thread: none
Recommendation: A (from ① only). In two years a batch step should report per-item status, with cancelled items continuing in place — the shape mainstream orchestrators use (AWS Step Functions' Map reports per-item results/failures with the item identity; Power Automate's apply-to-each keeps iterating and marks the failed iteration). It is also the per-item status record the deferred concurrent fan-out (ADR-0037) needs. B is not taken: it breaks results[i] = item i silently. C is not taken: it makes the approval recall strand every batch sign-off. Confidence gap: the marker's exact keys are the dev's proposal and get fixed in the contract review; the approval-recall path is read, not measured.
Self-check: on ① alone, A. Do ②③④ flip it? No — ③④ favour C, but they only argue for keeping the marker closed and small, not for a different letter.
After the ruling. A: the seat re-grades the claim to Clause-②: yes, adds packages/spec (MapConfigSchema.outputVariable describe) and content/docs (flows.mdx batch paragraph) to the surface, the card thread builds it, fable contract review on the final head, then the seat lands. C: the card thread adds the refusal and the docblock sentence, S, no contract review beyond the code-meaning sentence. Either way the rider/docs PR lands first.
Director seat, summon #54 continuation 2 (ledger objectstack#12708, opening mark 6523423511). Presented to the maintainer in batch #431 as item 3 of 3 — server seat 1's decision analysis 6523020455 on dev report 6522980749 (needs_decision; the rider and the docs half are already on the branch).
Seat increment (re-read at origin/main59547058, 2026-10-30T13:03Z):
Premises re-run, hold: MapConfigSchema.outputVariable is described "Each item's subflow output, collected in order" (builtin-node-config.zod.ts L1490) — a bare array with no place for a status; PR fix(service-automation)!: a parent-id resume of a map whose item run is still suspended is refused, naming the item run #27951 (the still-suspended-item refusal) merged 2026-10-30T11:48Z; cancelRun (run-resume.ts L727) does not touch the parent; the branch claude/project-thread-2p9rtc is on the remote (head moved past the report's 24787eeb, the rider and docs half pushed). The dev measured the skip live through engine.resume on f6adf480: a cancelled item 1 vanishes, results = [item 2's output], count = 1.
Facets: ① a batch step reports per-item status with cancelled items kept in place — the shape of AWS Step Functions' Map (per-item results and failures by item identity) and Power Automate's apply-to-each (the failed iteration is marked, the loop continues); it is also the per-item record ADR-0037's fan-out will need. A keeps results[i] = item i; B breaks it silently once anything is cancelled; C makes "continue past a cancelled item" impossible. ② real today: any operator cancel on a batch item, and the approval recall path on batch sign-offs (plugin-approvals revise-window recall calls cancelRun on the parked run; read, not measured) — under C every such recall strands the batch until the operator cancels it whole. ③ C is loudest, A is a closed marker an author can test for ($-keyed, so it cannot collide with a child output), B fails silently. ④ A declares one marker shape forever; the marker's exact keys are fixed in the contract review.
Seat's letter: A — the cancelled item's slot in results / outputVariable holds a closed $-keyed marker naming status cancelled and the item run id; count = items that ran; the spec describe and the flows.mdx batch paragraph say an element can be that marker. Not B (silent index misalignment), not C (the approval recall strands every batch sign-off).
Workload of A: domain:server · the open claim re-graded to Clause-②: yes (widening), surface + packages/spec (MapConfigSchema.outputVariable describe) + content/docs/automation/flows.mdx · M · contract review at CONTRACT_REVIEW_TIER on the final head (packages/spec/src/** hit) · Metadata-protocol: added (a readable output shape, no new authored key — the seat confirms the arm from the diff) · the rider / docs PR lands first as Part of #27971.
After the ruling: needs-user-decision → pm:dispatched (the claim 6522041026 and the card thread stand); server seat 1 re-grades the claim and the thread builds.
Ruling: batch #431 item 3 · letter A · maintainer 「27971 同意」 2026-10-30T13:20Z
Director seat, summon #54 continuation 2 (ledger objectstack#12708, opening mark 6523423511, brief 6523502465). The maintainer answered in the summon chat, verbatim: 「27971 同意」 — the seat's letter A.
What the ruling binds (letter A): when a map item's run was cancelled and the parent is resumed, the map advances and the cancelled item keeps its slot: results[i] / outputVariable[i] holds a closed, $-keyed marker naming status cancelled and the item run id (a child flow's output keys never start with $, so the marker cannot collide); count = items that ran; a cancelled item is never counted as a run or a success; downstream nodes read the marker where they read results. The MapConfigSchema.outputVariable describe text and the flows.mdx batch paragraph say an element can be that marker. A map that cannot record it refuses, as #27877 does. Not taken: B (a side list; results[i] silently stops matching item i), C (refuse; the approval recall path strands every batch sign-off). The marker's exact keys are the dev's proposal and are fixed in the contract review, not here.
Governing text: triage 6516093830 (graded rule: advance, record cancelled, readable downstream, never a success); MapConfigSchema.outputVariable (builtin-node-config.zod.ts L1490); 6513867475 (#27877). Premises re-read at origin/main59547058 (brief 6523502465).
Workload of the letter:domain:server · the open claim 6522041026 re-graded to Clause-②: yes (widening) with packages/spec (MapConfigSchema.outputVariable describe) and content/docs/automation/flows.mdx added to the surface · M · contract review at CONTRACT_REVIEW_TIER on the final head · Metadata-protocol: added (a readable output shape; the seat confirms the arm from the diff) · the rider / docs half already on claude/project-thread-2p9rtc lands first as Part of #27971.
Execution:needs-user-decision → pm:dispatched in this act (claim 6522041026 by os-elon-musk and the card thread stand); server seat 1 re-grades the claim with Ruling-ref: naming this comment and the thread builds the main fix.
os-decision-facets
① 长远:批处理逐项报状态、取消项原位保留,是 Step Functions Map 与 Power Automate apply-to-each 的形状,也是 ADR-0037 扇出需要的逐项记录
② 拉动:操作员取消批次项,以及审批撤回路径(批量签批时撤回即取消一个 map 项)
③ 防错:封闭的 $ 标记作者可测试;B 静默错位;C 把整批卡死
④ 不扩散:一个封闭标记形状,键名在契约复核里定死
{
"issue": 27971,
"status": "done",
"branch": "claude/project-thread-2p9rtc",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28176",
"session": "session_016WV4d1oZadHHFdEZXANxiG",
"premise_still_valid": true,
"summary": "Ruling A (6523833308) built on fresh main (merged origin/main after PR #28101's squash; the branch name is not claude/issue-*, so no force-push). The resume gate reads getRun(itemRunId) when the chain ends on a map: link with the item row gone and the run is parked at the built-in map node: cancelled → resume continues with an engine-built signal setting MAPNODE.$mapItemCancelled, and the map pushes the closed marker {$status: 'cancelled', $itemRunId} into the item's slot, count = items that ran; anything else (no row, unreadable, other status) → 409 RESUME_CHILD_PENDING with nothing moved. A caller signal writing the $ namespace still gets 400 INVALID_SIGNAL. MapConfigSchema.outputVariable describe, the map node's config-schema description and the flows.mdx batch paragraph say an element can be the marker; reference page regenerated. Head 84b980b3.",
"tests": "@ 84b980b3 tree (code at 0dfb6177): service-automation Test Files 231/231, Tests 2911/2911; typecheck OK; spec Test Files 756/756, Tests 22513 passed + 1 todo; spec check:generated 14/14 up to date. New map-cancelled-item-resume.test.ts 7 cases. Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty: status check → item == null: 1 red; caller-signal guard deleted: 1 red; count filter → results.length: 2 red; marker push deleted: 2 red; nodeType map guard deleted: concurrent-replica-resume-race THE CONDITION (correlation) red. src imports, no dist on the path.",
"gates": "dispatch-gates --commands: 107 derived, --ran reconciliation 107 run, 0 NOT MEASURED (all with exit codes) at 0dfb6177; check-adr-0087-registration first exit 1 (no disposition), fixed in the changeset, re-run exit 0 at 84b980b3 together with the five changeset families (all 0). check-platform-checklist --changed-from origin/main OK (17 anchors, 0 lost); check-nul-bytes 0; git merge-tree vs origin/main clean. PR check-runs: in_progress at report time (CI's to converge; seat reads them).",
"dogfood_battery": "0dfb6177 · VERDICT command-exit 0 · Test Files 358 passed, 3 skipped (361) · Tests 2883 passed, 20 skipped (2903) · no failures; the later commit only adds the changeset's ADR-0087 line",
"mcp_calls": "7 — read only: issue_read x2, search_pull_requests x1, pull_request_read get_check_runs x1, ToolSearch loads; subscribe_pr_activity x2 (harness subscription, not a GitHub write); no MCP GitHub write tool",
"api_writes": "4 this card — comment 6522980749 (needs_decision report), pr_create #28101, pr_create #28176, this report comment (all via fleet-write relay)",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #27971 contract review — the approval revise-window recall (plugin-approvals calling cancelRun) is a real producer of a cancelled item in a batch sign-off; read, not measured · noted, not filed"
]
}
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 审批与自动化 | none | P②
Triage: p2 ·
domain:server·area:automation·pm:blockedon PR #27951 · 2026-10-30T05:57Z⛔ Not a claim.
engine.resume. Graded rule: a cancelled item is an explicit operator act, so the map may advance, but the item's result recordscancelled. It is never counted as a run or a success, and downstream nodes can read it. A map that cannot record it refuses, the same as service-automation: resuming a run paused at amapnode by the PARENT run id completes the parent and leaves the parked item child suspended — accepted shape or a skipped item? #27877. No silent skip either way.packages/clientcode list, the service-automation README and the checklist) rides along. The checklist edit is declared in the claim.Blocked-by: #27951is now in the body.pm:blocked.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Rider for whichever PR takes this card · 2026-10-30T06:55Z
The contract review of PR #27951 (6515887943) left one should-fix on the same walk this card touches:
resolveSuspensionChaininservice-automation/src/engine.tsnever seedsseenwith the root run id, so a corrupt A→B→A correlation cycle yields the chain[A, B, A]and the refusal names the caller's own run as the one to resume. Seedingseenwith the root before the loop fixes it. It rides here with the docs half already listed (client and README code lists, QA checklist row for409 RESUME_CHILD_PENDING).objectstack-fleet commentedon Oct 30, 2026
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk
Claim: PM loop round 2 · 2026-10-30T11:53Z · unblocked: PR #27951 merged, so
Blocked-by: #27951is clearedSession:
session_016WV4d1oZadHHFdEZXANxiG(card thread; seatsession_016WV4czVnUdQQCVKSW5J2uk)Account:
os-elon-muskBranch:
claude/project-thread-2p9rtc(the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread opened)Worktree:
objectstack-issue-27971Domain:
domain:serverSeat:
domain:server#1File surface:
packages/services/service-automation/src/engine.tsandrun-resume.ts(themapparent-id resume past a cancelled item;resolveSuspensionChain'sseenseeding — rider 6516883756), the map node's item-result recording underpackages/services/service-automation/src/, tests there; docs half per triage: the409 RESUME_CHILD_PENDINGentry inpackages/client/src/index.ts's code list andpackages/services/service-automation/README.md, and thedocs/qa/platform-checklist/areas/automation.jsonrow;.changeset/*. Re-graded by the seat 2026-10-30 ~14:15Z under ruling A (6523833308): pluspackages/spec/src/automation/builtin-node-config.zod.ts(theMapConfigSchema.outputVariabledescribe text only, with its regenerated spec artifacts) andcontent/docs/automation/flows.mdx(the batch paragraph): an element can be the closed$-keyedcancelledmarker. Amended by the seat ~15:50Z from the contract review of PR #28176 (should-fix S1): one sentence each namingRESUME_CHILD_PENDING's second arm (the item row is gone and its history is notcancelled) and its remedy, inpackages/spec/src/contracts/automation-service.ts(docblock),packages/spec/src/api/error-code-ledger.zod.ts(the code's row text, with regenerated artifacts),packages/client/src/index.ts(theresume()docblock), the service-automation README code list anddocs/qa/platform-checklist/areas/automation.json; and the remedy in that arm's refusal message. Stop and report on any other breach.Container & model:
M,mode:thread,model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate),effort: highClause-②: yes (widening) — ruling A,
Ruling-ref: 6523833308; contract review atCONTRACT_REVIEW_TIERon the final headMetadata-protocol: added (a readable output shape; the seat confirms the arm from the diff)
Responsibility:
service-automation'smapresume path | none (the cancel path does not touch the parent) | any flow whose map item is cancelled by an operator, todayThread-read: 6516883756
Premise: holds (triage 6516093830 measured-first rule; PR #27951 merged 2026-10-30T11:48:11Z)
Serial constraints cleared: open PR — none touches
service-automation/src/engine.tsorrun-resume.tsat this reading. Seat 3's in-flight #28016 (branchclaude/project-thread-0vmpzv) declares the connector-delegate region ofengine.tsonly, disjoint from the resume paths; whichever lands second mergesmain.objectstack-fleet commentedon Oct 30, 2026
domain:server#1 · os-elon-musk · session_016WV4d1oZadHHFdEZXANxiG
os-dev-report
{ "issue": 27971, "status": "needs_decision", "branch": "claude/project-thread-2p9rtc", "pr": null, "session": "session_016WV4d1oZadHHFdEZXANxiG", "premise_still_valid": true, "summary": "Measured on main f6adf480 through engine.resume: a 2-item map whose item 1 parks, cancelRun(item1) (true; getRun(item1).status = cancelled), then resume(parentId) answers success/paused and the map parks on item 2 with $mapState {started:2, results:[]}; after item 2 completes the parent row is completed and a downstream node sees results = [item 2's output] and signoffs.count = 1, so item 1 is silently skipped and results[0] is no longer item 0. The graded rule needs the cancelled item recorded where downstream nodes read it, and the map's per-item result has no place for a status: outputVariable (and the node output signoffs.results) is a bare array of child outputs, spec-described as \"Each item's subflow output, collected in order\" (MapConfigSchema.outputVariable). Every way to record cancelled adds an author-readable shape, so per the dispatch order this is Clause-②: yes and I stopped before the main fix. Shipped on the branch (pushed, no PR yet): rider 6516883756 (seen seeded with the root run id, A→B→A cycle pinned) and the docs half (client resume docblock, service README code list, automation checklist acceptance[4] + variant + history revision 9).", "tests": "@ 24787eeb: pnpm --filter @objectstack/service-automation test → Test Files 226 passed (226), Tests 2863 passed (2863); typecheck (check:test-typecheck) OK. New pin map-parent-resume-refusal.test.ts 'names the item run, never the caller's own, when a corrupt correlation cycles back to the caller': 6/6 green. Ablation (committed fix first; test imports ./engine.js from src, no dist on the path): scripts/ablation-replace.mjs anchor 'const seen = new Set(TYPE)([runId]) (the seeded constructor; TYPE = string)' x1→x0, blob c3ff057c→43ab85a7 → the new pin red (1 failed | 5 passed), restored blob == HEAD c3ff057c, git diff HEAD empty. Repro of the cancelled-item skip is a scratch test, not committed.", "gates": "@ 24787eeb, local subset: dispatch-gates --commands derived 58 families; --ran reconciliation: 58 accounted, 55 run exit 0, 3 NOT MEASURED (exit 3, prerequisite): node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone cannot reach its pinned positive control), pnpm --filter @objectstack/spec run check:skill-examples (client-react/client dist not built), pnpm check:dual-build-cjs-loads (adapter/app/client dist not built) — all three re-run on the final head once the main fix lands. node scripts/check-platform-checklist.mjs --changed-from origin/main :: exit 0 (14 anchors judged, 0 lost). pnpm lint: not run (repo-wide scan, CI-owned). No PR, so no CI check-runs yet.", "dogfood_battery": "n/a — no `!` in a PR title and no (narrowing) arm; no PR opened", "mcp_calls": "5 — read only: issue_read get + get_comments (#27971), ToolSearch loads; no MCP write tool", "api_writes": "1 — this os-dev-report comment via post-stamped (relay)", "open_questions": [ { "question": "Where does a map record an item whose run was cancelled, so the map may advance (triage 6516093830) and downstream nodes can read it?", "options": [ "A — a marker element at the item's index in results / outputVariable (e.g. a `$`-keyed object naming status cancelled and the item run id; a child flow's output keys can never start with `$`, so it cannot collide), count = items that ran. Keeps results[i] = item i. Needs the MapConfigSchema.outputVariable describe text and the flows.mdx batch-approval paragraph to say an element can be that marker: packages/spec + docs, outside this claim's surface; additive Clause-② yes. Cost: M, contract review owed.", "B — results unchanged (outputs of the items that ran); the node output gains a cancelled list of {index, runId}, read downstream as `NODE.cancelled`; count = items that ran. No spec text change (the map node output is undeclared in spec), but it is a new author-readable key, and once an item is cancelled results[i] is no longer item i — a silent misalignment for any downstream that pairs results with the collection. Cost: S-M.", "C — refuse: a parent-id resume of a map whose item run was cancelled answers 409 RESUME_CHILD_PENDING (the triage's own fallback arm, no new code), the message naming the cancelled item run and telling the operator to cancel the parent run. No new shape, but the code's spec docblock says the item 'is still suspended', so its meaning widens (a docblock sentence in packages/spec), and an operator can never continue a batch past a cancelled item. Cost: S." ], "recommendation": "A, because it is the triage's graded reading ('the item's result records cancelled', not counted as a success, readable downstream), keeps the ordered-collection invariant results[i] = item i that B silently breaks, and is the per-item status record the deferred concurrent fan-out (ADR-0037) would need anyway; its cost is an additive spec describe edit under a re-graded claim and a contract review. C is the zero-surface fallback if the maintainer prefers that a cancelled item stops the batch." } ], "out_of_scope_findings": [ "class: a · reach: named producer — plugin-approvals' revise-window recall calls cancelRun on the run parked at the approval (packages/plugins/plugin-approvals/src/approval-service.ts, per run-resume.ts cancelRun's docblock); on a batch sign-off (showcase_release_signoff → one_task_signoff) that run is a map item, so the batch run stays parked with nothing to wake it and a raw resume of it is the skip this card measures. Read, not measured. carrier: #27971 (this card's ruling decides it) · noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk
Decision analysis · #27971 · 2026-10-30T12:37Z · from os-dev-report 6522980749 (
needs_decision)pm:dispatched→needs-user-decision. The claim (6522041026) stays; the card thread holds the rider and the docs half onclaude/project-thread-2p9rtc@24787eeband opens them as aPart of #27971PR now (no behaviour question in either). The main fix waits for the ruling. ⛔ The seat does not answer this.One-line problem. When an operator cancels one item of a batch (a
mapnode) and the batch is resumed, the batch finishes as if that item never existed: downstream steps get one result fewer, and every result after the gap sits at the wrong position.Background (measured by the dev on
mainf6adf480, throughengine.resume). A two-item map parks on item 1;cancelRun(item1)answers true;resume(parentId)advances, the map parks on item 2, and when item 2 completes the parent iscompletedand a downstream node readsresults = [item 2's output],count = 1. Triage 6516093830 graded the rule: the map may advance, but the item's result recordscancelled, is never a success, and is readable downstream; a map that cannot record it refuses, as #27877 does.Governing text.
MapConfigSchema.outputVariable(packages/spec): "Each item's subflow output, collected in order." The result is a bare array of outputs, so there is no place for a status.RESUME_CHILD_PENDING's docblock: the item "is still suspended". No ADR decides a cancelled map item (check-prior-rulings --card 27971: 13 hits on broad terms, none on this question).Protocol. Every way to record
cancelleddownstream adds an author-readable shape →Clause-②: yesfor A and B. C changes no shape but widens a refusal code's meaning (one docblock sentence).results[i]for a cancelled item is a$-keyed marker (statuscancelled, the item run id);count= items that ran; spec describe +flows.mdxsay an element can be that markeriis still itemi; a downstream step can see which item was cancelledpackages/specdescribe + service-automation + docs · one PR · contract review owed · no precedentresultsunchanged; the node output gainscancelled: [{index, runId}]results[i]silently stops matching itemionce anything is cancelled409 RESUME_CHILD_PENDING, naming the cancelled run and telling the operator to cancel the parentpackages/specBusiness reading. A = a payroll run where one employee's payslip was voided: the run finishes and the register shows that line as "voided". B = the register drops the line and everyone below moves up one row. C = voiding one payslip forces voiding the whole run.
Named producer (read, not measured — dev's out-of-scope finding).
plugin-approvals' revise-window recall callscancelRunon the run parked at the approval. On a batch sign-off (showcase_release_signoff→one_task_signoff) that run is a map item, so today the batch is left parked and a raw resume is exactly this skip. Under C every such recall strands the batch until the operator cancels it.Prior rulings read: map,cancelled,resume_child_pending,outputvariable → 13 hits; none on a cancelled map item (ADR hits are broad-term: ADR-0014 §4, ADR-0019 D4, ADR-0020 D1/D2, ADR-0032 §3, ADR-0062 D7, ADR-0065 §2, ADR-0080 §2, ADR-0081 §4, ADR-0099 D2); thread: none
Recommendation: A (from ① only). In two years a batch step should report per-item status, with cancelled items continuing in place — the shape mainstream orchestrators use (AWS Step Functions' Map reports per-item results/failures with the item identity; Power Automate's apply-to-each keeps iterating and marks the failed iteration). It is also the per-item status record the deferred concurrent fan-out (ADR-0037) needs. B is not taken: it breaks
results[i] = item isilently. C is not taken: it makes the approval recall strand every batch sign-off. Confidence gap: the marker's exact keys are the dev's proposal and get fixed in the contract review; the approval-recall path is read, not measured.Self-check: on ① alone, A. Do ②③④ flip it? No — ③④ favour C, but they only argue for keeping the marker closed and small, not for a different letter.
After the ruling. A: the seat re-grades the claim to
Clause-②: yes, addspackages/spec(MapConfigSchema.outputVariabledescribe) andcontent/docs(flows.mdxbatch paragraph) to the surface, the card thread builds it, fable contract review on the final head, then the seat lands. C: the card thread adds the refusal and the docblock sentence, S, no contract review beyond the code-meaning sentence. Either way the rider/docs PR lands first.objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Brief-claimed-by: session_01Utp6CbQLcy8yg6vhJVXXi8 · batch #431 · 2026-10-30T13:03Z
Director seat, summon #54 continuation 2 (ledger objectstack#12708, opening mark 6523423511). Presented to the maintainer in batch #431 as item 3 of 3 — server seat 1's decision analysis 6523020455 on dev report 6522980749 (
needs_decision; the rider and the docs half are already on the branch).Seat increment (re-read at
origin/main59547058, 2026-10-30T13:03Z):MapConfigSchema.outputVariableis described "Each item's subflow output, collected in order" (builtin-node-config.zod.ts L1490) — a bare array with no place for a status; PR fix(service-automation)!: a parent-id resume of a map whose item run is still suspended is refused, naming the item run #27951 (the still-suspended-item refusal) merged 2026-10-30T11:48Z;cancelRun(run-resume.ts L727) does not touch the parent; the branchclaude/project-thread-2p9rtcis on the remote (head moved past the report's24787eeb, the rider and docs half pushed). The dev measured the skip live throughengine.resumeonf6adf480: a cancelled item 1 vanishes,results = [item 2's output],count = 1.cancelled, never a success, readable downstream; a map that cannot record it refuses as service-automation: resuming a run paused at amapnode by the PARENT run id completes the parent and leaves the parked item child suspended — accepted shape or a skipped item? #27877); 6513867475 (service-automation: resuming a run paused at amapnode by the PARENT run id completes the parent and leaves the parked item child suspended — accepted shape or a skipped item? #27877, still-suspended item).check-prior-rulings.mjs --card 27971names ADR-0019 D1–D3, ADR-0014 §3, ADR-0027 §13 — term matches; no ADR decides a cancelled map item; ADR-0037 (deferred concurrent fan-out) is the future reader of a per-item status.results[i] = item i; B breaks it silently once anything is cancelled; C makes "continue past a cancelled item" impossible. ② real today: any operator cancel on a batch item, and the approval recall path on batch sign-offs (plugin-approvalsrevise-window recall callscancelRunon the parked run; read, not measured) — under C every such recall strands the batch until the operator cancels it whole. ③ C is loudest, A is a closed marker an author can test for ($-keyed, so it cannot collide with a child output), B fails silently. ④ A declares one marker shape forever; the marker's exact keys are fixed in the contract review.results/outputVariableholds a closed$-keyed marker naming statuscancelledand the item run id;count= items that ran; the spec describe and theflows.mdxbatch paragraph say an element can be that marker. Not B (silent index misalignment), not C (the approval recall strands every batch sign-off).domain:server· the open claim re-graded toClause-②: yes (widening), surface +packages/spec(MapConfigSchema.outputVariabledescribe) +content/docs/automation/flows.mdx· M · contract review atCONTRACT_REVIEW_TIERon the final head (packages/spec/src/**hit) ·Metadata-protocol: added(a readable output shape, no new authored key — the seat confirms the arm from the diff) · the rider / docs PR lands first asPart of #27971.needs-user-decision→pm:dispatched(the claim 6522041026 and the card thread stand); server seat 1 re-grades the claim and the thread builds.Prior rulings read: map, cancelled, RESUME_CHILD_PENDING, outputVariable, fan-out → triage 6516093830, 6513867475 (#27877), ADR-0037; thread: 6522980749, 6523020455; repo: objectstack-ai/objectstack
objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Ruling: batch #431 item 3 · letter A · maintainer 「27971 同意」 2026-10-30T13:20Z
Director seat, summon #54 continuation 2 (ledger objectstack#12708, opening mark 6523423511, brief 6523502465). The maintainer answered in the summon chat, verbatim: 「27971 同意」 — the seat's letter A.
What the ruling binds (letter A): when a
mapitem's run was cancelled and the parent is resumed, the map advances and the cancelled item keeps its slot:results[i]/outputVariable[i]holds a closed,$-keyed marker naming statuscancelledand the item run id (a child flow's output keys never start with$, so the marker cannot collide);count= items that ran; a cancelled item is never counted as a run or a success; downstream nodes read the marker where they read results. TheMapConfigSchema.outputVariabledescribe text and theflows.mdxbatch paragraph say an element can be that marker. A map that cannot record it refuses, as #27877 does. Not taken: B (a side list;results[i]silently stops matching itemi), C (refuse; the approval recall path strands every batch sign-off). The marker's exact keys are the dev's proposal and are fixed in the contract review, not here.Governing text: triage 6516093830 (graded rule: advance, record
cancelled, readable downstream, never a success);MapConfigSchema.outputVariable(builtin-node-config.zod.ts L1490); 6513867475 (#27877). Premises re-read atorigin/main59547058(brief 6523502465).Workload of the letter:
domain:server· the open claim 6522041026 re-graded toClause-②: yes (widening)withpackages/spec(MapConfigSchema.outputVariabledescribe) andcontent/docs/automation/flows.mdxadded to the surface · M · contract review atCONTRACT_REVIEW_TIERon the final head ·Metadata-protocol: added(a readable output shape; the seat confirms the arm from the diff) · the rider / docs half already onclaude/project-thread-2p9rtclands first asPart of #27971.Execution:
needs-user-decision→pm:dispatchedin this act (claim 6522041026 byos-elon-muskand the card thread stand); server seat 1 re-grades the claim withRuling-ref:naming this comment and the thread builds the main fix.os-decision-facets
① 长远:批处理逐项报状态、取消项原位保留,是 Step Functions Map 与 Power Automate apply-to-each 的形状,也是 ADR-0037 扇出需要的逐项记录
② 拉动:操作员取消批次项,以及审批撤回路径(批量签批时撤回即取消一个 map 项)
③ 防错:封闭的
$标记作者可测试;B 静默错位;C 把整批卡死④ 不扩散:一个封闭标记形状,键名在契约复核里定死
objectstack-fleet commentedon Oct 30, 2026
domain:server#1 · os-elon-musk · session_016WV4d1oZadHHFdEZXANxiG
os-dev-report
{ "issue": 27971, "status": "done", "branch": "claude/project-thread-2p9rtc", "pr": "https://github.com/objectstack-ai/objectstack/pull/28176", "session": "session_016WV4d1oZadHHFdEZXANxiG", "premise_still_valid": true, "summary": "Ruling A (6523833308) built on fresh main (merged origin/main after PR #28101's squash; the branch name is not claude/issue-*, so no force-push). The resume gate reads getRun(itemRunId) when the chain ends on a map: link with the item row gone and the run is parked at the built-in map node: cancelled → resume continues with an engine-built signal setting MAPNODE.$mapItemCancelled, and the map pushes the closed marker {$status: 'cancelled', $itemRunId} into the item's slot, count = items that ran; anything else (no row, unreadable, other status) → 409 RESUME_CHILD_PENDING with nothing moved. A caller signal writing the $ namespace still gets 400 INVALID_SIGNAL. MapConfigSchema.outputVariable describe, the map node's config-schema description and the flows.mdx batch paragraph say an element can be the marker; reference page regenerated. Head 84b980b3.", "tests": "@ 84b980b3 tree (code at 0dfb6177): service-automation Test Files 231/231, Tests 2911/2911; typecheck OK; spec Test Files 756/756, Tests 22513 passed + 1 todo; spec check:generated 14/14 up to date. New map-cancelled-item-resume.test.ts 7 cases. Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty: status check → item == null: 1 red; caller-signal guard deleted: 1 red; count filter → results.length: 2 red; marker push deleted: 2 red; nodeType map guard deleted: concurrent-replica-resume-race THE CONDITION (correlation) red. src imports, no dist on the path.", "gates": "dispatch-gates --commands: 107 derived, --ran reconciliation 107 run, 0 NOT MEASURED (all with exit codes) at 0dfb6177; check-adr-0087-registration first exit 1 (no disposition), fixed in the changeset, re-run exit 0 at 84b980b3 together with the five changeset families (all 0). check-platform-checklist --changed-from origin/main OK (17 anchors, 0 lost); check-nul-bytes 0; git merge-tree vs origin/main clean. PR check-runs: in_progress at report time (CI's to converge; seat reads them).", "dogfood_battery": "0dfb6177 · VERDICT command-exit 0 · Test Files 358 passed, 3 skipped (361) · Tests 2883 passed, 20 skipped (2903) · no failures; the later commit only adds the changeset's ADR-0087 line", "mcp_calls": "7 — read only: issue_read x2, search_pull_requests x1, pull_request_read get_check_runs x1, ToolSearch loads; subscribe_pr_activity x2 (harness subscription, not a GitHub write); no MCP GitHub write tool", "api_writes": "4 this card — comment 6522980749 (needs_decision report), pr_create #28101, pr_create #28176, this report comment (all via fleet-write relay)", "open_questions": [], "out_of_scope_findings": [ "carrier: #27971 contract review — the approval revise-window recall (plugin-approvals calling cancelRun) is a real producer of a cancelled item in a batch sign-off; read, not measured · noted, not filed" ] }Generated by Claude Code