fix(build): inherit persisted directed flag on incremental rebuild (#2342) - #2350
Closed
MANOJ21K wants to merge 1 commit into
Closed
fix(build): inherit persisted directed flag on incremental rebuild (#2342)#2350MANOJ21K wants to merge 1 commit into
MANOJ21K wants to merge 1 commit into
Conversation
…raphify-Labs#2342) A graph saved with --directed was silently rebuilt as undirected by any routine refresh, then written back with force=True, so the downgrade was permanent and nothing in stdout or the report said the graph changed type. Betweenness is only meaningful on a directed graph. Undirected, a leaf utility that everything imports becomes a fake bridge between every pair of its importers, so God Nodes and the betweenness-derived Suggested Questions start reporting ubiquity instead of architecture. Two paths dropped the flag: build_merge() defaulted to directed=False and the incremental extract call never passed it, and _rebuild_code() — the function behind `graphify update` and the post-commit hook — called build_from_json(result) with no directed= at all. Both now inherit the persisted value. _load_existing_graph() already parses the graph dict for the merge, so it returns `directed` alongside the node/edge lists rather than re-reading a file that can reach the 512 MiB cap; _rebuild_code() reads it from existing_graph_data, which it already loads for the topology compare. build_merge's directed parameter becomes bool | None, where None means inherit — an explicit True/False still wins, so no existing caller changes behavior, and with no graph on disk there is nothing to inherit and the historical False default stands. This mirrors the round-trip cluster-only already does via build_from_json(_raw, directed=_directed).
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2342.
Problem
An incremental refresh silently rebuilds a
--directedgraph as undirected, andwrites it back with
force=True. Nothing in stdout or the report says the graphchanged type.
Betweenness centrality is only meaningful on a directed graph. Undirected, a leaf
utility that everything imports becomes a fake bridge: every pair of its importers
sits two hops apart "through" it, though no control flow passes through it at all.
So
## God Nodesand the betweenness-derived## Suggested Questionsquietly startreporting ubiquity instead of architecture.
Root cause
Two paths drop the persisted graph type. Both re-serialize with
force=True, so thedowngrade is permanent:
build.py::build_merge()tookdirected: bool = False, andcli.py's incrementalextract calls it without
directed=(_build_merge([merged], graph_path=..., ...)).watch.py::_rebuild_code()— the function behindgraphify updateand thepost-commit hook — called
build_from_json(result)with nodirected=at all.The flag already round-trips correctly everywhere else.
cluster-onlyinherits it(
cli.py,build_from_json(_raw, directed=_directed)), and #801 went to the troubleof preserving per-edge
source/targetthrough the merge. It is specifically thegraph type that was dropped.
Fix
Inherit the persisted flag instead of defaulting it.
_load_existing_graph()already parses the raw JSON dict for the merge, so it nowreturns
directedalongside(nodes, edges, hyperedges)— no second read of agraph.json that may be up to the 512 MiB cap.
build_merge(directed=...)becomesbool | None, defaulting toNone= "inheritfrom the graph on disk". An explicit
True/Falsestill wins, so existing callersare unaffected. With no graph on disk there is nothing to inherit and the historical
Falsedefault stands._rebuild_code()passesdirected=fromexisting_graph_data, which it has alreadyloaded and uses two lines later for the topology compare.
Reproduction
The script from the issue, before and after:
Tests
Two regression tests, both failing on clean
v8(assert G2.is_directed()→assert False,assert rebuilt["directed"] is True→assert False is True), plus two guards that pin thebehaviour this must not change:
tests/test_build.py::test_build_merge_inherits_persisted_directed_flag— merge anempty chunk set into a directed graph; asserts the graph stays a
DiGraph, that there-written JSON keeps
"directed": true, and that the sink's betweenness is0.0(the symptom the issue is actually about).
tests/test_build.py::test_build_merge_directed_flag_still_forceable— explicitdirected=True/Falsestill overrides the inherited value.tests/test_build.py::test_build_merge_defaults_undirected_without_existing_graph—pins the no-existing-graph default.
tests/test_watch.py::test_rebuild_code_preserves_directed_graph— full_rebuild_coderound trip: rebuild a directed graph after a corpus change and assertthe persisted flag survives.
uv run pytest tests/ -q→ 3903 passed, 3 skipped (baseline onv8is 3899 passed,3 skipped — the delta is exactly these four tests, no regressions).
Out of scope
extract --no-cluster/update --no-clusterraw-dumps a graph payload that has nodirectedkey at all, so that path loses the flag by a different mechanism (the rawdict is assembled from the fresh extraction, not from the loaded graph). It needs its
own decision about carrying the key forward through
merge_raw_extraction, andtouching it here would drag the shrink-guard's
same_graphcomparison into this diff.Happy to open it as a separate issue if useful.