Skip to content

Epic: can:// identity grammar gains a <service> segment (schema 2.1.0) #39

Description

@rahlk

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:39fn: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

  1. Keystone grammarcldk-devtools + cldk-skillset. These define what the emitters
    implement, so they land first and are what the emitter PRs get reviewed against.
  2. codeanalyzer-python 2.1.0 and codeanalyzer-typescript 2.1.0 — independent, parallel.
  3. cocoa — gated on both analyzer releases, since it consumes both.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    EpicCross-repo coordination epic

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions