The Oban worker a %Statifier.Effect.SendDelayed{} becomes.
Uniqueness is the whole point of this module: jobs are unique on the
{scope, ordinal} dedup pair (StatifierOban.Timer.DedupKey), read off
the args at the top level. Re-executing the same drive after a crash
rebuilds a byte-identical pair - both components are pure fold state
(st-ADR-0059) - so the duplicate insert conflicts with the stored job
and becomes a no-op. That conflict, not any check in host code, is what
makes at-least-once redelivery safe.
The unique window is every state over an infinite period: a timer that
already completed, was cancelled (sob-2hx.4's path), or was discarded
must still swallow a replayed insert, because the replay is the same
scheduling decision, not a new one. The unique fields exclude :queue
on purpose - a host moving its timers queue must not turn a replay into
a second job - and exclude the meta the delivery module rides on, so a
host reconfiguring its delivery does not turn a replay into a second
job either.
perform/1 decodes the stored effect and hands it to the job's
StatifierOban.Timer.Delivery module (from the meta written at
schedule time; absent meta falls back to the documented default,
StatifierOban.Timer.Delivery.Session), which owes the execution-liveness
check st-ADR-0054 decision 4 requires before any fired event is fed
back. The outcomes map onto Oban states so each is observable on the
job row:
- delivered -> the job completes (
:ok); - the execution is not live -> the job cancels with
{:discarded, reason}recorded, the spec 6.2 discard as data; - the execution is not finished but cannot take the event now -> the
delivery answers
{:snooze, seconds},secondsa positive integer, and the job snoozes: Oban reschedules it at leastsecondslater without spending a retry, and it stays a pending timer (ADR-0010). A snooze-shaped answer that is not that - zero, a negative count, an Oban period tuple - matches no clause and retries like a raise; - an undecodable row cancels with
{:undecodable, reason}- no number of retries makes a corrupt row decodable; - a codec named on the row that this node cannot resolve, or one that
cannot decode the row right now, returns
{:error, {:invalid_codec, _}}or{:error, {:codec_failed, _}}and retries - an environment fact, fixable by a deploy or by making the key available, not a fact about the row; - a delivery module that cannot be resolved returns
{:error, {:invalid_delivery, _}}and retries - an environment fact about the host's code, fixable by a deploy, unlike the row facts above. A raise or exit out of the delivery module retries the same way, per the behaviour's contract.
timeout/1 returns the job's run-time bound: the config's
:timer_timeout, written into the job's meta by
StatifierOban.Timer.schedule/3, or :infinity for a job whose row
carries none. Oban enforces it from outside the job process; an
attempt that runs past it fails with Oban.TimeoutError and retries
while attempts remain, exactly as a raise out of the delivery module
does. A fired timer has no failure door, so a timed-out last attempt
discards the job the way any other exhausted one does.