--- id: INT-20 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-20 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # INT-20 — Call-and-wait creates the interrupt before it makes the call *RT-INT (Part II) · level: core · profiles: runtime · added in 1.0* The order is the rule. The callback address is built from the interrupt's identifier, so there is nothing to tell the remote until the interrupt exists, and a fast remote can answer before the outbound request has even returned. ## The rule > **Normative.** This is the rule. > > 1. An outbound call-and-wait creates its interrupt first, then makes the remote call, then pauses. > > 2. The outbound body carries the callback address, the interrupt identifier and the caller's payload, and the answer returns through the callback route (INT-19). > > 3. A call that fails to go out cancels its interrupt before failing the node; a run waiting on a message nobody was asked to send is worse than a failed node. > > 4. A target address refused before the call leaves nothing behind: the address is validated before the interrupt is created, so a rejected target creates no pending record. > > 5. A target refused only mid-flight, because a redirect led the call somewhere it may not go (NET-2), is a failed outbound call and takes that path: the interrupt is cancelled and nothing pauses. > > 6. The wait is bounded by construction: there is no value meaning "wait forever", a non-positive expiry is refused, and every such interrupt is stamped with an expiry, so with INT-18 a remote that goes silent yields a cancelled run rather than a permanently paused one. > > 7. The wait is operational, not human-facing: it must not appear in a human inbox, where an operator could hand the workflow a fabricated response as though the remote had sent it. > > 8. Resumption passes the callback body through verbatim; only the workflow's author knows the remote's contract. ## What it means The callback address a remote system is told to answer at is built from the interrupt's identifier, so the interrupt has to exist before there is anything to tell the remote — the order is the rule, not an incidental detail of how the node happens to be written. A fast remote can answer the callback before the outbound call has even returned, and the interrupt must already be there to receive it. The reverse direction is guarded just as deliberately. A call that fails to go out cancels the interrupt it already created rather than pausing on it: a run waiting on a message nobody was ever asked to send is worse off than a failed node. The wait itself is bounded by construction — there is no value meaning "forever" — and it is operational rather than human-facing, so it never surfaces where an operator could hand the workflow a fabricated answer. ## Example ```json title="What the remote receives — the interrupt already exists" {"payload": {"order": 42}, "interrupt_id": "{interrupt}", "callback_url": "…/api/flowdrop/interrupts/{interrupt}/callback"} ``` The `interrupt_id` in the body and the id embedded in `callback_url` are the same value: the identifier the interrupt was given before this request was ever sent. ```json title="An answer arriving through the callback" verdict="unchanged" {"decision": "approved", "nested": {"score": 7}} ``` Resumption hands this payload downstream exactly as received; only the workflow's author knows what the remote's contract means. ## Related rules - Names: INT-18, INT-19, INT-23, NET-2 - Referenced by: INT-18, INT-19, INT-23 --- 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.