Spec: docs/design/specs/can-uri-service-segment.md
Summary
The canonical can:// identity grammar gains a service segment as its outermost element, and
the existing <app> segment collapses into it:
before can://<lang>/<app>/<file>/<type>/<callable-signature>
after can://<service>/<lang>/<file>/<type>/<callable-signature>
Contract impact: breaking, not additive. It reshapes the id on every durable node (≥ callable),
therefore every edge endpoint that references one, and the Neo4j CanNode uniqueness key. Ships as
schema_version 2.0.0 → 2.1.0, amending v2 in place.
Motivated by cocoa, which already needs service-scoped identity and builds it outside the
schema by wrapping our id in a second key (cocoa/system/facts.py:39 → fn:emailservice/can://python/app/email_server.py/…).
Under the new grammar that key is simply the id.
Affected repos
codeanalyzer-python — live v2 emitter (83 can:// refs); schema/ids.py, --service, cache gate, Neo4j projection
codeanalyzer-typescript — live v2 emitter (34 refs); schema/v2/emit.ts, model.ts, build/neo4j/schema.ts
codeanalyzer-java — still v1, emits no can:// id; folded into its v2 migration, lands on 2.1.0 first time
cldk-devtools — the keystone where the grammar is defined (canonical-schema.md § Identity, schema-contract.md)
cldk-skillset — second copy of the keystone
cocoa — the driving consumer; drops its fn:<service>/ wrapper for v2 languages
codeanalyzer-dotnet — greenfield, absorbs at zero cost before it is built
docs — grammar / identity page
- Not affected:
python-sdk, typescript-sdk — both pre-v2, zero can:// references, address nodes by signature
Design decisions
Full rationale in the spec; locked decisions in summary:
- D1 —
<service> is outermost, above <lang>. The only position that lets one service span
several languages under a single id root; with <lang> outermost a polyglot service fragments
into unrelated roots.
- D2 —
<app> collapses into <service>; --app-name becomes --service, same default
(input directory name). The two are the same slot at different scale. The default is not
invented here — cocoa/system/detect.py:71-74 already falls back to "no services detected →
the root is one service named after the root dir".
- D3 — ships as 2.1.0, amending v2 in place. A MINOR number for a breaking id change is a
deliberate erratum, valid only because no consumer holds a can:// id today. Not precedent
once an SDK consumes v2.
- D4 — scope guard: cross-service edges are OUT. This makes services addressable; it does
not model RPC/HTTP/gRPC calls between them. That is a new edge family plus per-framework
detection — several contract decisions, not one. Stays Epic E.
- D5 — shared code gets one id per service (duplicated). The id answers "where does this
run?", not "what code is this?". Falls out of one-run-per-service with no new mechanism.
Cross-service dedup is the consumer's job.
Invocation model: one analyzer run per service, which is already what cocoa/system/driver.py:104
does. Monorepos are handled at invocation, not in the grammar.
Release plan
- Keystone grammar —
cldk-devtools + cldk-skillset. These define what the emitters
implement, so they land first and are what the emitter PRs get reviewed against.
codeanalyzer-python 2.1.0 and codeanalyzer-typescript 2.1.0 — independent, parallel.
cocoa — gated on both analyzer releases, since it consumes both.
codeanalyzer-dotnet spec § Identity — any time before the analyzer is built.
docs — any time after step 1.
No single lockstep train: codeanalyzer-java stays v1 until its own migration lands, so cocoa
is a mixed-version consumer regardless.
Java is tracked elsewhere. codellm-devkit/codeanalyzer-java#179 carries this change as part of
its v2 migration and is a sub-issue of #35, so it is not re-parented here —
an issue has one parent. Its grammar and version goals were updated in place.
Definition of done (epic-level)
- Every sub-issue closed and its gate green.
- Both live emitters produce
can://<service>/<lang>/… with no <app> segment, verified on a
real run rather than asserted.
- A no-flag run defaults the service to the input directory name.
- Zero dangling edge endpoints at L4 across
call_graph, param_in/param_out, backfilled
callee, extends_ids/implements_ids.
- Ordinal ids below the callable unchanged (
<callable-id>@<line>:<col>, @tag).
L1 ⊆ L2 ⊆ L3 ⊆ L4 monotonicity still holds on a real repo.
- Both emitters report
schema_version: "2.1.0"; 2.0.0 caches rejected and rebuilt.
- The keystone grammar in
cldk-devtools and cldk-skillset matches what shipped, in both copies.
cocoa builds a polyglot system graph with the wrapper removed on Python/TS and the Java path
still working.
Spec:
docs/design/specs/can-uri-service-segment.mdSummary
The canonical
can://identity grammar gains a service segment as its outermost element, andthe existing
<app>segment collapses into it:Contract impact: breaking, not additive. It reshapes the id on every durable node (≥ callable),
therefore every edge endpoint that references one, and the Neo4j
CanNodeuniqueness key. Ships asschema_version2.0.0 → 2.1.0, amending v2 in place.Motivated by
cocoa, which already needs service-scoped identity and builds it outside theschema by wrapping our id in a second key (
cocoa/system/facts.py:39→fn:emailservice/can://python/app/email_server.py/…).Under the new grammar that key is simply the id.
Affected repos
codeanalyzer-python— live v2 emitter (83can://refs);schema/ids.py,--service, cache gate, Neo4j projectioncodeanalyzer-typescript— live v2 emitter (34 refs);schema/v2/emit.ts,model.ts,build/neo4j/schema.tscodeanalyzer-java— still v1, emits nocan://id; folded into its v2 migration, lands on 2.1.0 first timecldk-devtools— the keystone where the grammar is defined (canonical-schema.md§ Identity,schema-contract.md)cldk-skillset— second copy of the keystonecocoa— the driving consumer; drops itsfn:<service>/wrapper for v2 languagescodeanalyzer-dotnet— greenfield, absorbs at zero cost before it is builtdocs— grammar / identity pagepython-sdk,typescript-sdk— both pre-v2, zerocan://references, address nodes by signatureDesign decisions
Full rationale in the spec; locked decisions in summary:
<service>is outermost, above<lang>. The only position that lets one service spanseveral languages under a single id root; with
<lang>outermost a polyglot service fragmentsinto unrelated roots.
<app>collapses into<service>;--app-namebecomes--service, same default(input directory name). The two are the same slot at different scale. The default is not
invented here —
cocoa/system/detect.py:71-74already falls back to "no services detected →the root is one service named after the root dir".
deliberate erratum, valid only because no consumer holds a
can://id today. Not precedentonce an SDK consumes v2.
not model RPC/HTTP/gRPC calls between them. That is a new edge family plus per-framework
detection — several contract decisions, not one. Stays Epic E.
run?", not "what code is this?". Falls out of one-run-per-service with no new mechanism.
Cross-service dedup is the consumer's job.
Invocation model: one analyzer run per service, which is already what
cocoa/system/driver.py:104does. Monorepos are handled at invocation, not in the grammar.
Release plan
cldk-devtools+cldk-skillset. These define what the emittersimplement, so they land first and are what the emitter PRs get reviewed against.
codeanalyzer-python2.1.0 andcodeanalyzer-typescript2.1.0 — independent, parallel.cocoa— gated on both analyzer releases, since it consumes both.codeanalyzer-dotnetspec § Identity — any time before the analyzer is built.docs— any time after step 1.No single lockstep train:
codeanalyzer-javastays v1 until its own migration lands, sococoais a mixed-version consumer regardless.
Java is tracked elsewhere. codellm-devkit/codeanalyzer-java#179 carries this change as part of
its v2 migration and is a sub-issue of #35, so it is not re-parented here —
an issue has one parent. Its grammar and version goals were updated in place.
Definition of done (epic-level)
can://<service>/<lang>/…with no<app>segment, verified on areal run rather than asserted.
call_graph,param_in/param_out, backfilledcallee,extends_ids/implements_ids.<callable-id>@<line>:<col>,@tag).L1 ⊆ L2 ⊆ L3 ⊆ L4monotonicity still holds on a real repo.schema_version: "2.1.0"; 2.0.0 caches rejected and rebuilt.cldk-devtoolsandcldk-skillsetmatches what shipped, in both copies.cocoabuilds a polyglot system graph with the wrapper removed on Python/TS and the Java pathstill working.