# `Cairnloop.Workers.ApprovalExpiryWorker`
[🔗](https://github.com/szTheory/cairnloop/blob/main/lib/cairnloop/workers/approval_expiry_worker.ex#L1)

Oban worker that performs the scheduled `:pending → :expired` flip for governed tool approvals.

This is the scheduled-sweep half of the dual-mechanism TTL enforcement (D15-12):
1. This worker (scheduled enqueue) — flips `:pending` approvals to `:expired` at `expires_at`.
2. Lazy guard in `ApprovalResumeWorker` — catches any approval the sweep missed.

The scheduled enqueue of this worker (`scheduled_at: approval.expires_at`) lives in
plan 15-03's lane-opening path (`Governance.approve/3`). This worker only performs the flip.

## Branch Logic

1. `nil` approval (deleted) → `:ok` — idempotent no-op.
2. `:pending` approval → status `:expired` + `:expired` `ToolActionEvent` co-committed.
3. Any other status (`:approved`, `:rejected`, `:expired`, etc.) → `:ok` — idempotent no-op.

## Idempotency

The no-op catch-all guarantees safety if the worker runs late or twice: a non-`:pending`
approval will not be re-expired (mirrors `SlaCountdownWorker` `:active → :breached` idiom).

## Telemetry

`:approval_transition` event emitted AFTER the `with` pipeline succeeds (D-29).

---

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