Problem
The canonical schema gives the application root exactly one identity field:
| Field |
Type |
Level |
Notes |
id |
string |
1 |
can://<lang>/<app> — the app segment disambiguates apps in one language. |
No name field is defined on the root. Every analyzer honours this in analysis.json — but two of the three Neo4j projections drop id from the root node and substitute a non-contractual name.
Measured on live graphs, one per analyzer:
| projection |
root label |
id |
name |
| codeanalyzer-python 1.4.x |
:PyApplication |
absent |
"odoo-slim-19" |
| codeanalyzer-java 3.0.x |
:JApplication |
absent |
"daytrader8" |
| codeanalyzer-typescript 1.3.0 |
:TSApplication |
"can://typescript/superset-frontend" |
absent |
The JSON side is uniform and conformant in all three: application.id is can://java/daytrader8, can://typescript/platform, and so on, with no name anywhere. So this is a projection-only divergence, introduced independently in two analyzers.
Why it matters
Every other node in those same graphs is addressed by its can:// id — that is the whole basis of prefix scoping, and it is what lets one database hold several applications and several languages at once. The root is the one node that cannot be addressed that way in a Java or Python graph, so the SDK has to compensate per language:
JNeo4jBackend MATCH (a:JApplication {name: $app}) # non-contractual field
PyNeo4jBackend MATCH (a:PyApplication {name: $app}) # non-contractual field
TSNeo4jBackend MATCH (a:Application {id: $application_id}) # contractual
Three backends, one concept, two different anchors — decided by which analyzer emitted the graph rather than by the schema. That is the shape the parity clause exists to prevent: a term coined twice is permanently wrong, and here it was coined twice in the same direction and once in the other.
It also means a consumer cannot write one query against "the application root" that works across languages, even though the contract says it can.
Not a naming quibble
name is genuinely useful — it is the --app-name a caller passed, and reading it off the root is convenient. The problem is that it replaced id rather than joining it. The fix that costs nothing is to project both: id because the contract requires it and prefix scoping depends on it, name as the convenience it already is, on all three.
Suggested resolution
- Project
id on the application root in codeanalyzer-python and codeanalyzer-java.
- Decide whether
name becomes a contractual root field (in which case add it to the schema and to codeanalyzer-typescript) or stays an unspecified projection extra. Either is defensible; the current split is not.
- Once
id is present everywhere, the three SDK backends can share one anchor.
Filed here rather than on one analyzer because it is a contract question first and two analyzer changes second. Happy to open the analyzer-side issues once the contract call is made.
Problem
The canonical schema gives the application root exactly one identity field:
idcan://<lang>/<app>— the app segment disambiguates apps in one language.No
namefield is defined on the root. Every analyzer honours this inanalysis.json— but two of the three Neo4j projections dropidfrom the root node and substitute a non-contractualname.Measured on live graphs, one per analyzer:
idname:PyApplication"odoo-slim-19":JApplication"daytrader8":TSApplication"can://typescript/superset-frontend"The JSON side is uniform and conformant in all three:
application.idiscan://java/daytrader8,can://typescript/platform, and so on, with nonameanywhere. So this is a projection-only divergence, introduced independently in two analyzers.Why it matters
Every other node in those same graphs is addressed by its
can://id — that is the whole basis of prefix scoping, and it is what lets one database hold several applications and several languages at once. The root is the one node that cannot be addressed that way in a Java or Python graph, so the SDK has to compensate per language:Three backends, one concept, two different anchors — decided by which analyzer emitted the graph rather than by the schema. That is the shape the parity clause exists to prevent: a term coined twice is permanently wrong, and here it was coined twice in the same direction and once in the other.
It also means a consumer cannot write one query against "the application root" that works across languages, even though the contract says it can.
Not a naming quibble
nameis genuinely useful — it is the--app-namea caller passed, and reading it off the root is convenient. The problem is that it replacedidrather than joining it. The fix that costs nothing is to project both:idbecause the contract requires it and prefix scoping depends on it,nameas the convenience it already is, on all three.Suggested resolution
idon the application root incodeanalyzer-pythonandcodeanalyzer-java.namebecomes a contractual root field (in which case add it to the schema and tocodeanalyzer-typescript) or stays an unspecified projection extra. Either is defensible; the current split is not.idis present everywhere, the three SDK backends can share one anchor.Filed here rather than on one analyzer because it is a contract question first and two analyzer changes second. Happy to open the analyzer-side issues once the contract call is made.