Skip to content

automation: resumeAuthority defaults to 'any', so every future pausing node ships fail-open — ADR-0044 says this is "tracked separately" and nothing tracks it #5561

Description

@os-zhuang

Found while implementing #3823 (PR #5560). Not a defect anyone hits today — filed as an observation so the ADR's "tracked separately" is true.

What

ActionDescriptor.resumeAuthority (packages/spec/src/automation/node-executor.zod.ts) defaults to 'any'. The #3801 resume gate therefore covers a pause only when its node type's author remembered the flag: a new pausing node type ships raw-resumable by omission, which is the "declared ≠ enforced" trap (Prime Directive #10) one node away.

That is not hypothetical drift — it is exactly how #3823 happened. ADR-0044 pointed the revise edge at a generic wait, wait is 'any' (correctly, for a signal wait), and the pause in the service-owned position inherited the fail-open default. The demonstrated cost there was an unaudited resubmit plus, opportunistically, remote run destruction.

ADR-0044's 2026-07-28 amendment records the long-term fix and defers it:

Fail-closed descriptor default. … The long-term-correct default is that a pause is not raw-resumable unless it opts into 'any' (screen / wait-signal declaring it explicitly). A breaking change to the descriptor default; needs a migration; tracked separately.

A search of open + closed issues for resumeAuthority returns #3823, #3853 and an unrelated typecheck issue — no issue tracks this. So the ADR points at a tracking item that does not exist, which is how a deferred decision quietly becomes a forgotten one.

Current state (why this is observation-class, not a defect)

Every pausing type in the tree today is correct on its own terms:

type resumeAuthority correct?
screen 'any' (default) yes — the UI flow-runner owns it
wait 'any' (default) yes — an external producer is meant to resume a signal wait
approval 'service' (explicit) yes (#3801)
approval_revise 'service' (explicit) yes (#3823 / PR #5560)
subflow / map 'any', but the gate follows the linked run to the child yes (#3853)

So nothing is exploitable through the default right now. The exposure is prospective: the next pausing node type — ours or a plugin's — is fail-open unless someone remembers, and the two 'service' declarations that exist were both added after a bypass was demonstrated rather than at design time.

What a fix would involve (sketch, not a decision)

  • Flip the default to 'service' (or introduce a required field / an explicit 'any' opt-in) — a breaking descriptor change: every third-party pausing executor that relies on today's default would start refusing the generic resume route.
  • Migration surface: the ADR-0087 conversion/migration registries, screen and wait declaring 'any' explicitly, the changeset FROM→TO for plugin authors, and the resume-authority-gate tests.
  • Worth weighing against the cheaper half-measure: keep the default and make the absence loud instead (e.g. a descriptor-registration warning when supportsPause: true and resumeAuthority was never declared), which needs no migration and no breaking change.

Refs #3801, #3823, #3853; ADR-0044 amendment ("Directions recorded but deliberately not built here"), ADR-0019's #3801 addendum.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions