Found while implementing the resume gate for #3801 (PR #3822). Adjacent seam, same
trust boundary, different shape — filing rather than widening that PR.
Reasoned from code, not demonstrated — no repro was written, same caveat as
#3801.
Requirement
#3822 gates POST /api/v1/automation/:name/runs/:runId/resume on the node type
that produced the suspension: a descriptor may declare resumeAuthority: 'service',
and approval does, so an approval pause continues only through ApprovalService.
ADR-0044's revise window is the same trust boundary in a shape that key cannot
see. Send-back resumes the run down the revise edge onto an ordinary wait node
(signal flavor) placed by the flow author (ADR-0044 §D3: "The revise edge targets an
ordinary wait node — the durable pause already shipped for timers/signals"). The run
parks there until ApprovalService.resubmit, which enforces submitter-only, refuses
a colliding pending request, and audits a resubmit action before resuming.
Nothing enforces that path. wait is resumeAuthority: 'any' — correctly, since a
signal-flavored wait is meant to be resumable by an external producer — so a raw
resume of a run parked in a revise window walks the declared back-edge into the
approval node, opening round N+1 with no sys_approval_action row and none of
resubmit's checks. Lower severity than the approval bypass #3801 closed (it forces a
resubmit rather than an approval), but it is the same class: a service-owned
continuation reachable around the service.
Why the #3822 mechanism doesn't extend to it
The gate keys on the node's registry type, and the type here is a generic wait
indistinguishable from every other author-placed wait in the system. Gating all wait
nodes would break the documented signal/webhook/manual resume contract.
Closing it needs a per-suspension owner claim rather than a per-type one — the
suspension itself recording that a service owns this particular pause. Sketch, for
discussion rather than a settled design: let a resume signal carry an owner claim that
is persisted onto the continuation, so the service that drove the run into the wait
also owns driving it out; the type-keyed declaration then becomes the default when no
claim is recorded.
Refs #3801, #3822. Context recorded in the 2026-07-28 addendum to
ADR-0019.
Found while implementing the resume gate for #3801 (PR #3822). Adjacent seam, same
trust boundary, different shape — filing rather than widening that PR.
Reasoned from code, not demonstrated — no repro was written, same caveat as
#3801.
Requirement
#3822 gates
POST /api/v1/automation/:name/runs/:runId/resumeon the node typethat produced the suspension: a descriptor may declare
resumeAuthority: 'service',and
approvaldoes, so an approval pause continues only throughApprovalService.ADR-0044's revise window is the same trust boundary in a shape that key cannot
see. Send-back resumes the run down the
reviseedge onto an ordinarywaitnode(signal flavor) placed by the flow author (ADR-0044 §D3: "The revise edge targets an
ordinary
waitnode — the durable pause already shipped for timers/signals"). The runparks there until
ApprovalService.resubmit, which enforcessubmitter-only, refusesa colliding pending request, and audits a
resubmitaction before resuming.Nothing enforces that path.
waitisresumeAuthority: 'any'— correctly, since asignal-flavored wait is meant to be resumable by an external producer — so a raw
resume of a run parked in a revise window walks the declared back-edge into the
approval node, opening round N+1 with no
sys_approval_actionrow and none ofresubmit's checks. Lower severity than the approval bypass #3801 closed (it forces aresubmit rather than an approval), but it is the same class: a service-owned
continuation reachable around the service.
Why the #3822 mechanism doesn't extend to it
The gate keys on the node's registry type, and the type here is a generic
waitindistinguishable from every other author-placed wait in the system. Gating all
waitnodes would break the documented signal/webhook/manual resume contract.
Closing it needs a per-suspension owner claim rather than a per-type one — the
suspension itself recording that a service owns this particular pause. Sketch, for
discussion rather than a settled design: let a resume signal carry an owner claim that
is persisted onto the continuation, so the service that drove the run into the wait
also owns driving it out; the type-keyed declaration then becomes the default when no
claim is recorded.
Refs #3801, #3822. Context recorded in the 2026-07-28 addendum to
ADR-0019.