Oban worker driving idempotent ingest of streaming-provider webhooks.
Enqueued by Rindle.Delivery.WebhookPlug after the Plug verifies the HMAC
signature. The worker trusts upstream verification (single trust boundary
at the Plug edge per D-19); it does NOT re-verify.
Queue & retry posture (D-18, D-20)
- Queue:
:rindle_provider(shared withRindle.Workers.MuxIngestVariant). max_attempts: 5with default Oban exponential backoff for Repo errors.timeout/1 -> 30_000ms.- Unique on the Mux event UUID (top-level
event_idarg) for 24h (states: [:scheduled, :executing, :retryable]) — re-delivery during Mux outages is a no-op.
Race-snooze (D-21) — divergence from sibling workers
This is the ONLY Rindle worker that uses {:snooze, n}. The race window is
data-visibility (webhook for video.asset.ready arrives before
MuxIngestVariant's Repo commit on the row is visible to this worker), not
computation. Snoozes preserve the max_attempts: 5 budget for genuine
errors (Oban semantics: snoozed jobs do not consume :attempt).
- attempt 1 → snooze 5s
- attempt 2 → snooze 15s
- attempt 3 → snooze 45s
- attempt 4 → snooze 90s
- attempt ≥ 5 →
{:cancel, :provider_asset_row_missing}
Cumulative budget ~155s. After cancel, the polling backstop
(Rindle.Workers.MuxSyncProviderAsset, Phase 34) reconciles the row.
Dispatch table (D-27)
| Event type | FSM transition | Broadcast | Telemetry |
|---|---|---|---|
video.asset.ready | * -> ready | :provider_asset_ready | :processed |
video.asset.errored | * -> errored | :provider_asset_errored | :processed |
video.asset.deleted | * -> deleted | :provider_asset_deleted | :processed |
video.asset.created | uploading -> processing | none (Phase 37) | :processed kind: :deferred_to_phase_37 |
video.upload.asset_created | none (last_event_at bump only) | none | :ignored kind: :deferred_to_phase_37 |
| other | none (last_event_at bump only) | none | :ignored kind: :unknown_event |
Telemetry (D-26 + security invariant 14)
[:rindle, :provider, :webhook, :processed]— happy path with FSM transition.[:rindle, :provider, :webhook, :ignored]— no-op (unknown / deferred / race-snooze).[:rindle, :provider, :webhook, :exception]— raised / FSM-rejected / race-snooze-exhausted.
Metadata always routes asset_id through MediaProviderAsset.redact_id/1
(security invariant 14).
PubSub (D-31, D-32, D-33)
Two-topic broadcast on "rindle:provider_asset:#{media_asset_id}" AND
"rindle:asset:#{media_asset_id}". Payload contract:
{:rindle_event, event_type, %{
asset_id, playback_ids, profile, provider, state
}}provider_asset_id is FORBIDDEN in the payload (security invariant 14).
:provider_asset_created is RESERVED for Phase 37 / MUX-23 — Phase 35
handles video.asset.created with FSM transition + no broadcast.
Summary
Functions
Public unique-job opts for callers (e.g., the Plug) that build their own
Oban.Job.changeset and need the same idempotency key (D-20).
Functions
@spec unique_job_opts() :: keyword()
Public unique-job opts for callers (e.g., the Plug) that build their own
Oban.Job.changeset and need the same idempotency key (D-20).
Includes :available in states because Oban inserts newly-enqueued jobs
in :available state by default — without it the unique constraint never
fires for the most common dedup case (re-delivery right after the first
insert, before the worker picks up the job). This mirrors
Rindle.Workers.MuxIngestVariant.unique_job_opts/0 (Phase 34).