You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3e17091
Browse filesBrowse the repository at this point in the historyBrowse files
fix(automation): tell approval decide() callers when a subflow parent strands
bubbleToParent already resumes a subflow's parent when the child completes,
but a parent that then strands downstream was reported only to the engine's
own error log -- the decide() caller (an approvals decision) still answered
resumed: true with nothing to distinguish it from a healthy composition, and
its runId still named the healthy child, never the stranded parent.
Fill the already-declared, previously-unfilled ApprovalDecisionResult.
resumeFailure slot (#16472 family ruling, decision batch #76, option A): the
door's status code does not move, but a decide() whose own run resumed while
its subflow parent stranded now also carries resumeFailure (the parent's
runId + repairable) and a matching resumeError, sourced from a new internal
AutomationEngine.takeSubflowParentStrand(childRunId) read-once channel that
bubbleToParent populates on its 'stranded' exit only -- RESUME_IN_PROGRESS /
STORE_UNAVAILABLE stay the untouched functional degradation, and the engine's
own error log line is unchanged.
Updates the #15556 reproduction test to its designed-to-go-red truthful
shape, keeping both its healthy-composition and direct-throw controls.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
An approval `decide()` that resumes a subflow CHILD now tells the caller when that resume bubbles into a PARENT run that stranded — instead of answering full success with nothing to distinguish it from a healthy composition (#15556; the #16472 family ruling, decision batch #76, option A).
7
+
8
+
**The composition.** A parent flow parks at a `subflow` node whose child hosts the `approval` node, so the approvals row names the CHILD run. The decision door resumes the child, the child completes, `bubbleToParent` resumes the parent, and the parent's own downstream node throws. The parent lands on the engine's `'stranded'` exit — it consumed its suspension and is now terminal, repairable only by an operator's `restoreConsumedSuspension` — and `bubbleToParent` already logged that at `error` (unchanged by this fix). What the caller was TOLD did not: `resumed: true`, no `resumeError`, and a `runId` naming the healthy child — identical to what a fully healthy composition answers.
9
+
10
+
```
11
+
FROM service.decide(requestId, { decision: 'approve' }, ctx)
**Additive only — no migration.**`ApprovalDecisionResult.resumeFailure` was already declared (and pinned) in `@objectstack/spec` ahead of this card; this fix is the first producer that fills it. No existing field changes shape, no status code moves (the door still never throws for this shape — `AGENTS.md`'s "a failure handed to the caller" answer does not apply here, since before this fix no caller was told at all), and the door's `error` log line is untouched. A consumer that already ignores unknown fields sees no difference; a consumer that reads `resumeFailure` can now tell a bubbled parent strand from a clean resume without diffing `runId` against a durable run history.
24
+
25
+
**What did not move, on purpose.**`RESUME_IN_PROGRESS` / `STORE_UNAVAILABLE` bubble outcomes stay the functional degradation they always were (`warn`, unreported on `resumeFailure`) — the #16472 ruling is scoped to the one exit the engine calls `'stranded'`. The sibling `recall` door (`ApprovalRecallResult.resumeFailure`, #15970) is a separate card and is not touched here.
0 commit comments