fix(extract): scope fresh semantic results to dispatched files (#2926) - #2928
fix(extract): scope fresh semantic results to dispatched files (#2926)#2928SinghAman21 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds scope_semantic_result in graphify/cache.py, a graph-side allowlist filter that drops nodes/edges/hyperedges attributed to files not dispatched this run (and prunes edges/hyperedges dangling to dropped node ids, sparing duplicate-attribution ids). Wires it into dispatch_command in cli.py to scope the fresh extraction result against uncached_paths before it reaches build_merge, with a printed summary of dropped items, mirroring the existing save_semantic_cache write-guard. Covers the new function and CLI path with tests in test_cache.py and test_extract_cli.py.
No blocking issues surfaced. 8 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1471 functions depend on the 384 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 474 callers, 42 callees - new:
_rebuild_code()— 98 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_semantic_cache()— 55 callers, 11 callees - new:
load_cached()— 43 callers, 7 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - new:
file_hash()— 44 callers, 6 callees - new:
dispatch_command()— 2 callers, 120 callees - …and 20 more — each is listed as a finding
Verification — 1471 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: 1155 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
· 1 grounded finding(s) anchored inline below; 27 more finding(s) on lines outside this diff (see the check run).
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds scope_semantic_result to graphify/cache.py, a graph-side scope filter mirroring save_semantic_cache's allowed_source_files write guard, and factors the shared path-matching into a new _semantic_source_matcher helper used by both. Wires it into dispatch_command in cli.py to drop (and log) fresh nodes/edges/hyperedges attributed to files not dispatched this run before they reach build_merge, plus dropping edges/hyperedges that dangle to pruned node ids. Covers the new behavior with tests in tests/test_cache.py and tests/test_extract_cli.py.
No blocking issues surfaced. 10 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1473 functions depend on the 386 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 474 callers, 42 callees - new:
_rebuild_code()— 98 callers, 50 callees - new:
detect()— 108 callers, 15 callees - new:
save_semantic_cache()— 55 callers, 9 callees - new:
load_cached()— 43 callers, 7 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - new:
file_hash()— 44 callers, 6 callees - new:
dispatch_command()— 2 callers, 120 callees - …and 20 more — each is listed as a finding
Verification — 1473 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: 1157 function(s) in the blast radius were not formally verified this run
Formal verification
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
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
· 2 grounded finding(s) anchored inline below; 26 more finding(s) on lines outside this diff (see the check run).
| return source_identity, normalize_value | ||
|
|
||
|
|
||
| def save_semantic_cache( |
There was a problem hiding this comment.
save_semantic_cache()
fans out to 9 callees (efferent coupling); 55 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return saved | ||
|
|
||
|
|
||
| def scope_semantic_result( |
There was a problem hiding this comment.
scope_semantic_result()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Closes #2926
Summary
During incremental extraction, LLM output whose
source_filepointed at a filethat was not dispatched silently deleted that file's entire prior
contribution from
graph.json. The semantic cache has been guarded against thissince #1757 (
allowed_source_files); the graph build was not.Root cause
On an incremental run only the changed subset is dispatched, and unchanged files
are never read back from the semantic cache — their graph contribution survives
precisely because they are absent from the new chunks. A stray fragment the
model attributes to such a file puts it back into the replace-set:
_save_semantic_cache(..., allowed_source_files=uncached_paths)correctlydiscards the strays from the cache (
skipped out-of-scope source_file),freshresult was extended intosem_result,build_merge()saw the file amongnew_chunks, dropped its priornodes/edges per tier, and merged in only the fragment,
Loss was permanent until a full rebuild.
Strays attributed to nonexistent paths (
src/foo.ts) became phantom nodes nodelete pass ever matched.
Fix
cache.py— newscope_semantic_result(): filters a result dict in placeto the dispatched-file allowlist, using the exact path normalization as
save_semantic_cache's guard (so an item one keeps, the other can't skip).Mirrors fix(cache): prune edges/hyperedges referencing never-written node groups #1916 by also dropping surviving edges/hyperedges that reference a
dropped node id — except duplicate-attribution ids, which stay reference-safe.
cli.py: applies the filter tofreshimmediately after extraction,before both the cache save and the
sem_resultextension. This also closes aresidual hole where strays from chunk A attributed to a file dispatched in
chunk B polluted B's cache entry at the final save. Drops are logged:
[graphify extract] dropped N out-of-scope item(s) attributed to M file(s) ...Testing
scope_semantic_result: group dropping (incl. nonexistentpaths), absolute/relative identity equivalence, dangling-edge pruning with
duplicate-attribution protection, unscoped no-op.
OTHER.mda two-node contribution; next run changes onlyREADME.mdand thestubbed model answers with README's node plus strays for
OTHER.mdandsrc/foo.ts. AssertsOTHER.md's original nodes survive, no stray/phantomenters the graph, and the drop is reported. Verified red without the fix,
green with it.
test_ollama_retry_capfailure, present on the clean tree).