Optional Oban worker for async audit forwarder dispatch.
Wrapped in if Code.ensure_loaded?(Oban.Worker) per D-18 — when Oban is
absent from deps, this module does not exist and the :async dispatch path
is a no-op (see Sigra.Audit.Forwarders.dispatch_async/3).
Thin Job Args (D-13)
Receives only three keys in job args — a thin reference, never a full payload (security: T-3-INFRA-01):
"forwarder"— forwarder module name string (e.g."Elixir.Sigra.Audit.Forwarders.Threadline")"audit_event_id"— UUID of the audit event row to reload"occurred_at"— ISO8601 timestamp for tracing (not used to load the row)
The worker reloads the full audit row from the configured repo + audit_schema
at perform time (D-13 — full payload from DB, not from job args).
Cancel Taxonomy (D-16)
{:cancel, :audit_event_not_found}— row deleted between enqueue and perform (e.g. retention cleanup raced the job). Non-retryable.{:cancel, :unknown_forwarder}— forwarder module no longer compiled/loaded. Non-retryable (configuration change removed the dep).{:cancel, :schema_mismatch}— Threadline shipped a breaking schema change or actor configuration error. Non-retryable.{:error, reason}— network / timeout / transient failure. Retryable with exponential backoff.
Retry (D-14, D-15)
max_attempts: 5 — bumped from EmailDelivery's max_attempts: 3 because
audit retries don't spam users. backoff/1 curve mirrors EmailDelivery
verbatim (D-15): exponential with jitter.
Never Raises (D-17)
perform/1 NEVER raises. Non-:ok exits fire
[:sigra, :audit, :forward, :error] telemetry. The originating audit/auth
transaction already committed before this job was enqueued — failures here
cannot roll it back (boundary doctrine D-21, Pitfall 2).
Config Resolution (D-27)
:repo is read from Application.fetch_env!(:sigra, :repo) — mirrors
EmailDelivery exactly (:repo is a top-level :sigra key, not nested under
:audit). :audit_schema is read from
Application.get_env(otp_app, :sigra_config)[:audit][:audit_schema] following
the single config-resolution idiom across all Sigra boot diagnostics.