Skip to content

feat(neo4j)!: schema v2 graph projection; v2 becomes the default schema (3.0.0) #198

Description

@rahlk

Plan (optional)

Spec: docs/design/specs/2026-08-27-v2-neo4j-projection.md — all design decisions, the full graph vocabulary, and the release plan live there.

Problem

--emit neo4j is refused at --schema v2 (CodeAnalyzer.analyzeV2, guard at src/main/java/com/ibm/cldk/CodeAnalyzer.java:379-382); the Neo4j projection consumes the v1 model only, so nothing L3 produces can reach a graph. Per the accepted spec this issue also flips --schema to default to v2 everywhere (json and neo4j) — a breaking CLI change shipping as 3.0.0 — and makes --emit schema always emit the v2 graph catalog (2.0.0).

Scope boundary

Projects the canonical v2 model; does not change the v2 JSON schema. --schema v1 retains the legacy JSON and legacy graph byte-identical to 2.4.x. python-sdk's compat change (pin bump + explicit --schema v1) is a separate work item on that repo, shielded meanwhile by its exact 2.4.1 pin. No L4 facts; J_PARAM_IN/OUT/J_SUMMARY are declared in the catalog only.

Goals

  • V2GraphProjector: pure (Analysis, appName) → GraphRows per the spec vocabulary (JSymbol merge label, JBodyNode convergence, parameters_json/docstring flattening)
  • L3 overlay edges J_HAS_BODY_NODE, J_CFG_NEXT (kind, _k), J_CDG, J_DDG (var, prov, _k); RowBuilder gains the optional edge _k MERGE discriminant
  • --emit neo4j at v2: full-depth forced (L3 + external symbols); -a/--graph-field-depth alongside it exit non-zero
  • Wipe preamble covers v1 + v2 containment rels so a v2 push replaces a same-app v1 graph
  • Bolt incremental push works against the v2 rows (content_hash diff unchanged)
  • --schema default flips to v2; version to 3.0.0
  • --emit schema emits the 2.0.0 catalog; repo-root schema.neo4j.json regenerated; conformance test byte-matches it

Caveats and known risks

  • L3 body-node volume (~30k+ rows on a mid-size app): mitigated by the existing UNWIND batching (500/file, 1000/bolt).
  • Same-label replacement means one app name holds one graph generation; mixed-version fleets writing the same DB must coordinate — accepted, stamped via JApplication.schema_version.
  • Python reference has two wipe bugs (lists removed PY_HAS_CALLSITE, omits PY_HAS_BODY_NODE) — deliberately not copied; java's wipe includes J_HAS_BODY_NODE.
  • Default flip breaks any caller parsing default stdout as v1 — that is the point of the major bump; release notes must say so.

Definition of done

  • codeanalyzer -i <app> --emit neo4j -o out/ produces graph.cypher that loads into Neo4j 5 via cypher-shell with zero errors, twice (idempotent)
  • Loaded counts equal analysis.json counts exact-set: modules, types, callables, body nodes, J_CALLS/J_CFG_NEXT/J_CDG/J_DDG edge counts (modulo containment edges)
  • Pushing a v2 graph over an existing v1 graph of the same app leaves zero v1-only nodes (JCompilationUnit, JCallSite, JParameter, JComment count 0 for that app)
  • --schema v1 --emit json and --schema v1 --emit neo4j byte-identical to 2.4.x output on the same input
  • -a 2 --emit neo4j exits non-zero with the depth-rule error
  • Conformance test: checked-in schema.neo4j.json byte-matches --emit schema; every projected label/rel/prop is declared

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