@@ -7,11 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
88## [ Unreleased]
99
10- Python legs 1 and 1.5 of the CLDK 2.0 agent-facing query facade (see
11- ` docs/design/specs/2026-09-03-agent-facing-query-facade.md ` and
12- ` docs/design/specs/2026-09-05-leg-1.5-bounded-queries-and-dataflow.md ` ). Targets ` 2.0.0-rc.1 ` .
10+ Python legs 1, 1.5 and 1.6 of the CLDK 2.0 agent-facing query facade (see
11+ ` docs/design/specs/2026-09-03-agent-facing-query-facade.md ` ,
12+ ` docs/design/specs/2026-09-05-leg-1.5-bounded-queries-and-dataflow.md ` and
13+ ` docs/design/specs/2026-09-06-leg-1.6-id-prefix-scoping.md ` ). Targets ` 2.0.0-rc.2 ` .
1314
1415### Changed
16+ - ** Pinned ` codeanalyzer-python ` 1.4.0 → 1.4.1** (` pyproject.toml ` ` dependencies ` and
17+ ` [tool.backend-versions] ` ). 1.4.1 removes the ` _module ` node property from every graph it emits
18+ (upstream #183 ), so the Neo4j backend no longer scopes on it: ** every statement is scoped to the
19+ application by its ` can:// ` id prefix** (` n.id STARTS WITH 'can://python/<app>/' ` ), the rule the
20+ analyzer's own destructive statements use, and a callable's repo-relative ` path ` is derived from
21+ its id and verified against the application's module keys rather than projected from a property.
22+ No accessor changes name, signature, return type or value; the ` _module ` scoping is simply gone.
23+ Ghosts (` :PyExternal ` ) fall inside the prefix, so every call-graph walk pins its traversal source
24+ to ` :PyCallable ` by label -- an ` @external ` node is still reached and never traversed through.
25+ - ** The schema probe reads the graph's analyzer generation.** Attaching to a graph whose
26+ ` :PyApplication.analyzer_version ` is below ** 1.4.0** (or missing) raises ` GraphSchemaMismatch `
27+ naming the version found and the floor -- before this it was served with silent empties. A
28+ ** 1.4.0** graph and a ** 1.4.1** graph are served identically and silently: both carry the unique
29+ ` :PySymbol(id) ` range index, so ` locate ` / ` locate_many ` and ` resolve_callable ` anchor on
30+ ` (c:PyCallable:PySymbol) ` and the prefix * seeks* on either generation (40 positions: 381 → 46 ms
31+ on 1.4.1, 427 → 53 ms on 1.4.0; ` resolve_callable ` 19.2 → 15.3 ms on 1.4.1, a wash on 1.4.0).
32+ 1.4.1's ` :PyCanNode(id) ` index was measured and rejected -- it spans all 955,961 application
33+ nodes, so seeking it made ` resolve_callable ` 10× slower -- and no statement names it. Statements
34+ whose prefix is the whole application stay on the ` :PyCallable ` label scan, which is faster there.
1535- ** BREAKING: Neo4j graph vocabulary migration.** The Python Neo4j backend
1636 (` cldk.analysis.python.neo4j.PyNeo4jBackend ` ) now queries ` PyBodyNode ` / ` PY_HAS_BODY_NODE `
1737 instead of the pre-1.4.0 ` PyCallSite ` / ` PY_HAS_CALLSITE ` / ` PySymbol ` vocabulary, matching what
@@ -43,8 +63,9 @@ Python legs 1 and 1.5 of the CLDK 2.0 agent-facing query facade (see
4363 ` PyClassOverview.path ` and the keys of ` get_symbol_table() ` , which it previously could not be
4464 joined against. Both backends changed together: the Neo4j projections behind
4565 ` get_callables_overview() ` , ` get_decorated_callables() ` , ` get_entrypoints() ` and
46- ` get_config_readers() ` now read ` :PyCallable._module ` rather than ` :PyCallable.path ` , and the
47- local backend projects the symbol table key rather than ` PyCallable.path ` .
66+ ` get_config_readers() ` now project the module key rather than ` :PyCallable.path ` (from the
67+ ` _module ` property on a 1.4.0 graph in leg 1; derived from the node's ` can:// ` id since leg 1.6),
68+ and the local backend projects the symbol table key rather than ` PyCallable.path ` .
4869 ** Migration:** a caller that stored or persisted these paths will see different strings for the
4970 same callable, and one that stripped a project-root prefix off them must stop.
5071
@@ -104,7 +125,7 @@ Python legs 1 and 1.5 of the CLDK 2.0 agent-facing query facade (see
104125| SDK version | Requires ` codeanalyzer-python ` | Graph vocabulary |
105126| --- | --- | --- |
106127| <= 1.5.0 | 0.3.x | ` PyCallSite ` / ` PY_HAS_CALLSITE ` / ` PySymbol ` |
107- | 2.0.0-rc.1 (this) | >= 1.4.0 | ` PyBodyNode ` / ` PY_HAS_BODY_NODE ` |
128+ | 2.0.0-rc.2 (this) | 1.4.1 pinned; graphs emitted by >= 1.4.0 served identically | ` PyBodyNode ` / ` PY_HAS_BODY_NODE ` , scope by ` can:// ` id prefix |
108129
109130### Added
110131- ** Slices and reachability: ` slice_backward(src, within=) ` / ` slice_forward(src, within=) ` /
@@ -312,6 +333,23 @@ Python legs 1 and 1.5 of the CLDK 2.0 agent-facing query facade (see
312333 schema probe at attach time; see the breaking-change note above.
313334
314335### Fixed
336+ - ** ` get_entrypoint_coverage() ` over Neo4j reads the projected report.** It answered with an
337+ ` entrypoint_report_unavailable ` diagnostic unconditionally; codeanalyzer-python 1.4.1 (#182 )
338+ projects ` entrypoint_frameworks ` / ` entrypoint_report_json ` onto ` :PyApplication ` , so on such a
339+ graph the answer is now the pass's own ` PyEntrypointReport ` , field for field what the local
340+ backend returns. The diagnostic survives only for a graph that genuinely lacks the property (one
341+ emitted by 1.4.0).
342+ - ** Resolved through the 1.4.1 pin** -- python-sdk's upstream reports #176 / #177 / #178 , fixed
343+ in codeanalyzer-python as #180 (body nodes and parameters carry their ` id ` in ` analysis.json ` ;
344+ the SDK's composed body-node id is now pinned equal to the analyzer's own per run), #182 / #185
345+ (entrypoint report projected, Odoo ` @http.route ` / ` http.Controller ` detected: 534 callables and
346+ 94 classes on the same checkout that 1.4.0 flagged 0 / 0), #181 (` PY_EXTENDS ` is actually emitted:
347+ 1,573 edges where every 2.0.0 graph had none) and #183 (` _module ` retired, ` :PyCanNode ` range
348+ index on ` id ` ).
349+ - ** The N+1 timing assertion measured the coverage tracer, not the query.** The two timed live
350+ tests (` get_symbol_table ` / ` get_classes ` under 15 s) ran under ` sys.settrace ` , which adds ~ 5 s
351+ to a ~ 10 s Python-side reconstruction; they passed the ceiling by luck. They now run with coverage
352+ paused (` pytest-cov ` 's ` no_cover ` ), so the ceiling measures the round trips it exists to bound.
315353- ** Java: a missing JDK cache root is refused, not crashed on.** ` JCodeanalyzer._get_codeanalyzer_exec `
316354 now raises ` CodeanalyzerExecutionException ` ("no cache directory and no project directory")
317355 when neither is available, instead of letting ` ensure_jdk ` fail with `TypeError: ... not
@@ -328,7 +366,8 @@ Python legs 1 and 1.5 of the CLDK 2.0 agent-facing query facade (see
328366 child fetches (` get_class() ` and everything reconstructing one declaration) matched by a bare
329367 ` {signature: $sig} ` / ` {file_key: $fk} ` while their bulk twins were application-scoped, so in a
330368 database holding two applications ` get_class() ` merged another application's members and
331- ` get_all_classes() ` did not. All twelve carry the same ` _module IN $mods ` predicate now, and so
369+ ` get_all_classes() ` did not. All twelve carry the same application-scope predicate now (`_ module
370+ IN $mods` when this landed, the ` can://` id prefix since leg 1.6), and so
332371 do the leg-1.5 call-graph statements behind ` reaches ` , ` backward_cone ` , ` call_paths_between ` and
333372 ` flows_to_call ` , which had matched by signature unscoped too. Statements keyed only by a
334373 body-node or ghost id (` slice_* ` , ` paths_between ` , the value-reachability predicate) are scoped
0 commit comments