Skip to content

fix(export): tolerate id-less persisted hyperedges in attach_hyperedges (#2775) - #2776

Closed
ousamabenyounes wants to merge 1 commit into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2775
Closed

fix(export): tolerate id-less persisted hyperedges in attach_hyperedges (#2775)#2776
ousamabenyounes wants to merge 1 commit into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2775

Conversation

@ousamabenyounes

Copy link
Copy Markdown
Contributor

Summary

Fix #2775 — an incremental re-extract died at the merge step with KeyError: 'id'.

attach_hyperedges (graphify/export.py) seeded its dedup set from the
previously-persisted hyperedges with a hard subscript:

seen_ids = {h["id"] for h in existing}

The semantic/LLM extractor routinely emits hyperedges as
{"nodes": [...], "type": "...", "attributes": {...}} with no id, and
build.py persists them verbatim. So a prior graph.json can — and in practice
does — contain id-less hyperedges. The first graphify extract on a corpus has
no prior graph to read back and succeeds; every incremental re-extract
afterwards
hits that comprehension and raises, writing nothing. Because the
first run works, it looks like flaky extraction; it is deterministic.

The loop directly below already tolerates id-less entries in the incoming set
via .get("id"). This change makes the read symmetric — it skips id-less
persisted entries when seeding the dedup set:

seen_ids = {h["id"] for h in existing if h.get("id")}

Scope is intentionally the crash only: the pre-existing behavior of the loop
(id-bearing incoming hyperedges are deduped and appended; id-less incoming are
skipped) is unchanged, and id-less persisted entries are retained in the graph.

Test verification (RED → GREEN)

New regression test test_attach_hyperedges_tolerates_id_less_persisted seeds an
id-less persisted hyperedge — the exact input the semantic extractor produces.

RED — on the unmodified v8 code (prod fix reverted):

tests/test_hypergraph.py:148: in test_attach_hyperedges_tolerates_id_less_persisted
    attach_hyperedges(G, [{"id": "flow_a", "label": "Flow A", "nodes": ["A", "B"]}])
graphify/export.py:166: in attach_hyperedges
    seen_ids = {h["id"] for h in existing}
E   KeyError: 'id'
1 failed in 1.72s

GREEN — with the fix:

tests/test_hypergraph.py::test_attach_hyperedges_tolerates_id_less_persisted PASSED
21 passed in 0.58s   # full tests/test_hypergraph.py

Full suite is unchanged aside from the new passing test (pytest tests/
4514 passed, 7 skipped; the 3 test_ollama::test_detect_backend_* failures are
pre-existing on v8 and unrelated — backend env-var detection). skillgen --check is clean; no generated skill files touched.

Files changed

File Change
graphify/export.py attach_hyperedges: skip id-less persisted entries when seeding the dedup set
tests/test_hypergraph.py regression test for id-less persisted hyperedges
CHANGELOG.md entry under 0.9.44

…es (Graphify-Labs#2775)

The semantic extractor emits hyperedges with no `id` and build.py persists
them verbatim, so a prior graph.json can carry id-less hyperedges. Seeding the
dedup set with a hard `h["id"]` raised KeyError: 'id' on every incremental
re-extract, symmetric with the .get("id") guard already applied to the incoming
set. Skip id-less persisted entries when seeding instead.

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

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

This PR fixes a KeyError: 'id' that occurred in attach_hyperedges (in graphify/export.py) during incremental re-extraction when a prior graph.json contained hyperedges lacking an id field. The dedup-set construction now filters persisted entries to only those with a truthy id, mirroring the existing guard applied to incoming hyperedges. A new regression test (test_attach_hyperedges_tolerates_id_less_persisted) and a CHANGELOG entry are added to cover this scenario. The touched surface is limited to the one-line dedup change, the test file, and documentation.

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 653 functions depend on the 260 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 95 callers, 51 callees
  • new: build_merge() — 41 callers, 14 callees
  • new: to_obsidian() — 29 callers, 12 callees
  • new: to_json() — 44 callers, 6 callees
  • new: dispatch_command() — 2 callers, 117 callees
  • new: _make_graph() — 30 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: to_canvas() — 17 callers, 4 callees
  • …and 4 more

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

Formal verification

No difference found (not proven): No behavior difference found in attach\_hyperedges (not a proof).

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

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

@safishamsi

Copy link
Copy Markdown
Collaborator

Landed in v0.9.45, just published to PyPI. Cherry-picked onto v8 with your authorship preserved in the commit, so both the change and the credit are in the history. Thanks @ousamabenyounes. Closing since it is now released.

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.

Incremental extract dies at merge with KeyError: 'id' — attach_hyperedges reads persisted hyperedges that the writer never gave an id (0.9.41)

2 participants