Skip to content

Report edge loss on rebuild: the node-only shrink guard misses a 12% edge drop - #2898

Open
Ymx1ZQ wants to merge 1 commit into
Graphify-Labs:v8from
Ymx1ZQ:edge-loss-guard
Open

Report edge loss on rebuild: the node-only shrink guard misses a 12% edge drop#2898
Ymx1ZQ wants to merge 1 commit into
Graphify-Labs:v8from
Ymx1ZQ:edge-loss-guard

Conversation

@Ymx1ZQ

@Ymx1ZQ Ymx1ZQ commented Aug 20, 2026

Copy link
Copy Markdown

The node-only shrink guard passes a rebuild that loses 12% of its edges

to_json's #479 guard refuses to write when the new graph has fewer nodes
than the existing one. It does not look at edges, so a rebuild that gains nodes
and loses relations is written silently.

Measured on a real corpus:

nodes  2,100 -> 2,172   (+72)
edges  3,378 -> 2,978   (-400, -11.8%)

The guard passed. The graph still answers, but every traversal query returns a
shorter path or none, and nothing in the output says why.

What this changes

Step 4 reads the previous graph's node and edge counts before the write, and
after the write reports an edge drop as a proportion, alongside the node delta.

Reported, not fatal, and that is deliberate. An edge drop can be legitimate:
files deleted, a noisy extractor tightened. A guard that aborted here would be
switched off during exactly the rebuild it should be watching. It prints at the
point of writing rather than leaving it to a later step, so the number is in
front of whoever is looking at the rebuild.

When nodes did not shrink, it adds the diagnosis, because that case has one
common cause worth naming: node ids that are not stable across passes, so cached
edges point at nodes the new pass renamed.

Which key it reads

to_json writes node_link_data(G, edges="links"), with a TypeError fallback
to the networkx default. The read takes links first and falls back to edges,
so it works against both shapes. Reading only edges returns 0 on every graph
the primary path writes, and the guard would never fire.

Where the change lives

In tools/skillgen/fragments/core/{core,aider,devin}.md, so all sixteen
committed skill bodies get it from one edit. graphify/skill*.md and
tools/skillgen/expected/ in this diff are regenerated output
(python -m tools.skillgen then --bless).

aider and devin are monoliths under --monolith-roundtrip, which requires
every added line to match a documented change-class, so this adds one predicate,
_is_edge_loss_guard_fix_line, alongside the existing ones. If you would rather
the monoliths stay frozen, dropping that predicate and reverting those two
fragments leaves the other fourteen platforms covered.

Verified locally: --check, --audit-coverage, --schema-singleton,
--monolith-roundtrip and --always-on-roundtrip all pass, the sixteen
rendered Python bodies compile, and the guard was exercised against a stubbed
graph for six cases — the measured regression under both key shapes, an edge
gain, a legitimate corpus shrink, a first build, and a corrupt graph.json
(which must not crash the build).

Why it is in the skill body rather than in to_json

Following the existing #479 guard, which lives there too. If you would rather
have it inside build.to_json as a returned warning, say which shape you prefer
and I will re-cut the PR.

🤖 Generated with Claude Code

…edge drop

to_json's Graphify-Labs#479 guard refuses to write when the new graph has fewer nodes than
the existing one, and does not look at edges. A rebuild that gains nodes and
loses relations is written silently: measured on a real corpus, 2,100 -> 2,172
nodes (+72) while edges went 3,378 -> 2,978 (-11.8%), and the guard passed.

Step 4 now reads the previous graph's node and edge counts before the write and
reports an edge drop as a proportion after it. Reported, not fatal: an edge drop
can be legitimate, and a guard that aborted here would be switched off during
exactly the rebuild it should be watching.

The read takes the 'links' key first and falls back to 'edges', because to_json
writes node_link_data(G, edges="links") with a TypeError fallback to the
networkx default. Reading only 'edges' returns 0 on every graph the primary path
writes.

Edited in tools/skillgen/fragments/core/{core,aider,devin}.md; the sixteen
graphify/skill*.md bodies and tools/skillgen/expected/ are regenerated output.
aider and devin are monoliths under --monolith-roundtrip, so this adds one
sanctioned change-class predicate, _is_edge_loss_guard_fix_line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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.


Graphify review — findings

Adds an edge-loss check to the graph-persistence step across all skill-*.md variants (agents, aider, amp, claw, codex, devin, windows, copilot, etc.) and their expected/fragment fixtures. Reads the previous graph.json node/edge counts before overwrite, tolerating both links and edges keys, and prints a non-fatal EDGE LOSS warning when edges drop while nodes hold or grow — closing the gap where the #479 node-only shrink guard passes silently.

Worth a look

  • Overly-broad sanctioned-diff predicate matches unrelated try/except linestools/skillgen/gen.py:1178 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Unqualified try:/except Exception: predicate over-broadly sanctions any bare try/excepttools/skillgen/gen.py:1178 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Edge-loss allowlist accepts unrelated try/except linestools/skillgen/gen.py:1186 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1096 functions depend on the 1032 functions this change touches.

Health — this change adds coupling hotspots:

  • new: render() — 13 callers, 5 callees
  • new: audit_coverage() — 8 callers, 6 callees
  • new: main() — 3 callers, 11 callees
  • new: monolith_roundtrip() — 3 callers, 5 callees
  • new: test_audit_catches_a_dropped_non_allowlisted_heading() — 0 callers, 6 callees

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

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

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.

1 participant