Skip to content

Same-pair same-relation collision downgrades an EXTRACTED edge to INFERRED and rewrites its provenance #3703

Description

@fa137

What happened?

When two edges land on the same node pair with the same relation, the guard added for #2803 cannot fire, so G.add_edge() overwrites and the last write wins. Because semantic edges are always concatenated after AST edges, a semantic INFERRED edge silently overwrites an AST EXTRACTED edge and takes its source_file / source_location with it.

Expected: on a same-pair, same-relation collision, the higher-confidence edge should survive, and confidence should never be downgraded from EXTRACTED to INFERRED.

Actual: the surviving edge reports confidence: INFERRED, the semantic confidence_score, and the doc's source_file. Nothing in the output records that a deterministic AST edge existed.

Steps to reproduce

from graphify.build import build_from_json

extraction = {
    "nodes": [{"id": "a", "label": "A"}, {"id": "b", "label": "B"}],
    "edges": [
        # AST edge first — the skill pipeline builds `ast['edges'] + sem['edges']`,
        # so AST edges always come first for the same pair.
        {"source": "a", "target": "b", "relation": "calls", "confidence": "EXTRACTED",
         "confidence_score": 1.0, "source_file": "a.py", "source_location": "L724"},
        # semantic edge second, SAME pair and SAME relation
        {"source": "a", "target": "b", "relation": "calls", "confidence": "INFERRED",
         "confidence_score": 0.95, "source_file": "some_note.md", "source_location": None},
    ],
}
G = build_from_json(extraction)
print(G.get_edge_data("a", "b"))

Reversing the two edges in the list does not help either, because add_edge is still last-write-wins and the caller controls the order.

Error output or graph output

surviving edge: {'relation': 'calls', 'confidence': 'INFERRED', 'confidence_score': 0.95,
                 'source_file': 'some_note.md', 'source_location': None}

Expected:

surviving edge: {'relation': 'calls', 'confidence': 'EXTRACTED', 'confidence_score': 1.0,
                 'source_file': 'a.py', 'source_location': 'L724'}

Observed on a real 2019-node corpus. api/app.py:724 is broker = MockTDAAccount(...). AST extraction emits it correctly:

$ graphify ...  # AST side
api_app_build_app -- calls -> broker_mock_tda_mocktdaaccount | EXTRACTED | loc L724

But graph.json contains only this edge for that pair:

{"relation": "calls", "confidence": "INFERRED", "confidence_score": 0.95,
 "source_file": "graphify-out/memory/query_20260920_232355_....md", "source_location": null}

So GRAPH_REPORT.md reports a relationship stated in plain code as an inference guessed from one of graphify's own internal memory files, and the EXTRACTED fact is unrecoverable from graph.json.

Scale on that corpus: 1 of 3701 edges for this exact case, but 27 pairs total where an EXTRACTED edge was replaced by a non-EXTRACTED one (the other 26 are covered in the follow-up issue below).

Graphify version

0.9.61. Re-verified against the v0.9.65 sources: graphify/build.py L1415-1423 and _GENERIC_RELATIONS (L64) are unchanged, so the defect is present in the latest release.

Environment

macOS 15 (darwin arm64), Python 3.12, uv tool install graphifyy, /graphify invoked from an OpenCode session.

Additional context

Related and distinct:

Suggested direction: before G.add_edge, compare the incoming and existing edges and keep the stronger fact rather than the later write, e.g. rank by EXTRACTED > INFERRED > AMBIGUOUS, and on an equal-confidence tie prefer the AST-sourced edge over a semantic one. The _GENERIC_RELATIONS logic can then layer on top of that instead of standing in for it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions