--- id: INT-19 family: RT-INT level: core profiles: [runtime] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/rt-int/int-19 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # INT-19 — A machine answers an outbound wait through its own route *RT-INT (Part II) · level: core · profiles: runtime · added in 1.0* A remote system cannot use the route a human uses; it has no session and so cannot present the token that route requires. It gets a route built for its threat model, and an authority that buys it nothing else. ## The rule > **Normative.** This is the rule. > > 1. A machine caller resolves an outbound wait by posting `{"value": …}` (the same key the human resolution route takes) to a dedicated callback route addressed by the interrupt's unguessable identifier. > > 2. The human route requires a session-bound request token that a machine cannot obtain; the callback route does not require one, and must therefore admit only authentication schemes a cookie-authenticated browser cannot present, so there is no session to ride and nothing for such a token to protect. > > 3. Authorisation is a dedicated authority to resolve interrupts by callback: it is not satisfied by, and does not grant, the authority to resolve interrupts as a human. > > 4. The route is scoped by interrupt type, not by direction: only an external-call interrupt (the one shape a remote system was invited to answer) is resolvable here, and any other is refused `409`. > > 5. Every human prompt shape is stamped outward exactly as an external call is, so a direction-only guard would hand the callback credential a person's approval prompt and resume the run as though someone had answered; a direction check is kept behind the type check as defence in depth. > > 6. An unknown identifier answers `404`; an interrupt that is not pending, or has expired, answers `409`, so a replayed callback never resolves twice. > > 7. A request omitting `value` is refused `400` before the interrupt is looked up, so a malformed request reveals nothing about which identifiers exist. ## What it means A remote system has no session to ride, so the human route's request token is not something it can ever present; it gets a callback route built for what it actually is, addressed by the interrupt's own unguessable identifier instead of a session. Reaching that route does not make a caller a human: resolving by callback is a distinct authority that grants nothing else, in particular not the authority to answer as a person would. The bite is in the guard order, not just its existence. The route is scoped by interrupt *type* first — only the one shape a remote system was invited to answer is resolvable here — and the direction check sits behind it. Every human prompt is stamped outward exactly as an external call is, so checking direction alone would let the callback credential answer a person's approval prompt. And the missing-`value` check runs before the interrupt is even looked up: a malformed request gets refused without learning whether the identifier it named exists at all. ## Example ```http title="A machine caller answering the call it was invited to answer" verdict="200 resolved" POST /api/flowdrop/interrupts/{interrupt_id}/callback {"value": {"ok": true}} ``` ```http title="A malformed request against an identifier that does not exist" verdict="400 refused" POST /api/flowdrop/interrupts/{interrupt_id}/callback {} ``` ```http title="The same body, against a human-facing prompt" verdict="409 refused" POST /api/flowdrop/interrupts/{interrupt_id}/callback {"value": true} ``` The second request is refused for what it is missing, before anything is looked up; only a well-formed request against an unknown identifier reaches `404`. ## Related rules - Names: INT-20, INT-22 - Referenced by: INT-20, INT-22 --- Rule identifiers are permanent and are never renumbered. This specification carries no implementation status: each implementation publishes its own standing against these rules. Licensed CC BY 4.0.