Skip to content

fix(global): give global add the community offset and shared type linking merge-graphs already has - #3578

Open
ayushcodes10 wants to merge 25 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3100-global-add-community-offset
Open

ayushcodes10 wants to merge 25 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3100-global-add-community-offset

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Fixes #3100.

Summary

graphify merge-graphs got two fixes for building a cross-repo store: #3014 offsets each input's community ids into a shared id space as it composes them (every graph.json numbers its own communities from 0, so ids carried across unchanged collide), and #3007 links identically declared types across repos so a traversal can cross the repo boundary (a type both repos declare otherwise arrives as two unconnected nodes, since every id is repo-prefixed).

graphify global add builds the same kind of multi-repo store with the same prefix_graph_for_global prefixer, but never got either fix — it called the prefixer with the default (no) offset, and never called the type-linking pass at all. Two independent reporters confirmed this on real stores of very different shape and scale: one with 226 repos / 1.21M nodes (992 colliding community ids, id 0 alone claiming 1,042 unrelated nodes across 15 repos, 0 shared-type links despite 14 real cross-repo shared types), the other with 11 repos on an older release (52 colliding ids, 532 nodes fused at id 0).

Changes

  • graphify/global_graph.py: global_add now computes a community offset from the store's own current community ids (after pruning this repo's stale entries, so re-adding the same repo doesn't inflate it forever) and passes it to prefix_graph_for_global, reusing the exact function merge-graphs already calls. It also now calls link_shared_type_declarations over the whole store on every add — the same idempotent pass (only adds an edge where none exists yet) the existing member-call linking already does just above it, so a type declared in a repo added long ago still gets linked against one added today. The new edge count is threaded through the return dict alongside the existing cross_repo_calls.
  • graphify/cli.py: prints the new shared_type_links count, mirroring the existing cross_repo_calls print right above it.
  • tests/test_global_graph.py: two new tests — community ids landing on disjoint sets across two repos added one at a time, and a same_type_as edge appearing (with the reported count matching) once a second repo declares a type the first one also declared.
  • CHANGELOG.md: entry under 0.9.61 (unreleased).

Testing

  • python -m pytest tests/test_global_graph.py -q — 22 passed
  • Full suite: python -m pytest -q — 5487 passed, 68 skipped (pre-existing unrelated failures excluded: tests/test_ollama_retry_cap.py missing the optional openai module, and test_ts_normalizer_scales_linearly_on_large_files / test_hyperedge_convex_hull_js_is_geometrically_sound, both known environment-specific flakes unrelated to this change)
  • python -m tools.skillgen --check — OK
  • Manually reproduced end to end with isolated _GLOBAL_DIR/_GLOBAL_GRAPH/_GLOBAL_MANIFEST patches (matching the existing test fixture pattern): two repos each with their own communities land on disjoint ids, and two repos declaring the same namespaced type gain a same_type_as edge on the second add.

🤖 Generated with Claude Code

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. 2 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_id\_prefixes changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_id\_prefixes behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"source\_file":"'h\\u00e9llo w\\u00f6rld'"\}, the old code produced \{'héllo\_wörld'\} but the new code produces \{'h\_llo\_w\_rld'\}. Paste that input straight into a regression test.

Behavior changes: \_claude\_pretooluse\_hooks changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_claude\_pretooluse\_hooks behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"strict":"True","project":"True"\}, the old code produced \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search', 'timeout': 10\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'grap… but the new code produces \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search'\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guar…. Paste that input straight into a regression test.


Graphify review — findings

Brings global_add to parity with merge-graphs: it now offsets each incoming repo's community ids past those already in the store (reading them after pruning the repo's own stale nodes so re-adds don't inflate forever) and runs link_shared_type_declarations over the whole store on every add to create same_type_as edges between identically declared types across repos. Previously the incremental add path prefixed every repo's communities from 0 (fusing unrelated repos at id 0) and never linked shared types at all. The CLI reports the new shared_type_links count alongside the existing cross-repo call count.

