Skip to content

fix: 6 bugs found running the ArcadeDB driver end-to-end (cross-tenant leak, MVCC retry, DDL syntax, edge save, temporal filter)#1

Merged
lvca merged 3 commits into
ArcadeData:feat/arcadedb-backendfrom
agc-63:fix/arcadedb-driver-bugs
Jul 5, 2026
Merged

fix: 6 bugs found running the ArcadeDB driver end-to-end (cross-tenant leak, MVCC retry, DDL syntax, edge save, temporal filter)#1
lvca merged 3 commits into
ArcadeData:feat/arcadedb-backendfrom
agc-63:fix/arcadedb-driver-bugs

Conversation

@agc-63

@agc-63 agc-63 commented Jul 3, 2026

Copy link
Copy Markdown

Follow-up to getzep#1310 — two driver-side bug fixes found while running this driver end-to-end against a real ArcadeDB instance.

Based on feat/arcadedb-backend @ e9e4b47, targeting that branch (not main) so it merges cleanly into getzep#1310.

What's fixed

  1. Cross-tenant data leak in execute_query()'s database routing — database_ was set inside the Cypher query params dict instead of passed as a real kwarg to the underlying neo4j driver call, so per-tenant database routing was silently ignored.
  2. build_indices_and_constraints() DDL syntax invalid over the Cypher/Bolt channel — get_range_indices()/get_fulltext_indices() generated ArcadeDB SQL DDL sent over the Cypher/Bolt channel, which rejects it entirely. Rewritten with standard Cypher CREATE INDEX syntax; fulltext indexes dropped (not supported by ArcadeDB via Bolt).

What changed since the original 6-bug version

This PR originally included 4 more fixes (MVCC retry, single edge-save UNWIND workaround, datetime(...) wrap in retrieve_episodes(), plus a documented dehydrate_datetime app-level workaround). @lvca confirmed all 4 are now fixed upstream in ArcadeDB itself (arcadedb#4906, #4910, #4921, #4923 — see the comment on #1310), so those workarounds were reverted from this PR — no longer needed on the driver side. Verified end-to-end against a live arcadedata/arcadedb:26.8.1-SNAPSHOT container that the 2 remaining fixes are sufficient without them.

Testing

  • 21/21 driver unit tests pass (tests/driver/test_arcadedb_driver.py), trimmed to cover only the 2 fixes above.
  • 339 non-integration tests pass across the rest of the suite (same env vars/ignores as the CI unit-tests job).
  • ruff check and ruff format --check clean.
  • End-to-end verified against a real ArcadeDB instance: cross-tenant isolation (30 tenants × 15 rounds, 450 checks, 0 leaks) and build_indices_and_constraints() completing with zero syntax errors.

@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@lvca

lvca commented Jul 3, 2026

Copy link
Copy Markdown
Member

@agc-63 All six of your findings are now resolved upstream in ArcadeDB - four fixed and merged, two kept as genuine graphiti-side fixes. Thank you again; the repros and fail-before/pass-after tests made every one easy to act on, and you're credited on each issue.

Fixed upstream (merged to main):

So on a build with these, four workarounds can go:

Keep the two genuine driver-side fixes:

One heads-up: ArcadeDB stores a datetime as an instant (zone normalized to UTC) and reads sub-millisecond precision back as a zone-less value - the instant is preserved and comparisons stay correct, just noting the offset isn't round-tripped. Non-issue for graphiti's semantics.

Could you trim the PR to the two keepers? Happy to do that pass and push to your branch if you'd prefer. Really appreciate you driving this.

agc-63 added 3 commits July 4, 2026 16:17
database_ was set inside the Cypher query params dict instead of
passed as a real kwarg to the underlying neo4j driver call, so
per-tenant database routing was silently ignored -- a real
cross-tenant data leak (verified: querying tenant B's driver
returned tenant A's data).

Verified: 30 tenants x 15 rounds concurrent isolation check (450
verifications, 0 leaks).
…ported fulltext DDL

get_range_indices()/get_fulltext_indices() generated ArcadeDB SQL DDL
(CREATE VERTEX TYPE ... IF NOT EXISTS, CREATE INDEX ... NOTUNIQUE,
FULL_TEXT ENGINE LUCENE) but sent it over the Cypher/Bolt channel,
which rejects it entirely -- verified all 28 statements failed with
'mismatched input TYPE/ON' syntax errors.

- Vertex/edge type declarations are unnecessary: ArcadeDB creates
  types implicitly on first MERGE/CREATE (schemaless behaviour,
  verified throughout this driver).
- Range indexes rewritten with standard Cypher syntax
  (CREATE INDEX <name> IF NOT EXISTS FOR (n:Label) ON (n.prop)) --
  ArcadeDB does support standard/RANGE indexes via Bolt, just not
  FULLTEXT.
- Fulltext indexes dropped entirely (return []): not supported by
  ArcadeDB via Bolt regardless of syntax, and graphiti-core already
  runs without BM25/fulltext search enabled.

Verified: build_indices_and_constraints() completes with zero syntax
errors (previously all 28 statements failed).
- test_execute_query_success / test_execute_query_with_params: updated to
  assert the correct behaviour (database_ passed as a kwarg, not inside
  parameters_) -- the previous versions asserted the buggy behaviour.
- test_execute_query_routes_to_cloned_database: with_database() must route
  execute_query() to the cloned database.
- test_range_indices_use_valid_cypher_syntax / test_fulltext_indices_empty_for_arcadedb:
  DDL generation.
@agc-63 agc-63 force-pushed the fix/arcadedb-driver-bugs branch from e1d48cb to b430440 Compare July 4, 2026 15:49
@agc-63

agc-63 commented Jul 4, 2026

Copy link
Copy Markdown
Author

Thanks for the fast, thorough review — and for tracking down and fixing those 4 bugs upstream so quickly. Really appreciate it.

Trimmed the PR down to the two driver-side keepers, per your review.

Removed (verified end-to-end against a live arcadedata/arcadedb:26.8.1-SNAPSHOT container):

  • MVCC retry-with-backoff loop in execute_query() — 20 concurrent writes to the same node, 0 losses, with zero retry logic on our side (the neo4j driver's own managed-transaction retry now handles it, since ArcadeDB maps the conflict to a proper transient error code).
  • UNWIND [$edge_data] wrap in the single edge-save query — reverted to the plain MATCH (...) {uuid: $edge_data.source_uuid}, relationship still created correctly.
  • datetime(...) wrap in retrieve_episodes() (graph_data_operations.py) — fully reverted, file now has zero diff against feat/arcadedb-backend, so it no longer touches the other providers. Temporal filter still matches correctly.
  • (The dehydrate_datetime monkeypatch was never part of this PR — it lived at the application layer, as noted in the original PR description.)

Kept: database_ routing fix and the Cypher CREATE INDEX DDL rewrite. Re-ran the full unit suite (21/21 driver tests + 339 non-integration tests) plus ruff check/format — all clean.

@lvca

lvca commented Jul 5, 2026

Copy link
Copy Markdown
Member

@agc-63 perfect thanks!

@lvca lvca merged commit ff690cc into ArcadeData:feat/arcadedb-backend Jul 5, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants