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-09-19
Feature release, and the first: the Broadway front and the binding, addressing and delivery layer that routes external events to durable statifier executions, creating them when absent. A host gets bindings over predicator programs, the (scope, document, key) address table, get-or-create-and-deliver in one transaction under three create modes, dedupe with a horizon, the recorded outcome vocabulary, a resolver behaviour for the chart a new execution starts on, and two reapers it schedules itself.
Added
StatifierRouter.Binding:new/1validates a binding and compiles itsmatchandkeyprograms once,match/2andkey/2evaluate them over a normalized event, andproject/2builds the delivered event's data from the binding's field paths.StatifierRouter.Migrations:up/1anddown/1create and drop the address table, the dedupe table and the routing ledger from a host's one-line delegating migration, withfrom:,version:,table_prefix:andprefix:options.StatifierRouter.Config:new/1resolves the host's repo, table prefix and Postgres schema, andtable/2,put_meta/2andqueryable/2point theStatifierRouter.Schemamodules at the configured tables.StatifierRouter.Schema.Address,StatifierRouter.Schema.DedupeandStatifierRouter.Schema.Ledger: Ecto schemas over the three tables.StatifierRouter.route/3routes one event through the configured bindings and returns one outcome per enabled binding for the event's source, in configuration order, writing a routing ledger row for each key_refused and reporting each no_match as the telemetry event[:statifier_router, :route, :no_match].StatifierRouter.Config.new/1takes:bindings, built throughStatifierRouter.Binding.new/1with a duplicate binding id refused, and an optional:deliverymodule thatroute/3hands each delivery to, defaulting toStatifierRouter.Delivery.StatifierRouter.Delivery, the default delivery module: for a binding whosecreateis:if_absent, it gets or creates the execution an address names and steps the event into it in one transaction on the host's repo, and returns{:created_and_delivered, binding_id, execution_id},{:delivered, binding_id, execution_id}or{:dropped, binding_id, :finished}.StatifierRouter.Config.new/1takes:store,:executor,:resolverand:chart_resolver, the four optionsStatifierRouter.Deliveryrequires.StatifierRouter.Broadway, the Broadway front: a pipeline the host starts in its own supervision tree with any producer, which hands each message toStatifierRouter.route/3, partitions each message by the address of the first enabledorder: :by_keybinding for its source whosematchholds and whosekeyresolves, or by its message id when no such binding addresses it, and fails rather than acknowledges a message whose routing returns an error or raises.StatifierRouter.Dedupe.claim/4, called first in everyStatifierRouter.Deliverytransaction: a message a binding already handled within its dedupe horizon is{:duplicate, binding_id}, recorded on the ledger and delivered nowhere; an expired dedupe row counts as absent.StatifierRouter.Dedupe.reap/2, a plain function the host schedules, deletes expired dedupe rows and returns{:ok, count}.StatifierRouter.route/3returns{:error, :no_message_id}for an event whosemessage_idisnilor empty, before any binding is evaluated.StatifierRouter.Addresses.reap/2, a plain function the host schedules, stamps address rows whose execution it first sees finished and deletes those whose longest enabled binding horizon has elapsed; one call examines at most:limitrows and returns anextcursor.StatifierRouter.Deliverydelivers for:neverbindings, returning{:dropped, binding_id, :no_execution}when the address has no row, and for:always_newbindings, creating one execution per delivery with no address row.StatifierRouter.Resolver, the behaviour a host implements to name the chart a new execution of a document starts on;StatifierRouter.Config.new/1accepts as:resolvera module implementing it or an arity-2 fun.StatifierRouter.Resolver.Static.new/1, a resolver over a map from{scope, document}to a compiled machine, for tests and for charts compiled at boot.StatifierRouter.route/3returns{:error, {:unresolved_document, document, reason}}when the resolver answers{:error, reason}for a document: the delivery's transaction rolls back, so nothing is created and no row is written.