Skip to content

feat(service-automation)!: decline to arm a region-nested call to a child that can suspend, and cancel the child run the region refusal leaves behind - #29096

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-9kerqb
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-9kerqb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA

Fixes #28897

Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: none — skills/** does not teach region bodies around a subflow / map call or the arming gate's unbound causes, and this changes no authorable shape

维护者速读

  • BREAKING(收窄):记录触发的流程里,如果 loop、parallel 分支或 try_catch 里的 subflow / map 调用了一个会挂起的子流程,这个流程现在照常注册,但不启用触发器。以前它会启用,子流程第一次挂起时,运行在区域边界失败(区域里不能持久挂起,裁定 D)。
  • 新的 unboundCause 值 suspending_subflow_in_region(加进 spec 的 FLOW_UNBOUND_CAUSES)。/_status 的原因点名区域节点(最内层的容器)、调用节点和子流程里的挂起节点。只打一次 warn。没有新闸门,复用现有的启用闸门,排在停用子流程、save_notice 两半之后。
  • 不依赖加载顺序:每注册一个流程,调用链能到达它的流程都会重新判定,和 automation: a save_notice after a subflow or map whose child suspends passes publish and strands every decision of the parent run #28840 一样。子流程去掉挂起后重新发布,调用方自动重新启用。
  • 实测的泄漏,已修:改动前,区域拒绝之后子运行一直停在持久存储里(paused)。之后有人恢复它,会把子流程剩下的步骤跑完,再去恢复已经失败的调用方,结果只是一条 warn。现在区域拒绝抛出之前,会用引擎已有的 cancelRun 取消这个子运行,以及它再挂着的孙运行,记录为 cancelled。拒绝本身(错误类型、文字、不可路由)没有改。
  • 已发布的流程里没有这种形状。HotCRM 的 campaign_enrollment 在 loop 里调用两个不会挂起的子流程,仍然可用;它是 screen 流程,本来就不经过启用闸门。
  • 有一点请席位确认(见 Readings 1):调用方在顶层调用一个子流程,而这个子流程自己在区域里调用会挂起的孙流程,调用方不在本次拒绝启用的范围内。合并前由席位做契约复核。

What changes

The arming gate's region half (packages/services/service-automation/src/engine.ts, declineOntoRegionSuspendingCall). activateFlowTrigger asks it after declineOntoDisabledSubflows and declineOntoSuspendingSubflowNotice, before the trigger lookup. A flow whose region-nested subflow / map node calls a child that can suspend:

  • registers, but is not armed;
  • warns once per reason, naming the innermost container node, the call node and where the child suspends;
  • is reported by GET /automation/_status as bound: false, with that sentence and unboundCause: 'suspending_subflow_in_region'.

The decline record lives in subflowRegionDeclinedFlows. describeUnboundReason reads it last, in the gate's own order, and withdrawFlow drops it. Any flow is judged, packaged or authored here, as the save-notice half is.

One reading of the calls (subflow-region-guard.ts, new). subflowCalls(flow) reads every subflow / map node through collectFlowGraphs and keeps its node id and the container holding it. subflowTargets is now subflowCalls(flow).map(…), so the §7.3 guards and the region half read the same calls and no second region walk exists. regionSuspendingCall asks the #28840 question of each region-nested call: suspendingNode (a pausing node reachable from the child's start, or a top-level call of the child's whose child can suspend). The sentence shares describeSuspendingNode with the save-notice half, factored out of describeSubflowNoticeTrap (its wording is unchanged).

The re-judge (rejudgeSubflowCallers, subflow-guards.ts). Callers come from transitiveSubflowCallers, which already reads region calls (#28846). A caller is settled only when neither the save-notice half nor the region half would decline it, so a child that turns suspending disarms an armed caller, and a child republished without the pause arms it again.

The orphaned child run (dag-traversal.ts, runRegion). A subflow / map node suspends by parking the child run it started and linking it through its correlation (subflow:<id> / map:<id>). When the region refusal fires on such a node, runRegion now:

  • reads the child run id with nestedChildRunId;
  • walks the chain the child is parked on with resolveSuspensionChain;
  • cancels each run, deepest first, with cancelRun, giving the refusal's own text as the reason.

The refusal is then thrown as before. If a cancellation fails, it is logged at error (a DURABILITY verdict, as cancelRun's own store failure is). It never takes the refusal's place.

Prose.

  • The region-suspension-refusal.ts header now says the arming gate is the first door and this refusal the second. It also names the cancellation.
  • The subflow-guards.ts file header no longer says every body was moved unedited, and it names the bodies edited since (review 6569776675).
  • FLOW_UNBOUND_CAUSES gains a TSDoc entry for the new value, with its owner and remedy.

Ride-along enable-door pin (review 6569776675). In subflow-guard-region-calls.test.ts, toggleFlow(caller, true) re-enables a switched-off caller whose loop body calls a switched-off packaged child. It is refused with RESOURCE_CONFLICT / 409, naming the child.

Why

#28897, found on #28846: a region-nested call whose child suspends armed. It failed the run at the region boundary the first time the child suspended, and left the child run parked.

Verification

  • Measured first (a scratch test, not committed), for every region slot × subflow / map, with a child that suspends at a wait:
    • execute() answered failed with the region refusal.
    • The child run was in both listSuspendedRuns() and listSuspendedRunsDurable(), hasSuspendedRun was true, and its run record read paused.
    • resume(childRunId) answered success: true and ran the rest of the child. The record became completed. The up-bubble then failed with the warn "subflow run '…' completed but resuming parent '…' failed". So this was a leak.
  • Red first. subflow-region-suspension.test.ts has 20 cases. On base, 17 failed and 3 passed (the 3 controls). On head, 20 of 20 pass.
    • The 6 slot × call-type gate cases each assert unboundCause and the sentence's region, call and pause names, plus one warn.
    • The 6 slot × call-type cancel cases each assert that the refusal text is unchanged, that no run is left parked, and that the child records cancelled.
  • Ablations, through scripts/ablation-replace.mjs, each red and then restored (blob == HEAD, tree clean). Each ran over this file and subflow-guard-region-calls.test.ts, 35 cases:
Ablation Result
A1: the gate half switched off 11 red: the 10 gate cases, plus the updated #28846 notice pin
A2: the re-judge ignores the region half 2 red: both re-judge cases
A3: no cancellation 7 red: the 6 cancel cases and the chain case
A4: only the child cancelled, not the chain 1 red: the chain case
A5: subflowTargets reads top-level calls only 13 red: the 11 region-guard pins, including the new enable-door pin, and the 2 re-judge cases
A6: the outermost container named instead of the innermost 1 red: the nested case
  • One existing pin changed. The first automation: subflow guards read top-level nodes only, so a subflow or map inside a region body escapes every subflow guard #28846 save-notice pin (a save_notice after a loop whose body calls a suspending child) asserted the caller armed. It is now declined with suspending_subflow_in_region, and the pin asserts that the reason does not name the save_notice. The notice half itself stays top-level.
  • Shipped flows.
    • examples/** and packages/qa/**: every subflow / map node is top-level. The showcase file mixing regions and calls (app-showcase/src/automation/flows/index.ts) holds its calls at the top level.
    • HotCRM origin/main (d4ffe99): campaign_enrollment is the one region-nested caller. Its children, campaign_lead_member_enroll and campaign_contact_member_enroll, hold no pausing node and no call, and the flow is type: 'screen' with no trigger. A pin reproduces the shape (two loop bodies, two packaged children that do not suspend): it arms and the run completes.
  • Gates. dispatch-gates.mjs --commands derived 83. --ran reconciles 83 run, 0 NOT-MEASURED and 0 UNRUN, all exit 0.
  • Suites. Full vitest run, exit 0 on each:
    • @objectstack/service-automation: 257 files, 3319 tests.
    • @objectstack/plugin-approvals: 107 files, 1376 tests.
    • @objectstack/trigger-record-change: 12 files, 121 tests.
    • @objectstack/example-showcase: 42 files, 511 tests.
    • @objectstack/spec src/automation and src/contracts: 95 files, 2214 tests.
  • Typecheck. The workspace pnpm typecheck (6 GB heap) stopped when @objectstack/spec's test-layer tsc was killed without diagnostics (exit null) on the shared box, the trap automation: subflow guards read top-level nodes only, so a subflow or map inside a region body escapes every subflow guard #28846 met. Run alone, @objectstack/spec (8 GB heap) and @objectstack/service-automation pnpm typecheck each exit 0. The other packages were not re-run one by one: NOT MEASURED.
  • eslint --no-inline-config over packages/services/service-automation and packages/spec/src/automation exits 0.
  • Dogfood battery (owed by the (narrowing) arm), under os-verify-lock.sh: the closure build gave VERDICT command-exit 0. pnpm --filter @objectstack/dogfood test was still running when this PR opened (wrap-up order): NOT MEASURED when this PR opened. The os-dev-report on 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 carries its verdict.

Readings for the seat

  1. Only the caller's own region calls are read. A caller with a top-level call to a child whose own region body calls a suspending grandchild is not declined. That child's run fails at its own region, so the caller's subflow node fails on the child's failure. This is a failed child, not a pause the caller inherits, and the order names the region-nested caller. Say if it should also count; it would be one more walk over top-level calls.
  2. Every region-nested call counts, reachable or not, as the automation: subflow guards read top-level nodes only, so a subflow or map inside a region body escapes every subflow guard #28846 guards read them.
  3. Flows with no trigger (screen and manual flows, such as campaign_enrollment) never reach the arming gate. The second door (the cancellation) covers their runs.

Acceptance notes

  • Metadata-protocol: none. Registration, the save door and objectstack validate accept the same flows. A flow that registered still registers, and a run that failed at the region still fails there with the same refusal.
  • Clause-②: yes (narrowing). The flows armed is narrowed, and FLOW_UNBOUND_CAUSES, a published tuple, gains suspending_subflow_in_region. The type Eq pin in automation-service.test.ts moved with it. Both packages ship patch, under ruling A for Metadata-protocol: none, with the BREAKING banner and the ADR-0087 no-migration-prescription marker.
  • Unchanged: the region refusal (its class, text, fields and guard-refusal brand), FLOW_PAUSE_CAPABLE_NODE_TYPES (ruling D), the save-notice half's walk and ui/**. No new gate. No hotcrm change.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched (one tuple value and its TSDoc): generated artifacts are checked by check-generated and the spec artifact gates on this head.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…hild that can suspend, and cancel the child run the region refusal leaves behind

A region body cannot carry a durable pause, so a `subflow` / `map` node inside
a `loop` body, a `parallel` branch or a `try_catch` slot whose child suspends
fails the run at the region boundary. The arming gate now declines such a
caller with its own unbound cause, `suspending_subflow_in_region`, re-judged
whenever a flow its calls reach is registered. The region refusal itself is
unchanged, but before it is thrown the engine cancels the child run the call
parked, and any run that child is parked on.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec, touching 40 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/services/service-automation/src/region-suspension-refusal.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via cancelRun (symbol, a field of interface DagTraversalHost), flowName (symbol, a field of interface SubflowCall), cancelRun (literal, a string literal in cancelRun; a string literal in runRegion))
  • content/docs/automation/jobs.mdx (via flowName (symbol, a field of interface SubflowCall))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level class), flowName (symbol, a field of interface SubflowCall))
  • content/docs/releases/v17/17-5.mdx (via flowName (symbol, a field of interface SubflowCall))
  • content/docs/releases/v17/17-6.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v18/18-0.mdx (via flowName (symbol, a field of interface SubflowCall))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-automation/src/region-suspension-refusal.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 68f2981eeb8e2a1b610861d2c993b18f37f30ab2 — the merge of head 65a9454675b4b557c5ca827a63b9748c1ba1ac26 into base d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 68f2981eeb8e2a1b610861d2c993b18f37f30ab2 && git checkout 68f2981eeb8e2a1b610861d2c993b18f37f30ab2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e 65a9454675b4b557c5ca827a63b9748c1ba1ac26 && git checkout -B drift-repro d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e && git merge --no-ff 65a9454675b4b557c5ca827a63b9748c1ba1ac26

node scripts/docs-audit/affected-docs.mjs --json d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head 65a9454 · 2026-11-02T08:15Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 65a9454675b4b557c5ca827a63b9748c1ba1ac26
Local-runs: none
Scope: packages/spec/src/contracts/automation-service.ts, packages/spec/src/contracts/automation-service.test.ts, packages/services/service-automation/src/engine.ts, packages/services/service-automation/src/dag-traversal.ts, packages/services/service-automation/src/flow-definitions.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/region-suspension-refusal.ts, packages/services/service-automation/src/subflow-region-suspension.test.ts, packages/services/service-automation/src/subflow-guard-region-calls.test.ts + packages/spec/src/automation/control-flow.zod.ts (collectFlowGraphs path shape), packages/spec/src/automation/region-slots.ts (FLOW_REGION_SLOTS), packages/services/service-automation/src/run-resume.ts (cancelRun, loadSuspendedRun), builtin/subflow-node.ts and builtin/map-node.ts (correlation), scripts/check-changeset-no-major.mjs, AGENTS.md Post-Task §3, PR #28959 review 6569776675. Prose face (.changeset/28897-…md) is the lane seat's ACCEPT.

Isolated at-tier review subagent of the seat session; it did not write the change. Net diff origin/main...65a94546 (merge-base cae71fed, 12 files); check-runs on the head: 55 success, 9 skipped, 0 failure (incl. Dogfood Regression Gate, Type Check · workspace, Governed Surface Queue Guard).

① Derived judgments

  1. Arming decline (engine.ts:declineOntoRegionSuspendingCall, after the disabled-subflow and notice halves, before the trigger lookup): a region-nested subflow/map whose child suspendingNode(...) resolves registers, is not armed, warns once per reason, record deleted when no trap. Right; 6 slot×type pins + nested-container pin.
  2. subflowCalls / containerAt (subflow-region-guard.ts): collectFlowGraphs paths over the raw node list, so lastIndexOf('nodes')+2 is the innermost container (checked against control-flow.zod.ts). subflowTargets = subflowCalls(flow).map(...), identical output. Right.
  3. /_status cause (flow-definitions.ts:describeUnboundReason): read after the notice record, matching gate order; withdrawFlow drops it. Right.
  4. Re-judge (subflow-guards.ts:rejudgeSubflowCallers): bound callers settled only when both traps are undefined, unbound ones only when neither record exists; pinned both directions and two calls away. Right.
  5. Orphan cancel (dag-traversal.ts:runRegion): nestedChildRunId(err.correlation) reads the exact string the subflow/map nodes write; chain resolved child → grandchild, cancelled deepest first; refusal unchanged and thrown after; fires once at the innermost boundary. Right; 6 cancel pins + chain pin + top-level control.
  6. Published surface: FLOW_UNBOUND_CAUSES gains one value with TSDoc; the only other consumer reads the type, not the literal union; no export added/removed. Clause-②: yes (narrowing) is the correct arm.

② Semver level

@objectstack/service-automation and @objectstack/spec patch, Clause-②: yes (narrowing), Metadata-protocol: none, BREAKING banner, one adr-0087: not-required (no-migration-prescription) marker. Under ruling A (check-changeset-no-major.mjs) none ⇒ every entry patch; title !, PR body and changeset agree; Check Changeset green. Consistent.

③ Boundary flags

④ Design judgment

The narrowing makes ruling D's own refusal (region-suspension-refusal.ts) real at arming with the child in hand, takes no accept set away at the save door or objectstack validate, and reuses the one region walk. Tried: nested regions, call cycles (seen), stale records across halves, chain cancel order. Holds.

Must-fix: none. Should-fix (not blocking): 1. runRegion uses the degrading loadSuspendedRun, so an unreadable store yields an empty chain, zero cancelRun calls and only a warn — the body's "a failed cancellation is logged at error" does not hold there; call cancelRun(childRunId, reason) once when the chain is empty. 2. Follow-up card for the transitive shape in reading 1. 3. Update the PR body's dogfood line.

Implemented-by: claude/project-thread-9kerqb
Reviewed-by: session_01ADHkmQJdgarUjPLNgFBY67

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

席位核对 · ACCEPT · PR #29096 · 2026-11-02T08:24Z

Head-sha: 65a9454675b4b557c5ca827a63b9748c1ba1ac26

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #28897,与原认领 6570160746 及接手认领 6572306742 一致;12 个文件均在申报面内(含 spec FLOW_UNBOUND_CAUSES 一个新值及其 Eq pin)。
  2. 前提实测:dev 改动前实测 6 种 slot × 调用类型全部泄漏(子运行 paused,可恢复,恢复调用方失败);红先 17/20 失败、改后 20/20;A1–A6 消融全部转红并复原。
  3. 门禁:本 head check-runs 64 条:55 success、9 skipped、0 失败(含 Dogfood Regression Gate、Type Check · workspace)。全量 dogfood:前任席位在交接时读到绿(383 文件,VERDICT 0,Release: 6571724612);PR 正文那行 NOT MEASURED 是过时文字。
  4. 接受的偏差:dev 读法 1(调用方在顶层调用子流程、子流程在区域里调用会挂起的孙流程,不在本次拒绝范围)——运行时会响亮失败、无孤儿,卡只点名区域内的调用方,接受。
  5. 范围外去向:复审 should-fix 1(存储读不出时取消链为空,只打 warn,与正文「取消失败记 error」不符)与 should-fix 2(读法 1 的传递形状)合立一张后续卡,立后在本卡贴指针。Metadata-protocol: none,无 Skills-docs: 欠账。
  6. 落地条款:Clause-②: yes (narrowing) ⇒ CONTRACT_REVIEW_TIER 复审 PASS 6572481670(Head-sha 与现 head 一致)。semver:@objectstack/service-automation、@objectstack/spec 皆 patch,与 changeset 一致。check-governed-merges --pr 29096:NOT governed。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z),及本次接手令「接手所有开发,包括跟进之前未完成的开发落地」。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 08:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 1ac1c24 Nov 2, 2026
65 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-9kerqb branch November 2, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant