# `Cairnloop.Governance.ToolApproval`
[🔗](https://github.com/szTheory/cairnloop/blob/main/lib/cairnloop/governance/tool_approval.ex#L1)

Durable approval record for a governed tool proposal.

Mirrors the `ReviewTask` idiom exactly (D15-01..04):
- Denormalized `status` enum for read-your-writes
- Last-decision fields (`decided_by`, `last_decision`, `decided_at`, `reason`)
- Transitions co-committed with an append-only `ToolActionEvent` in one transaction
- One-active-lane enforced by a partial unique index on `tool_proposal_id WHERE status = 'pending'` (APRV-04)

## Append-Only Invariant

This schema is **insert-only** for the approval record itself. Status transitions are
written via `decision_changeset/6` which updates the denormalized fields. There is no
`update/1` or `delete/1` function — the `ToolActionEvent` trail is the immutable audit log.

## Status Axis (D15-02, D16-08)

The approval status axis is separate from `ToolProposal.status`:
- `:pending` — awaiting operator decision (one active lane enforced by partial unique index)
- `:approved` — operator approved; resume worker will re-validate and transition to `:execution_pending`
- `:execution_pending` — re-validation passed; `ToolExecutionWorker` enqueued (Phase 16)
- `:rejected` — operator rejected with reason (FLOW-03)
- `:deferred` — operator deferred with reason (FLOW-03)
- `:expired` — TTL elapsed; dual mechanism (scheduled Oban job + lazy guard at resume time)
- `:invalidated` — re-validation failed; policy/scope changed since approval
- `:executed` — Phase 16 success terminal; `run/3` returned `{:ok, result}` and outcome co-committed
- `:execution_failed` — Phase 16 failure terminal; exhausted retries or permanent re-validation failure

# `changeset`

Standard changeset for creating or updating a `ToolApproval`.
Requires: `tool_proposal_id`, `status`.
Status defaults to `:pending`.
Registers the partial unique index constraint for one-active-lane enforcement (APRV-04).

# `decision_changeset`

Decision changeset for transitioning an approval to a new status.

Parameters:
- `approval` — the existing `ToolApproval` struct
- `status` — the new status atom
- `decision` — free-form decision label (e.g. `"approved"`, `"rejected"`)
- `reason` — operator-provided reason; REQUIRED for `:rejected` and `:deferred` (FLOW-03)
- `actor_id` — the actor making the decision
- `decided_at` — timestamp of the decision

Enforces FLOW-03: reason is required for `:rejected` and `:deferred` transitions.

# `status_values`

Returns the locked status values for `ToolApproval`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
