The host-injected Oban worker (ADR-0004): the consuming app defines ONE
module, and the Oban beam compiles only where Oban exists — this macro
expands use Oban.Worker inside the HOST's compilation, so the package
itself never references a loaded Oban module and compiles Oban-free
(the CI no-optional leg's proof).
defmodule MyApp.WebhookDeliveryWorker do
use AshHooks.Worker,
deliveries: MyApp.OutboundDelivery,
endpoints: MyApp.WebhookEndpoint,
secret_resolver: {MyApp.Secrets, :webhook_secret},
queue: :webhooks,
oban: MyApp.Oban
endConsumers pass enqueue: {MyApp.WebhookDeliveryWorker, :enqueue} to
AshHooks.dispatch/4 — the generated enqueue/2 IS that seam.
Options:
:deliveries,:endpoints(required) — the consumer's resource modules carrying theAshHooks.OutboundDelivery/AshHooks.Endpointextensions.:secret_resolver(required,{m, f}) — resolves an endpoint's secret REFERENCE:f(ref) :: {:ok, secret_binary} | {:error, term}. The returned value ALWAYS signs the Standard Webhooks envelope (itswhsk_/whsec_prefix only selects the key slot for rotation); legacy envelopes, when the signing mode uses them, are signed from the endpoint'slegacy_secret_ref/legacy_previous_secret_refreferences through this same resolver.:tenant_aware_secrets(optional, defaultfalse) — switches the resolver contract to 2-arity:f(ref, tenant). The tenant is the run's args-threaded tenant (the delivery row's tenant, serialized at enqueue). Explicit, no arity magic: the default 1-arity contract is unchanged.:snippet_redactor({m, f}, optional) — a consumer callback run on the RAW captured body ahead of the package's snippet floor (domain-specific tokens need raw input). Only consulted on per-callsnippet_capture: truediagnostic runs; a crash or invalid return degrades to the sanitized summary, never raw bytes. The capture flag itself is deliberately NOT a macro option (ADR-0005's snippet amendment: compile-time knobs are broad and quiet) — pass it in theAshHooks.Delivery.run/2config for a one-row diagnostic re-drive.:http— theAshHooks.Httpadapter (defaultAshHooks.Http.Bounded).:oban— the Oban instance name (default the unnamed instance).:queue,:timeout,:max_attempts— Oban Worker options (the job's timeout defaults to 30s; its max_attempts is advisory only — snoozes extend it, the ROW's ceiling governs dead-letter).:delivery_max_attempts(default 10),:base_backoff_seconds(2),:max_backoff_seconds(3600),:retry_after_cap_seconds(86_400) — the row-driven retry policy.
Uniqueness (verified against deps/oban 2.23.1, ADR-0007):
fields: [:args], keys: [:endpoint_id, :event_uuid], period: :infinity, states: :all — the defaults (60s / :successful) would re-admit a
duplicate trigger after success or window expiry; both are overridden.
A uniqueness conflict is {:ok, %Oban.Job{conflict?: true}} — the
generated enqueue/2 maps it to :ok (a conflict IS dedup success).
On multitenant deliveries the enqueue also serializes the row's tenant
into job args (the attribute value INVERTED through the resource's
tenant_from_attribute, so Delivery.run/2's forward
parse_attribute round-trips for non-identity parsers too); the
uniqueness keys stay the pair (endpoint PKs are globally unique, and
Oban's keys: containment is indifferent to the extra arg). The
tenant must round-trip Oban's JSON encoding — string tenants (uuids,
slugs) are the supported shape; adopters with a custom parse_attribute
pair it with tenant_from_attribute (the default inverse is identity).