Repository navigation
[finding] service-automation: map rolls a refused child up as an ordinary success too — the same fail-open shape as #18110, second file #18555
Description
Activity
Pointer — director seat,
session_01Wj1HUjzyeiBQ8atRf1ZhaL· 2026-09-17T12:21ZGoverned by the ruling on objectstack#18110 (batch #145 item 4, letter A, maintainer 「同意,其他也同意」):
refuse?/refusalMessage?joinNodeExecutionResultbesidesuspend?, the engine throwsFlowRefusalSignalwhere the suspend signal is thrown, andmap-node.tsgains the samerefusedarm in the same PR assubflow-node.ts— one channel, two sites. ⛔ Not closed here; the implementing PR carriesPart of #18555and the lane seat closes it at landing.
Generated by Claude Code
huangyiirene commented
on Sep 17, 2026 CollaboratorMore actionsClaim: PM loop round 1 — member of a folded family dispatch; chain head is #18110
Session:
session_01QGMBhvUoyD8t5zY8xHQhnP
Branch:claude/issue-18110-subflow-refused-rollup(⭐ shared, named after the chain-head card — ⛔ this card gets no branch of its own)
Worktree:objectstack-issue-18110
Domain:domain:services
Seat:domain:services#1
File surface:packages/services/service-automation/src/—engine.ts,builtin/subflow-node.ts,builtin/map-node.tsand their co-located tests (stop on breach; explain in the report)
Container & model:M,mode:subagent, model: default judgment tier — one dispatch covers both members; the tier derivation is quoted in full on the chain-head claim.
Clause-②: yes
Thread-read: 5716379289
Serial constraints cleared:none— the full serial-constraint check for this family is recorded once, on the chain-head claim at #18110 (issuecomment-5716693954), measured at 2026-09-17T15:11Z: lanepm:dispatcheddisjoint, all 23 open PRs intersected file-by-file with 0service-automationhits, batch siblings disjoint, H17 trigger index 0 intersection.Why this card is folded rather than dispatched on its own
One channel, two sites. The director's ruling on #18110 (batch #145 item 4, letter A, maintainer 「同意,其他也同意」) governs this card by name and states it directly: "
subflow-node.tsandmap-node.tsgain therefusedarm that sets it. One channel, two sites — this ruling governs #18555 too." A node executor has no terminal-refusal channel today; both files need the same new one, so splitting them would mean either building the channel twice or landing one consumer of it and leaving the other dead.⭐ And the triage grading of this card names the concrete cost of NOT folding: a successor seat fixes
subflow, leavesmapin place — the shape recorded in #18559 (one slot fixed for two consumers, a third missed) and #18540. ⛔ That account has already been paid once this shift; it is ⛔ not being paid again knowingly.Delivery shape for this member (from the ruling, ⛔ not this seat's invention)
The implementing PR carries
Part of #18555— ⛔ notFixes. The ruling is explicit that this card is ⛔ not closed by the PR's closing keywords; the lane seat closes it at landing, after verifying themaparm independently of thesubflowarm.Dispatched by the
domain:servicesPM seat (objectstack#6021) at 2026-09-17T15:11Z.needs:contract-reviewhung on this card in the same claim act (dual-carrier rule). ⛔ The dev leaves no second claim — this comment is the identity.
Generated by Claude Code
huangyiirene commented
on Sep 17, 2026 CollaboratorMore actionsPointer — implementing PR is open: #18706 (draft), branch
claude/issue-18110-subflow-refused-rollup, carryingPart of #18555— ⛔ notFixes; the lane seat closes this card at landing after verifying themaparm independently.The
maparm is driven, ⛔ not reasoned by analogy fromsubflow, exactly as this card asked.src/builtin/map-refused-rollup.test.ts(7 tests) pins it on its own: a parent whosemapunit reaches a refusing child recordsrefused(notcompleted), does not fire its ownsuccessMessage, stops the batch at that item (later items never run), does not walk past the map node, and keeps the batch'sselected/actedrollup. A control with no refusing item is green on both sides.Proven independent of the
subflowarm by ablation: deleting onlymap-node.ts'srefusedarm reds 6 tests in this file whilesubflow-refused-rollup.test.tsstays entirely green, and deleting only thesubflowarm does the reverse. Each ablation leg proved its mutation reached disk (HEAD blob hash vs mutated blob hash) and restored byte-identical fromHEAD.The full structured report is on the chain-head card, #18110.
Generated by Claude Code
huangyiirene commented
on Sep 17, 2026 CollaboratorMore actionsLANDED — PR #18706 merged. This card's arm (
map) was verified on its own, then closed by this seat.domain:servicesseat (objectstack#6021),session_01QGMBhvUoyD8t5zY8xHQhnP, R1, written at 2026-09-17T17:25Z. Judged onorigin/mainafter the merge, ⛔ never on the PR-closed event.The PR carried
Part of #18555— ⛔ notFixes— so the ruling put this close on the seat, after verifying this card's arm independently of its sibling's. That is the whole reason the two cards were folded into one PR rather than trusted to one test run.This card's arm, measured by itself on
origin/mainorigin/main tip: 5762eaf7e … (#18706) commit window (400): (#18706) = 1 · positive control (#18713) = 1 · (#99999) = 0 ARM 2 — this card: `refuse` in packages/services/service-automation/src/builtin/map-node.ts = 6 builtin/map-refused-rollup.test.ts present on main = yes negative control in the same file = 0⭐ And it is pinned separately from the
subflowarm: at review time, revertingmap-node.tsalone reddened 6 map tests while all 6 subflow tests stayed green, and revertingsubflow-node.tsalone reddened 5 subflow tests while all 7 map tests stayed green. ⇒ neither arm is riding the other's coverage.Why this card existed at all
Triage graded it p1 rather than p2 on exactly this reasoning, and it was right: the branch set in
map-node.tswas line-for-line identical tosubflow-node.ts's, missing the same arm. ⭐ Had it been left for later, the successor would have fixedsubflowand leftmapin place — the shape recorded in #18559 and #18540. One channel, two sites, one PR.Full landing record, the channel's own readings and the contract-review pointer:
#18110(issuecomment-5718303504).⚠️ Scope limit, same as the siblingThe synchronous path only. A child that pauses then refuses on its resumed leg is ⛔ not covered on either site — filed as #18714, and named in the changeset's own scope paragraph so the release note does not overclaim.
Generated by Claude Code
Reported by the
domain:servicesdev dispatched on #18110 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/map-node.tshas the identical shape as #18110's file: it branches onchild.status === 'paused'(:191) and on!child.success(:207), and has norefusedarm. Every other child status falls through the same success path, and the child's output is pushed intostate.resultsat:232.⇒ A refusing
endinside amapunit's child flow is rolled up by the parent as an ordinary success — the same fail-open direction #18110 describes forsubflow, in a second file.Seat verification, ⛔ not carried from the report
Re-taken by the
domain:servicesseat onorigin/main79a046f8withgit show origin/main:PATH, ⛔ not a worktree grep:child.status === 'paused':191!child.success:207refusedanywhere in the filechildin the same file⛔ The runtime effect was not driven on
mapspecifically. #18110's dev did reproduce the equivalent onsubflowlive (parent recordscompleted, fires its ownsuccessMessage, downstream nodes run). Whoever takes this card should drive it onmaprather than reason by analogy fromsubflow.Why this is its own card and ⛔ not part of #18110
subflowchild that endsrefusedis rolled up by the parent as an ordinary success — the refusal reaches nobody #18110 names onlysubflow. Its body, its triage grading and its dispatch order are all scoped tosubflow-node.ts. Ridingmapin would widen a p1 card's verification surface after grading.subflowandmaproll a child's contained failures into the parent'sfailedthrough theExecutionStepMetricsfailure slot ruled on #15617 #16314 treatedsubflowandmapas ONE surface — but for the FAILURE direction (rolling a child's contained failures into the parent'sfailed). This card and [finding] service-automation: asubflowchild that endsrefusedis rolled up by the parent as an ordinary success — the refusal reaches nobody #18110 are the refusal direction, which service-automation:subflowandmaproll a child's contained failures into the parent'sfailedthrough theExecutionStepMetricsfailure slot ruled on #15617 #16314 explicitly does not cover. ⛔ Do not close either as a duplicate of service-automation:subflowandmaproll a child's contained failures into the parent'sfailedthrough theExecutionStepMetricsfailure slot ruled on #15617 #16314.Blocked on the same decision
Blocked-by: #18110
Both files need the same new channel: a node executor currently has no way to terminate its run as
refused.NodeExecutionResult(barrel-exported fromsrc/index.ts:9) declaressuspend?but no refusal member, andFlowRefusalSignalis thrown from exactly one site (engine.ts:9319), only fornode.type === 'end'. #18110 is in the decision box awaiting the maintainer's choice of mechanism; that one decision governs this card too, so ⛔ this should not be dispatched ahead of it.Dedupe words
map node refused child rollup·map-node.ts child.status paused·refused map unit parent continues·ADR-0037 A2 map refused·#18110 inverse mapRelated: #18110 · #16314 · #14945 · #15788
⛔
typeandpriorityare the triage seat's; this card is filed ungraded and unassigned.domain:servicesapplied because the landing site ispackages/services/service-automation, this lane's.Generated by Claude Code