THE propagation rule: how a change reaches a parent, decided by what the parent
DECLARED. ReactiveDag.Drain calls this — there is nothing to configure.
Node records each node's key_rule in cell.meta; this reads it:
:all— any input change escalates to a whole-cell recompute (the drain turns this into"*"). For aggregate / cross-range (reduce) cells.:identity— a changed input key maps to the same output key (pass through). For key-local (map) cells. The default.
A recompute_by unit lowers to the :group forms below, which are richer than
either: they map a changed CHILD key to the parent UNIT it belongs to, so a
fold reprices one group rather than escalating to the whole cell.
rule/3 takes the specific child input, not just the parent — so a node whose
legs propagate differently (a change to the members leg passing keys through
while a change to the fn leg escalates) is expressible without a bespoke
module. That was the last thing a host wrote its own rule for.
Summary
Functions
rule/3 with the SNAPSHOTS the changed keys were marked with — the child rows
as they were, which ReactiveDag.Frontier captured at mark time.
Functions
@spec rule(ReactiveDag.Cell.t(), ReactiveDag.Cell.id(), [String.t()], %{ required(String.t()) => map() }) :: ReactiveDag.KeyRule.result()
rule/3 with the SNAPSHOTS the changed keys were marked with — the child rows
as they were, which ReactiveDag.Frontier captured at mark time.
A snapshot survives its row, so a :group claim stays precise in the two
cases a live lookup cannot handle:
- the row was deleted — nothing to read, but the snapshot still names the unit it belonged to;
- the row moved between units — the live row names where it landed, the snapshot names where it came from, and BOTH need repricing.
Keys with no snapshot (a source-fed leaf has no Ash row behind it) fall back
to rule/3 exactly as before, so the two paths coexist.