Worth a look

  • Concurrent global_add calls can overwrite each other's graph updatesgraphify/global_graph.py:120 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 527 functions depend on the 308 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 14 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 5 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 527 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: 469 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

275 of 275 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • tests/test_csharp_enum_members.py — full-run-safety
  • … and 225 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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

Behavior changes: \_id\_prefixes changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_id\_prefixes behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"source\_file":"'h\\u00e9llo w\\u00f6rld'"\}, the old code produced \{'héllo\_wörld'\} but the new code produces \{'h\_llo\_w\_rld'\}. Paste that input straight into a regression test.

Behavior changes: \_claude\_pretooluse\_hooks changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_claude\_pretooluse\_hooks behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"strict":"True","project":"True"\}, the old code produced \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search', 'timeout': 10\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'grap… but the new code produces \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search'\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guar…. Paste that input straight into a regression test.

Could not verify: Could not verify build\_merge.

The verifier did not have enough to check build\_merge, 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 `graph_path` is annotated `str | Path | None` — 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

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)

No difference found (not proven): No behavior difference found in cohesion\_score (not a proof).

The verifier ran both versions of cohesion\_score 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.

No difference found (not proven): No behavior difference found in link\_cross\_repo\_member\_calls (not a proof).

The verifier ran both versions of link\_cross\_repo\_member\_calls 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 \_is\_prose\_note.

The verifier did not have enough to check \_is\_prose\_note, 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 extract.

