# APPROVAL_REQUIRED

`APPROVAL_REQUIRED` means the action is **inside** the agent's effective authority but **above its autonomous
ceiling** (the owner's ask-first line), or a policy / delegation rule requires a human. A human owner decides;
the agent never approves itself.

Golden Path: standing $50 per action, ask-first above $20 -> `$25.00` is `APPROVAL_REQUIRED`.

## Dry run vs binding

| | `dry_run` | `binding` |
|---|---|---|
| `decision` | `APPROVAL_REQUIRED` | `APPROVAL_REQUIRED` |
| `status` | `DRY_RUN` | `APPROVAL_PENDING` |
| `approval.state` | `would_be_required`, `approval_id: null` | `pending`, `approval_id: "apr_..."` |
| owner inbox / webhook | nothing created | approval created; `notification.created` (`approval_request`) emitted |

Real binding response (core fields):

```json
{
  "decision": "APPROVAL_REQUIRED", "mode": "binding", "status": "APPROVAL_PENDING",
  "authorization_id": "auth_is58azks5gbso83uilcm",
  "amount_minor": 2500, "effective_maximum_minor": 5000, "autonomous_maximum_minor": 2000,
  "approval": { "required": true, "state": "pending", "approval_id": "apr_ly8u7o2rivq0ils2sd0y", "decided_by": null, "decided_at": null },
  "approval_reasons": [{ "code": "above_autonomous_limit", "canonical": "above_autonomous_maximum", "family": "AMOUNT_LIMIT", "severity": "approval",
    "message": "Amount exceeds this agent's autonomous limit, so approval is required", "rule": "policy.transaction_auto_limit",
    "remedy": "Ask the owner to approve this amount (above the autonomous limit)." }],
  "question": { "question": "Amazon costs $25.00, $5.00 above the current autonomous limit. Allow up to $25.00 for this task?",
                "dimension": "money.autonomous_ceiling_minor", "requested_delta_minor": 500, "how_to_obtain": "owner_approval",
                "safe_actions_remaining": ["NON_CONSEQUENTIAL_RESEARCH", "EXTERNAL_COMMUNICATION"], "asked_at": "consequence_point" },
  "expires_at": "2026-09-26T05:22:16Z"
}
```

## What the agent does

1. Tell the human the `question.question` (it already contains the amount and the delta).
2. **Wait.** Either poll or subscribe - never re-submit, never split the amount, never retry with new wording.

### Poll (agent credential)

`GET /api/v1/agent/approvals/{approval_id}` (scope `approvals.read`) or `GET /api/v1/authorizations/{authorization_id}`.

```json
{ "approval_id": "apr_...", "kind": "economic_authorization", "authorization_id": "auth_...", "status": "pending",
  "expires_at": "2026-09-26T05:22:16Z", "next_action": "keep_waiting",
  "instructions": "A human has not decided yet. Poll again later (or GET /api/v1/authorizations/auth_...); do not re-submit the authorization." }
```

Poll every 3+ seconds. `next_action` is `keep_waiting` -> `none` (approved) or `stop` (rejected / expired).

### Subscribe (owner registers a webhook)

Authorize-only approvals arrive as `notification.created` with `data.notification_type: "approval_request"` and
`data.metadata.{authorization_id, approval_id}`. After the decision, read the authorization. See [WEBHOOKS.md](WEBHOOKS.md).

## What the owner does

- Web app: **Approvals** inbox (`/app/approvals`) -> approve / reject with a note.
- API (owner session, permission `transactions.approve`):
  `GET /api/v1/approvals?status=pending` then `POST /api/v1/approvals/{approval_id}/respond {"approve": true, "note": "ok"}`.
  (Sending `decision` instead of `approve` returns `422 validation_error: Specify approve: true or false`.)

## After the decision

| owner | authorization `status` | `approval.state` | agent poll `next_action` | then |
|---|---|---|---|---|
| approves | `AUTHORIZED` | `approved` (+ `decided_by`, `decided_at`, `note`) | `none` | `GET /authorizations/{id}` -> terms, `expires_at`, `portable_proof` (external). Execute within the terms, then `POST /outcome`. **Quattrino executed nothing.** |
| rejects | `APPROVAL_REJECTED` | `rejected` | `stop` | stop. Do not retry. |
| does nothing | `EXPIRED` when `expires_at` passes | `pending` -> expired | `stop` | ask the human whether to request again. |

Real `GET /authorizations/{id}` after approval: `"status": "AUTHORIZED", "approval": {"required": true, "state": "approved", "decided_by": {"type": "user", "email": "...", "role": "owner"}, "decided_at": "...", "note": "ok"}, "portable_proof": {...}`.

## Boundaries

- Approval can only unlock what is **inside** standing authority. `$75.00` against a $50 policy is `DENIED`, not
  `APPROVAL_REQUIRED` - no approval can widen it; only a policy change can.
- Disclosure decisions (`ASK_OWNER` on a data scope) never change the money verdict: the field is simply not released until the owner answers.
- The approval inherits the authorization TTL (default 15 minutes for binding authorizations). An approval that
  arrives after expiry does not resurrect the authorization.
