blame's path descent must inflate keeper objects because the DAG index records only (COMMIT,commit_h)→(TREE,tree_h) root-tree edges and (COMMIT,commit_h)→(COMMIT,parent_h) parent edges (graf/DAG.c:847-863); the DOG_OBJ_TREE/DOG_OBJ_BLOB cases are explicit no-ops (graf/DAG.c:869-876, graf/INDEX.c:16-18). The graf/DAG.h header's "materialising every tree entry" claim is stale. Adding (tree,name)→child records lets graf descend a path via in-memory binary searches with zero keeper inflate, collapsing blame's descent to O(nord·depth) lookups + a blob fetch only on change. This is the structural complement to the keeper-side win in BLAME-001. See Plan.
The index cannot descend a path; every blame commit re-inflates trees from keeper.
(COMMIT,*)-keyed (graf/DAG.c:847); no (TREE,*) key exists; tree/blob ingest is a no-op (:869).GRAFTreeStep→KEEPGetExact), the cost BLAME-001 measured.None hard. Reindex is required to populate the records; old stores stay correct (descent falls back to keeper on index miss).
Encode (tree,name)→child as ordinary wh128 records, emit them once per distinct tree at ingest, descend index-first with keeper fallback.
DAGPack(DAG_T_TREE, DOGChildPathHash(name, tree_h60)), val=(child_type, child_h60); DOGChildPathHash (dog/DOG.h, rapidhash seeded by parent) already exists.DAGRange) and survived by falling back to keeper for that one step — never silent.GITu8sDrainTree, emit child edges; dedup with a per-run seen-tree set on dag_ingest so each distinct tree is walked once.GRAFTreeStepIndexed/GRAFPathDescendIndexed in graf/BLOB.c, threading hashlets; fall back to the existing keeper descent on any miss (legacy/un-indexed stores).DAGAllCommits graf/DAG.c:344 even skips "(TREE,*)" edges); graf index rebuild populates them.graf status entry count.None yet.