Approvals: the pending card now shows its full origin — flow, step, run id (previously dropped at the Dart mapping layer), project, and requested-at — under an ORIGIN heading, led by a one-line intro strip that says what the inbox is and what Approve/Reject do. Approve/Reject buttons carry tooltips; the history dialog gains project + run id. Fixes the approvals doc drift (title/details/reviewer -> prompt/show/timeout_seconds). Guard: approvals_origin_test renders the card via the hermetic fake hub and pins every origin fact. Runs: the "hub too old" state now leads with an in-place update button (same `chain update apply` path as the Diagnose page), the Diagnose deeplink demoted to secondary, with a CLI-absent fallback. Guard: two new RunsLoadErrorView widget tests. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
83 lines
2.5 KiB
Markdown
83 lines
2.5 KiB
Markdown
# 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. The
|
|
`prompt` is the sentence the reviewer sees; `show` selects the
|
|
data put in front of them (a prior step's output).
|
|
|
|
## How a flow declares one
|
|
|
|
```yaml
|
|
steps:
|
|
- id: classify
|
|
use: text.classify@^1
|
|
|
|
- id: review
|
|
use: system.approval@^0
|
|
with:
|
|
prompt: "Review the classifier output"
|
|
show: "$classify.response"
|
|
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. Each card
|
|
shows its origin (flow, step, run, project, requested-at), the
|
|
`prompt`, and the `show` data. Read them, then 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 prompt + show-data 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.
|