A lazy, watermark-driven commit indexer that extracts each commit ONCE
into the graf .graf.idx LSM family, so first-touch/mtime queries run
index-only (no keeper inflate). Record set: p1 parentage, p2 commit->tree,
p3 LAZY tree-child edges (/todo/BLAME/BLAME-002 shape, new-node-only),
p4 commit-ts. First-touch = a p1 walk with p2/p3 descent-by-name, ts read
from p4 (NOT a seek). Unifies today's three independent DAG walkers:
/todo/LIST/LIST-001 (shared/lastcommit.js), log:path file-history
(views/log/log.js fileHistory), and the wanted mtime-sorted todo board.
Three consumers re-derive the same per-commit tree-diff on demand, none
cached; graf.js (GRAF-001) only caches ahead/behind pairs.
shared/lastcommit.js (LIST-001) walks tip->parent, changedpaths.
changedCommits per commit — O(history x tree-diff), every call.views/log/log.js fileHistory dag.ancestors(tip) over the WHOLE
reachable set + a tree descent per commit — quadratic across paths.An index that answers "newest commit that changed path P, and its ts" with ONE seek, built incrementally, sized by diff volume not tree count.
tip -> last-indexed, extract
only new commits, append records, advance the mark.pathhash60 -> (ts, commit_h60, blob_h60) emitted
only where a leaf differs from the mainline parent (add/mod), plus a
del tombstone (blob_h60 = 0) where it disappears.del newest = gone.list:, and log:path off one family.Reuse the existing LSM/idxmaint plumbing and graf's 60-bit hashlet identity; keep the change unrelated to the ahead/behind pair cache.
shared/idxmaint.js under .lock.graf, a NEW ext
(e.g. touch.idx) or a disjoint type nibble in .graf.idx.hashlet[60] | type[4]; new types must not
collide with native 1..5 (DAG_T_*) or graf.js 0xA/0xB..graf.idx is trunk): board still blends the wtlog for
uncommitted edits on top — the index gives COMMITTED mtime only.Record set: p1 parentage, p2 commit->tree, p4 commit-ts (sentinel+DAG), p3 = TREE-CHILD edges (content-addressed, NOT change postings — AMENDED 2026-07-23, see below). Rulings pinned below.
combine(parent_tree_h60, name) -> child_h60 (the C BLAME-002
DAGChildStep shape), materialised ONCE per DISTINCT tree/blob node,
LAZILY — only for a node NEWLY appearing at ingest (dedup by node
hash). Supports descent BY KNOWN NAME, NOT enumeration.commit_h60 -> author-ts ron60, dag.commitTs);
p3 carries NO ts. First-touch of a KNOWN path = a p1 walk newest-
first, descending p2->p3 by name at C vs its mainline parent, comparing
the leaf child_h60; the commit where it changes/first-appears is the
touch, ts read from p4[C]. NOT a single seek.parent^name edge is
ABSENT in the newer tree. A p3 miss on a COVERED tree = genuine
absence; a miss on an UNCOVERED tree => keeper-inflate fallback, so
the indexer tracks the set of materialised tree hashes.lastcommit.mainlineParent rule): the first-touch walk descends the
mainline p1 line. log fileHistory's differs-from-EVERY-parent is a
distinct simplification — note it at that call site.shared/commitindex.js p3 => tree-child edges
combine(parent_tree_h60,name)->child_h60, lazy new-node-only via a
store.readTree walk; strip ts from p3; drop the blob-0 tombstone.firstTouch(path) = p1 walk + p2/p3 descent-by-name, ts from p4,
keeper-inflate fallback on a p3 miss over a covered tree set.(tree_h60,name) via p3, inflate on miss) fed to changedpaths'
existing readTreeRecursive reader slot — the ONE shared descent.
Indexer materialises p3 by its own tree walk, NOT changedCommits.todo recent, lowercase arg route in
views/todo/todo.js) onto firstTouch, wtlog blend on top.lastcommit.js (LIST-001) + log:path fileHistory onto the
index reader with keeper-inflate fallback on any coverage miss.journal).todo/) if it bites.parent^name->child) so it
FITS the .graf.idx family; a later pass lands it via idxmaint.Nothing landed yet. Suggested first commit once the indexer + board land:
GRAF-002: lazy commit indexer, change postings, mtime todo board.