Found by the at-tier Clause-② contract review of PR #16635 (card #15617, the spec half of decision batch #55) while verifying that PR's new roll-up statements against the engine. ⛔ Not a defect in that PR — filed out of it because it is engine behaviour, and because it falsifies a control reading the card rests on.
⛔ No domain:* set — routing is triage's. It lands in service-automation (engine.ts).
Measured on origin/main 0a61db1f5d
A subflow / map child that is durably paused and then fails after resume terminally fails the parent through a different path from the ordinary one:
engine.ts:5549-5552 → failSuspendedRun (:6358-6372) → up-bubble failAncestors (:7001)
That path fails the parent with steps: run.steps frozen at suspend time. ⇒ No total of any kind rides up — not selected, not acted, not unmeasuredEffect, and (once #16314 lands) not failures either.
Contrast the ordinary path, where a failed child DOES contribute: subflow-node.ts:144-147 spreads the same rolled object onto the failure return, and map-node.ts:197-203 adds the failed item's summary.acted.
Why it is worth a card rather than a footnote
⚠️ It falsifies a control that #15617's evidence base rests on. That card's measurement was reported as a matched pair:
Control, in the same run: when the child fails rather than contains, the parent's subflow node failure is counted (call: {runs:5, failures:1}, parent failed=1). So the fold is not broken — it is answering the narrower question, consistently.
On the durably-paused path that control does not hold: the parent is failed, but nothing is counted, because its step list never advanced past the suspend. The control is true of the path it was measured on and silently untrue of this one — which is exactly the class of gap the reviewer was looking for, arriving one path over.
⭐ It is also the third roll-up site, and the one the spec half's PR body does not name. AutomationEngine.creditChildRun (engine.ts:5351-5371, call sites :5485 up-bubble and :5560 down-delegation) is the only path by which a durably-paused child that later completes reaches the parent's step. The failure twin of that path is this card.
Scope note for whoever takes it
⛔ Not a rider on #16314 — but #16314 is the card that will make it matter. Once the ruled failures slot folds contained child failures into the run-level failed, a delegating parent whose child paused and then failed will read failed: 0 while its child lost rows, which is the exact misreading #13681's visibility rider exists to prevent and the reason #15617 was filed at all. ⇒ Sequence it with #16314, or at minimum make #16314's implementer read this first.
⚠️ Deliberately NOT prejudged here: whether the right fix is to credit the frozen steps at failSuspendedRun time, to carry the child's summary onto the parent's step before freezing, or to declare this path's silence explicitly. All three are contract-visible and none is this seat's to rule.
Related but distinct (checked, none covers this): #15222 (restoreConsumedSuspension cannot reach a nested run — restoration, not totals) · #14379 (closed; a retryable child refusal treated as terminal) · #16314 (the engine half of the roll-up) · #15617 · #13681.
Refs: contract review verdict on #15617 (5573894010) · PR #16635.
Found by the at-tier Clause-② contract review of PR #16635 (card #15617, the spec half of decision batch #55) while verifying that PR's new roll-up statements against the engine. ⛔ Not a defect in that PR — filed out of it because it is engine behaviour, and because it falsifies a control reading the card rests on.
⛔ No
domain:*set — routing is triage's. It lands inservice-automation(engine.ts).Measured on
origin/main0a61db1f5dA
subflow/mapchild that is durably paused and then fails after resume terminally fails the parent through a different path from the ordinary one:That path fails the parent with
steps: run.stepsfrozen at suspend time. ⇒ No total of any kind rides up — notselected, notacted, notunmeasuredEffect, and (once #16314 lands) notfailureseither.Contrast the ordinary path, where a failed child DOES contribute:
subflow-node.ts:144-147spreads the samerolledobject onto the failure return, andmap-node.ts:197-203adds the failed item'ssummary.acted.Why it is worth a card rather than a footnote
On the durably-paused path that control does not hold: the parent is failed, but nothing is counted, because its step list never advanced past the suspend. The control is true of the path it was measured on and silently untrue of this one — which is exactly the class of gap the reviewer was looking for, arriving one path over.
⭐ It is also the third roll-up site, and the one the spec half's PR body does not name.
AutomationEngine.creditChildRun(engine.ts:5351-5371, call sites :5485 up-bubble and :5560 down-delegation) is the only path by which a durably-paused child that later completes reaches the parent's step. The failure twin of that path is this card.Scope note for whoever takes it
⛔ Not a rider on #16314 — but #16314 is the card that will make it matter. Once the ruled
failuresslot folds contained child failures into the run-levelfailed, a delegating parent whose child paused and then failed will readfailed: 0while its child lost rows, which is the exact misreading #13681's visibility rider exists to prevent and the reason #15617 was filed at all. ⇒ Sequence it with #16314, or at minimum make #16314's implementer read this first.failSuspendedRuntime, to carry the child's summary onto the parent's step before freezing, or to declare this path's silence explicitly. All three are contract-visible and none is this seat's to rule.Related but distinct (checked, none covers this): #15222 (
restoreConsumedSuspensioncannot reach a nested run — restoration, not totals) · #14379 (closed; a retryable child refusal treated as terminal) · #16314 (the engine half of the roll-up) · #15617 · #13681.Refs: contract review verdict on #15617 (
5573894010) · PR #16635.