All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Entries for unreleased work are not written here directly. Each issue drops a
fragment in changelog.d/; the fragments are assembled
into a version section at release. See that README for the format and for when a
change warrants an entry at all.
[0.1.0] 2026-08-22
First release: durable timers and async invoke execution for the
statifier statechart engine, on the
host's own Oban instance. This package is one implementation of the
host-facing pattern statifier specifies (its docs/durable-timers.md
recipe and ADR-0054/0055/0059/0051), not the definition of it.
Added
StatifierOban.Configcarries the host-supplied Oban instance name plus the required:timers_queueand:invoke_queue(no defaults - a missing queue is a typed error, never a silent fallback), and the:delivery/:invoke_deliveryseams. The package never starts or names an Oban instance of its own (ADR-0002).StatifierOban.Timer.schedule/3consumes a%Statifier.Effect.SendDelayed{}into one Oban job on the host's instance, unique on the{scope, ordinal}dedup key across every job state, with the fire time computed at insert from the relativedelay_ms. Onlynil-target sends are schedulable (st-ADR-0055); a duplicate insert is a conflict no-op.StatifierOban.Timer.cancel/3consumes a%Statifier.Effect.Cancel{}into cancellation of every timer job stored under the{scope, send_id}cancellation key - several jobs may match, per spec 6.3. A cancel matching nothing returns{:ok, 0}, a no-op rather than an error. A cancel racing execution resolves to whichever transition commits first: a job already in a terminal state keeps its outcome and is not counted.- Fired timer jobs deliver:
StatifierOban.Timer.Workerfeeds the stored event back through aStatifierOban.Timer.Deliverymodule, behind the run-liveness check st-ADR-0054 decision 4 requires - a run that is not live discards the event (spec 6.2), recorded on the cancelled job as{:discarded, reason}. The default (StatifierOban.Timer.Delivery.Session, configurable viaStatifierOban.Config's:deliveryoption) checks a liveStatifier.Sessionin two steps - registry lookup, thenstatus/1- so a halted-but-alive session discards rather than queueing. - An Oban-backed invoke handler base:
use StatifierOban.Invoke.Handlerimplements statifier'sStatifier.Invoke.Handlerbehaviour (st-ADR-0051) with pure planning callbacks whoseperform/2inserts oneStatifierOban.Invoke.Workerjob - unique on{scope, invoke_id}over every state, so an at-least-once replay conflicts instead of duplicating - into the host's Oban instance. Theuse-ing module suppliesconfig/0andrun/1; the worker runsrun/1inside the job and delivers{:ok, donedata}back into the run asdone.invoke.<invoke_id>(the session runs<finalize>off the arriving event). Exiting the invoking state cancels the stored job through the same handler. StatifierOban.Invoke.Delivery, the seam a completed invoke'sdone.invokegoes back through - the same run-liveness shape asStatifierOban.Timer.Delivery: a completed invoke against a dead or halted run is discarded the way a fired timer is, recorded on the cancelled job as{:discarded, reason}. The default (StatifierOban.Invoke.Delivery.Session) delivers throughStatifier.Session.done_invocation/3behind the two-step liveness check.