Spec: 2026-09-02-prune-scope-on-can-id-prefix.md · Epic: codellm-devkit/.github#50
Problem
The per-module purge on main is MATCH (x:CanNode {_module: $m}) ... DETACH DELETE x. Its comment — "a sibling analyzer's nodes sharing this _module key are never in scope" — is true across languages and false across applications: two TypeScript projects in one database that share a module path delete each other's nodes for it. The orphan prune and EAGER_PURGE already prefix-scope correctly; the per-module purge does not.
This analyzer covers two language namespaces, so it carries two markers.
What to do
- Scope the per-module purge and the orphan prune on the application's
can:// id prefix (can://<lang>/<app>), replacing the _module predicate and the label-list anchor.
- Drop
_module from the projection, from the graph catalog, and from any index on it.
- Add a
TSCanNode / JSCanNode marker label to every node keyed by a can:// id, as an index anchor only — Neo4j property indexes are label-scoped, so MATCH (n) WHERE n.id STARTS WITH $p scans the whole store without one. STARTS WITH is index-backed on a range index; CONTAINS/ENDS WITH are not.
- MAJOR graph-contract bump: a property is removed.
- Grep this analyzer's SDK Neo4j backend for
_module so a consumer is updated in the same train. The removal is not conditional on the answer — that was decided — but a surprise later is worse than a check now.
Two things not to get wrong
- Prefix boundary.
can://<lang>/<app> also prefixes can://<lang>/<app>Xtra. Match descendants on prefix + '/' and the node itself by equality.
- Empty prefix.
STARTS WITH '' matches every node in the database. Refuse a null or empty application id rather than falling through.
Goals
Definition of done
Spec:
2026-09-02-prune-scope-on-can-id-prefix.md· Epic: codellm-devkit/.github#50Problem
The per-module purge on
mainisMATCH (x:CanNode {_module: $m}) ... DETACH DELETE x. Its comment — "a sibling analyzer's nodes sharing this_modulekey are never in scope" — is true across languages and false across applications: two TypeScript projects in one database that share a module path delete each other's nodes for it. The orphan prune andEAGER_PURGEalready prefix-scope correctly; the per-module purge does not.This analyzer covers two language namespaces, so it carries two markers.
What to do
can://id prefix (can://<lang>/<app>), replacing the_modulepredicate and the label-list anchor._modulefrom the projection, from the graph catalog, and from any index on it.TSCanNode/JSCanNodemarker label to every node keyed by acan://id, as an index anchor only — Neo4j property indexes are label-scoped, soMATCH (n) WHERE n.id STARTS WITH $pscans the whole store without one.STARTS WITHis index-backed on a range index;CONTAINS/ENDS WITHare not._moduleso a consumer is updated in the same train. The removal is not conditional on the answer — that was decided — but a surprise later is worse than a check now.Two things not to get wrong
can://<lang>/<app>also prefixescan://<lang>/<app>Xtra. Match descendants onprefix + '/'and the node itself by equality.STARTS WITH ''matches every node in the database. Refuse a null or empty application id rather than falling through.Goals
_modulegone from projection, catalog and indexesDefinition of done
_module's removal and the added marker label, shown by diffing a fixture projection