Files
graphify/tests
Kaushik Samadder c5d432710f fix(cache): re-anchor cached ids so a warm hit can't replay another root (#2257)
Extractors mint node ids from the path STRING they are handed
(_make_id(str(path)), _file_node_id(path)), so an AST cache entry written
under root A embeds A's slug in every id and edge endpoint. save_cached
relativized only source_file, never the ids. Because extract()'s
id-remap / final-canonicalization passes key their rewrites off the
CURRENT run's paths, an A-derived id matches no key on a warm hit under
root B (a clone, a moved checkout, a second mount) and the stale
machine slug survives into graph.json. Distinct from #2231/#2243, which
fix producers on a cold run, and from #2199 (stat-index portability).

Entries are now stored root-anchored and re-anchored on read, the same
store-portable/re-anchor-on-load contract source_file (#777) and the
stat index (#2199) already use: _relativize_ids_in replaces the root's
contribution with a $graphify-root$ marker on write, _absolutize_ids_in
restores what the current run's extractor would mint on read. That is
the pre-remap form every downstream pass in extract() expects, so a
replay reproduces a cold run exactly and no other pass changed.

The anchor is derived per entry rather than assumed equal to the scan
root (normalize_id distributes over path joins), so a symlinked root or
relative inputs decompose exactly; only absolute root spellings may
anchor, or a relative root ("src") would rewrite an already-canonical
src_utils_foo into an absolute-derived id on the semantic path. The
walk covers the whole payload rather than a bucket list, since the id
form is self-identifying: that also reaches raw_calls[].caller_nid,
swift_extensions[].nid, edges[].target_file, bash_sources[].source_file
and *_type_table.path, the last three being resolution inputs that
would otherwise still point at root A. save_cached's deepcopy is now
unconditional; the old truthiness gate skipped it for a payload whose
only content lives outside nodes/edges, which would have let the
transform mutate the caller's dict and break cold-run remapping.

Pre-fix entries carry no marker and their content hash never changes,
so they cannot self-heal; they are swept when the release bumps the
version, since AST entries live under cache/ast/v{version}/.

Tests: extract a python/C/bash/markdown corpus under root A, copy the
tree and graphify-out to root B, extract under B on the warm cache, and
assert the run is genuinely warm (zero extractor calls), that no node id
or edge endpoint carries A's slug, that the on-disk entries hold neither
A's slug nor an absolute path, that a cold run still yields canonical
ids, and that warm and cold match exactly. The fixture avoids JS/TS on
purpose: those suffixes bypass the cache, which would make the warm
assertions vacuous.
2026-07-29 17:00:15 +01:00
..