Spec: docs/design/specs/2026-09-05-body-node-id-in-analysis-json.md
Summary
Every body node and every parameters entry gains an id in analysis.json: the global ordinal <callable-id>@<local> that all three Neo4j projections already write as their body-node merge key. Schema impact: one additive field on two spine node shapes; schema_version stays 2.0.0; the graph contract is unchanged. This closes the gap where a node is addressable in Neo4j and anonymous in JSON, so the SDK stops recomposing the can:// grammar itself (python-sdk#320 composed it on signature and joins to nothing).
Affected repos
- codeanalyzer-python — emits the field (codeanalyzer-python#176)
- codeanalyzer-java — emits the field
- codeanalyzer-typescript — emits the field
- codeanalyzer-schema — spine
BodyNode and Parameter gain id; each language schema and sample updated as its analyzer ships
- python-sdk — pin bump only; the python model is re-exported from the analyzer; consumer fix is python-sdk#320
Design decisions
- Spine field, not a python leaf: all three analyzers mint the same value for Neo4j and the grammar lives in the keystone, so a python-only field would put a spine concept under
x-cldk.divergences.
id is stamped from the same function each projector uses for its Neo4j merge key, never re-derived at emit sites. The @ rule is on the local key: keys starting with @ concatenate, others get a @ separator.
parameters[i].id == <callable-id>@formal_in:<i> by list position. A forward reference below L4. Formal-vertex index order now equals parameters order, a contract each analyzer gates with an L4 test.
- Scope guard, OUT:
PyCallsite (legacy list #120 already folded into body nodes); class attributes and local variables (their Neo4j ids are signature-minted and being redone under 2026-09-02-prune-scope-on-can-id-prefix.md; revisit after that lands).
Release plan
- codeanalyzer-python 1.5.0 first; it is the requesting analyzer and the only gate for python-sdk.
- codeanalyzer-java 3.1.0 and codeanalyzer-typescript 1.3.0 on their own clocks; no analyzer reads another's output, so no lockstep among the three.
- codeanalyzer-schema trails each analyzer release with the regenerated sample and closed schema.
- python-sdk pins
codeanalyzer-python==1.5.0; python-sdk#320 reads BodyNode.id in the local backend and b.id in Neo4j.
Definition of done (epic-level)
- Every sub-issue closed and its gate green.
- Per analyzer: parity test that
body[k].id equals the Neo4j merge key for every k in a fixture; L4 test that body[parameters[i].id].of is parameters[i].name for every callable; monotonicity CI green.
- codeanalyzer-schema
scripts/check.py green with regenerated samples from the releases that emit the field.
- python-sdk local and Neo4j backends return equal ids for the same node, asserted by a parity test.
Spec:
docs/design/specs/2026-09-05-body-node-id-in-analysis-json.mdSummary
Every
bodynode and everyparametersentry gains anidinanalysis.json: the global ordinal<callable-id>@<local>that all three Neo4j projections already write as their body-node merge key. Schema impact: one additive field on two spine node shapes;schema_versionstays2.0.0; the graph contract is unchanged. This closes the gap where a node is addressable in Neo4j and anonymous in JSON, so the SDK stops recomposing thecan://grammar itself (python-sdk#320 composed it onsignatureand joins to nothing).Affected repos
BodyNodeandParametergainid; each language schema and sample updated as its analyzer shipsDesign decisions
x-cldk.divergences.idis stamped from the same function each projector uses for its Neo4j merge key, never re-derived at emit sites. The@rule is on the local key: keys starting with@concatenate, others get a@separator.parameters[i].id == <callable-id>@formal_in:<i>by list position. A forward reference below L4. Formal-vertex index order now equalsparametersorder, a contract each analyzer gates with an L4 test.PyCallsite(legacy list #120 already folded into body nodes); class attributes and local variables (their Neo4j ids are signature-minted and being redone under2026-09-02-prune-scope-on-can-id-prefix.md; revisit after that lands).Release plan
codeanalyzer-python==1.5.0; python-sdk#320 readsBodyNode.idin the local backend andb.idin Neo4j.Definition of done (epic-level)
body[k].idequals the Neo4j merge key for everykin a fixture; L4 test thatbody[parameters[i].id].ofisparameters[i].namefor every callable; monotonicity CI green.scripts/check.pygreen with regenerated samples from the releases that emit the field.