Repository navigation
Commit ab94656
docs(flows): split the subflow-strand paragraph into its three cases (#20377)
Fixes #17940
Clause-②: no
## What was wrong
`content/docs/automation/flows.mdx`'s subflow-chain repair paragraph
conflated three distinct outcomes into one block of prose, and its
closing
sentence — "an ancestor is never *stranded*, because resuming it is not
what moves it" — was written for exactly one of them. #15556 (PR #17908,
merged `c8a006fc41`) shipped a case the paragraph never described, where
that sentence is false: the child completes, `bubbleToParent` resumes
the
parent, and the parent's own downstream node throws. There the bubble is
exactly what moves the parent, and the parent itself lands on the
engine's
`'stranded'` exit.
## Before
> A child that fails terminally after the pause fails every waiting
ancestor, so
> no run is stranded as resumable-forever. **When the child's failure is
a
> strand** — its resume consumed the pause and a downstream node threw —
each
> ancestor's consumed pause is recorded too, and the repair verb puts
the whole
> chain back in one call: `POST …/runs/{runId}/restore-suspension` on
**any**
> member re-arms every member, deepest first, so the continuation
re-issued on
> the run you named flows back up through the ancestors instead of
completing a
> leaf into a parent that never continues. Re-arming an ancestor is all
the verb
> does — an ancestor is never *stranded*, because resuming it is not
what moves
> it. A cascade from a child that is **not** repairable records no
ancestor
> snapshot, deliberately, so the verb never promises a chain repair it
could not
> finish.
## After (revised per PM review — no self-reference, repair verb named)
> A child that fails terminally after the pause fails every waiting
ancestor, so
> none of them is left stranded as resumable-forever. **When the child's
failure
> is a strand** — its resume consumed the pause and a downstream node
threw —
> each ancestor's consumed pause is recorded too, and the repair verb
puts the
> whole chain back in one call: `POST …/runs/{runId}/restore-suspension`
on
> **any** member re-arms every member, deepest first, so the
continuation
> re-issued on the run you named flows back up through the ancestors
instead of
> completing a leaf into a parent that never continues. Re-arming an
ancestor is
> all the verb does in this case — no ancestor is stranded here, because
> resuming it is not what moves it. A cascade from a child that is
**not**
> repairable records no ancestor snapshot, deliberately, so the verb
never
> promises a chain repair it could not finish.
>
> **A third case is the opposite: the bubble itself is what strands an
> ancestor.** The child completes cleanly, `bubbleToParent` resumes the
parent
> on the child's behalf, and the parent's own downstream node throws.
There the
> bubble — not a resume the caller issued — is exactly what moves the
parent,
> and it is the parent, not the child, that lands on the engine's
`'stranded'`
> exit, terminal. The child's own resume genuinely succeeded, so its
resumer
> (an approvals decision door, a wait timer) is told the resume
succeeded; as
> of #15556 an approval `decide()` also reports the stranded parent on
> `resumeFailure` (`{ code: 'RESUME_FAILED', runId: '<parent>', status:
> 'stranded', repairable: true }`). Repair it the same way: the same
> `restore-suspension` verb (`restoreConsumedSuspension` underneath),
issued on
> the parent's run id — the `runId` `resumeFailure` names, not the
child's.
## Code measured on `origin/main` `a88a1bb39` (not copied from the card)
- `packages/services/service-automation/src/engine.ts:1737` — the
`SubflowParentStrand` interface (`runId`, `repairable: true`, `error`),
recorded only on the arm `AutomationResult.status` calls `'stranded'`.
- `packages/services/service-automation/src/engine.ts:7434` —
`bubbleToParent`:
`if (parentRes.status === 'stranded')` records the `SubflowParentStrand`
under the **child's** own run id.
- `packages/services/service-automation/src/engine.ts:7511` —
`takeSubflowParentStrand(childRunId)`, the delete-on-read hand-off.
- `packages/plugins/plugin-approvals/src/approval-service.ts:3476` — the
approvals decision door's `resumeFailure` on a `bubbleStrand`:
`{ code: 'RESUME_FAILED', runId: bubbleStrand.runId, status: 'stranded',
repairable: bubbleStrand.repairable }` — matches the card's claimed
shape.
- `packages/services/service-automation/src/engine.ts:7994` —
`restoreConsumedSuspension`, the repair verb for the parent strand.
- `packages/runtime/src/domains/automation.ts:2752` — the REST door,
`POST /:name/runs/:runId/restore-suspension`, routes `parts[2]` (the
`:runId` path segment) straight into
`automationService.restoreConsumedSuspension(parts[2], …)` — so the same
`restore-suspension` verb an operator calls in case (a)/(b) is what a
third-case operator calls too, on the parent's run id (the `runId`
`resumeFailure` names).
Binding honoured (thread comment `5697194225`): #17541 owns the naming
of any
new `AutomationResult.status` member. This PR coins none — `'stranded'`
is
the status the engine and the approvals door already use today.
## PM review addendum
PM review verified all code anchors and asked for two prose fixes,
applied
in commit `28c110796`:
1. Dropped the two sentences that referred to the page's own prose
("that sentence is scoped to…" / "the claim above does not hold for
it") and restated the scoping as behaviour.
2. Named the third case's repair verb the same way the other two cases
do
— the `restore-suspension` REST verb (`restoreConsumedSuspension`
underneath), issued on the parent's run id — instead of only the engine
method name.
## Gates run (docs-only change, no changeset — `content/docs/**` is not
a
published package surface)
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands content/docs/automation/flows.mdx` derived 40 command(s),
same 40
before and after the revision. All 40 ran green both times (reconciled
with
`--ran`: `40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN`). One-time
prerequisite builds these gates needed (`@objectstack/formula` +
`@objectstack/lint`, and `@objectstack/client` +
`@objectstack/client-react`)
— neither package's source was touched by this diff. Full command list
and
outputs are in the report comment on #17940.
Serial neighbour: draft PR #20344 edits the same file at `:1357` and
below;
this diff's hunk sits at `:1104`–`:1129`, 245+ lines above it — no
overlap.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c577e66 commit ab94656
1 file changed
Lines changed: 24 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1102 | 1102 | | |
1103 | 1103 | | |
1104 | 1104 | | |
1105 | | - | |
1106 | | - | |
1107 | | - | |
1108 | | - | |
1109 | | - | |
1110 | | - | |
1111 | | - | |
1112 | | - | |
1113 | | - | |
1114 | | - | |
1115 | | - | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
1116 | 1129 | | |
1117 | 1130 | | |
1118 | 1131 | | |
| |||
0 commit comments