Repository navigation
Description
Spec: codellm-devkit/.github → docs/design/specs/can-uri-service-segment.md
Coordination: codellm-devkit/.github#39
Problem
codeanalyzer/schema/ids.py hard-codes the language into the scheme constant and builds the
application root beneath it:
_SCHEME = "can://python"
def application_id(app_name: str) -> str:
return f"{_SCHEME}/{app_name}"So every durable id is can://python/<app>/<file>/<type>/<sig>, with no deployment dimension.
A polyglot microservice has no single id root, and cocoa has had to invent one outside the
schema (cocoa/system/facts.py:39 wraps our id as fn:<service>/can://python/…).
Per the spec, <service> becomes the outermost segment and <app> collapses into it:
before can://python/<app>/<file>/<type>/<sig>
after can://<service>/python/<file>/<type>/<sig>
Because the service must sit above the language, _SCHEME can no longer be a literal prefix —
it becomes can://{service}/python. Everything below application_id composes from it unchanged
(module_id, child_id, callable_sig_segment, ordinal_id are pure and parent-relative).
Scope boundary
This repo only. Does not add cross-service edges (no RPC/HTTP call family — Epic E), does not
touch the keystone grammar docs in cldk-devtools/cldk-skillset, does not touch cocoa, and
does not touch codeanalyzer-typescript (its own issue). The can:// scheme name is settled.
Does not change the Neo4j property-graph version in codeanalyzer/dataflow/builder.py:741
(schema_version="1.0.0") — that is a separate contract from the analysis.json envelope.
Goals
-
schema/ids.py— service becomes the outermost segment;<app>removed - CLI:
--app-name→--service, same default (input directory name) -
schema/py_schema.py—Analysis.schema_versiondefault →"2.1.0" -
core.py:728cache freshness gate →!= "2.1.0"(auto-invalidates every 2.0.0 cache) - Neo4j projection (
neo4j/project.py,neo4j/schema.py) — ids andSCHEMA_VERSIONfollow - v2 test suite updated (
test_v2_ids.py,test_v2_conformance.py,test_v2_cache.py,conftest_v2.py,test_cli.py) -
.claude/SCHEMA_DECISIONS.md— record the five locked decisions from the spec
Caveats and known risks
- The version number understates the break. 2.1.0 is MINOR for a change that rewrites every
durable id and the Neo4j MERGE key. Deliberate erratum, valid only because no consumer holds a
can://id today (python-sdkis pre-v2 and addresses by signature). Not precedent later. - Persisted Neo4j graphs need a full re-ingest, not an incremental
MERGE— every node key
changes, so merging against a 2.0.0 database builds a parallel graph. - Shared libraries are analyzed once per service by design: one run per service, so a
libs/
imported by six services yields six id sets for the same source. Accepted (spec D5). - Renaming
--app-nameis a CLI break for any existing caller. Whether to keep it as a
deprecated alias is an open call — the spec does not decide it. - Must land in lockstep with
codeanalyzer-typescript's equivalent change;cocoais gated on
both.
Definition of done
-
canpy -i ./python-sdkwith no flags emitscan://python-sdk/python/cldk/core.py/CLDK/…
— verified on a real run, not an asserted shape -
canpy -i ./svc --service emailserviceemitscan://emailservice/python/… - No emitted id contains an
<app>segment distinct from the service - Every edge endpoint resolves —
call_graph,param_in/param_out, backfilledcallee—
with zero dangling references on an L4 run of a real repo - Ordinal ids below the callable unchanged (
<callable-id>@<line>:<col>,@entry,
@formal_in:0,@16:2/actual_in:0) -
schema_versionreads"2.1.0"; a cached 2.0.0analysis.jsonis rejected and rebuilt -
L1 ⊆ L2 ⊆ L3 ⊆ L4monotonicity holds on a real repo — theprov:["ssa"]DDG set is
identical at L3 and L4, with L4 adding onlypoints-toandreaching-defs -
--emit neo4jproduces a graph whose node keys are the new ids and whose declared schema
matches the emitter (existing conformance test still passes)
Activity
rahlk commentedon Aug 20, 2026
Won't fix. The
<app>segment stays;can://ids keep the formcan://python/<app>/<file>/<type>/<callable-sig>.Closing means the id grammar is settled, which unblocks the contract work: codellm-devkit/.github#36 no longer has to wait on an id-shape change before it can extract and freeze what
codeanalyzer-pythonemits.Consequence to record:
cocoacontinues to key service scope outside the schema (cocoa/system/facts.pywraps the canonical id rather than the id carrying a service segment). That stays a consumer concern rather than an identity-grammar one.