fix(build): normalize legacy numeric edge confidences to INFERRED on load

A pre-enum graph.json stored a numeric edge confidence (a float) instead of a
string tag, which tripped the per-edge 'invalid confidence' validator warning on
every incremental reload. _fold_edge_aliases now moves a numeric confidence into
confidence_score (when none is present) and sets the tag to INFERRED — never
EXTRACTED, since a recovered legacy float is not structural provenance. Modern
string tags are untouched (no churn) and the raw float survives in
confidence_score.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trantor-develops
2026-08-18 15:54:27 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent 558df6d57d
commit 5ad221c3f4
2 changed files with 70 additions and 0 deletions
+14
View File
@@ -210,9 +210,23 @@ def _fold_edge_aliases(edge: dict) -> None:
is not provenance) and never a threshold mapping of the float. The
``confidence_score`` key itself is NOT popped: it is a legitimate companion
field that the edge loop sanitizes and to_json round-trips.
A NUMERIC ``confidence`` (pre-enum graphs stored the LLM pass's float —
1.0/0.95/0.9/0.85 — directly in the field) normalizes to ``INFERRED``:
numeric confidences only ever came from the LLM semantic pass, and
LLM-derived edges are INFERRED by definition. The original float moves to
``confidence_score`` unless an explicit one is already present (the
companion field is the authority). Without this fold, every reload of a
pre-enum graph re-warns once per legacy edge, forever. ``bool`` is
excluded despite subclassing ``int``: ``True`` is not a score.
"""
if not edge.get("relation") and isinstance(edge.get("type"), str) and edge["type"]:
edge["relation"] = edge.pop("type")
_conf = edge.get("confidence")
if isinstance(_conf, (int, float)) and not isinstance(_conf, bool):
if edge.get("confidence_score") is None:
edge["confidence_score"] = float(_conf)
edge["confidence"] = "INFERRED"
if not edge.get("confidence") and edge.get("confidence_score") is not None:
edge["confidence"] = "INFERRED"
+56
View File
@@ -172,6 +172,62 @@ def test_legacy_edge_type_confidence_score_aliases_folded():
assert "type" not in data
def test_legacy_numeric_confidence_normalized_to_inferred(capsys):
"""Pre-enum graphs stored the LLM pass's float directly in `confidence`
(1.0/0.95/0.9/0.85). Reloading such a graph must not warn once per edge on
every load — the number normalizes to INFERRED (numeric confidences only
ever came from the LLM semantic pass, and LLM-derived edges are INFERRED
by definition) with the original float preserved in confidence_score."""
ext = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"},
{"id": "n3", "label": "C", "file_type": "code", "source_file": "c.py"}],
"edges": [{"source": "n1", "target": "n2", "relation": "calls",
"confidence": 0.95, "source_file": "a.py"},
{"source": "n2", "target": "n3", "relation": "references",
"confidence": 1.0, "source_file": "b.py"}],
"input_tokens": 0, "output_tokens": 0}
G = build_from_json(ext)
err = capsys.readouterr().err
assert "invalid confidence" not in err
assert "Extraction warning" not in err
d12 = edge_data(G, "n1", "n2")
assert d12["confidence"] == "INFERRED"
assert d12["confidence_score"] == 0.95
d23 = edge_data(G, "n2", "n3")
assert d23["confidence"] == "INFERRED"
assert d23["confidence_score"] == 1.0
def test_legacy_numeric_confidence_existing_score_wins():
"""A numeric `confidence` next to an explicit `confidence_score` must not
overwrite the explicit score — the companion field is the authority."""
ext = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"}],
"edges": [{"source": "n1", "target": "n2", "relation": "calls",
"confidence": 0.9, "confidence_score": 0.4, "source_file": "a.py"}],
"input_tokens": 0, "output_tokens": 0}
G = build_from_json(ext)
data = edge_data(G, "n1", "n2")
assert data["confidence"] == "INFERRED"
assert data["confidence_score"] == 0.4
def test_legacy_numeric_confidence_links_spelling_reload(capsys):
"""The on-disk shape of the defect: a NetworkX-serialized graph.json
(`links` spelling) from a pre-enum version reloads without a validator
warning per edge and its edges land INFERRED."""
raw = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"}],
"links": [{"source": "n1", "target": "n2", "relation": "calls",
"confidence": 0.85, "source_file": "a.py"}]}
G = build_from_json(raw)
err = capsys.readouterr().err
assert "invalid confidence" not in err
data = edge_data(G, "n1", "n2")
assert data["confidence"] == "INFERRED"
assert data["confidence_score"] == 0.85
def test_node_alias_canonical_field_wins():
"""#2194: when both the canonical field and its alias are present, the
canonical value wins and the alias key is left untouched."""