Skip to content

build_from_json doesn't canonicalize name/path/type/confidence_score aliases, so schema errors in a persisted graph.json are permanent and hide nodes from query #2194

Description

@drmzperx

Summary

build_from_json() canonicalizes some legacy field aliases before validation, but not others. Nodes and edges persisted in a graph.json that use the unhandled aliases fail validation on every subsequent build, and because graphify update merges from graph.json, the errors are permanent — no amount of re-running fixes them.

Worse than the warning noise: affected nodes end up with norm_label == "", which makes them unmatchable by query/explain.

Version

Reproduced on 0.8.13. The gap is still present in current main (0.9.26) — verified by reading graphify/build.py and graphify/validate.py from this repo, not by re-running, so the reproduction numbers below are from 0.8.13.

What happens

A real graph (Java/Spring backend, 9,019 nodes / 17,394 edges) emitted this on every build:

[graphify] Extraction warning (1911 issues): Node 8537 (id='repo:PIFAnnexAIVerdictRepository') missing required field 'label'

Breaking the 1,911 errors down:

count error
864 missing source_file (496 nodes + 368 edges)
370 node missing label
368 edge missing confidence
309 edge missing relation

Every one of the 370 label-less nodes had file_type: "concept" and carried the data under a different key:

validate.py requires what was persisted
node.label node.name (345 nodes; the other 25 only had id)
node.source_file node.path (149 nodes)
edge.relation edge.type (309 edges — all 309 had type)
edge.confidence edge.confidence_score (float; all 368 had it)

Where the gap is

build.py's canonicalization block runs before validate_extraction() and already handles several aliases:

It does not handle namelabel, pathsource_file, typerelation, or confidence_scoreconfidence. Since REQUIRED_NODE_FIELDS = {"id", "label", "file_type", "source_file"}, those nodes can never validate.

Why it matters

  1. Self-perpetuating. graphify update rebuilds AST nodes but merges concept nodes straight through from graph.json. Once a semantic pass writes these aliases, every future run re-reports the same errors forever. The only escape today is hand-editing graph.json or a full (paid) semantic rebuild.
  2. Silent functional loss, not just noise. export.py computes norm_label = _strip_diacritics(node.get("label", "")).lower(). With no label that is "", and serve.py's matching falls back to norm_label — so all 370 nodes (Repositories, Entities, Services) were invisible to query and explain. The graph looked healthy; the nodes were simply unreachable.
  3. The one-line warning shows only real_errors[0], so the scale (1,911 errors spanning four distinct causes) isn't visible without inspecting graph.json manually.

Suggested fix

Extend the existing canonicalization block to fold these aliases the same way sourcesource_file is already folded:

if "label" not in node and node.get("name"):
    node["label"] = node["name"]
if "source_file" not in node and node.get("path"):
    node["source_file"] = node["path"]

and for edges, typerelation.

confidence_scoreconfidence is the one that needs a project decision rather than a mechanical mapping: turning a float into the EXTRACTED/INFERRED/AMBIGUOUS audit enum is a claim about provenance. Given the honesty rules in the skill doc, defaulting alias-recovered edges to INFERRED (never EXTRACTED) seems the defensible choice, keeping the original float alongside.

It would also help if the warning reported a per-cause breakdown rather than only the first error, so a 4-cause / 1,911-error graph is diagnosable from the console output.

Workaround

For anyone hitting this: the aliases can be folded onto the canonical names directly in graph.json — 1,911 → 0 errors, with node/edge counts unchanged — after which norm_label repopulates and the nodes become queryable again.

Related

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