# reactive_dag v0.16.0 - Table of Contents

> Reactive DAG engine as an Ash extension: dirty frontier, depth-ordered incremental drain, change propagation, and a shared coordination-tuple spine. Author nodes as Ash resources with reduce/join/aggregate combinators; the domain plugs in at three seams.

## Pages

- [reactive_dag](readme.md)

- Guides
  - [Getting started](getting-started.md)
  - [Authoring nodes](authoring-nodes.md)
  - [Sources and scanning](sources.md)
  - [Attestations](attestations.md)
  - [The seams](seams.md)

- Design
  - [ADR-001 — Extract the reactive-DAG engine as reactive_dag](adr-001-reactive-dag-library.md)

## Modules

- [ReactiveDag.Attestation.Record](ReactiveDag.Attestation.Record.md): An **Ash resource extension** for the attestation record store — the
Ash-idiomatic storage pattern (as `ash_authentication`'s token resource): the
HOST defines the resource (choosing repo, table, domain, policies), this
extension stamps the required shape onto it, and the library reaches it
through `config :reactive_dag, attestation_resource: MyApp.Attestation.Record`.
- [ReactiveDag.Attestation.Record.SignChange](ReactiveDag.Attestation.Record.SignChange.md): The `:sign` action's change: applies the resource's `who_from_actor` (an
actor present → `who` forced from it), validates the polarity, and errors
a `:reject` with a blank `reason` — a rejection asserts the data is WRONG,
and a bare "no" leaves whoever must act with nothing to fix and an auditor
with an unexplained refusal. (`:withdraw` — "I no longer vouch" — asserts
nothing about the data, so its reason stays optional.)

- [ReactiveDag.Drain.Report](ReactiveDag.Drain.Report.md): What a drain ACTUALLY did — the processing trace, returned by
`ReactiveDag.Drain.run/2`.
- [ReactiveDag.Dsl](ReactiveDag.Dsl.md): The DSL compile pipeline over the flat `Cell` IR — the resolve→lower→validate
machinery both host DSLs share, parameterized by app hooks so each keeps its
own domain vocabulary.
- [ReactiveDag.Frontier](ReactiveDag.Frontier.md): The dirty frontier, owned by the library and backed by the `reactive_dag_dirty`
table (created by `ReactiveDag.Migration`). The host is an Ash/AshPostgres app,
so we go through its repo with raw SQL — values always parameterized; the
table name (the one identifier SQL cannot parameterize) comes from config and
is validated against an identifier grammar at read time, so a typo fails
loudly instead of as a syntax error deep in a query. Claim-as-delete is a raw
`DELETE … RETURNING` that Ash actions don't express cleanly.
- [ReactiveDag.Migration](ReactiveDag.Migration.md): The library-owned DDL, callable from a host migration — the dirty-frontier
table `ReactiveDag.Frontier` reads and writes (coalesced by `(cell_id, key)`;
claim is a `DELETE … RETURNING`)
- [ReactiveDag.Node.Aggregate](ReactiveDag.Node.Aggregate.md): A PURE-ASH-QUERY reduce: the datastore does the grouping via a RELATIONSHIP
aggregate. The node's own resource is the group's resource — ONE row per group
— and `over` names its `has_many` to the rows being aggregated. The library loads
the aggregates in ONE Ash query — Postgres computes the `GROUP BY` — and each
parent row's aggregate values are its payload. No rows cross into the BEAM; no
`into`/`read`/`upsert` (contrast the in-BEAM `reduce`, which loads every row).
- [ReactiveDag.Node.Attested](ReactiveDag.Node.Attested.md): The ATTESTED VIEW combinator: this node is the derived cell whose rows are
`over`'s rows joined against currently-applying attestation records under a
named requirement — both cells exist in the graph (the raw list AND the
signed list), and a consumer picks per edge. `ref :x, gate: :req` is sugar
that interposes an anonymous cell of exactly this shape.
- [ReactiveDag.Node.Compose](ReactiveDag.Node.Compose.md): An anonymous nested op-expression leg: composes inline as an intermediate
cell (its `as` id, or a positional id derived from the parent). Its own legs
are `ref`/`compose`, so the algebra reads as an expression tree.

- [ReactiveDag.Node.Compute](ReactiveDag.Node.Compute.md): The ESCAPE HATCH: declare an arbitrary recompute MODULE (a `ReactiveDag.Op`)
for a node whose computation the `reduce`/`join` combinators can't express —
an LLM call, a PDF/Tigris fetch, a bespoke multi-input recompute. `compute
MyApp.EventsExtract` sits in the block alongside the combinators, mirroring
Ash's `calculate :x, :type, MyModule` (the arbitrary case is an entity too,
not a schema key beside the declarative ones).

- [ReactiveDag.Node.Join](ReactiveDag.Node.Join.md): A declarative JOIN: read ONE input's payload, index it into a LEFT and a
RIGHT side (each a `%{join_key => item}` built from a per-side key fn), then
emit one row per left key joined to its right item (right may be absent). The
common declared-vs-observed reconcile/variance shape — the author writes the
two side keys + the join row, not the read/write/changed plumbing.
- [ReactiveDag.Node.KeyRule](ReactiveDag.Node.KeyRule.md): A GENERIC `ReactiveDag.KeyRule` for graphs declared with `ReactiveDag.Node`.
`Node` records each node's `key_rule` (`:identity | :all`) in `cell.meta`;
this reads it
- [ReactiveDag.Node.Recompute](ReactiveDag.Node.Recompute.md): A GENERIC `ReactiveDag.RecomputeStrategy` for graphs declared with
`ReactiveDag.Node`. Because `Node` standardizes where a cell's op module lives
— `cell.meta.compute`, a `ReactiveDag.Op` — the dispatch is uniform and the
host no longer hand-writes it
- [ReactiveDag.Node.Reduce](ReactiveDag.Node.Reduce.md): A declarative REDUCE (fold): read an input node's payload, group it, and
reduce each group to one output row — the common map/fold shape, so the
author writes the grouping + reduction, not the read/write/changed plumbing.
Anything the combinator can't express (an LLM call, an external fetch, a
bespoke join) uses the `compute:` module escape hatch instead.

- [ReactiveDag.Node.Ref](ReactiveDag.Node.Ref.md): A by-name input edge to another named node (`ref :id`). The general form —
nestable inside `compose`. The flat `depends_on: [:a, :b]` schema key is sugar
that lowers to one `%Ref{}` per id.
- [ReactiveDag.Node.Reference](ReactiveDag.Node.Reference.md): A by-name REFERENCE input edge: the node READS the target as context but is NOT
recomputed when the target changes. Still a real input (validated, ordered by
depth so the target settles first, read at recompute) — it just doesn't
propagate. For a node whose recompute is expensive/non-deterministic and
consults mutable reference data it shouldn't be re-triggered by (an LLM step
that looks up a human-curated people/positions table). Contrast `ref`, which
dirties this node on change.

- [ReactiveDag.Op](ReactiveDag.Op.md): The behaviour a node's `compute` module implements — the recompute for ONE op,
the per-cell unit of work.
- [ReactiveDag.SetOp](ReactiveDag.SetOp.md): A generic `RecomputeStrategy` for SET-BASED ops — the layering for hosts whose
recompute is set algebra over the coordination tuple (the compliance portal),
the counterpart to `ReactiveDag.Node.Recompute` for per-key/BEAM hosts.
- [ReactiveDag.Tuple](ReactiveDag.Tuple.md): The shared COORDINATION tuple — the reactive layer's projection of a cell into
a thin `(cell_id, key, status, freshness)` row. A cell IS its set of these
rows; a parent reads a child's set by `(cell_id, key)`.
- [ReactiveDag.Tuple.Writer](ReactiveDag.Tuple.Writer.md): The DEFAULT `ReactiveDag.CoordinationWriter` — spine-only, over the configured
tuple table via `ReactiveDag.Tuple`. Suitable for a host with no extension
columns on its coordination tuple. A host with extensions (cascade's
`source_ref`/`tombstoned_at`, the portal's `strength`) configures its own
writer that does the spine + extension write in one atomic upsert.
- [ReactiveDag.Verdict](ReactiveDag.Verdict.md): The generic READ layer over the coordination-tuple spine — a cell's live verdict
and its failing-sample, rolled up from the tuple `status` histogram. This is the
engine piece a host used to hand-write (the portal's `ModelEval.Verdict`); it's
domain-neutral, so it lives here and the host keeps only its own addressing
sugar (`for_guarantee`, `for_control`, typed `detail` joins) on top.

- Authoring
  - [ReactiveDag.Node](ReactiveDag.Node.md): An **Ash resource extension** that makes a resource a node in a reactive DAG.
The resource IS the node **and** its own payload table: its `reactive` block
defines the computation, its `attributes` are the rows the node materializes.
This is the intended shape — one resource, both roles.
  - [ReactiveDag.Node.Payload](ReactiveDag.Node.Payload.md): Closes the payload loop for a resource-backed node: writes a combinator's output
row into the node's OWN resource (`cell.meta.resource`), keyed by the cell key.
  - [ReactiveDag.Node.Recompute.Aggregate](ReactiveDag.Node.Recompute.Aggregate.md): Runs a pure-Ash-query `aggregate` node: the datastore groups + aggregates the
node's `over` relationship in ONE query (a relationship aggregate — Postgres does
the `GROUP BY`), and each parent row's aggregate values become its payload.

- Attestation
  - [ReactiveDag.Attestation](ReactiveDag.Attestation.md): The attestation RECORD STORE — human assertions about a cell's data, as
immutable append-only history (the host project's ADR-002).
  - [ReactiveDag.Attestation.Basis](ReactiveDag.Attestation.Basis.md): The content-addressed BASIS of an attestation — a digest of what the scope
selected at signing time (ADR-002 decision 4, in the host's docs).
  - [ReactiveDag.Attestation.Evaluation](ReactiveDag.Attestation.Evaluation.md): The read-time force of attestation records: stance ⨝ basis ⨝ eligibility ⨝
tolerance → an ADMISSION per scope. Pure — takes everything as data (raw
rows, stances, eligibility keys, a requirement, `now`), so the whole
semantics is testable without a database; `ReactiveDag.Attestation.Op` is the
thin DB glue around it.
  - [ReactiveDag.Attestation.Op](ReactiveDag.Attestation.Op.md): The recompute of an ATTESTED cell — the derived view an `attested` combinator
or a `gate:`d edge lowers to. Thin DB glue: read the three inputs, run the
pure `ReactiveDag.Attestation.Evaluation`, write ordinary spine rows.
  - [ReactiveDag.Attestation.Requirement](ReactiveDag.Attestation.Requirement.md): A named attestation REQUIREMENT — the policy for one kind of sign-off,
declared once on the node that owns the raw data (`attestation :name do … end`
in the `reactive` block) and consumed by name from an `attested` combinator or
a `gate:` on an edge.
  - [ReactiveDag.Attestation.Scope](ReactiveDag.Attestation.Scope.md): WHAT an attestation is about — one row, or the set a filter selects.

- Seams
  - [ReactiveDag.CoordinationWriter](ReactiveDag.CoordinationWriter.md): The seam for WRITING a cell's coordination tuples — the third seam, alongside
`RecomputeStrategy` (how a cell recomputes) and `KeyRule` (how a change
propagates). An op, mid-recompute, records which of its keys are present /
gone; this behaviour is where those writes land.
  - [ReactiveDag.KeyRule](ReactiveDag.KeyRule.md): How a change to a child propagates to a parent — the op-aware propagation
seam. When `changed` keys of `child` feed `parent`, the rule decides which of
the parent's keys become dirty
  - [ReactiveDag.RecomputeStrategy](ReactiveDag.RecomputeStrategy.md): The seam where the host app's OP ALGEBRA + RECOMPUTE MODEL plug in.
  - [ReactiveDag.Source](ReactiveDag.Source.md): A **scanner** — the fourth seam, alongside `ReactiveDag.RecomputeStrategy`,
`ReactiveDag.KeyRule`, and `ReactiveDag.CoordinationWriter`.

- Engine
  - [ReactiveDag.Cell](ReactiveDag.Cell.md): One node in the DAG — the domain-neutral IR both host apps compile down to.
  - [ReactiveDag.Drain](ReactiveDag.Drain.md): The reactive propagation loop — the heart of the substrate, shared by both
hosts.
  - [ReactiveDag.Graph](ReactiveDag.Graph.md): Pure DAG construction: a list of `ReactiveDag.Cell` → a `ReactiveDag.Plan`.
  - [ReactiveDag.Lowering](ReactiveDag.Lowering.md): Shared machinery for lowering a NESTED op-expression into a flat cell list —
the recursion both host DSLs independently grew (cascade's `Lower.resolve_legs`,
the portal's `Graph.build_node`). Same algorithm: walk an op's legs, recurse
into each, a `ref` resolves to an existing cell-id (no new cell), a nested op
becomes an intermediate cell whose inputs are the recursed leg ids.
  - [ReactiveDag.Plan](ReactiveDag.Plan.md): The compiled DAG plan — pure data the drain executes. Decoupled from any DSL:
a host app lowers its declarations into a `Cell` list and `Graph.build/1`
produces this. The drain only ever sees the Plan.

- Exceptions
  - [ReactiveDag.Drain.RunawayError](ReactiveDag.Drain.RunawayError.md): The drain exceeded its pass budget — likely a cycle, or a recompute that
keeps re-dirtying its own inputs. `:report` carries the PARTIAL trace up to
the abort: `report.steps`' tail shows exactly which cells keep triggering
each other, which is the diagnostic for the loop this error suspects.

