Skip to content

Neo4j: the per-module purge is application-blind, so two java apps in one database delete each other's nodes #218

Closed

Description

@rahlk

Problem

#213 stopped the per-module purge reaching other languages' graphs by anchoring it on java-owned labels. It did not scope it to an application:

MATCH (x:JCompilationUnit|JType|JCallable|...|JBodyNode)
WHERE x._module = $m AND NOT coalesce(x.id, x.file_key) IN $keys
DETACH DELETE x

_module is the bare project-relative file key — V2GraphProjector sets mp.put("_module", fileKey) with no application qualifier. So two java applications pushed to the same database that both contain, say, src/main/java/Foo.java share a _module value, and pushing app A deletes app B's nodes for that file.

Unlike the cross-language case, this needs no unusual setup: sharing a source path across projects is close to universal.

The labels cannot help here — both applications' nodes carry exactly the same ones. Only identity distinguishes them.

Why the orphan prune is not affected

The prune traverses from MATCH (:JApplication {name: $app})-[:J_HAS_UNIT|J_HAS_MODULE]->(c), so it is already scoped to one application by construction. Only the per-module purge is exposed.

Scope boundary

The two purge statements in BoltWriter (PURGE_MODULE_EDGES, PURGE_VANISHED_NODES). Not in scope: the orphan prune, the snapshot path, or the label anchor from #213 — which stays until the wider change below lands.

Fix

Add the application to the predicate. CanId.applicationId(appName) already yields can://java/<app>, and every node's id is that prefix plus its path, so:

AND x.id STARTS WITH $appPrefix + '/'

Two things not to get wrong:

  • Prefix boundary — match descendants on prefix + '/', and the node itself by equality. A bare STARTS WITH prefix also matches can://java/appXtra/....
  • Empty prefix — STARTS WITH '' matches every node in the database. Refuse a null or empty application id rather than falling through to it.

Relationship to the cross-repo spec

codellm-devkit/.github → docs/design/specs/2026-09-02-prune-scope-on-can-id-prefix.md proposes scoping every destructive statement on the can:// id prefix and retiring _module entirely, across all three analyzers. It records that this same application-blind purge exists in typescript and python too.

This issue is the narrow, immediate fix for java: add the application predicate alongside the existing _module match. If the spec is adopted, this becomes redundant — the prefix subsumes both the label anchor and _module. Filed separately because it is a live defect and should not wait on a three-repo decision.

Goals

  • Both purge statements scoped to the application being pushed
  • A test showing a node belonging to a second java application, with a colliding _module, survives a push
  • The null/empty application id case is refused rather than matching the store

Definition of done

  • A test that fails before and passes after
  • Full suite green apart from the pre-existing Docker-dependent CodeAnalyzerIntegrationTest

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions