# Conformance flows (mode contract v0.1) `flows.json` describes complete conversations that an email-side implementation must handle. A run needs only this bundle: `flows.json`, `scenarios.json` and `statute.json`. Every expectation is observable at the contract boundary: messages the stub parties receive, responses to contract requests, and mail the email side sends. Nothing depends on how an implementation stores data or answers the gateway. ## Running a flow For each flow, start fresh: 1. **Email side.** An email side for the statute in `statute.json` (namespace `modes.example`), configured with the flow's `setup`, i.e. `setups[flow.setup].roles`. Its own peer id is `email`, and it knows the keys in `peers`. A master role needs a store for its cases. The reference uses its ticket service, but any store that creates a case on intake mail and correlates mail to `ticket=ID` works. 2. **Callback stub.** Peer `callback-1` speaks the contract and plays `scenarios.json`, with `flow.scenario` as its default. It sends nothing until a `mode` step. 3. **Outside master stub.** Peer `cms-1` records every message it gets, and sends commands only in `master` steps. Other masters named in a setup (`cms-2`) are only claimed in `inject` steps, signed with their key from `peers`. 4. **Clock.** A controllable clock for the email side's timeouts. Only `advance` steps move it. Signatures use real time. 5. **Send cap.** When `flow.max_sent` is set, the email side's send cap is that number, and an SMTP sink counts what is actually sent. Without it there is no cap. Role configuration fields are those of CONTRACT.md §3. In a setup: - `modes: {MODE: {"peer": ID}}` means the mode endpoint of that peer; - `master_peer` is the outside master, which implies its event URL; - `""` stands for the handoff URL of peer ID. The implementation wires peer ids to its own URLs. The `peers` keys are public **test keys**, never to be used in a deployment. ## Steps | Step | Meaning | |---|---| | `{"mail": {to, text, dmarc?, subject?}}` | Inbound mail from `customer@example.net` to `TO@modes.example`, through the normal gateway path. `dmarc` (default `"pass"`) is the trusted Authentication-Results verdict; `null` means no verdict. | | `{"mode": "run"}` | The callback stub sends every status that is due, until it is waiting, silent or terminal. | | `{"advance": N}` | Move the email side's clock N seconds and let it run its timeouts. | | `{"master": {"command": C, ...}}` | The stub master sends a signed contract command: `assign` (`case.assign`; `resource_id`, plus `intake_ref`, by default the captured one), `send` (`mail.send`; `in_reply_to`, `body`), `cancel` (`handoff.cancel`; `handoff_id`), or `policy` (`case.policy`). The case is the captured case. | | `{"inject": {endpoint, as?, sign?, token?, message}}` | Send a crafted request straight to the email side: `endpoint` is `master`, `status` (the captured handoff's status URL) or `events`. `as` is the claimed peer id. `sign` is `valid` (default), `none`, `stale` (timestamp 400 s old) or `wrong-key`. `token: "bogus"` replaces the status URL token. In `message`, `"case": "current"` means the captured case, and the envelope (`mas_modes_version`, `id`) is added. | Strings in steps may contain the captures `{case_id}`, `{message_ref}`, `{intake_ref}` and `{handoff_id}`. ## Captures Captures are updated after every step, from what that step produced, in this order: 1. every message the mode stub and the master stub received; 2. the response to a `master` or `inject` step. The latest value wins. | Capture | From | |---|---| | case (`address`, `key`) | the `case` of any of those messages or responses | | `{case_id}` | the resource id in the captured case address (`TYPE=ID@D`) | | `{message_ref}` | `message_ref` of `case.message`, or of the `case.assign` response | | `{intake_ref}` | `intake_ref` of `case.intake` | | `{handoff_id}` | `handoff_id` of `handoff.request` (mode) or `handoff.requested` (master) | ## Expectations `expect` is checked after the step. Every key listed must match. | Key | Value | |---|---| | `event_accepted` | the email side answered the step's inbound mail event with 2xx | | `mode_statuses` | statuses of every `handoff.status` the mode stub sent, in order | | `mode_received` | number of contract messages the mode stub received | | `mode_received_last` | `type` of the last one | | `master_received` | `{type: count}` of messages the master stub received; only the listed types are compared | | `relayed` | statuses of every `handoff.status` the master stub received, in order | | `mails_sent` | number of mails the email side sent (held and refused mail excluded) | | `last_mail_contains` | the body of the last mail sent contains this text | | `response_ok` | `ok` in the response to the step's command | | `response_reason` | `reason` in that response | | `response_held` | `held` in that response (CONTRACT.md §6) | | `case_address` | `case.address` in that response | | `http_status` | HTTP status of that response | | `error` | `error` in that response | A runner MUST reject a flow that uses a step or expectation key not defined here.