PROBLEM
The Neo4j backends are read-only in practice but not by construction. A sweep of cldk/ finds no MERGE, CREATE, execute_write, or write transaction anywhere — every backend goes through session.run(query, **params) and returns record.data(). But the driver is opened with GraphDatabase.driver(neo4j_uri, auth=...) (e.g. cldk/analysis/java/neo4j/neo4j_backend.py:113) and sessions are taken with self._driver.session(database=...), neither of which requests read access. Nothing stops a future write from silently succeeding against a production graph.
SCOPE BOUNDARY
Enforcement only. The original "separate ingestor" half of this issue is moot: there is no ingestion code in this SDK to separate out — graphs are populated outside it. This issue does not add an ingestor, and does not change any query.
GOALS
- Open the driver with
default_access_mode=READ_ACCESS (or take explicit read sessions) in all three backends: Java, Python, TypeScript.
- A write attempted through a backend fails loudly rather than succeeding silently.
CAVEATS AND KNOWN RISKS
- Read access mode routes to followers in a cluster; against a single instance the behaviour is unchanged, so this is cheap, but on a cluster it changes which server serves the query.
- This is enforcement of an invariant that already holds. It should be a no-op for every existing test — if any test breaks, that test was writing through a read path and the breakage is the finding.
DEFINITION OF DONE
- All three Neo4j backends request read access explicitly.
- A test asserts that a write through a backend session raises rather than committing.
- The existing backend test suites pass unchanged.
Re-scoped 2026-08-03: originally "read-only client + separate ingestor". The ingestor half was dropped after verification showed no write path exists in cldk/ at all — see the PROBLEM section for the evidence.
PROBLEM
The Neo4j backends are read-only in practice but not by construction. A sweep of
cldk/finds noMERGE,CREATE,execute_write, or write transaction anywhere — every backend goes throughsession.run(query, **params)and returnsrecord.data(). But the driver is opened withGraphDatabase.driver(neo4j_uri, auth=...)(e.g.cldk/analysis/java/neo4j/neo4j_backend.py:113) and sessions are taken withself._driver.session(database=...), neither of which requests read access. Nothing stops a future write from silently succeeding against a production graph.SCOPE BOUNDARY
Enforcement only. The original "separate ingestor" half of this issue is moot: there is no ingestion code in this SDK to separate out — graphs are populated outside it. This issue does not add an ingestor, and does not change any query.
GOALS
default_access_mode=READ_ACCESS(or take explicit read sessions) in all three backends: Java, Python, TypeScript.CAVEATS AND KNOWN RISKS
DEFINITION OF DONE
Re-scoped 2026-08-03: originally "read-only client + separate ingestor". The ingestor half was dropped after verification showed no write path exists in
cldk/at all — see the PROBLEM section for the evidence.