The ONLY place run/3 is ever called in Cairnloop.
Consumes a :execution_pending ToolApproval lane, re-validates the proposal
against current policy/scope, calls the tool's run/3 on success, and
co-commits the outcome (:executed + :succeeded result state + append-only event).
Fail-closed on re-validation: policy/scope change since the resume worker ran causes
:invalidated/:execution_failed with a humanized reason — nothing is written (T-16-02).
Idempotency layers (D16-05, T-16-01)
- Oban job uniqueness —
unique: [period: :infinity, keys: [:approval_id]]prevents double-enqueue (no two workers race for the same approval). - Pre-execution terminal guard (LAYER-1) — any non-
:execution_pendingstatus is a true no-op (replayed/duplicate job, wrong-lane approval, already executed). - LAYER-2 terminal guard — if
ToolProposal.result_state == :succeeded, the approval has already been executed; another no-op (guards against TOCTOU between LAYER-1 check and commit, e.g. very fast replay). - Run-level idempotency key — passed into
run/3context as:run_idempotency_key; the tool uses an indexed existence check to dedup its own host write (Stripe-style).
Retry semantics (D16-07)
- Transient
{:error, reason}fromrun/3withattempt < max_attempts→ return{:error, reason}so Oban's built-in backoff retries. - Permanent failure (re-validation fail, or
attempt >= max_attempts) → record terminal:execution_failedand return{:cancel, reason}(Oban discards; never retries).
Telemetry (D16-10)
[:cairnloop, :governance, :action_executed] and [:cairnloop, :governance, :action_failed]
are emitted AFTER a successful with pipeline via Governance.Telemetry — never inside
the clause list and never instead of a ToolActionEvent (D-29).