fix: canonicalize .graphify_root across update paths - #3676
Rarebuffalo wants to merge 1 commit 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. 4 change(s) tested, no difference found (not proven).
Graphify review — findings
Records the canonical absolute scan root in .graphify_root via a new atomic write_root_marker helper, replacing the previous behaviour of writing the user-supplied (possibly relative) path — so scan-root recovery resolves the original root regardless of the current working directory or Git-hook CWD. Adds a _StoredSourcePaths branch that recognizes a saved absolute marker nested under the invocation root and re-anchors stored source paths to the invocation CWD, preserving the existing relative-marker and anchor-detection fallbacks. Note this changes the on-disk marker format from a portable relative path (e.g. .) to an absolute one, which the older tests asserting portability across clones no longer expect.
No blocking issues surfaced. 5 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2525 functions depend on the 499 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 645 callers, 45 callees - new:
_rebuild_code()— 131 callers, 55 callees - new:
build_from_json()— 214 callers, 20 callees - new:
build_merge()— 76 callers, 14 callees - new:
save_semantic_cache()— 63 callers, 9 callees - new:
to_obsidian()— 38 callers, 14 callees - new:
save_manifest()— 40 callers, 11 callees - new:
to_json()— 56 callers, 7 callees - …and 60 more — each is listed as a finding
Verification — 2525 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: 1891 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
116 of 286 test file(s) selected (41%) via static blast radius.
tests/test_affected_cli.py— impacttests/test_agents_platform.py— impacttests/test_analyze.py— impacttests/test_atomic_canvas_export.py— impacttests/test_atomic_version_stamp.py— impacttests/test_atomic_writes.py— impacttests/test_benchmark.py— impacttests/test_benchmark_raw_graph.py— impacttests/test_build.py— impacttests/test_build_merge_dedup_scope.py— impacttests/test_build_merge_hyperedges_and_prune.py— impacttests/test_build_merge_shrink_guard.py— impacttests/test_cache.py— impacttests/test_callflow_html.py— impacttests/test_carried_hyperedge_remap.py— impacttests/test_charmap_encoding.py— impacttests/test_chunking.py— impacttests/test_cli_export.py— impacttests/test_cluster.py— impacttests/test_codebuddy.py— impacttests/test_community_labels_skill.py— impacttests/test_confidence.py— impacttests/test_corrupt_graph_json.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_dedup.py— impacttests/test_dedup_remaps_hyperedges.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_devin.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_elixir_import_resolution.py— impacttests/test_evidence_binding.py— impacttests/test_explain_cli.py— impacttests/test_export.py— impacttests/test_export_control_characters.py— impacttests/test_export_path_length.py— impacttests/test_external_stub_endpoints.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_extract_cli.py— impacttests/test_falkordb_integration.py— impacttests/test_file_label_disambiguation.py— impacttests/test_global_add_tag_inference.py— impacttests/test_global_graph.py— impacttests/test_go_qualified_resolution.py— impacttests/test_god_nodes_cli.py— impacttests/test_god_nodes_exclude_hubs.py— impacttests/test_hollow_chunks_arm_shrink_guard.py— impacttests/test_hook_guard_token_match.py— impact- … and 66 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
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: no capturable inputs from the test suite; property tier: 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)
Could not verify: Could not verify diagnose\_extraction.
The verifier did not have enough to check diagnose\_extraction, 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: no capturable inputs from the test suite; property tier: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify format\_diagnostic\_report.
The verifier did not have enough to check format\_diagnostic\_report, 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: no capturable inputs from the test suite; property tier: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly KeyError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify extract\_php.
The verifier did not have enough to check extract\_php, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_cpp\_member\_calls.
The verifier did not have enough to check \_resolve\_cpp\_member\_calls, 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: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
No difference found (not proven): No behavior difference found in \_resolve\_rust\_self\_member\_calls (not a proof).
The verifier ran both versions of \_resolve\_rust\_self\_member\_calls on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: concolic exploration (CrossHair). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_resolve\_js\_import\_target (not a proof).
The verifier ran both versions of \_resolve\_js\_import\_target on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify extract\_rust.
The verifier did not have enough to check extract\_rust, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_terraform.
The verifier did not have enough to check extract\_terraform, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, 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: no capturable inputs from the test suite; property tier: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous
No difference found (not proven): No behavior difference found in \_node\_search\_text (not a proof).
The verifier ran both versions of \_node\_search\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_score\_query.
The verifier did not have enough to check \_score\_query, 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: no capturable inputs from the test suite; property tier: not verifiable: all 45 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)
No difference found (not proven): No behavior difference found in \_subgraph\_to\_text (not a proof).
The verifier ran both versions of \_subgraph\_to\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_rebuild\_code.
The verifier did not have enough to check \_rebuild\_code, 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: no capturable inputs from the test suite; property tier: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_reconcile\_markdown\_links.
The verifier did not have enough to check \_reconcile\_markdown\_links, 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: no capturable inputs from the test suite; property tier: parameter `project_root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, 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: no capturable inputs from the test suite; property tier: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
· 68 more finding(s) on lines outside this diff (see the check run).
Summary
Fixes #3375.
watch.pyandcli.pypreviously wrote.graphify_rootusing different path semantics:watch.pystored the raw user-supplied path, such as.or./srccli.pystored the resolved absolute pathThis could cause scan-root recovery to depend on the working directory used by a later command.
Changes
Added
write_root_marker()ingraphify/paths.pyas the single writer for.graphify_root.Canonicalize the scan root with
Path(...).resolve().Reuse the existing
write_text_atomic()implementation.Updated both
watch.pywriters and bothcli.pywriters to use the helper.Preserved hook install: post-commit rebuild can silently collapse a graph built on a subfolder #2603 subfolder/source-path anchoring behavior when the marker is now absolute.
Added regression coverage for:
Validation
tests/test_watch.py -k "graphify_root"— 4 passedtests/test_watch.py— 156 passed, 3 skippedtests/test_paths.py tests/test_atomic_writes.py— 64 passed, 2 skippeduv run graphify update .writes the canonical absolute scan rootNo unrelated files or generated artifacts are included.