Repository navigation
fix(neo4j)!: scope the v2 purge on the can:// id prefix; retire _module from v2 - #220
Merged
Merged
Conversation
…le from v2 The incremental purge scoped deletion by `_module`, a bare project-relative file key stamped onto every node. That key carries no language and no application, so after #213 anchored it on java-owned labels it was still application-blind: two java applications in one database sharing src/main/java/Foo.java have identical labels and an identical file key, and only identity distinguishes them. The v2 purge now scopes on the module's own can:// id -- the module by equality, its declarations by `id STARTS WITH <id> + '/'`. The id is a path, so a prefix match is containment, and it is simultaneously scoped to one language, one application and one module. Cross-language and shared nodes fall outside by construction: artifacts are can://artifact/..., packages are pkg:..., and name-keyed nodes have no id to match. `_module` retires from the v2 contract. It survives as NodeRow.moduleKey, an in-memory field the writer uses to group nodes -- the graph never needed telling, since a node's owning module is already its id prefix. RowBuilder lifts it off the emitted properties in one place, so no projector call site changes. v1 keeps exactly what it had. Its ids are FQN-shaped (com.l4.Chain#a(int)@5:16) with no application segment, so there is no prefix to scope by; that path keeps `_module` and the #213 label anchor, and BoltWriter branches on whether the module's id is a can:// id. MODULE_OWNED is therefore now the v1 vocabulary alone, which its test asserts rather than assumes. Nodes keyed on a can:// id gain a :JCanNode marker, declared as a marker label and indexed. It exists only so the prefix predicate can seek: Neo4j property indexes are label-scoped, so the match would otherwise scan the whole node store once per changed module. It makes no semantic claim, unlike :JSymbol, and being java-namespaced means an anchoring mistake still cannot reach another language. The descendant separator is a named function rather than an inlined `+ "/"`, because dropping it is silent: `can://java/app/src/Foo.java` also prefixes `can://java/app/src/Foo.javaX`. Graph contract 2.2.0 -> 3.0.0: a property is removed. analysis.json is untouched and byte-identical on daytrader8; `_module` was always projection-only. Part of codellm-devkit/.github#50. Closes #219. Closes #218. Closes #217.
… mechanisms v2 is the schema as of 3.0.0. Its ids are can:// paths, so a prefix match scopes a purge to one language, one application and one module at once. v1 ids are FQN-shaped and carry no application segment, so they cannot be scoped that way at all -- and rather than keep a second, weaker mechanism alive for a legacy path, the purge is now skipped for v1 rows and says so in the log. A v1 push still upserts; it just never deletes. That retires MODULE_OWNED and both label-anchored statements from #213, which existed only to make the _module match safe. Nothing anchors on _module any more, so there is nothing left for them to protect. The purge test now pins the properties that make the new scoping correct -- the predicate shape, the label that lets the prefix seek, the separator that stops a sibling path matching, and the can:// recognition that selects the path -- rather than the label vocabulary that no longer guards anything.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Epic: codellm-devkit/.github#50 · Spec:
2026-09-02-prune-scope-on-can-id-prefix.mdCloses #219, #218, #217.
Problem
The purge scoped deletion by
_module— a bare project-relative file key stamped on every node. It carries no language and no application, so even after #213 anchored it on java-owned labels it was still application-blind: two java applications in one database sharingsrc/main/java/Foo.javahave identical labels and an identical file key. Only identity distinguishes them, which is why no label could fix it.Fix
The v2 purge scopes on the module's own
can://id — module by equality, declarations byid STARTS WITH <id> + '/'. The id is a path, so a prefix match is containment, and it is scoped to one language, one application and one module at once. Shared nodes fall outside by construction: artifacts arecan://artifact/…, packages arepkg:…, and name-keyed nodes have noidto match at all._moduleretires from the v2 contract. It survives asNodeRow.moduleKey, an in-memory field the writer uses to group nodes — the graph never needed telling, since a node's owning module is already its id prefix.RowBuilderlifts it off the emitted properties in one place, so no projector call site changed.The v1 carve-out, which the spec did not anticipate
v1 ids are FQN-shaped and carry no application segment:
There is nothing to prefix, so v1 cannot be prefix-scoped. It keeps exactly what it had —
_moduleplus the #213 label anchor — andBoltWriterbranches on whether the module's id is acan://id.MODULE_OWNEDis consequently now the v1 vocabulary alone, which its test now asserts rather than assumes.This means
_moduleretires from the v2 contract but survives on the legacy path, contradicting the spec's claim that it retires outright. The spec needs amending; flagged rather than glossed.:JCanNodeNodes keyed on a
can://id gain a:JCanNodemarker, declared as a marker label and indexed.It exists only so the prefix predicate can seek — Neo4j property indexes are label-scoped, so the match would otherwise scan the whole node store once per changed module (that is #217, resolved here by making the property unnecessary rather than by indexing it). It makes no semantic claim, unlike
:JSymbol, and being java-namespaced means an anchoring mistake still cannot reach another language's nodes.STARTS WITHis index-backed on a range index;CONTAINS/ENDS WITHare not — the predicate is written that way deliberately.One detail worth not inlining
The descendant separator is a named function, not a
+ "/"at the call site, because dropping it is silent:can://java/app/src/Foo.javaalso prefixescan://java/app/src/Foo.javaX, and would purge a different module's nodes. There is a test on it.Verification
CodeAnalyzerIntegrationTestgraph.cypher(daytrader8)_moduleproperties, 45JCanNode, 1 id indexgraph.cypher_module, 0JCanNode— legacy path untouchedanalysis.json_modulewas always projection-onlyBreaking
Graph contract 2.2.0 → 3.0.0: a property is removed.
analysis.jsonis unaffected.