docs(adr-0044): the revise pause must be service-owned — record why the generic wait reverses (#3823) - #3892
Merged
Merged
Conversation
…he generic `wait` reverses (#3823) ADR-0044 D3 routes the `revise` edge into an ordinary author-placed `wait`, chosen for reuse of the already-shipped durable pause. #3801 later made `resume` authorization-bearing (a node's descriptor declares `resumeAuthority`, and an `approval` pause is refused to anyone but its owning service). That reframes the D3 trade-off: a generic node in a service-owned position is exactly what a type-keyed gate cannot see. Demonstrated in #3823 (repro on the real engine): a raw resume of the revise-window run walks the resubmit back-edge with no submitter check and no `resubmit` audit row, and — when a colliding pending request exists — bypasses the `DUPLICATE_REQUEST` guard that keeps the run alive, permanently destroying it. Records the decision of record before any code moves: - the `revise` edge targets a DEDICATED service-owned pause (still visible on the canvas, no longer raw-resumable) — which answers the original "hides a state machine" objection: that was against re-suspending inside the approval node, not against a dedicated node type. The real axis was reuse-vs-a-new-type. - publish-time graph-lint rejects a `revise` edge wired into a bare `wait`, so the vulnerable shape is un-authorable — the only fix that survives AI authoring. - fail-closed descriptor default and a per-suspension owner claim are recorded as directions, deliberately not built (ADR-0049 posture). Inline supersede-notes at D3 and the rejected-alternative bullet point to the amendment. No code change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gvN32u1EiuvY9uQEMJiMR
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gvN32u1EiuvY9uQEMJiMR
os-zhuang
marked this pull request as ready for review
July 28, 2026 16:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design decision-of-record for #3823, before any code moves — so the fix lands against a settled design rather than inventing one in the PR. Docs-only.
Why an amendment, not a patch
ADR-0044 D3 routes the
reviseedge into an ordinary author-placedwait, chosen to reuse the durable pause already shipped for timers/signals. That was sound under the information ADR-0044 had:resume(runId)then had no authorization model, so which node the run is parked on carried no security weight.#3801 changed that — a node's descriptor now declares
resumeAuthority, and anapprovalpause is refused to anyone but its owning service. New information reversing an earlier trade-off, not a defect in the original call. The consequence, demonstrated in #3823 with a repro on the real engine:resume(runId)of the revise-window run walks theresubmitback-edge with no submitter check and noresubmitaudit row (empty body suffices); andresubmitrefuses withDUPLICATE_REQUESTspecifically to keep the run alive — the raw resume bypasses that guard, the approval node's re-entry fails after the suspension is consumed, and the run is permanently destroyed.What the amendment records
reviseedge targets a dedicated service-owned pause (a node type carryingresumeAuthority: 'service', or the approval node re-suspending in a revise mode). Still visible on the canvas and in run logs; no longer raw-resumable, so the existing automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801 gate covers it with no new machinery.reviseedge wired into a barewait— the previously-recommended shape becomes un-authorable. Decisive for a metadata-driven platform: an AI author following the original D3 sketch would generate the vulnerable graph verbatim, since nothing in the metadata expressed that the wait sat in a privileged position. Making the wrong shape unrepresentable is the only fix that survives AI authoring.resumeAuthoritydefault (every future pausing node is currently fail-open) and a per-suspension owner claim (the Step Functions task-token shape) — with the caveat that such a claim must not be inherited by a downstreamscreen, or it breaks screen flows.A mainstream-engine survey (Salesforce / ServiceNow / Camunda / Temporal / Step Functions — none exposes a generic node-type-agnostic resume to untrusted callers) is included as the rationale for why the generic resume door is the outlier and how the platform would generalise if a third case appears.
Inline supersede-notes at D3 and the rejected-alternative bullet point to the amendment; the Status line flags it.
No changeset (docs-only, no npm-consumer change).
check:doc-authoringclean.Generated by Claude Code