Skip to content

Commit 60ecee3

Browse files
chore(beads): close br-r37-c1-e04a1
1 parent 372a6c6 commit 60ecee3

1 file changed

Lines changed: 2 additions & 0 deletions

File tree

.beads/issues.jsonl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
{"id":"br-r37-c1-dgicx","title":"florentine_families_graph node + edge + adj order drifts from nx","description":"fnx.florentine_families_graph builds the graph with edges in alphabetical/canonical order, but nx uses a specific narrative-order list of add_edge calls (Castellani's edges before Medici's middle edges, etc.). This produces different node-insertion order, edge iteration order, and per-node adj order.\n\nRepro:\n fnx nodes: ['Acciaiuoli','Medici','Albizzi','Ginori','Guadagni','Barbadori','Castellani','Bischeri',...]\n nx nodes: ['Acciaiuoli','Medici','Castellani','Peruzzi','Strozzi','Barbadori','Ridolfi','Tornabuoni',...]\n\n fnx adj['Medici']: ['Acciaiuoli','Albizzi','Barbadori','Ridolfi','Salviati','Tornabuoni']\n nx adj['Medici']: ['Acciaiuoli','Barbadori','Ridolfi','Tornabuoni','Albizzi','Salviati']\n\nDrop-in code that iterated this classic dataset's nodes/edges in nx order broke. Fix: replicate nx's exact add_edge sequence.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T16:03:25.731453322Z","created_by":"ubuntu","updated_at":"2026-04-26T16:05:45.516706801Z","closed_at":"2026-04-26T16:05:45.514515137Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0}
9494
{"id":"br-r37-c1-dtkhh","title":"[code-review] stale graph_clique_number tests/docs after public removal","description":"Audit source: commits fc786a16 and 6fd81c1a. fc786a16 correctly removed fnx.graph_clique_number from the public NetworkX 3.6-compatible surface, but stale callers/docs still expect it. Repro: PYTHONPATH=python python3 -m pytest tests/python/test_clustering.py::TestClustering::test_graph_clique_number tests/python/test_e2e_smoke.py -q. Current result: test_clustering and e2e smoke fail with AttributeError: module 'franken_networkx' has no attribute 'graph_clique_number'. Additional stale public-surface references remain in README.md and python/franken_networkx/_fnx.pyi. Expected: tests/docs/stubs should match the public surface: use find_cliques/large_clique_size alternatives or assert absence where parity requires absence.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-05-04T02:46:51.119610648Z","created_by":"ubuntu","updated_at":"2026-05-04T02:55:41.848290049Z","closed_at":"2026-05-04T02:55:41.848086043Z","close_reason":"Shipped in ddd6cc0e + b29cb661. README, test_clustering, test_e2e_smoke and _fnx.pyi all cleaned up to match the public surface (graph_clique_number absent post-fc786a16). Rust binding for graph_clique_number remains internally for fnx-algorithms callers but is no longer imported into the Python wrapper. 136 tests across affected files pass.","source_repo":".","compaction_level":0,"original_size":0,"labels":["code-review","parity-gap"]}
9595
{"id":"br-r37-c1-dwasp","title":"Implement weighted all_shortest_paths for DiGraph","description":"mock-code-finder found a public stub: fnx.all_shortest_paths on DiGraph with weight set raises builtin NotImplementedError ('weighted all_shortest_paths is not yet supported for DiGraph' / bellman-ford variant), while upstream NetworkX returns all shortest directed weighted paths. Repro: DiGraph with edges 0->1, 0->2, 1->3, 2->3 all weight=1; list(fnx.all_shortest_paths(G, 0, 3, weight='weight')) raises, but nx returns [[0, 1, 3], [0, 2, 3]]. Implement directed weighted parity for dijkstra and bellman-ford methods and add tests.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-28T19:42:04Z","created_by":"codex","updated_at":"2026-04-28T19:43:53Z","closed_at":"2026-04-28T19:43:53Z","close_reason":"Routed directed weighted all_shortest_paths through the NetworkX parity oracle and added dijkstra/bellman-ford regression coverage.","source_repo":".","compaction_level":0,"original_size":0}
96+
{"id":"br-r37-c1-e04a1","title":"[code-review] no metamorphic guards on directed-graph distance metrics","description":"Audit: tests/python/test_review_mode_metamorphic.py covers undirected algorithms (complement involution, cycle_basis circuit rank, etc.) but has zero metamorphic guards on the DIRECTED distance-metric surface that was just patched in br-r37-c1-89n9d (center/periphery) and br-r37-c1-wojl3 (diameter/radius).\n\nA future regression that re-introduces the directed-collapse defect at the wrapper level (e.g., a refactor that re-routes via the buggy raw _fnx) would only be caught by the recently-added differential fuzz test_fuzz_directed_diameter_radius_center_periphery_oracle_match_nx (br-r37-c1-kitjs). That fuzz test compares against nx as oracle — but if BOTH libraries drift the same way (highly unlikely, but possible if nx is pinned to a vulnerable version), the fuzz silently passes.\n\nMetamorphic relations are a complementary guard that catches independent-of-nx drift. Concrete relations to add:\n\n 1. diameter(reverse(G)) == diameter(G) for strongly-connected DiGraphs.\n 2. radius(G) <= diameter(G) (fundamental graph-distance inequality).\n 3. center(G) == {n : ecc[n] == radius(G)} (definitional, but verifies cross-algo coherence).\n 4. periphery(G) == {n : ecc[n] == diameter(G)} (same).\n\nAll four hold by mathematical definition independent of any nx implementation; a wrapper that returns garbage for any one of them violates the relation.\n\nTagged P1 because the directed-distance-metric fix surface is fresh (last 60 min) and lacking metamorphic-test coverage means future regressions could land silently if both libs drift.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-05-04T22:56:34.823180065Z","created_by":"ubuntu","updated_at":"2026-05-04T22:57:05.327003147Z","closed_at":"2026-05-04T22:57:05.326783341Z","close_reason":"Shipped 88a10381 successor commit. Adds 4 metamorphic-property tests (20 cases total) for the directed distance-metric surface: diameter(reverse(G)) == diameter(G), radius <= diameter, center == ecc-minimizers, periphery == ecc-maximizers. Each holds by mathematical identity independent of any nx behavior, so they catch both-libs-drift cases that pure nx-oracle differential testing misses. 86 metamorphic tests pass total.","source_repo":".","compaction_level":0,"original_size":0}
9697
{"id":"br-r37-c1-ec18d","title":"Refactor 11 remaining fnx wrappers out of NX_DELEGATED","description":"11 public wrappers still classify as NX_DELEGATED:\n- 4 RCM ordering: cuthill_mckee_ordering, reverse_cuthill_mckee_ordering, connected_cuthill_mckee_ordering, pseudo_peripheral_node\n- 2 branchings: branching_weight, minimal_branching\n- 1 flow: flow_matrix_row\n- 1 matching: matching_dict_to_set\n- 3 misc: is_valid_tree_degree_sequence, geometric_soft_configuration_graph, random_uniform_k_out_graph\n\nEach currently imports nx + calls nx.* directly. Refactor each to use _xxx_via_nx private helper. Drops NX_DELEGATED count by 11 (15 → 4). The remaining 4 (draw_networkx_*) are nx re-exports — different category, out of scope.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T21:36:59.423898032Z","created_by":"ubuntu","updated_at":"2026-04-26T21:40:24.923599863Z","closed_at":"2026-04-26T21:40:24.921543268Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0}
9798
{"id":"br-r37-c1-emxwl","title":"[parity-gap] all_shortest_paths should defer target errors until generator iteration","description":"Audit source: suspicious commit 54ecea7a plus regression-lock commit 2eaa97e8. NetworkX all_shortest_paths returns a generator for missing-target and unhashable-target inputs, then raises when the generator is advanced. franken_networkx raises at call time, so inspect/generator-control code sees different behavior. Repro: PYTHONPATH=python python3 - <<'PY'\nimport inspect, networkx as nx, franken_networkx as fnx\nfor label, source, target in [('missing target', 0, 99), ('unhashable target', 0, [])]:\n print('CASE', label)\n for mod, graph in [(nx, nx.path_graph(3)), (fnx, fnx.path_graph(3))]:\n try:\n gen = mod.all_shortest_paths(graph, source, target)\n print(mod.__name__, 'call_ok', inspect.isgenerator(gen))\n except Exception as exc:\n print(mod.__name__, 'call_exc', type(exc).__name__, str(exc)); continue\n try: next(gen)\n except Exception as exc: print(mod.__name__, 'next_exc', type(exc).__name__, str(exc))\nPY\nExpected: missing source and invalid method still raise eagerly, but missing-target / unhashable-target errors occur on first iteration while the call returns a true generator.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-05-04T02:34:59.102047194Z","created_by":"ubuntu","updated_at":"2026-05-04T02:40:46.598286743Z","closed_at":"2026-05-04T02:40:46.598078359Z","close_reason":"Shipped in fc786a16. Target validation moved back inside the inner generator body — confirmed against nx 3.6.1 that target/unhashable errors are raised on next() not on call. test_all_shortest_paths_defers_target_errors_until_iteration parametrized over missing-target and unhashable-target now passes.","source_repo":".","compaction_level":0,"original_size":0,"labels":["code-review","parity-gap"]}
9899
{"id":"br-r37-c1-f0i2s","title":"Parity: negative_edge_cycle rejects weight=int / weight=None (Rust binding)","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-04-27T11:59:05.044584065Z","created_by":"ubuntu","updated_at":"2026-04-27T12:03:38.205445055Z","closed_at":"2026-04-27T12:03:38.202734573Z","source_repo":".","compaction_level":0,"original_size":0}
@@ -140,6 +141,7 @@
140141
{"id":"br-r37-c1-k9mhc","title":"[parity-gap] graph_clique_number exists in fnx but was removed from nx (3.6+)","description":"fnx.graph_clique_number returns int (the size of the maximum clique). nx 3.6.1 removed this function entirely — accessing nx.graph_clique_number raises AttributeError because the deprecation period ended. The function was relocated/deprecated in favour of computing it inline as max(len(c) for c in nx.find_cliques(G)).\n\nRepro:\n >>> import networkx as nx, franken_networkx as fnx\n >>> nx.__version__\n '3.6.1'\n >>> hasattr(nx, 'graph_clique_number')\n False\n >>> fnx.graph_clique_number(fnx.complete_graph(4))\n 4\n\nDrop-in code that does try: nx.graph_clique_number except AttributeError: ... (defensive deprecation handling) won't trigger the except branch on fnx.\n\nThree resolution options:\n 1. Keep with a DeprecationWarning, mirroring nx's deprecation policy\n 2. Remove entirely, matching strict-parity contract\n 3. Leave as fnx-extension (current state)\n\nRecommendation: option 1 — emit DeprecationWarning on call so users get migration nudge, then remove in a future release.\n\nAudit-found 2026-05-03 review-mode pass.","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-05-04T02:09:27.059384262Z","created_by":"ubuntu","updated_at":"2026-05-04T02:10:53.871358963Z","closed_at":"2026-05-04T02:10:53.871142704Z","close_reason":"Shipped. fnx.graph_clique_number now emits DeprecationWarning matching nx's deprecation policy (nx 3.6 removed it). Backwards-compat preserved; migration path documented. 220 clique tests pass.","source_repo":".","compaction_level":0,"original_size":0}
141142
{"id":"br-r37-c1-kcue6","title":"[code-review] pagerank fast path ignores max_iter non-convergence","description":"[HIGH] fnx.pagerank uses the Rust fast path for simple non-negative graphs but pagerank_with_weight always returns a PageRankResult after the iteration loop, even when no convergence criterion was met. Repro: fnx.pagerank(fnx.path_graph(3), max_iter=0) returns uniform scores, while networkx.pagerank(nx.path_graph(3), max_iter=0) raises PowerIterationFailedConvergence. Similarly a directed graph with max_iter=1 and tol=1e-20 returns partial scores in fnx but raises in NetworkX. Fix should propagate convergence state from the Rust PageRank implementation to the PyO3 wrapper and raise PowerIterationFailedConvergence(max_iter) on exhaustion.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-05-03T01:44:38.043043317Z","created_by":"ubuntu","updated_at":"2026-05-03T02:06:10.815316791Z","closed_at":"2026-05-03T02:06:10.813150693Z","close_reason":"Fixed convergence check","source_repo":".","compaction_level":0,"original_size":0,"labels":["bug","code-review"]}
142143
{"id":"br-r37-c1-kilv4","title":"test_stochastic_block_model_default_uses_native_fast_path asserts wrong contract since br-sbmrng","description":"Pre-existing failure in test_sbm_generators.py::test_stochastic_block_model_default_uses_native_fast_path: asserts that fnx.stochastic_block_model calls _rust_stochastic_block_model (the native Rust fast path).\n\nBut br-sbmrng made fnx.stochastic_block_model delegate to nx for exact parity — fnx's native fast path produced ~25% of nx's edge count for symmetric block matrices, and the Python fallback also diverged from nx's geometric-skip sampling. So fnx now goes through nx via _sbm_impl, never touching the Rust binding.\n\nThe test is stale. Fix: drop the test (asserts the pre-br-sbmrng contract). The companion test_stochastic_block_model_fallback_preserves_nodelist_and_attrs already verifies the delegation path produces correct output.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T20:28:22.255837080Z","created_by":"ubuntu","updated_at":"2026-04-26T20:30:50.693534025Z","closed_at":"2026-04-26T20:30:50.691421874Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0}
144+
{"id":"br-r37-c1-kitjs","title":"[code-review] _fnx.diameter/radius/center/periphery directed-collapse: 9 mismatches per algorithm in fuzz audit","description":"Audit: ran a 30-seed differential fuzz against nx oracle on the raw _fnx Rust functions. Restricted to strongly-connected random DiGraphs (so distance metrics are well-defined). Confirms br-r37-c1-89n9d / br-r37-c1-wojl3 finding at scale:\n\n Algorithms with directed-graph mismatches (out of 9 strongly-connected seeds tested):\n diameter: 9/9 mismatches (e.g. seed=0: nx=5 fnx_raw=3)\n periphery: 9/9 mismatches\n center: 9/9 mismatches\n transitivity: 9/9 mismatches\n radius: 4/9 mismatches\n\nAll four distance-metric Rust functions call gr.undirected() before computing, so antiparallel directed edges collapse and metrics are computed on the wrong projection. The Python wrappers fnx.diameter, fnx.radius, fnx.center, fnx.periphery were patched in br-r37-c1-89n9d (center/periphery) and br-r37-c1-wojl3 (diameter/radius) to bypass this by reusing fnx.eccentricity (already directed-aware). But the Rust functions themselves are still broken; backend.py and direct _fnx callers still see the wrong answers.\n\nThe transitivity divergence is a separate finding: nx.transitivity on a DiGraph counts directed triangles (paths u->v->w->u), while _fnx.transitivity probably collapses to undirected count. Worth a follow-up bead but separate from this fuzz lock.\n\nConcrete fix plan (this tick):\n 1. Add fuzz-test test_fuzz_directed_diameter_radius_center_periphery_oracle_match_nx parametrized over 25 random seeds. Tests the Python WRAPPER (which is correct after the prior fixes) against nx — locks the contract independently of the underlying Rust binary state.\n 2. Defer the underlying Rust _fnx fix to a future tick (requires touching the Rust planar/distance code).\n\nTagged P1 because backend dispatch or direct _fnx callers still see wrong answers; the fuzz lock is the minimum guard until the Rust fix lands.","status":"in_progress","priority":1,"issue_type":"bug","created_at":"2026-05-04T22:43:23.623449140Z","created_by":"ubuntu","updated_at":"2026-05-04T22:43:44.775212217Z","source_repo":".","compaction_level":0,"original_size":0}
143145
{"id":"br-r37-c1-kp6aw","title":"Refactor 13 utility/MST/cc funcs out of NX_DELEGATED","description":"Continue NX_DELEGATED reduction. Refactor 13 simple delegating wrappers:\n- 3 equality utils: edges_equal, graphs_equal, nodes_equal\n- 4 utils: make_list_of_ints, powerlaw_sequence, zipf_rv, cumulative_distribution\n- 3 cc_* bipartite: cc_dot, cc_max, cc_min\n- 3 MST edge generators: boruvka_mst_edges, kruskal_mst_edges, prim_mst_edges\n\nEach currently imports nx + calls into nx.* directly. Refactor to use _xxx_via_nx private helpers (or yield_from_nx for generators). Drops NX_DELEGATED count by 13 (28 → 15).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T21:26:19.820096716Z","created_by":"ubuntu","updated_at":"2026-04-26T21:28:56.061388756Z","closed_at":"2026-04-26T21:28:56.059280804Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0}
144146
{"id":"br-r37-c1-kpnc8","title":"max_weight_matching tuple direction + matching choice drift from nx","description":"fnx.max_weight_matching returns matched pairs with different tuple directions than nx, and on graphs where multiple equally-optimal matchings exist, picks a different one.\n\nRepro:\n edges = [(0,1),(2,3),(4,5)] (no shared nodes)\n fnx -> {(0,1), (2,3), (4,5)}\n nx -> {(1,0), (3,2), (5,4)} <- tuple direction reversed\n\n edges = [('a','x'),('a','y'),('b','x'),('b','z'),('c','y'),('c','z')]\n fnx -> {('a','y'),('b','x'),('c','z')}\n nx -> {('a','x'),('b','z'),('c','y')} <- different valid matching\n\nDrop-in code that compared the matching to a reference {(u,v), ...} pairs broke. Both implementations return correct max-weight matchings, but nx returns each pair (v,u) in a specific direction tied to its DFS-augmenting-path traversal, and the choice between equally-weighted matchings depends on adj-iteration order.\n\nFix: delegate max_weight_matching to nx via _call_networkx_for_parity (mirrors the existing pattern for articulation_points / find_cliques / cycle_basis).","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T14:38:55.459659876Z","created_by":"ubuntu","updated_at":"2026-04-26T14:42:53.230854933Z","closed_at":"2026-04-26T14:42:53.228893940Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0}
145147
{"id":"br-r37-c1-kw1ke","title":"parity: lexicographical_topological_sort silently returns partial order on cyclic graph (nx raises NetworkXUnfeasible)","description":"fnx.lexicographical_topological_sort(cyclic_graph) silently yields the prefix of nodes that drain into the cycle and then stops. nx.lexicographical_topological_sort raises NetworkXUnfeasible('Graph contains a cycle or graph changed during iteration'). Drop-in code that catches the exception to detect cycles fails on fnx — silent partial result masks the contract violation. Fix: after the heap-driven loop completes, check that all nodes were yielded; if not, raise NetworkXUnfeasible with nx's exact message.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-04-26T09:18:52.515777528Z","created_by":"ubuntu","updated_at":"2026-04-26T09:20:58.500855578Z","closed_at":"2026-04-26T09:20:58.498875989Z","close_reason":"Fixed in commit 87e35fa. lexicographical_topological_sort now raises NetworkXUnfeasible on cyclic input matching nx exactly. 9 parity tests freeze the contract.","source_repo":".","compaction_level":0,"original_size":0}

0 commit comments

Comments
 (0)