The verifier did not have enough to check extract, 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 `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

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: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_js\_extra\_walk.

The verifier did not have enough to check \_js\_extra\_walk, 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 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_ruby\_extra\_walk.

The verifier did not have enough to check \_ruby\_extra\_walk, 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: signature changed: no shared positional arity

Could not verify: Could not verify extract\_markdown.

The verifier did not have enough to check extract\_markdown, 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 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: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify generate.

The verifier did not have enough to check generate, 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 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly ValueError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify resolve\_ruby\_member\_calls.

The verifier did not have enough to check resolve\_ruby\_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: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 5 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 7 commits September 16, 2026 13:56
Toward issue 3100: reorders the existing steps in global_add with no
behavior change yet, so the community offset computed in the next
commit can read the store's community ids as they stand once this
repo's own stale entries are already pruned, rather than before.
Pruning does not depend on the prefixed graph and prefixing does not
depend on the loaded store, so the two steps were independent and
safe to swap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes half of issue 3100 (the number 3014 fix).

The CLI batch merge command already offsets each input community ids
into a shared id space as it composes them, since every graph.json
numbers its own communities from 0 and ids carried across unchanged
collide with whatever id another repo already occupies. global_add
builds the same kind of multi repo store with the same prefixer, but
kept calling it with the default offset, so a global store built
through the incremental add path still had exactly that defect: two
repos claiming the same community id fuse into one unrelated meta
community in any aggregated view, worst of all at id 0, which every
repo starts numbering from.

Computed from the pruned store own current community ids, so each
successive add lands past everything already there, the same
invariant a single batch merge keeps across its inputs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the other half of issue 3100 (the number 3007 fix).

The CLI batch merge command already links identically declared types
across repos so a traversal can cross the repo boundary, since every
id is repo prefixed and two repos declaring the same type otherwise
arrive as two unconnected nodes. global_add never called this pass,
so an incrementally built store held zero same_type_as edges no
matter how many repos actually shared a type.

Runs again over the whole store on every add, the same pattern the
existing member call linking pass already uses just above it: the
pass only adds an edge where none exists yet, so repeated calls across
successive adds stay cheap and cannot double an edge, and a type
declared in a repo added long ago still gets linked against one added
today.

Also threads the new edge count through the return dict, alongside
the existing cross_repo_calls count, for callers that want to report
it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors the existing cross_repo_calls print right above it, now that
global_add reports the count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two repos each numbering two communities from 0 must land on
disjoint ids once both are in the store, matching the invariant a
single batch merge already keeps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two repos declaring the same namespaced type must gain a same_type_as
edge once both are in the store, and the reported link count on each
add matches: zero for the first repo since there is nothing yet to
link against, one once the second repo lands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ayushcodes10
ayushcodes10 force-pushed the fix-3100-global-add-community-offset branch from ea3cf81 to d17f7e0 Compare September 16, 2026 08:28
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Thanks for catching the stale-base issue — rebased onto the current v8 tip (post 0.9.62) in d17f7e0, so the diff no longer appears to touch _id_prefixes or _claude_pretooluse_hooks; those were both already-shipped fixes my branch predated, not something this PR changes. Full suite (5595 tests) and skillgen check both pass clean on the rebased branch.

On the concurrent global_add race: confirmed this is pre-existing, not introduced or worsened in kind by this change. global_graph.py has no locking at all (unlike watch.py's _rebuild_lock for the equivalent update path), so two concurrent global_add calls could already clobber each other's writes via the same load-modify-save pattern before this PR. This change does add more work inside that window (the offset computation and the shared-type linking pass), which widens it somewhat, but doesn't create a new race class. Fixing it properly would mean adding a lock to global_graph.py mirroring watch.py's, which is a broader change than this issue's scope (#3100, community offset + shared type parity with merge-graphs) — happy to file a follow-up issue for it if that's useful, but treating it as out of scope for this PR.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Brings global_add's incremental store-building to parity with merge-graphs: it now offsets each incoming repo's community ids past the max already in the store (pruning the repo's own stale nodes first, so re-adds don't inflate the space) instead of prefixing everyone's communities from 0 and fusing unrelated repos at id 0, and it runs link_shared_type_declarations over the whole store on every add so identically declared types across repos get same_type_as edges. The CLI reports the new shared_type_links count, and the result dict carries it (as 0 on the skipped-unchanged path).

Worth a look

  • Concurrent global_add calls can reuse the same community offsetgraphify/global_graph.py:132 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 528 functions depend on the 309 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 125 callees
  • new: global_add() — 14 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 5 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 528 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: 470 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 5 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 18, 2026 14:20
A review finding pointed out that global_add and global_remove each
load the graph and manifest, mutate them in memory, then save both
back, with no locking anywhere. Two concurrent calls would both read
the same pre write snapshot, compute conflicting results from it, and
the second save would silently discard the first's work entirely, not
just a duplicated community id which the narrower earlier fix on this
issue already closes. An entire repo's added nodes could vanish this
way. Adds an exclusive advisory lock around the whole cycle in both
functions, blocking until acquired so a caller waits its turn rather
than failing, released automatically if the process is killed, and a
no op on platforms without fcntl, matching the same fallback already
used by the per repo rebuild lock elsewhere in this codebase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the lock's own mutual exclusion guarantee directly (several
threads through the critical section, at most one active at once) and
an end to end check that two concurrently added repos both survive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. global_add/global_remove each load the graph and manifest, mutate them in memory, then save both back, with no locking anywhere. Two concurrent calls would both read the same pre-write snapshot, compute conflicting results, and the second save would silently discard the first's work entirely — not just a duplicated community id (which the narrower fix on this issue already closes), but potentially an entire repo's added nodes lost.

Added an exclusive advisory lock (fcntl.flock, matching the same technique and Windows no-op fallback already used by watch.py's per-repo rebuild lock) around the whole load/mutate/save cycle in both functions. Blocks until acquired rather than failing, since a CLI caller should just wait its turn. Two regression tests: one verifying the lock's own mutual exclusion directly, and an end-to-end check that two concurrently added repos both survive.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes graphify global add so an incrementally built store matches what merge-graphs produces: it now offsets each repo's community ids past those already in the store (via a new community_offset on prefix_graph_for_global) instead of colliding everyone at id 0, and links identically declared types across repos, reporting the count as shared_type_links in the CLI output. Wraps the whole load-mutate-save cycle of global_add/global_remove in a new _global_store_lock (an exclusive fcntl.flock, no-op on Windows) so concurrent calls can't clobber each other's writes.

Worth a look

  • Source hash can describe different bytes than the imported graphgraphify/global_graph.py:131 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Lock is a no-op when fcntl is unavailablegraphify/global_graph.py:30 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Lock test can pass while worker threads are deadlockedtests/test_global_graph.py:493 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 536 functions depend on the 317 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 125 callees
  • new: global_add() — 15 callers, 11 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 536 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit 26b02b5 (diverged from this PR's base — delta is approximate).

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: 478 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 1 grounded finding(s) anchored inline below; 5 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 19, 2026 23:02
A review finding pointed out that hashing the source path happened
before the lock while parsing it happened after, two reads of the
same file at two different times. A concurrent writer to that file in
between could make the recorded hash describe different bytes than
what actually gets imported, corrupting the unchanged hash skip check
on every later call. Both now come from one read inside the lock, and
the standalone hashing helper is gone since it had no other caller.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Counts calls to read_bytes/read_text on the source path and asserts
exactly one read happens, covering both hashing and parsing, so the
recorded hash can never describe different bytes than what got
imported.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. Hashing `source_path` happened before the lock while parsing it happened after, two reads of the same file at two different times. A concurrent writer to that file in between could make the recorded manifest hash describe different bytes than what actually got imported, corrupting the unchanged-hash skip check on every later call.

Both now come from a single read inside the lock (the standalone hashing helper is gone since it had no other caller). Added a regression test that counts reads of the source path and asserts exactly one happens.

(The Windows no-op fallback and the test timeout are as-designed — the former matches the existing per-repo rebuild lock's own documented fallback, and the latter is a safety bound on an otherwise-fast test, not a source of flakiness.)

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes graphify global add/global remove so the incremental add path matches merge-graphs: global_add now offsets each incoming repo's community ids past those already in the store (via a new community_offset on prefix_graph_for_global) and links identically declared types across repos, reporting the count as shared_type_links in the CLI output. Wraps each add/remove's whole load-mutate-save cycle in a new exclusive _global_store_lock (fcntl advisory, no-op on Windows) so concurrent calls can no longer clobber each other's writes. Hashes and parses the source graph from a single read_bytes, so a concurrent writer to the source file can't make the recorded hash describe different bytes than what was imported.

Worth a look

  • global_remove read-modify-write not protected by the new store lockgraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Unchanged graph skip is bypassed by size-cap failuresgraphify/global_graph.py:128 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Global store lock is a no-op when fcntl is unavailablegraphify/global_graph.py:31 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 539 functions depend on the 320 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 16 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 539 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit b9cd957 (diverged from this PR's base — delta is approximate).

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: 481 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 2 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
return h.hexdigest()[:16]


def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add()

fans out to 10 callees (efferent coupling); 16 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 19, 2026 23:20
A review finding pointed out that the read consolidation fix moved
the size cap check ahead of the unchanged hash skip check, so an
already tracked, unchanged graph started erroring on every later call
once the file, or a lowered GRAPHIFY_MAX_GRAPH_BYTES, crossed the cap
threshold. That check was never reached at all on the skip path
before this fix, so the skip must still win when nothing changed,
even if the file would now fail the cap on its own. The cap check
moves back to only guard the actual parse, right after the skip
check, matching where it ran before that fix, while keeping the
single read this fix already collapsed hashing and parsing onto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Covers a graph that succeeds while under the cap, then survives a
later cap drop for the same unchanged file (must skip, not raise),
while a genuinely different oversized file for the same repo is still
rejected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch, and sorry for the regression — fixed. Moving the size cap check ahead of the unchanged-hash skip (to consolidate hashing and parsing into a single read) meant it was reached on every call, even when nothing changed, so an already-tracked graph that crossed the cap (its own growth, or a lowered GRAPHIFY_MAX_GRAPH_BYTES) started erroring on every subsequent call instead of continuing to skip — the cap was never reached at all on that path before that fix.

The cap check moves back to guarding only the actual parse, right after the skip check, matching where it ran before, while keeping the single read. Regression test covers a graph that succeeds under the cap, survives a later cap drop while unchanged (skips, doesn't raise), and confirms a genuinely different oversized file is still rejected.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes graphify global add's incremental path to offset each repo's community ids past those already in the store and to link identically declared types across repos, matching what merge-graphs already did — previously separate repos' communities fused at id 0 and shared types were never linked. Wraps the whole load-mutate-save cycle of global_add/global_remove in an exclusive fcntl advisory lock (no-op on Windows) so concurrent calls can't clobber each other's writes, and hashes and parses a single read of the source graph so a concurrent writer can't desync the recorded hash from the imported bytes. Moves the size-cap check back behind the unchanged-hash skip so an already-tracked, unchanged graph keeps skipping instead of erroring once it crosses the cap, and reports shared_type_links in the CLI output.

Worth a look

  • global_add manifest read/save not under global store lock while global_remove isgraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Store lock not applied to global_removegraphify/global_graph.py:17 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Graph file is read into memory before enforcing size capgraphify/global_graph.py:142 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Timed thread joins do not assert completiontests/test_global_graph.py:576 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 541 functions depend on the 322 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 17 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 541 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit b9cd957 (diverged from this PR's base — delta is approximate).

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: 483 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 2 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
return h.hexdigest()[:16]


def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add()

fans out to 10 callees (efferent coupling); 17 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 20, 2026 02:32
A review finding pointed out that hashing source_path to check the
unchanged skip still read its full bytes into memory before the size
cap ever ran, defeating the cap's whole purpose of failing fast
before a multi GiB file is loaded, for exactly the case that matters
most: a new or genuinely changed oversized file, which has no prior
manifest entry to skip through.

Adds a stat only fast path first (mtime and size compared against
what a previous successful add recorded, no read at all), so an
already tracked, genuinely unchanged file keeps skipping even once it
or the configured cap crosses the threshold, the same guarantee the
previous ordering fix protected. Only once that fast path misses does
the cap check run, still with no read, and only past that does the
single real read for hashing and parsing happen. A manifest entry now
also records the source file's mtime and size for this fast path,
falling back to the content hash comparison for an older entry that
predates it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Covers a new, oversized, never before tracked file being rejected by
the cap before a single byte of it is read, and asserts the
concurrency tests' timed thread joins actually finish rather than
letting a deadlock pass silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Both good catches — fixed.

Size cap read order: hashing source_path (to check the unchanged skip) still read its full bytes into memory before the size cap ever ran, defeating the cap's whole purpose of failing fast before a multi-GiB file is loaded, for exactly the case that matters most: a new or genuinely changed oversized file. Added a stat-only fast path first (mtime and size compared against what a previous successful add recorded, no read at all), so an already-tracked, genuinely unchanged file keeps skipping even once it or the configured cap crosses the threshold — the same guarantee the earlier ordering fix protected — without needing to read anything or reach the cap check. Only once that fast path misses does the cap check run (still no read), and only past that does the single real read for hashing and parsing happen. Test asserts zero reads occur for a new oversized file before it's rejected.

Timed joins: added assert not t.is_alive() after each .join(timeout=...) in both concurrency tests, so a deadlock fails loudly instead of silently passing.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fixes graphify global add so the incremental path applies the same cross-repo handling as merge-graphs: it offsets each repo's community ids into a shared space and links identically declared shared types (now reported as shared_type_links in the CLI output), instead of fusing unrelated repos' communities at id 0 and never linking shared types. Wraps the whole load-mutate-save cycle of global_add/global_remove in an exclusive flock (_global_store_lock, a no-op without fcntl) so concurrent calls can no longer silently clobber each other's writes, and hashes and parses the source from a single read so a mid-flight writer can't desync the recorded hash from the imported bytes. Restores the file-size cap to guard only the actual parse and adds a stat-only (mtime+size) fast path, so an unchanged tracked file always skips without reading and a new oversized file is rejected before a single byte is read.

Worth a look

  • global_add skips updates for a different source path with matching mtime and sizegraphify/global_graph.py:136 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 544 functions depend on the 325 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 18 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 544 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit b9cd957 (diverged from this PR's base — delta is approximate).

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: 486 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 2 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
return h.hexdigest()[:16]


def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add()

fans out to 10 callees (efferent coupling); 18 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 20, 2026 02:55
A review finding pointed out the mtime and size fast path never
compared the source path itself. A repo tag re pointed at a genuinely
different file that happens to share the old file's mtime and size, a
real possibility with a preserved timestamp copy or sync tool, or a
checkout that restores original timestamps, fast skipped on that
coincidence alone, silently keeping stale data in the store with no
warning at all, since that early return happens before the path
change warning further down is even reached.

The resolved source path is now computed once up front and compared
in the fast path alongside mtime and size, and reused everywhere else
this function already needed it, instead of calling resolve three
separate times.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Two files padded to the exact same size with their mtime forced to
match at nanosecond precision, added under the same repo tag one
after the other. The second add must not fast skip, the manifest must
record the new path, and the global graph must reflect the new
file's content rather than the stale one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. The mtime/size fast path never compared the source path itself, so a repo tag re-pointed at a genuinely different file that happens to share the old file's mtime and size (a real possibility with a preserved-timestamp copy or sync tool, or a checkout that restores original timestamps) would fast-skip on that coincidence alone, silently keeping stale data in the store — the early return happens before the "repo tag now points elsewhere" warning further down is even reached.

The resolved source path is now part of the fast path's own comparison, computed once up front and reused everywhere else this function already needed it (previously called three separate times). Regression test uses two files padded to the exact same size with mtime forced to match at nanosecond precision, confirming the second add is not skipped and the global graph reflects the new content, not the stale one.

On the pattern across rounds: I take the point that this is now 5 rounds on the same ~15-line block. Looking back at each one, they've been genuinely independent, non-overlapping properties (concurrency safety, read consistency, cap-vs-skip ordering, cap-vs-read ordering, then this path-identity gap) rather than the same bug resurfacing — each fix has held up under the next round's scrutiny. I don't think a rewrite would have caught this last one any more reliably than the incremental fixes did; happy to do a closing pass reading the whole function fresh end-to-end if that would help confidence, but didn't want to reshape working, tested code speculatively without a concrete finding driving it.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes graphify global add/global remove to offset each repo's community ids into a shared space and link identically declared types across repos (bringing the incremental add path in line with merge-graphs), and reports the shared type link count in CLI output. Wraps the whole load-mutate-save cycle in an exclusive fcntl advisory lock (_global_store_lock, a no-op on Windows) so concurrent adds can't clobber each other's writes. Reworks the skip logic to a stat-only fast path (path + mtime + size, no read) that skips genuinely unchanged files even when oversized, while running the size cap before any byte is read and hashing/parsing from a single read so a new oversized file is rejected before load and a concurrent writer can't desync the recorded hash from the imported bytes.

Worth a look

  • global_add can skip changed content when mtime and size are preservedgraphify/global_graph.py:139 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Global store lock is a no-op without fcntlgraphify/global_graph.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Legacy manifest hash matches now call size cap before skipgraphify/global_graph.py:158 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 546 functions depend on the 327 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 19 callers, 10 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 546 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit b9cd957 (diverged from this PR's base — delta is approximate).

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: 488 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 2 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
return h.hexdigest()[:16]


def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add()

fans out to 10 callees (efferent coupling); 19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 and others added 3 commits September 20, 2026 11:40
A manifest entry written before the mtime and size fields existed (any
manifest from before this fix) has no stat baseline for the fast path
added earlier in this branch, so it fell straight through to the size
cap check and raised even when the file's content was genuinely
unchanged, just because it had grown past the cap since it was last
recorded.

Add a streaming hash helper that reads the file in bounded chunks
regardless of its size, and fall back to it when the cap check fails.
The cap only needs to protect the eventual in memory parse, which
never happens for a file whose content still matches the stored hash,
so a streaming comparison can safely rescue that case for any manifest
vintage, legacy or not, before finally raising the original cap error
for a file that is both oversized and genuinely different.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
One test confirms an unchanged file tracked by a legacy manifest entry
(only a stored hash, no mtime or size baseline) skips instead of
raising once it becomes oversized. A companion test confirms a legacy
entry whose file genuinely changed still raises the cap error, so the
fallback only rescues a truly unchanged file and never lets new
content past the cap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Pushed one more fix for a gap found in review: a manifest entry written before the mtime/size fast path fields existed (every manifest from before this PR) has no stat baseline to compare against, so it fell straight through the fast path into check_graph_file_size_cap and errored on a genuinely unchanged file that had simply grown past the cap since it was last recorded. The existing hash based unchanged skip further down the function would have caught this, but was never reached because the cap check raised first.

Fix: added _hash_file_streaming, a SHA256 helper that reads the file in bounded chunks rather than one read_bytes() call, so its memory use never depends on the file's size. When the cap check fails, we now fall back to comparing the streaming hash against the entry's stored source_hash before giving up; a match skips (any manifest vintage, legacy or not), and only a genuine mismatch lets the original cap error through.

This also feels like the natural resting point for the cap vs hash tension that's been the throughline of the last several rounds here. The cap's only real job is protecting the eventual JSON parse, which genuinely does need the whole file in memory; hashing never needed that, it just wasn't being done in a way that respected the cap. Separating those two concerns (cheap stat check first, then a bounded-memory streaming hash as the fallback, only then the parse) means there's no longer a scenario where an unchanged file gets punished for growing past a size limit, regardless of which version of the manifest format tracked it.

Added two regression tests: one confirming a legacy hash-only manifest entry skips when the file is unchanged but now oversized, and a companion confirming a legacy entry with a genuinely different, still-oversized file still raises the cap error. Both were checked to fail against the pre-fix code first. Full test_global_graph.py (30 tests) and the full suite (5603 passed) are green.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes graphify global add to offset each repo's community ids into a shared space and link identically declared types across repos, matching the merge-graphs behaviour the incremental add path had been missing, and reports the shared-type link count in the CLI output. Wraps each global_add/global_remove in an exclusive _global_store_lock (fcntl advisory, no-op on Windows) so concurrent calls can't clobber each other's load-mutate-save cycle. Reworks the change-detection to a stat-only fast path (mtime, size, and resolved source path compared against the manifest) that skips unchanged files without reading them, so the size cap runs before any byte of a new oversized file is read, with _hash_file_streaming as a bounded-memory fallback that rescues pre-existing manifest entries lacking a stat baseline by comparing stored hashes before the cap error is allowed through.

Worth a look

  • Changed file content can be skipped when mtime and size are preservedgraphify/global_graph.py:160 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • global_add lacks the store lock that global_remove now holdsgraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Global store lock is a no-op when fcntl is unavailablegraphify/global_graph.py:32 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 552 functions depend on the 333 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 124 callees
  • new: global_add() — 21 callers, 11 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 552 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit b9cd957 (diverged from this PR's base — delta is approximate).

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: 494 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

283 of 283 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_external_call_guards.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • … and 233 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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 global\_add.

The verifier did not have enough to check global\_add, 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 `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_remove.

The verifier did not have enough to check global\_remove, 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 KeyError — names the real obstacle, not a sampling gap)

· 1 grounded finding(s) anchored inline below; 5 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py
"shared_type_links": shared_type_links}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant