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

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions