Rindle.Workers.IngestProviderWebhook (Rindle v0.1.5)

Copy Markdown View Source

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 with Rindle.Workers.MuxIngestVariant).
  • max_attempts: 5 with default Oban exponential backoff for Repo errors.
  • timeout/1 -> 30_000 ms.
  • Unique on the Mux event UUID (top-level event_id arg) 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 typeFSM transitionBroadcastTelemetry
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.createduploading -> processingnone (Phase 37):processed kind: :deferred_to_phase_37
video.upload.asset_creatednone (last_event_at bump only)none:ignored kind: :deferred_to_phase_37
othernone (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

unique_job_opts()

@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).