Reported by the domain:services dev that delivered #15788 (lane 2 of the #14945 ruling 2′) as an out-of-scope finding, and filed here by the seat — dev agents report findings with dedupe words; they ⛔ do not file.
Mechanism
packages/services/service-automation/src/builtin/subflow-node.ts branches only on child.status === 'paused'. Every other child status falls through the same success path, so a child run recorded refused returns success: true and the parent walks straight down the subflow node's out-edges.
⇒ An author who writes a refusing end inside a subflow gets the opposite of what the refusal means at the parent level: the parent continues as though the child said yes.
Why it is filed now, and why it is ⛔ NOT a regression of #15788
Before #15788's delivery nothing could write refused at all, so the parent's behaviour was identical (a refusing child merely completed, and the parent continued). ⛔ PR #18109 does not change subflow-node.ts and does not make any existing flow behave differently.
What changed is reachability: the end executor now produces refused, so this path becomes live the moment authors start writing refusals — which is exactly what lane 2 was ruled in to enable. ⚠️ The #14945 ruling says nothing about subflow rollup, so this is ⛔ not a shortfall in that ruling and ⛔ not something #18109 should have ridden in.
Relationship to the sibling card
⚠️ #16314 is the INVERSE direction and is a different card: it is about subflow and map rolling a child's contained failures into the parent's failed through the ExecutionStepMetrics failure slot. This card is about a refused child rolling up as success — the refusal reaching nobody. Same file, opposite failure mode. ⛔ Do not close either as a duplicate of the other.
Not prescribed here
The fix direction is a real fork and belongs to whoever takes this, not to the filer: does a refusing child terminate the parent run as refused, surface as a distinguishable branch the author can route on, or stay a success with the refusal recorded? A refusal is a successful evaluation that says no, ⛔ not a failure, so folding it into the failed rollup #16314 describes would be the wrong answer in a way that is easy to reach for.
Dedupe words
subflow child refused rollup · subflow-node.ts child.status paused · refused subflow parent continues · end node outcome refused subflow · #14945 lane 2 subflow
Related: #16314 · #14945 · #15788 · PR #18109
⛔ domain:*, type and priority are the triage seat's; this card is filed ungraded.
os-decision-facets
① 项目长远合理性:A(NodeExecutionResult 增 refuse? / refusalMessage?)缩小特例 —— 它把拒绝并入 suspend 已在用的同一条 unwinding 协议,而 FlowRefusalSignal 的 docblock 逐字要求「reuses the mechanism rather than inventing a second unwinding protocol」;B(不导出的内部暗缝)是特例增生,且按 dev 实测,为保住 #4354 的 metrics 上卷它会把同样大的面长回信号内部。
② 实际业务拉动:仓内 两个一方调用点 —— subflow-node.ts(本卡)与 map-node.ts(本席复验同形洞,已立 #18555),两者需要同一条通道;第三方执行器拉动 0,⛔ 未测得。
③ 防 AI 犯错:A 强 —— 公开成员有声明、可评审、紧挨既有 suspend,一条规则覆盖两个站点;B 弱 —— 面藏在 barrel 之外评审看不见,且两个站点要写成两个特例。
④ 创业阶段不扩散:唯一指向 B 的一棱 —— A 在已发布包 @objectstack/service-automation 上加公开成员,把终止-拒绝能力一并发给零实测拉动的第三方执行器。
Prior rulings read: subflow,refused,service-automation,child,ends,rolled,parent,ordinary,success,refusal,reaches,nobody → 35 hits; ADR-0119 D4, ADR-0119 D1, ADR-0006 D3, ADR-0019 D2, ADR-0029 D9.9, ADR-0039 D7, ADR-0044 D3, ADR-0056 D6, ADR-0068 D2 — ⭐ 本席逐个读了与本题最近的两个,⛔ 不是只抄工具输出:ADR-0119 是事务 / 原子批,关键词假阳性;ADR-0019 D2(approval-as-flow-node) 裁的是「approval 是插件贡献的节点而非引擎核心」,其 refusal 条款只管 resume 的授权答复,⛔ 未裁「refused 子运行该不该传播到父运行」。⇒ 无既有裁决答本题,本卡是决策而非执行。
推荐:A,并把本卡实施 PR 的 Clause-② 更正为 yes、走契约复审档复核。自检「只看①选 A;②③④ 是否翻转:③ 同向 A,② 同向 A(两个一方站点要同一条通道),仅 ④ 反向 ⇒ 不翻转,落在 A」。
置信缺口:第三方节点执行器是否真需要这条通道未测(实测拉动 0)。若维护者判定已发布公开面不可动,B 是可执行退路,但须接受 dev 实测的代价:为不丢 #4354 的 metrics 上卷,同样大的面会回流进内部信号,且评审看不见。
Generated by Claude Code
Reported by the
domain:servicesdev that delivered #15788 (lane 2 of the #14945 ruling 2′) as an out-of-scope finding, and filed here by the seat — dev agents report findings with dedupe words; they ⛔ do not file.Mechanism
packages/services/service-automation/src/builtin/subflow-node.tsbranches only onchild.status === 'paused'. Every other child status falls through the same success path, so a child run recordedrefusedreturnssuccess: trueand the parent walks straight down the subflow node's out-edges.⇒ An author who writes a refusing
endinside a subflow gets the opposite of what the refusal means at the parent level: the parent continues as though the child said yes.Why it is filed now, and why it is ⛔ NOT a regression of #15788
Before #15788's delivery nothing could write
refusedat all, so the parent's behaviour was identical (a refusing child merely completed, and the parent continued). ⛔ PR #18109 does not changesubflow-node.tsand does not make any existing flow behave differently.What changed is reachability: the⚠️ The #14945 ruling says nothing about subflow rollup, so this is ⛔ not a shortfall in that ruling and ⛔ not something #18109 should have ridden in.
endexecutor now producesrefused, so this path becomes live the moment authors start writing refusals — which is exactly what lane 2 was ruled in to enable.Relationship to the sibling card
subflowandmaprolling a child's contained failures into the parent'sfailedthrough theExecutionStepMetricsfailure slot. This card is about arefusedchild rolling up as success — the refusal reaching nobody. Same file, opposite failure mode. ⛔ Do not close either as a duplicate of the other.Not prescribed here
The fix direction is a real fork and belongs to whoever takes this, not to the filer: does a refusing child terminate the parent run as refused, surface as a distinguishable branch the author can route on, or stay a success with the refusal recorded? A refusal is a successful evaluation that says no, ⛔ not a failure, so folding it into the
failedrollup #16314 describes would be the wrong answer in a way that is easy to reach for.Dedupe words
subflow child refused rollup·subflow-node.ts child.status paused·refused subflow parent continues·end node outcome refused subflow·#14945 lane 2 subflowRelated: #16314 · #14945 · #15788 · PR #18109
⛔
domain:*, type and priority are the triage seat's; this card is filed ungraded.os-decision-facets
① 项目长远合理性:A(
NodeExecutionResult增refuse?/refusalMessage?)缩小特例 —— 它把拒绝并入suspend已在用的同一条 unwinding 协议,而FlowRefusalSignal的 docblock 逐字要求「reuses the mechanism rather than inventing a second unwinding protocol」;B(不导出的内部暗缝)是特例增生,且按 dev 实测,为保住 #4354 的 metrics 上卷它会把同样大的面长回信号内部。② 实际业务拉动:仓内 两个一方调用点 ——
subflow-node.ts(本卡)与map-node.ts(本席复验同形洞,已立 #18555),两者需要同一条通道;第三方执行器拉动 0,⛔ 未测得。③ 防 AI 犯错:A 强 —— 公开成员有声明、可评审、紧挨既有
suspend,一条规则覆盖两个站点;B 弱 —— 面藏在 barrel 之外评审看不见,且两个站点要写成两个特例。④ 创业阶段不扩散:唯一指向 B 的一棱 —— A 在已发布包
@objectstack/service-automation上加公开成员,把终止-拒绝能力一并发给零实测拉动的第三方执行器。Prior rulings read: subflow,refused,service-automation,child,ends,rolled,parent,ordinary,success,refusal,reaches,nobody → 35 hits; ADR-0119 D4, ADR-0119 D1, ADR-0006 D3, ADR-0019 D2, ADR-0029 D9.9, ADR-0039 D7, ADR-0044 D3, ADR-0056 D6, ADR-0068 D2 — ⭐ 本席逐个读了与本题最近的两个,⛔ 不是只抄工具输出:ADR-0119 是事务 / 原子批,关键词假阳性;ADR-0019 D2(approval-as-flow-node) 裁的是「approval 是插件贡献的节点而非引擎核心」,其 refusal 条款只管
resume的授权答复,⛔ 未裁「refused子运行该不该传播到父运行」。⇒ 无既有裁决答本题,本卡是决策而非执行。推荐:A,并把本卡实施 PR 的
Clause-②更正为yes、走契约复审档复核。自检「只看①选 A;②③④ 是否翻转:③ 同向 A,② 同向 A(两个一方站点要同一条通道),仅 ④ 反向 ⇒ 不翻转,落在 A」。置信缺口:第三方节点执行器是否真需要这条通道未测(实测拉动 0)。若维护者判定已发布公开面不可动,B 是可执行退路,但须接受 dev 实测的代价:为不丢 #4354 的 metrics 上卷,同样大的面会回流进内部信号,且评审看不见。
Generated by Claude Code