--- id: API-2 family: GR-API level: core profiles: [storage-api, runtime] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/gr-api/api-2 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # API-2 — The turn door and the launch door judge inputs identically *GR-API (Part I) · level: core · profiles: storage-api, runtime · added in 1.0* ## The rule > **Normative.** This is the rule. > > 1. A session turn's `inputs` are checked exactly as a launch's are: an undeclared key is refused, a declared required input is enforced, values are checked against the declared schema, and resolution into the run's initial data is strict. > > 2. The same body earns the same verdict at both doors. > > 3. A turn must not accept, by merging raw caller input into the run's initial data, anything the launch door would refuse. > > 4. A turn whose inputs are refused is refused with 400. ## What it means A workflow can be run through two doors: launched directly, or driven a turn at a time inside a session. The author decided what the workflow accepts once, by declaring its inputs (MAN-1). This rule says that decision means one thing regardless of which door a caller comes through. The four checks the launch door runs (MAN-13, MAN-15) are the four checks the turn door runs, on the same `inputs` object, with the same answer. The clause about merging is the one that bites. A turn carries its `inputs` alongside the message, and the shortest implementation copies them straight into the run's initial data. That is a second, unguarded door: a caller who knows a workflow's internal node-keyed shape (`chat_input.1`, say) could seed state the author never published, at the turn door, when the launch door would have refused the identical body. Not a declared input name is not a declared input name, whichever door it arrives at. "Identically" is a property of the whole surface, not of the cases someone happened to test at both doors. Two checkers maintained side by side drift exactly where one door has a case the other has not seen; the observable requirement is that no such case exists. ## Example The workflow declares one input, `greeting`. The same three `inputs` objects, sent as a turn, get the verdicts the launch door gives them. ```http title="An input the workflow never declared" verdict="400 refused" POST /api/flowdrop/session/{session}/turn {"content": "hi", "inputs": {"not_a_declared_input": "x"}} ``` ```http title="The internal node-keyed shape: not a declared name, so unknown" verdict="400 refused" POST /api/flowdrop/session/{session}/turn {"content": "hi", "inputs": {"chat_input.1": {"message": "seeded"}}} ``` ```http title="A declared input" verdict="202 accepted" POST /api/flowdrop/session/{session}/turn {"content": "hi", "inputs": {"greeting": "hello"}} ``` The first refusal names the inputs the workflow does accept, as MAN-13 requires of the launch door. A turn with no `inputs` at all is valid: absent is not the same as undeclared. ## Related rules - Names: MAN-13, MAN-15 --- 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.