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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions