AshHooks.Worker (AshHooks v2.0.3)

Copy Markdown View Source

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
end

Consumers 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 the AshHooks.OutboundDelivery / AshHooks.Endpoint extensions.
  • :secret_resolver (required, {m, f}) — resolves an endpoint's secret reference: f(ref) :: {:ok, secret_binary} | {:error, term}. The returned value signs the Standard Webhooks envelope (its whsk_/whsec_ prefix only selects the key slot for rotation); legacy envelopes, when the signing mode uses them, are signed from the endpoint's legacy_secret_ref / legacy_previous_secret_ref references through this same resolver.
  • :tenant_aware_secrets (optional, default false) — 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-call snippet_capture: true diagnostic 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 the AshHooks.Delivery.run/2 config for a one-row diagnostic re-drive.
  • :http — the AshHooks.Http adapter (default AshHooks.Http.Bounded).
  • :http_opts — adapter options as a literal keyword list or a {module, function, args} callback resolved inside each job's monitored delivery deadline.
  • :oban — the Oban instance name (default the unnamed instance).
  • :queue (default :ash_hooks), :timeout (35,000 ms), and :max_attempts (20) — Oban Worker options. The job timeout must be greater than :attempt_timeout + :finalization_allowance.
  • :attempt_timeout (25,000 ms) and :finalization_allowance (5,000 ms) — the driver deadline and extra send-lease interval. The attempt budget includes endpoint lookup, secret resolution, signing, destination and DNS checks, HTTP, and result persistence. Keep application clocks synchronized because lease comparisons use the application clock.
  • :delivery_max_attempts (default 10), :base_backoff_seconds (2), :max_backoff_seconds (3600), :retry_after_cap_seconds (86_400) — the row-driven retry policy. For direct AshHooks.Delivery.run/2 calls, :delivery_max_attempts becomes :max_attempts; the other retry option names and defaults are unchanged. Omitted or nil retry options use these defaults. The Retry-After cap is receiver-held-state budget: a receiver returning a large Retry-After holds its delivery row and its Oban job for up to the cap per attempt (the 86,400 default = up to 24 hours per attempt on one header; a receiver honoring Retry-After deliberately asks for exactly that). Lower the cap when the chosen posture is exhaust-fast (a wedged receiver dead-letters at the ceiling instead of holding state). The cap clamps Retry-After; it does not alter the backoff ladder.

The generated enqueue uses fields: [:args] with the complete delivery key, delivery and endpoint resource names, source, route, endpoint/event, and tenant keys. Uniqueness has an infinite period over runnable states (available, scheduled, executing, and retryable). Completed, canceled, and discarded jobs therefore permit a later recovery trigger. Enqueue succeeds only after Oban returns a persisted runnable job whose identity args match the request. Oban.Basic may briefly report a uniqueness conflict without the winning job's ID while its advisory-lock peer commits; the generated path retries that result for at most twenty 5 ms waits, then returns {:error, :job_not_persisted}. Before insertion it binds a deferred route once with a compare-and-set update; a different stored route returns {:error, :dispatch_route_conflict}. 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 identity includes the serialized tenant. 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).