# 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.
