ORIGINATES A CASCADE from a written record, so what depends on it recomputes.
Wired automatically by dirties_on and augmented_by — hosts do not add it.
Why a change, not a notifier
The obvious tool is an Ash.Notifier, and it is the wrong one: Ash dispatches
notifications after the transaction commits. A cascade enqueued there is
not covered by the write's transaction, so a crash between commit and dispatch
loses it — and a lost cascade is silent staleness, the failure mode this
feature exists to remove.
An after_action hook runs inside the transaction (its counterpart is
named before_transaction precisely because that one does not). The enqueue
therefore joins the host's transaction:
- a rolled-back write enqueues nothing, and
- a committed write always enqueues exactly one cascade.
Both halves matter. Neither is true of a notifier.
The cascade is the JOB'S transaction, not the writer's
What commits with the write is one INSERT — an Oban job naming what changed. The walk itself happens later, in its own transaction.
That is deliberate. A write that transitively touches fourteen cells should not hold a user's request open for all of them, and running the walk inline would put the host's transaction at risk of exactly the problem this design removes: a long-held connection. The visible consequence is that a host reading a derived table immediately after its own write sees the old value — which was equally true of the drain that preceded this.
Cost
One INSERT per write, inside a transaction the host already holds open.
Oban's uniqueness collapses a burst of writes to the same row into one
pending cascade. A host writing at a rate where even that contends should
feed the leaf from a ReactiveDag.Source poll instead — see the sources
guide.