Skip to content

fix(build): route carried hyperedges through the dedup survivor remap (#3102) - #3113

Closed
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/carried-hyperedge-remap
Closed

fix(build): route carried hyperedges through the dedup survivor remap (#3102)#3113
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/carried-hyperedge-remap

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Closes #3102.

The problem

build_merge() carries hyperedges forward from files that were neither re-extracted nor deleted (#1574). They were attached to G after build() and entity dedup had finished. Dedup rewires every edge endpoint — and every hyperedge member it sees — onto the survivor (#2805), but a hyperedge attached after the fact kept naming the merged-away node. The written graph then had:

  • ordinary edges rewired to the survivor;
  • the carried hyperedge still naming the node that no longer exists;
  • a member in G.graph["hyperedges"] with no backing graph node.

The reporter's synthetic repro reproduces on 0.9.50 exactly as filed: edge_rewired True, dangling_members ['alpha_concept_long_variant_id'].

The change

The carried set is computed before the build and rides into build() on the base chunk ({"nodes": existing_nodes, "edges": existing_edges, "hyperedges": carried}), so dedup remaps its members exactly as it remaps the new chunks' hyperedges, and build_from_json validates them against the built node set like any other. The prune-set helpers (_prune_match, the #2446 root derivation) move ahead of the build call — they never depended on G, only on the prune sources and the existing graph. The old attach_hyperedges step also id-deduped against hyperedges the new chunks re-emitted; that is preserved by leaving a same-id hyperedge to the new chunk.

Nothing about which hyperedges are carried changes: semantically re-extracted sources still replace theirs, pruned sources still drop theirs.

Tests

tests/test_carried_hyperedge_remap.py — 6 tests built on the issue's data: the carried hyperedge's members are remapped onto the survivor (not merely dropped); the written graph.json has no hyperedge member without a node; edges and hyperedges agree on the survivor; a hyperedge re-emitted by the new chunk under the same id wins and is not duplicated; a pruned source's hyperedge is still dropped; and an unchanged hyperedge with no dedup involved is carried verbatim. With the reorder reverted, 3 of 6 fail. test_build_merge_hyperedges_and_prune, test_build, the test_build_merge*, hyperedge and dedup suites are unchanged (219 passed); the full suite matches the v8 baseline.

…Graphify-Labs#3102)

build_merge() carries hyperedges from unchanged files across an incremental
rebuild (Graphify-Labs#1574). They were attached to G AFTER build() and entity dedup had
finished, so while every edge endpoint was rewired onto the dedup survivor
(Graphify-Labs#2805), a carried hyperedge kept naming the merged-away node: a dangling
member in graph.json with no backing node, and a member list that disagreed
with the edges about which node the concept is.

The carried set now rides into build() on the base chunk, so dedup remaps
its members exactly as it remaps the new chunks' hyperedges, and
build_from_json validates them against the built node set like any other.
The prune-set helpers move ahead of the build call (they never depended on
it); a hyperedge the new chunks re-emit under the same id is left to them,
preserving the id-dedup the old attach_hyperedges step provided.
Copilot AI lite review requested due to automatic review settings August 26, 2026 12:24

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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 2 advisory finding(s) below merit a look before merge.

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


Graphify review — findings

Fixes carried-forward hyperedges naming a dedup survivor's merged-away node during incremental build_merge rebuilds: the carried set now rides into build() on the base chunk instead of being attached to G afterward, so entity dedup rewires hyperedge members onto the survivor the same way it rewires edge endpoints, leaving no dangling members in the written graph. Carried hyperedges whose id the new chunks re-emit are skipped so the re-extracted version wins, while re-extracted/deleted sources are dropped via the existing semantic-remap and prune checks.

Worth a look

  • build_merge consumes one-shot new_chunks before buildinggraphify/build.py:1810 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Existing hyperedges cause one-shot new_chunks iterators to be consumed before buildgraphify/build.py:1807 · 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 — 1109 functions depend on the 93 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 98 callers, 50 callees
  • new: build_from_json() — 190 callers, 18 callees
  • new: build_merge() — 53 callers, 13 callees
  • new: to_obsidian() — 36 callers, 13 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: to_wiki() — 41 callers, 7 callees
  • new: extract_corpus_parallel() — 26 callers, 11 callees
  • new: _call_claude_cli() — 31 callers, 9 callees
  • …and 38 more — each is listed as a finding

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

Formal verification

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

· 46 more finding(s) on lines outside this diff (see the check run).

@abhay-codes07

Copy link
Copy Markdown
Contributor Author

Follow-up commit on the review's iterator point: build_merge already iterated new_chunks twice before this change (and the parameter is typed list), but it is now materialised with list() at the top so a one-shot iterator can never reach any of the passes.

@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 carried-forward hyperedges naming dedup-merged-away nodes during an incremental build_merge: the preserved hyperedges from unchanged files now ride in on the base chunk fed to build, so entity dedup rewires their members onto the survivor instead of leaving a dangling member with no backing node in the written graph. Also materialises new_chunks to a list up front since it's now iterated multiple times, and skips carrying any hyperedge whose id the new chunks re-emit (their version wins).

Worth a look

  • Carried-hyperedge replacement compares raw IDs before build-time ID coerciongraphify/build.py:1811 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Carried hyperedge de-dup by id assumes new chunk hyperedges always carry an idgraphify/build.py:1829 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Carried hyperedge re-emit dedup only skips by id, dropping non-id hyperedges from dedup and possibly duplicating themgraphify/build.py:1829 · 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 — 1109 functions depend on the 93 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 98 callers, 50 callees
  • new: build_from_json() — 190 callers, 18 callees
  • new: build_merge() — 53 callers, 13 callees
  • new: to_obsidian() — 36 callers, 13 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: to_wiki() — 41 callers, 7 callees
  • new: extract_corpus_parallel() — 26 callers, 11 callees
  • new: _call_claude_cli() — 31 callers, 9 callees
  • …and 38 more — each is listed as a finding

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

Formal verification

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

· 46 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.51 via authorship-preserving cherry-pick so you keep contributor-graph credit. Thanks @abhay-codes07! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.51

@safishamsi safishamsi closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

build_merge(): carried unchanged hyperedges bypass #2805 remap and retain dangling member IDs

3 participants