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
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,waitis'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:
A search of open + closed issues for
resumeAuthorityreturns #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:
resumeAuthorityscreen'any'(default)wait'any'(default)approval'service'(explicit)approval_revise'service'(explicit)subflow/map'any', but the gate follows the linked run to the childSo 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)
'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.screenandwaitdeclaring'any'explicitly, the changeset FROM→TO for plugin authors, and theresume-authority-gatetests.supportsPause: trueandresumeAuthoritywas 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