fix(cache): preserve symlink path identities - #2868
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Keys the extraction-cache stat-index salt on the walked path relative to root instead of the resolved path, so symlink aliases of one target get distinct portable entries while out-of-root targets keep the resolved-path fallback. Threads the walked identity through save_semantic_cache (source_path, group_skipped, allowed/partial sets, and per-group save/load) and adds _semantic_entry_matches_path in load_cached to reject legacy semantic entries written under a colliding symlink alias, letting the next extraction self-heal.
Worth a look
- Resolved allowed_source_files are no longer accepted for symlink-root semantic saves —
graphify/cache.py:1406· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1282 functions depend on the 221 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 468 callers, 41 callees - new:
_rebuild_code()— 98 callers, 51 callees - new:
detect()— 107 callers, 15 callees - new:
save_semantic_cache()— 53 callers, 11 callees - new:
load_cached()— 40 callers, 7 callees - new:
extract_corpus_parallel()— 26 callers, 10 callees - new:
file_hash()— 43 callers, 6 callees - new:
dispatch_command()— 2 callers, 117 callees - …and 17 more — each is listed as a finding
Verification — 1282 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 802 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify file\_hash.
The verifier did not have enough to check file\_hash, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify load\_cached.
The verifier did not have enough to check load\_cached, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify save\_semantic\_cache.
The verifier did not have enough to check save\_semantic\_cache, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 25 more finding(s) on lines outside this diff (see the check run).
4e00fcc to
0671165
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Namespaces the AST cache directory by a new _AST_CACHE_SCHEMA (v{version}-s{schema}) so key-semantics changes evict stale entries independently of the package version. Reworks file_hash to salt on the walked path relative to root instead of the resolved path, so distinct symlink aliases no longer share an extraction entry, and threads walked-vs-resolved root handling through save_semantic_cache's source-path normalization. Adds _semantic_entry_matches_path and wires it into load_cached to reject legacy semantic entries whose source_file doesn't match the requested walked path, letting the next extraction self-heal.
Worth a look
- save loop still gates on is_file() while load self-heal uses walked identity, causing symlink entries to be dropped —
graphify/cache.py:1497· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1288 functions depend on the 227 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 468 callers, 41 callees - new:
_rebuild_code()— 98 callers, 51 callees - new:
detect()— 107 callers, 15 callees - new:
save_semantic_cache()— 55 callers, 11 callees - new:
load_cached()— 43 callers, 7 callees - new:
file_hash()— 44 callers, 6 callees - new:
extract_corpus_parallel()— 26 callers, 10 callees - new:
dispatch_command()— 2 callers, 117 callees - …and 17 more — each is listed as a finding
Verification — 1288 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 808 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify cache\_dir.
The verifier did not have enough to check cache\_dir, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify file\_hash.
The verifier did not have enough to check file\_hash, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify load\_cached.
The verifier did not have enough to check load\_cached, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify save\_semantic\_cache.
The verifier did not have enough to check save\_semantic\_cache, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 25 more finding(s) on lines outside this diff (see the check run).
|
Shipped in v0.9.47 via authorship-preserving cherry-pick. Thanks @oleksii-tumanov! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.47 |
Summary
Fixes #2832
Result
Before this change, a cold extraction kept
real.pyandsub/link.py, but a warm run assigned all four nodes to the link. With this change, cold and warm runs retain both source paths, and the warm regression records zero extractor calls. Resolved policy paths under a symlinked scan root map to the matching walked file without authorizing a leaf alias.Testing
uv run --frozen pytest -q tests/test_cache.py tests/test_stat_index_portability.py tests/test_word_count_cache.py tests/test_extract_cache_location.py tests/test_extract.py --tb=short(275 passed, 4 skipped)uv run --frozen pytest tests/ -q --tb=short --ignore=tests/test_ollama_retry_cap.py(4578 passed, 73 skipped; optionalopenaidependency unavailable locally)uv run --frozen ruff check graphify/cache.py tests/test_cache.pyuv run --frozen python -m tools.skillgen --check(134 artifacts matched)graphify update .