Audit Decision schema
One append-only row per allow/deny, written and committed before the enforcement point is told the outcome. See Audit Log for why the write ordering matters.
Invariants enforced by the database itself, not just application code
| Constraint | Meaning |
|---|---|
outcome IN ('allow', 'deny') | No third outcome |
(outcome = 'deny') = (deny_reason IS NOT NULL) | An allow never carries a reason; a deny always does |
hop >= 1 | |
target_kind IN ('tool', 'agent') | |
chain_complete = (chain_break IS NULL) | A complete chain never carries a break reason, and vice versa |
hop > 1 OR parent_action_id IS NULL | A root hop (hop 1) has no parent |
UNIQUE(seq) | Total order for keyset pagination and chain-write ordering |
These are Postgres CHECK/UNIQUE constraints, not just Pydantic validation — an invariant
violation is rejected by the database even if application code has a bug.
AuditDecisionListItem (one row of GET /v1/audit/decisions)
Includes session_id, acting_agent, target, target_kind, data_class, outcome,
deny_reason, enforced, taint, policy_version, hop, action_id, ts. See
Audit API for the paginated envelope (AuditDecisionPage).