# Approvals An approval is a deliberate human-in-the-loop checkpoint inside a flow. When a step needs a human decision before it proceeds, the flow pauses at that step until a reviewer approves or rejects it. ## When to use one Add an approval step before: - writing to a system of record (cases, contracts, mailings) - spending money (LLM tokens above a threshold, API quotas) - destructive actions (delete, archive, send) - anything the operator's compliance owner asked you to gate The approval is a recorded event — every approval shows up in the audit log with the reviewer, decision, and reason. ## How a flow declares one ```yaml steps: - id: classify use: text.classify@^1 - id: review use: system.approval@^0 with: title: "Review the classifier output" details: "$classify.response" reviewer: "$inputs.reviewer" timeout_seconds: 600 - id: write use: erp.write@^1 with: record: "$review.payload" ``` `system.approval` is a built-in capability — no module install needed. It blocks the flow's run until the matching approval record reaches a `decision` (approved / rejected) or the configured `timeout_seconds` elapses. ## How an operator answers one The Approvals page lists every pending approval. Click an entry, read the title + details, decide: - **Approve** — the flow resumes from this step with the approval's `payload` as the step output. - **Reject** — the flow fails at this step with the reviewer's reason; the audit log records the rejection. ## Timeouts If no decision arrives within `timeout_seconds`, the engine returns `ApprovalTimedOut` and the flow fails. Set a timeout that reflects how long a reviewer can realistically take. ## What gets logged Every approval write hits the audit log: - approval requested (with the title + details snapshot) - reviewer-decision recorded (with reason on reject) - payload + signature on accept The hash chain catches tampering — an entry that was approved yesterday can't be quietly flipped to rejected today without breaking the chain. ## Architecture trade-off Approvals are stored alongside events in the SQLite audit DB. For higher-assurance deployments, mirror the audit DB to a WORM sink (Compliance Gap 3, option 2). The approval contract is the same either way — the WORM sink just makes the audit trail tamper-evident to outside reviewers, not just to the hub itself.