Skip to content

Schema-v2 migration hazard: Neo4j code property must be derived from source spans, not a model field #176

Open

Description

@rahlk

Is your feature request related to a problem? Please describe.

Heads-up filed from codellm-devkit/codeanalyzer-python#104 (propagation sweep).

When this analyzer migrates to canonical schema v2, the per-node code field moves off JCallable/JType (source is stored once per compilation unit, sliced by byte spans). GraphProjector currently reads c.getCode() directly (e.g. around the callable/type row builders) and Schema.java builds the j_code_fts fulltext index over c.code/c.docstring.

If the migration drops the model field without changing the projector, every :JCallable/:JType node silently loses code — the fulltext index goes dead and any SDK RETURN c.code query returns null. This exact failure shipped in codeanalyzer-python 1.0.0/1.0.1 and was fixed in 1.0.2 by deriving code at projection time from the owning module's source and the node's byte span (codellm-devkit/codeanalyzer-python#105).

Action for the v2 migration checklist: keep the graph's code property and derive it at projection time (span slice), or explicitly drop it from the SchemaCatalog + j_code_fts and bump the Neo4j SCHEMA_VERSION. Declared-but-null is the one option that breaks consumers silently.

Describe the solution you'd like

Not stated in the original issue.

Describe alternatives you've considered

Not stated in the original issue.

Activity

  1. rahlk commented on Sep 6, 2026

    @rahlk
    CollaboratorAuthor

    The hazard materialised in 3.0.1/3.0.2, though not as a null. code is still projected, so the fulltext index is alive — but its value changed meaning relative to v1, and the graph now disagrees with the analyzer's own JSON.

    Measured while porting the python-sdk Java Neo4j backend onto the 3.0.1 vocabulary (daytrader8, --emit neo4j, contract 2.0.0):

    • :JCallable.code in the graph is the declaration slice, beginning at the callable's span (so @Override\n public void cancelOrder().
    • JCallable.code derived from the JSON is the body block, sliced from body_span ({\n\n Connection conn = null; … \n }), which is what v1's per-callable code field held and therefore what v1's graph held too, since the projector read that field.
    • The two are related but not equal: graph_code.endswith(json_code) holds on all 1,117 callables that have a body, and they are identical on the 129 that do not.

    Two knock-on effects for consumers:

    • :JCallable carries neither body_span nor any span beyond start_line/end_line, and :JModule carries no source, so a graph-only consumer cannot recover the body block. It is not a lossy-but-derivable difference; the information is absent.
    • Anything that indexes into code by line drifts by the declaration prefix. In our parity run, code_start_line matched on 818 of 1,216 callables and tree-sitter-derived calling lines matched on 1,299 of 1,862 call-graph edges; every mismatch is a multi-line signature or an annotated declaration.

    Suggested resolution, in preference order: slice the projected code from body_span so it means what the JSON's code means, or project body_span (and ideally the module source) so a consumer can slice it themselves. Either makes the two backends agree; the current shape forces every SDK to document a divergence it cannot close.

    Unrelated to this issue but found in the same pass, in case it is useful: :JBodyNode ids end in an L:C that is a key, not a position — it equals the node's span.start column on only 629 of 4,006 call nodes, with deltas up to 12 — and the projection carries no column property, so call-site columns cannot be reconstructed from the graph at all. And LOCKS is in the contract but has zero relationships in a Maven projection, so locked_version is always null there.

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