Skip to content

BodyNode carries no id, though the Neo4j projection mints one for every body node #176

Description

@rahlk

Spec: 2026-09-05-body-node-id-in-analysis-json.md

Problem

BodyNode carries no id field while PyCallable does (codeanalyzer/schema/py_schema.py:127 vs :354). The id exists: the Neo4j projection mints <callable-id>@<local> for every :PyBodyNode via _global_ordinal (neo4j/project.py:123), which mirrors IdentityMap.global_id (dataflow/identity.py:84). So the analyzer computes the id, projects it to the graph, and drops it from the JSON model. PyCallableParameter (py_schema.py:286) has the same gap.

A consumer holding a BodyNode from the JSON path cannot name it. The only workaround is composing f"{callable.id}@{body_key}" in the consumer, which reimplements the can:// grammar outside the analyzer and drifts silently. python-sdk did exactly that on signature instead of id (python-sdk#320). On the Odoo graph these are the dataflow endpoints:

kind count
actual_in 229,035
formal_in 197,847
actual_out 133,267
formal_out 120,172
call 77,332
statement 55,169

Scope boundary

Adds id to BodyNode and PyCallableParameter in analysis.json only. Not PyCallsite (legacy list, folded into body nodes by #120). Not class attributes or variables (their ids are being redone under #173). No Neo4j change: the graph already carries the id. No schema_version bump.

Goals

  • BodyNode.id: str = "", populated for every body node at every level it exists (L1 call, L3 statements and @entry/@exit, L4 param vertices), value = _global_ordinal(callable.id, local_key)
  • PyCallableParameter.id: str = "", value = f"{callable.id}@formal_in:{i}" for position i in parameters
  • Stamp from the one existing function, not inlined at the four construction sites (schema/l1_body.py:12, dataflow/builder.py:276,279,291,538)
  • Parity test: for a fixture at -a 4, every body[k].id equals the PyBodyNode merge key the projector emits for k
  • Formal-order test at -a 4: for every callable and every i, body[parameters[i].id].of == parameters[i].name
  • Test that a call node's id is never equal to its callee
  • Monotonicity CI green
  • .claude/SCHEMA_DECISIONS.md entry; README schema section and docs/skills/analyzing-canpy-graphs vocabulary updated

Caveats and known risks

  • parameters[i].id names a formal_in node that exists in body only at L4. Below L4 it is a forward reference by design; document it rather than emitting it conditionally.
  • Python's formal_in vertices for captures and globals follow the declared parameters (dataflow/sdg.py:145), so indices past len(parameters) are not parameters. The formal-order test only covers 0..len(parameters)-1.
  • A cached analysis.json from 1.4.0 parses (default ""), and the analyzer version guard rebuilds it, so no stale id-less payload is served.

Definition of done

  • Both tests above fail on main and pass after.
  • analysis.json diff against a 1.4.0 fixture shows only added id keys.
  • Full test suite green.
  • Ships as codeanalyzer-python 1.5.0; python-sdk#320 unblocked.

Activity

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