You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(automation): one chokepoint for the resume signal — output reopened the hole inputs had just closed (#3879) (#3880)
#3853 guarded `signal.variables` at the route. That closed one of two
equivalent paths into the same variable map and left the other open:
`signal.output` keys are merged under `${run.nodeId}.${key}`, and for a run
parked on a `map` node `run.nodeId` IS the map node — so
{ "output": { "$mapItemDone": true, "$mapItemOutput": … } }
writes exactly the `<mapNodeId>.$mapItemDone` the `inputs` guard had refused,
making the map record a result for an item nobody decided. Demonstrated with a
repro, then fixed.
Scope: the #3853 map gate still held, so a batch whose pending item sits on an
`approval` was refused before any of this — the approval bypass stayed closed.
The residual was forging the recorded result of an item on an ungated pause.
Two escapes with one shape is a design signal, not two bugs. The seam had three
open-coded writers into one variable map (`output` prefixed, `variables` bare,
the engine's own map handoff), so "guard the field that was exploited" was
always going to invite the next field. Structural fix:
- applyResumeSignal is the ONE place a resume signal reaches the variable map.
Both fields become a single write list (already in final, prefixed form),
checked, then applied — a new signal field is covered by construction.
- All-or-nothing, and checked before the suspension is consumed: a rejected
signal applies nothing and the run stays parked, so the real continuation
still lands.
- The engine owns the rule; the transport maps the verdict. resume returns
code 'invalid_signal', the route answers 400. The SDK and any future adapter
inherit it. This corrects #3853's placement argument: "strict at the
untrusted boundary" is right about where a rule BINDS, not where it LIVES.
- Engine-built signals (subflow output mapping, map item handoff) are exempt
via a module-private symbol — deliberately not RESUME_AUTHORITY_SERVICE,
which answers a different question and does not license writing internals.
Claude-Session: https://claude.ai/code/session_013gvN32u1EiuvY9uQEMJiMR
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments