You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: explain the dependency-graph mechanism in reactive-recalc
Adds a "How the dependency graph is recorded and read" section answering the
questions that recur about the reactive system:
- the three data-sourcing functions (read_project_file / tracked_ / pinned_
expr_from_alias) and the edge each records, as a table
- where the graph lives (manifest.parents / manifest.sources, now defined
before first use)
- roots/leaves direction (data-flow convention, not build-system)
- cheap vs expensive parents = non-materialized vs materialized, and why a
cheap parent's sources land in the child's manifest.sources
- build-time side-channel capture, why it's tallyman not xorq, and why the
#73/#74 expression flattening makes the edge unrecoverable from xorq's output
- dependents.py as the single read seam
Also fixes errors the rename's blanket from_catalog->tracked_expr_from_alias
substitution introduced: the hash-pin paths now correctly say
pinned_expr_from_alias (tracked_expr_from_alias rejects hashes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@@ -73,17 +73,31 @@ tool: it operates on the session's active project (set by `project_switch`); the
73
73
optional `project=` kwarg on `read_project_file`/`tracked_expr_from_alias` defaults to that same
74
74
active project, so recipes omit it.
75
75
76
+
A recipe pulls in data through one of three functions in `tallyman_xorq.io`, and
77
+
which one you call is what records the dependency edge:
78
+
79
+
| Function | Reads | Records | Goes stale when |
80
+
|---|---|---|---|
81
+
|`read_project_file("orders.parquet")`| a raw file under `<project>/data/`| a **source** leaf (`rel_path → digest`) | the file's bytes change on disk |
82
+
|`tracked_expr_from_alias("orders")`| a catalog entry, by **alias**| a **parent** edge, `follow=True`| the alias advances to a new head |
83
+
|`pinned_expr_from_alias(...)`| a catalog entry, by alias **or** hash | a **parent** edge, `follow=False`| never (it pinned that exact revision) |
84
+
76
85
The follow relationship is the whole game:
77
86
78
-
-`tracked_expr_from_alias("orders")` — an alias *name* — records the edge as **follow=True**.
79
-
The recipe means "whatever `orders` is now," and at build time it resolves to
80
-
`orders`'s current head.
81
-
-`tracked_expr_from_alias("<hash>")` — a literal hash — records the edge as
82
-
**follow=False**: a pin to that exact revision. A pinned child never goes stale
83
-
when its parent advances, and recalc deliberately leaves it alone.
87
+
-`tracked_expr_from_alias("orders")` means "whatever `orders` is now." It accepts
88
+
an alias only — pass it a hash and it raises, because a hash has no head to
89
+
follow — resolves to the alias's current head at build time, and records
90
+
**follow=True**. The child goes stale and recomputes when `orders` advances.
91
+
This is normal chaining.
92
+
-`pinned_expr_from_alias("orders")` or `pinned_expr_from_alias("<hash>")` is the
93
+
deliberate opt-out. It accepts an alias or a hash, records **follow=False**, and
94
+
the child stays on that exact revision: it never goes stale on the alias axis,
95
+
and recalc finds it but leaves it alone.
96
+
-`read_project_file` is the root of every chain: a raw file with no catalog
97
+
identity, the leaf the graph bottoms out in.
84
98
85
99
So after these three calls: `orders → by_region → top_regions`, each aliased at
86
-
version 1, each following its parent by name.
100
+
version 1, each following its parent by name (`tracked_expr_from_alias`).
87
101
88
102
## Revising an alias and recomputing its dependents
89
103
@@ -189,7 +203,7 @@ Three edges make that precise:
189
203
intermediate node referenced only by a hash pin — produces a new hash and a
190
204
`remap` entry but **zero** alias revisions. A head carrying two aliases advances
191
205
both.
192
-
- A **hash-pinned** child (`tracked_expr_from_alias("<hash>")`) re-resolves to the same
206
+
- A **hash-pinned** child (`pinned_expr_from_alias("<hash>")`) re-resolves to the same
193
207
parent and is a `noop`, so it neither rebuilds nor advances its alias.
194
208
195
209
This per-alias revision is distinct from the catalog-level checkpoint. The alias
@@ -198,6 +212,120 @@ git revision that commits the whole walk (see *One revision, undoable atomically
198
212
below). One recalc can append many alias revisions and still take exactly one
199
213
checkpoint.
200
214
215
+
## How the dependency graph is recorded and read
216
+
217
+
Staleness, the cone, and the cascade all run on a dependency graph the reactive
218
+
system never introspects live. The graph is **recorded into each entry's manifest
219
+
at build time, then read back by scanning those manifests**. There is no separate
220
+
graph database and no global index; the adjacency is rebuilt from the manifests on
221
+
each query (cheap for a notebook-sized catalog — one small JSON read per entry).
222
+
223
+
### Where the edges live
224
+
225
+
Each entry has a `manifest.json` with two fields that carry the graph:
226
+
227
+
-**`manifest.parents`** — the resolved cross-entry edges, a list of
228
+
`{hash, ref, follow}`. `hash` is the parent's build-time content hash, `ref` the
229
+
original argument (alias or hash), `follow` its read-intent. Empty for a root (an
230
+
entry that reads no other entry).
231
+
-**`manifest.sources`** — the raw-file leaves, `{rel_path: digest}`: the project
232
+
files the recipe read via `read_project_file`, each with the content digest it
233
+
was built against. `None` when the entry was built under `off` identity mode (so
234
+
the source axis can't be evaluated); `{}` when the recipe read no raw files.
0 commit comments