Repository navigation
fix(service-automation)!: the subflow guards see a subflow or map call inside a region body - #28959
Conversation
…l inside a region body subflowTargets now reads every region depth through collectFlowGraphs, so the ADR-0126 7.3 arming decline and the switch-off and removal refusals see a call inside a loop body, a parallel branch or a try_catch slot. The save-notice half of the arming gate keeps reading top-level calls only: a region cannot carry a durable pause. The save_notice placement header and its flow.zod.ts wiring comment now name the arming gate as where a notice after a suspending subflow is met. Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1c0ec675ac1c5971c582625cbec010e03bfb32ca && git checkout 1c0ec675ac1c5971c582625cbec010e03bfb32ca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ff88105061012c8b14f7ec937d7b3113e404e922 87411da83042879ee3c0d8a72a22a73ac3f99fe0 && git checkout -B drift-repro ff88105061012c8b14f7ec937d7b3113e404e922 && git merge --no-ff 87411da83042879ee3c0d8a72a22a73ac3f99fe0
node scripts/docs-audit/affected-docs.mjs --json ff88105061012c8b14f7ec937d7b3113e404e922
|
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Contract reviewServed-tier: Review face: the brief asks the narrowing face; Inputs read: card #28846 body and all four comments (triage 6566019553, claim 6568384018, seat ruling 6568462471, os-dev-report 6569441106); PR #28959 body, file list (7 files, +296/−15) and the net diff Check-runs on the head, read 2026-11-02T05:05Z: 20 success, 11 skipped, 30 in progress. Success: ① Derived judgmentsAccept-set and surface changes the diff implies, each judged against the code as it stands on the head:
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on The card's three wants, in the tree: (1) measured first — the dev's 6/6 region cases armed and the top-level twins declined, consistent with the old body's Attempts to break it.
Findings.
Implemented-by: VERDICT: PASS Record rendered 2026-11-02T05:24Z. |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28959 · 2026-11-02T05:26ZHead-sha: 维护者速读(席位终稿)
核对六项
|
domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA
Fixes #28846
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none —
skills/**does not teach the ADR-0126 §7.3 subflow guards, and this changes no authorable shape维护者速读
subflow/map节点,放在loop、parallel分支或try_catch里的调用看不到。现在所有区域层级都会读取,读法复用 spec 已有的collectFlowGraphs,没有新写遍历器。/_status显示disabled_subflow;有已启用的调用方时,停用或删除子流程会被拒绝,返回DELETE_RESTRICTED409。save_notice判定仍然只看顶层:区域里的调用即使子流程会挂起,运行也会在区域边界直接失败,根本到不了save_notice。这个失败另立了 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。save-notice-placement.ts文件头和flow.zod.ts的注释改为说明,子流程这种情况由启用闸门(notice_after_suspending_subflow)处理,运行时按名字拒绝只是剩余情况。campaign_enrollment(loop 里调用两个已启用的子流程),仍然启用。以后它启用期间,停用或删除这两个子流程会被拒绝,席位已认可。What changes
subflowTargetsreads every region depth (packages/services/service-automation/src/subflow-guards.ts). It used to readflow.nodesonly. It now reads every graphcollectFlowGraphs(@objectstack/spec/automation) returns: the top level and eachloopbody,parallelbranch andtry_catchslot, at every depth the spec walks. That is the spec's one region walk, so no new walker exists. Every guard that readssubflowTargetsnow sees the same calls:declineOntoDisabledSubflows, viadisabledPackagedSubflows). A packaged caller whose region body calls a disabled packaged child registers but is not armed. It warns once, and/_statusreportsunboundCause: 'disabled_subflow'naming the child.rejudgeSubflowCallers, viapackagedSubflowCallers). A child republishedobsoleteunder an armed caller disarms it, and republishedactivearms it again.refuseUnderReachingCallers). Switching off or removing a packaged child that an enabled packaged caller calls from a region body is refused withDELETE_RESTRICTED/ 409, withsubflowCallersnaming the caller.refuseEnableOntoDisabledSubflow,ledgerDisabledChainReaches) read the same set.subflowTargetsdocblock says so.The save-notice half of the arming gate stays top-level (
subflow-notice-guard.ts; only its header prose changes). A region body cannot carry a durable pause. Measured before this change: a region-nestedsubflow/mapwhose child suspends fails the run at the region boundary ("durable pause inside a structured region"), before anything after the container runs. So the call cannot strand asave_noticeafter it, and a child whose only suspending call sits in a region cannot suspend its caller. The seat ruled this in 6568462471, withdrew the order's container rule, and filed the region-boundary failure (with the child run it leaves suspended) as #28897. Two pins hold the gate there.Spec prose, triage should-fix 1 (
6568019187). Thesave-notice-placement.tsheader and the comment at itsflow.zod.tswiring now say where asave_noticeafter a suspendingsubflow/mapis met. It is met by the engine's arming gate, which declines the flow withnotice_after_suspending_subflow, before any run. A region-nested call fails at the region first. The run-time refusal by name is the residue, for a plugin-registered pausing type. Comments only: nothing inpackages/spec/distchanges, so the spec package carries no changeset.Why
#28846, found on #28840: every §7.3 guard read only the top-level
nodes. So a packaged caller with its call in a loop body armed onto a disabled child and failed mid-run at that node, the late, inexplicable failure §7.3 exists to prevent. The child could also be switched off or removed under an enabled caller.Verification
unboundCause, while both top-level twins declined withdisabled_subflow.save_noticeafter the container: all 6 armed, butexecute()answeredfailedwith the region-boundary refusal. No notice was set, and the child run was left suspended. The top-level twins declined withnotice_after_suspending_subflow.service-automation/src/subflow-guard-region-calls.test.tshas 14 cases. On base, 10 failed and 4 passed (2 controls and the 2 notice-gate pins). On head, 14 of 14 pass.code: 'DELETE_RESTRICTED',status: 409,subflowCallersand the sentence.scripts/ablation-replace.mjs, each red and then restored (blob == HEAD):collectFlowGraphs(flow).slice(0, 1)): all 10 region pins went red. The controls and thesubflow-guard-every-door/subflow-notice-armingsuites stayed green (34 passed).examples/**andpackages/qa/**: everysubflow/mapnode is top-level.origin/main(d4ffe99):campaign_enrollmentcallscampaign_lead_member_enrollandcampaign_contact_member_enrollfrom two loop bodies. Both children arestatus: 'active'and hold no suspending node, so it still arms. No other region-nested call.dispatch-gates.mjs --commandsderived 83.--ranreconciles 83 run, 0 NOT-MEASURED and 0 UNRUN, all exit 0.vitest runexit 0:@objectstack/service-automation: 256 files, 3295 tests.@objectstack/plugin-approvals: 107 files.@objectstack/trigger-record-change: 12 files.@objectstack/example-showcase: 42 files.@objectstack/specsrc/automation: 45 files, 1745 tests.pnpm typecheck(6 GB heap) passed every task except@objectstack/spec, whose test-layertscwas killed without diagnostics (exitnull) while the run shared the box. Run alone with an 8 GB heap,@objectstack/specpnpm typecheckexits 0 and holds its pinned debt.eslint --no-inline-configoverpackages/services/service-automationandpackages/spec/src/automationexits 0.(narrowing)arm), on head87411da8underos-verify-lock.sh:VERDICT command-exit 0.pnpm --filter @objectstack/dogfood test:VERDICT command-exit 0. 382 files passed and 3 skipped (385); 3156 tests passed and 28 skipped (3184).Acceptance notes
Metadata-protocol: none. Registration, the save door andobjectstack validateaccept the same flows. The runtime guards now read calls they should already have read. Only@objectstack/service-automationships a change (patch, with the BREAKING banner and the ADR-0087no-migration-prescriptionmarker).Clause-②: no (narrowing). Nothing public widens: no export, type or served value is added.FLOW_PAUSE_CAPABLE_NODE_TYPES(ruling D), the save-notice gate's walk, the region-boundary refusal andui/**. No new gate. No hotcrm change.subflow/mapnode thatcollectFlowGraphsreaches is read. That covers regions up toMAX_REGION_DEPTH, where the spec's own walk stops.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched (comments only): no generated artifact moves (check-generatedand the spec artifact gates pass on this head).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code