Repository navigation
automation: a subflow or map in a region body whose child suspends arms, fails every run at the region boundary, and leaves the child run suspended #28897
Description
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Blocked-by: #28846 (its PR changes subflowTargets, which this reads)
Filing gate: ① the #28846 dev measured this before editing, in all six region cases (subflow and map, each in a loop body, a parallel branch and a try_catch slot). It was split off by seat ruling 6568462471. Filed by domain:server seat 3. ⛔ Not a claim.
Reach: a record-triggered (or any) flow whose region-nested subflow / map names a child that suspends (approval, wait, screen) has this behaviour:
- It arms today, because no arming gate judges it.
- Every run then fails at the region boundary with the ruled service-automation: a durable suspension inside a structured region (loop / parallel branch / try_catch) must fail the run with a named error, not leave progress state and report success — runtime half of #15646 ruling D #18881 refusal (
region-suspension-refusal.ts: "durable pause inside a structured region"). That run-time refusal is loud and is the ruled posture, ruling D. - The child run is left suspended (
kid@hold). The dev measured this with and without asave_notice. An orphaned suspended child holds whatever it was waiting on, for example an approval request no decision can complete usefully.
The module's own header says the pause "is knowable only at RUN time, and only the run can refuse it", because the child record is not in hand at parse. Since #28840, the arming gate does have the child in hand (declineOntoDisabledSubflows, declineOntoSuspendingSubflowNotice, re-judged on every child registration).
Wanted
- Measure the orphan. After the region refusal, is the child run left suspended in storage, and what does a later decision on it do? If it is a leak, the region refusal also settles or cancels the child it started. ⛔ Do not change the refusal itself (service-automation: a durable suspension inside a structured region (loop / parallel branch / try_catch) must fail the run with a named error, not leave progress state and report success — runtime half of #15646 ruling D #18881, ruling D).
- A caller whose region-nested
subflow/mapnames a child that can suspend is registered but not armed. Use the existing arming gate with its ownFLOW_UNBOUND_CAUSESvalue naming the region node, the call node and the child's suspending node, re-judged on child registration as automation: a save_notice after a subflow or map whose child suspends passes publish and strands every decision of the parent run #28840 does. ⛔ No new gate. ⛔ No change toFLOW_PAUSE_CAPABLE_NODE_TYPES(ruling D). Update theregion-suspension-refusal.tsheader so it names the arming gate as the first door.
Shipped flows: HotCRM campaign_enrollment has a region-nested call (a loop body, two member-enroll children). The dev measured that those children do not suspend, so it must keep arming. Pin that.
This narrows arming (a flow that arms today will not), and adds a published cause value. So it is Clause-②: yes (narrowing), with a CONTRACT_REVIEW_TIER review owed.
Dedupe
Read open issues for durable pause structured region, subflow region suspend orphan and region boundary pause subflow: only #28846 (the guards' target list). New.
Reader who acts: domain:server (packages/services/service-automation).
Activity
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Path: 能用的自动化 | none | P③
Triage: p3 ·
domain:server·area:automation· 2026-11-02T03:39Z⛔ Not a claim.
Clause-②: yes (narrowing),CONTRACT_REVIEW_TIERreview owed.subflowTargetsat every depth). It runs after that PR in the same thread.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim: PM loop round 1 · card thread · 2026-11-02T05:52Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5omF1nCbQTGgW1nbRAAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-9kerqb(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Seat 3 graded p3 under 「定级你可以自己决定」 (2026-11-01T12:11Z).
File surface:
packages/services/service-automation/src/(engine.tsarming path,region-suspension-refusal.ts,subflow-guards.tsheader,subflow-guard-region-calls.test.ts, new tests), theFLOW_UNBOUND_CAUSESdeclaration wherever it lives, changesetsContainer & model: card thread
cmsg_01TenzsRopZj4jMbvjX8uz1X8pL9LbgDKbpvwydZJP7bTD(save_notice, same thread as #28846),mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: yes (narrowing) — a flow that arms today will not, and a published unbound-cause value is added;
CONTRACT_REVIEW_TIERreview owed before landingMetadata-protocol: none (expected; the dev measures and declares)
Responsibility:
domain:serverseat 3 owns the PR to mergeThread-read: 6568479800
Premise: measured by the #28846 dev in all six region cases (os-dev-report
6569441106); the orphaned child run is re-measured firstSerial constraints cleared: PR #28959 (same files) merged; #28939's thread edits only the held-connector branch of
plugin.tsResume-from: none — the branch restarts from
origin/mainat4748bac8or later (its PR #28959 is merged)State: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28897.md(project folder).objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA
os-dev-report
{
"issue": 28897,
"status": "done, handed back on the wrap-up order; dogfood test verdict not measured at hand-off",
"branch": "claude/project-thread-9kerqb",
"pr": "#29096",
"head": "65a9454675b4b557c5ca827a63b9748c1ba1ac26",
"session": "session_01FEkQ5omF1nCbQTGgW1nbRA",
"premise_still_valid": "yes (measured: a leak)",
"summary": "Arming gate region half: activateFlowTrigger declines (registers, does not arm) a flow whose region-nested subflow/map calls a child that can suspend, after the disabled-subflow and save-notice halves, with new FLOW_UNBOUND_CAUSES value suspending_subflow_in_region; reason names the innermost container, the call node and the child's suspending node; re-judged via rejudgeSubflowCallers on every registration reaching it. Calls are read through one reading, subflowCalls (subflow-region-guard.ts, collectFlowGraphs), which subflowTargets now maps. Orphan: runRegion cancels the child run the subflow/map parked (nestedChildRunId + resolveSuspensionChain + cancelRun, deepest first) before throwing the unchanged refusal; a failed cancel logs at error. Headers: region-suspension-refusal.ts names the arming gate as the first door; subflow-guards.ts no longer claims every body moved unedited. Ride-along enable-door pin added.",
"measurement": {
"orphan_before": "6/6 slot x call-type: execute() failed with the region refusal; child in listSuspendedRuns and listSuspendedRunsDurable, hasSuspendedRun true, record paused; resume(child) answered success, ran the rest of the child (record completed), then the up-bubble warned 'resuming parent ... failed'. A leak."
},
"tests": {
"red_first": "subflow-region-suspension.test.ts: 20 cases; base 17 failed / 3 passed (3 controls); head 20/20",
"ablations": [
"A1 gate half off: 11 red",
"A2 re-judge ignores region half: 2 red",
"A3 no cancel: 7 red",
"A4 cancel child only, not chain: 1 red",
"A5 subflowTargets top-level only: 13 red incl. the enable-door pin",
"A6 outermost container named: 1 red",
"all via ablation-replace.mjs, restored blob == HEAD, tree clean"
],
"changed_pin": "#28846 notice pin 1 (save_notice after a loop calling a suspending child) now asserts the region decline, reason not naming the save_notice",
"suites": {
"@objectstack/service-automation": "exit 0 (257 files, 3319 tests)",
"@objectstack/plugin-approvals": "exit 0 (107 files, 1376 tests)",
"@objectstack/trigger-record-change": "exit 0 (12 files, 121 tests)",
"@objectstack/example-showcase": "exit 0 (42 files, 511 tests)",
"@objectstack/spec src/automation + src/contracts": "exit 0 (95 files, 2214 tests)"
},
"typecheck": "workspace pnpm typecheck (6 GB): spec test-layer tsc killed exit null on the shared box, the rest cancelled; alone: @objectstack/spec (8 GB) exit 0, @objectstack/service-automation exit 0; other packages NOT MEASURED one by one"
},
"gates": {
"derived": 83,
"run": 83,
"not_measured": 0,
"unrun": 0,
"reconcile": "dispatch-gates --ran exit 0, all 83 exit 0",
"check_changeset_no_major_with_pr_body": "exit 0 (narrowing, none, patch)",
"eslint": "--no-inline-config over packages/services/service-automation packages/spec/src/automation: exit 0"
},
"dogfood_battery": "under os-verify-lock: closure build VERDICT command-exit 0; dogfood test still running at hand-off: NOT MEASURED",
"shipped_flows": "examples/** + packages/qa/**: every subflow/map top-level; hotcrm origin/main d4ffe99: campaign_enrollment (type screen, no trigger) calls two non-suspending children from loop bodies; a pin of that shape arms and completes",
"line_budget": "no budget in the order; diff 12 files changed, 650 insertions(+), 35 deletions(-)",
"declarations": "Clause-②: yes (narrowing); Metadata-protocol: none; changeset patch on service-automation and spec with BREAKING banner and ADR-0087 no-migration-prescription marker. CONTRACT_REVIEW_TIER review owed (seat).",
"deviations": [
"describeSuspendingNode factored out of describeSubflowNoticeTrap (wording unchanged) so both halves name a pause one way."
],
"files_changed": [
".changeset/28897-region-call-suspending-subflow.md",
"packages/services/service-automation/src/dag-traversal.ts",
"packages/services/service-automation/src/engine.ts",
"packages/services/service-automation/src/flow-definitions.ts",
"packages/services/service-automation/src/region-suspension-refusal.ts",
"packages/services/service-automation/src/subflow-guard-region-calls.test.ts",
"packages/services/service-automation/src/subflow-guards.ts",
"packages/services/service-automation/src/subflow-notice-guard.ts",
"packages/services/service-automation/src/subflow-region-guard.ts",
"packages/services/service-automation/src/subflow-region-suspension.test.ts",
"packages/spec/src/contracts/automation-service.test.ts",
"packages/spec/src/contracts/automation-service.ts"
],
"mcp_calls": "1: mcp__github__issue_read (get_comments #28897); no write tool",
"api_writes": "3: git push (new branch), fleet-write pr_create (run 41405185491, draft PR #29096, no labels, no assignees, read-back identical 12019 bytes), this report (post-stamped). No GitHub MCP writes.",
"open_questions": [
"Reading 1: a caller whose TOP-LEVEL call reaches a child with its own region call to a suspending grandchild is not declined (the child fails its own run; the order names the region-nested caller). Seat to say if it should count."
],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Release: claim
6570160746(session_01FEkQ5kE5BrPUPNMMyv6oCH, card threadsession_01FEkQ5omF1nCbQTGgW1nbRA, branchclaude/project-thread-9kerqb) · 2026-11-02T07:38Z · seat 3 shift close (maintainer 2026-11-02T07:10Z 「尽快提交代码,然后收工」, 2026-11-02T07:33Z 「pm-dispatch 中车道下班的流程,都要走完」); handed back, no stay-behindProduce: draft PR #29096 (os-dev-report
6571401166)Resume-from: claude/project-thread-9kerqb@65a94546
CONTRACT_REVIEW_TIERreview (Clause-②: yes (narrowing), new unbound causesuspending_subflow_in_region), then ACCEPT and land. Dogfood finished green after the report (383 files, VERDICT 0); the PR body still says NOT MEASURED. Dev reading for the reviewer: a top-level call to a child whose own region call reaches a suspending grandchild is not declined.pm:dispatched→pm:queue, assignee cleared.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Claim: PM loop round 1 · takeover of a released claim · 2026-11-02T08:13Z
Session:
session_01ADHkmQJdgarUjPLNgFBY67(the seat; no new dev: the seat lands the existing draft)Account:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-9kerqbWorktree: none (review only)
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」 The predecessor's
Release:6571724612 names the next steps.File surface: unchanged from claim
6570160746(PR #29096's files); no new code unless a review round asks for itContainer & model: seat-held takeover,
mode:threadfor any REWORK round,model: fable (reason: the narrowing face owes one CONTRACT_REVIEW_TIER review before the queue, judged from the card content perdispatch-gates --tier; the review runs as an isolated at-tier subagent),effort: xhighClause-②: yes (narrowing)
Metadata-protocol: none
Responsibility:
domain:serverseat 3 owns the PR to mergeThread-read: 6571724612
Premise: holds (draft PR #29096 at
65a94546, CI 55 success / 9 skipped on that head)Serial constraints cleared: none (no open lane PR or claim names
packages/services/service-automation/src/engine.tsorsubflow-guards.tsbesides this one)Resume-from:
claude/project-thread-9kerqb@65a94546State:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
ACCEPT → PR #29096 comment 6572502759 (contract review PASS 6572481670, head
65a94546). Landing: ready + auto-merge now.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Follow-up: the two non-blocking should-fix items of the contract review on PR #29096 (6572481670) are carried by #29166 (
finding, ungraded).objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Landing record · #28897 · PR #29096
domain:serverseat 3 ·session_01ADHkmQJdgarUjPLNgFBY67· 2026-11-02T09:47Z (taken over fromsession_01FEkQ5kE5BrPUPNMMyv6oCH, sign-off brief on #26761)git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29096-*'git merge-base --is-ancestor 1ac1c24a74 origin/maingit grep -n "export function regionSuspendingCall" origin/main -- packages/services/service-automation/src/subflow-region-guard.ts1ac1c24a(2026-11-02T09:46Z). A region-nested call to a child flow that can suspend is now declined at arm time, and the child run a region refusal leaves behind is cancelled.CONTRACT_REVIEW_TIER) PASS6572481670; seat ACCEPT6572502759.Fixes #28897closed this card on merge.pm:dispatchedand the assignee removed in the same round.