Skip to content

PyNeo4jBackend._to_uri appends a spurious trailing '@' to every vertex id #295

Description

@rahlk

Summary

PyNeo4jBackend._to_uri (cldk/analysis/python/neo4j/neo4j_backend.py) mis-parses the
graph's PyCFGNode.id and appends a spurious trailing @ to every vertex URI it
produces, breaking parity between the local and Neo4j backends for program_graph(),
sdg_edges(), and resolve_location() (and, transitively, every cldk.graph.Engine
verb run against a PyNeo4jBackend provider).

Root cause

def _to_uri(self, cfg_node_id: str) -> str:
    sig, _, key = cfg_node_id.partition("#")
    can = self._sig_to_can().get(sig, sig)
    return f"{can}@{key.removeprefix('@')}"

This assumes the raw PyCFGNode.id stored in the graph has the shape
"<dotted_sig>#<local_key>". In codeanalyzer-python 1.0.2's actual projection, n.id is
already the fully-qualified can://... vertex id (no # separator at all). Since
partition("#") never matches, key is always "" and sig is the full id (a miss
against the dotted-signature→can map, so it falls through unchanged) — every call
returns f"{already_correct_id}@", i.e. the correct id with one extra trailing @.

Reproduction

tests/graph/test_py_parity_live.py (added in #270 Task 6, env-gated on
CLDK_TEST_NEO4J_URI) reproduces this against a real analyzer run + a real Neo4j
instance:

podman run -d --name cldk-graph-parity -p 7687:7687 -e NEO4J_AUTH=neo4j/testpassword neo4j:5
CLDK_TEST_NEO4J_URI=bolt://localhost:7687 CLDK_TEST_NEO4J_USER=neo4j \
CLDK_TEST_NEO4J_PASSWORD=testpassword uv run --extra neo4j pytest tests/graph/test_py_parity_live.py -v

test_program_graph_parity_per_callable, test_sdg_parity, and test_verb_parity all
fail; test_max_level_parity passes. Diffing node sets confirms the only discrepancy
is the trailing @ — stripping one trailing @ from every remote-side id makes every
set/edge comparison match exactly (hand-verified against the pyfix fixture: 3/3
callables' program graphs, sdg edges, and all three verb-parity checks agree modulo
that one character).

Fix sketch

_to_uri should treat cfg_node_id as already being the canonical URI when no # is
present (or, more robustly, the projector/reconstruction contract for PyCFGNode.id
should be re-confirmed and _to_uri updated to match it exactly — this may also affect
resolve_location's reliance on the same id shape).

Scope note (also worth fixing nearby)

While diagnosing this, tests/graph/test_py_parity_live.py's harness needed
app_name == proj.name (not an arbitrary custom name) when re-emitting to Neo4j from an
already-analyzed PyCodeanalyzer.application object: codeanalyzer.neo4j.emit.assign_ids
re-stamps every module/class/callable .id in place using whatever app_name is passed,
but does not retroactively update the already-baked-in cfg/ddg/param_in/param_out edge
src/dst strings from the original analysis. Passing a different app_name at emit time than
the one the analyzer used internally (options.app_name or project_dir.name) silently
corrupts the shared application object's internal id consistency. This is now documented
inline in the test, but may be worth a doc note on emit_neo4j/assign_ids itself for
anyone else populating Neo4j from an in-memory analysis rather than a fresh analyze() run.

Filed while executing #270 Task 6 (dual-backend parity suite).

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