Skip to content

automation: the revise-window wait pause is service-owned but type-keyed gating can't see it #3823

Description

@os-zhuang

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions