Cairnloop.Governance.ToolApproval (cairnloop v0.1.0)

Copy Markdown View Source

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

Summary

Functions

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 for transitioning an approval to a new status.

Returns the locked status values for ToolApproval.

Functions

changeset(approval, attrs)

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(approval, status, decision, reason, actor_id, decided_at)

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.