Skip to content

feat(nodes,ai): RocketRide cloud DB nodes — sql, vector, graph - #1713

Open
dylan-savage wants to merge 12 commits into
developfrom
feat/rocketride-db-nodes
Open

feat(nodes,ai): RocketRide cloud DB nodes — sql, vector, graph#1713
dylan-savage wants to merge 12 commits into
developfrom
feat/rocketride-db-nodes

Conversation

@dylan-savage

@dylan-savage dylan-savage commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What this adds

Three RocketRide-branded pipeline nodes that give every tenant a zero-setup managed
Postgres in RocketRide cloud, plus the shared machinery they stand on:

  • rocketride_sql — SQL tables on the tenant database
  • rocketride_vector — pgvector store with HNSW indexing (cosine/L2/IP, m/ef_construction configurable)
  • rocketride_graph — Cypher queries translated to Apache AGE, safe path runs in server-side READ ONLY transactions
  • DSN resolver seam (ai/common/rocketride_db.py) — one place that answers "which database is yours", with strict precedence:
    1. ROCKETRIDE_DB_DSN injected by the server at task start (hosted path)
    2. signed-in account via Account.resolve_db_dsn (broker → provisioner)
    3. actionable cloud-sign-in error (unconfigured/OSS builds)
  • Broker resolver + env injectionAccountBase.resolve_db_dsn calls the
    data-core provisioner (POST /provision {tenant_id}{database, role, dsn, created},
    idempotent, derived passwords); the task engine injects the DSN into node env only for
    pipelines that use these providers. extension.saas overlay needs zero code — cloud
    enablement is two env vars (ROCKETRIDE_DB_BROKER_URL, ROCKETRIDE_DB_BROKER_TOKEN).
  • Cypher→AGE translation layer (ai/common/graph/age/) — vendored openCypher M23
    parser (generated at ANTLR 4.13.2, committed; no Java for devs), resource firewall
    (depth/timeout/length caps on both paths), version-keyed capability table (every 1.5.0
    cell empirically verified — zero TBD), collision-proof cypher() envelope emission with
    PREPARE/EXECUTE param binding (transaction-pooling safe), vendored agtype decoder.
    apache-age-python deliberately NOT a dependency (stale on PyPI, pins antlr 4.11.1).

Review guide — anatomy of a 21k-line diff

The diff is large but ~71% of it is not human-written code. Where the 21,024 added
lines actually come from:

Lines What Review?
12,739 graph/age/_cypher/gen/machine-generated parser: deterministic ANTLR 4.13.2 output from the official openCypher M23 grammar, committed so devs never need Java/ANTLR installed. Regenerate from the same grammar → identical code. Skip
2,034 graph/age/_agtype/vendored agtype decoder from apache/age (Apache-2.0). PyPI's apache-age-python is stale and pins an incompatible ANTLR, so the upstream files are carried verbatim: original ASF license headers preserved, provenance banner naming the exact upstream commit (5a254d68), and the two local deviations itemized in __init__.py. Apache-2.0 is MIT-compatible. Skim the provenance banner
1,938 graph/age/hand-written translation layer: analysis, resource firewall, capability table, envelope emission, decode glue. Yes
2,138 nodes/src/nodes/rocketride_{sql,vector,graph}/ — the three nodes. Yes
1,876 Tests (nodes + ai). Yes
288 packages/ai/ core — resolver seam, Account.resolve_db_dsn broker, task-start DSN injection. Smallest and highest-leverage: this is the part wired into every task start. Closely

So the real review surface is ~6,200 lines, and the 288 in packages/ai deserve the
most attention.

Why do we need the Cypher parser?

Because Apache AGE cannot run bare Cypher — every query must be rewritten into a SQL
envelope, and building that envelope correctly requires understanding the query:

  1. The envelope needs a synthesized column list. AGE's form is
    SELECT * FROM cypher('graph', $$ ... $$) AS (c0 agtype, c1 agtype, ...) — the AS
    clause must match the query's RETURN projection exactly. You cannot know how many
    columns a query returns without parsing it.
  2. Parameters require PREPARE/EXECUTE. AGE rejects inline ::agtype parameter
    literals; the only safe binding path (especially through a transaction-mode pooler) is
    a prepared statement — which the layer emits and the cloud smoke verified live.
  3. Read/write classification. The node's safe path runs inside
    SET TRANSACTION READ ONLY; deciding a query is read-only requires structure, not
    string matching (CREATE appearing in a property value must not trip it).
  4. Pre-flight capability gating + resource firewall. Every AGE 1.5.0 gap is caught
    before the database with an actionable message ("use plain MERGE, then SET…") that
    the LLM repair loop can act on — instead of AGE's raw syntax error at or near ":".
    Same for caps: recursion depth, statement timeout, query length, unbounded * paths.

A regex can do none of that reliably, so the layer parses with the official openCypher
M23 grammar
(the standard AGE itself targets) run through ANTLR — which is the entire
reason for the 12.7k generated lines above. Full rationale in
packages/ai/src/ai/common/graph/age/README.md.

Credential model

  • Hosted: the server resolves at task start; nodes never see credentials — only an injected DSN.
    The tenant is derived server-side from the authenticated connection's AccountInfo and is
    never read from pipeline config (no tenant retargeting).
  • OSS: these are cloud-only nodes; an unconfigured build gets an actionable
    "sign into RocketRide cloud" error at task start. The OSS sign-in path
    (personal rr_ API key exchanged at a cloud door) is designed and was fully
    built + tested, but is deferred with its server side — see Deferred.

How it's verified

  • Contract gate: 1700+ passed / 0 failed (includes 50+ new unit tests: resolver
    precedence, broker client against a real fake-HTTP server, translation layer).
  • Integration: 25/25 against containers pinned to the exact cloud stack
    (PG 16.14 + AGE 1.5.0 + pgvector 0.8.0), incl. planner proof that HNSW indexes are used.
  • Local e2e rig: the real merged provisioner (saas Upload RocketRide server link to punkpeye/awesome-mcp-servers #381) run against a CNPG-faithful
    local stack (template-seeded extensions, pgbouncer 1.25.2 transaction mode + TLS +
    auth_query): provision → idempotent recall → pooled tenant connect → full node workload.
  • Cloud smoke (2026-07-28): FULL PASS, 9/9, in-cluster against the live provisioner
    and pooler — provision slow path, idempotency, TLS connect, public-schema DDL, HNSW+knn,
    ag_graph presence, cypher PREPARE/EXECUTE bound-param through transaction pooling,
    READ ONLY write-block. Also landed as a permanent CI test in the saas repo (Implement co-sponsor API integrations for Mar 30 hackathon #414).
  • AGE capability cells: all verified empirically against the exact pin; the four
    formerly-TBD constructs are syntax-level rejects on 1.5.0 and now fail pre-flight with
    actionable alternatives.

Cloud-side dependencies (all shipped)

saas #381 (provisioner, live in cluster), #405/#415 (image pins), #408 (pooler-host env
fix — found from this side pre-smoke, would have broken every minted DSN), #414 (CI smoke).

Deliberately deferred (documented in claude/research/connectors-project/rocketride-db-nodes/)

  • OSS sign-in (whole path, deferred as a unit). The client half — a per-node
    cloud_api_key field exchanging a personal rr_ key at a cloud door — was fully built
    and tested on this branch (8cf951ba), then deliberately reverted (5e732d22): its
    server half (an authenticated door route on api.rocketride.ai + external pooler
    exposure with verify-full DSNs) has no owner yet, and shipping a credential field
    that cannot work would mislead OSS users. Restore point: revert the revert when the
    server side is scheduled.
  • EMULATE rewrites (the capability table has the hook; no emulations are implemented
    and none are planned — unsupported constructs simply reject with guidance).
  • Connection caching / password-rotation invalidation (rotation is day-2 on the cloud side).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added RocketRide SQL, graph (Apache AGE), and vector (pgvector) node integrations with per-tenant DSN resolution.
    • Added Cypher→AGE translation with query validation, read-only safe execution, semantic firewall enforcement, and execution gating.
    • Added vector/document search, upserts, deletion, rendering, and graph schema reflection with caps and timeouts.
  • Documentation
    • Added RocketRide Graph and RocketRide AGE/Cypher translation documentation and guides.
  • Tests
    • Added unit and end-to-end tests for DSN resolution, translation/firewall behavior, and live SQL/graph/vector operations.

dylan-savage and others added 9 commits July 22, 2026 14:14
…rocketride_vector

Add the first two RocketRide-branded cloud database nodes. Both connect only
to the RocketRide cloud data-core and take no connection config: instead of
host/user/password they resolve a ready per-tenant DSN from the account layer,
keyed by the authenticated client_id.

- Account.resolve_db_dsn(client_id) contract on AccountBase; OSS stub raises
  'RocketRide cloud DB nodes require signing into RocketRide cloud'. Real SaaS
  resolver is out of scope (extension.saas).
- ai.common.rocketride_db: the shared seam all RR DB nodes use — client_id
  from ROCKETRIDE_CLIENT_ID (tool_filesystem precedent), asyncio.run bridge
  from sync node lifecycle, DSN normalisation/parsing. Tests inject a fake
  resolver via the ai.account singleton; node code is resolver-agnostic.
- rocketride_sql: mirrors db_postgres on DatabaseGlobalBase/InstanceBase;
  overrides only connection resolution. Structured query surface, EXPLAIN
  validation, and the raw EXECUTE path (allow_execute, default off) inherited.
- rocketride_vector: mirrors vectordb_postgres (node-local Store on
  DocumentStoreBase); adds the missing default index — HNSW created at first
  write with operator class derived from the similarity config
  (cosine/l2/inner_product -> vector_cosine_ops/vector_l2_ops/vector_ip_ops),
  m=16 / ef_construction=64 (config-overridable), skipped with a warning when
  vector_size > 2000. No execute path by design.
- Co-located READMEs with generated-params markers; unit tests (25) run in the
  nodes:test gate, integration tests (test_rocketride_db_full.py, 9) run
  against a local PG16+pgvector container and verify end-to-end connect,
  gated execute, and that semantic search plans an HNSW index scan.

Gate: ./builder nodes:test — 1690 passed, 48 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integrations/neo4j page still linked /nodes/db_neo4j, which broke
docs:build (Docusaurus broken-link check) after the node was renamed to
graph_neo4j.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ rocketride_graph

Apache AGE cannot run bare Cypher, so ship the translation layer the design
mandates at ai/common/graph/age/ and the third cloud DB node on top of it.

Translation layer (pure transform, no DB access):
- analysis: openCypher ANTLR parse (vendored M23 grammar, parser generated
  at ANTLR 4.13.2 and committed — no Java needed by developers). Extracts
  RETURN projection/aliases, write clauses as typed contexts, $params,
  variable-length depth bounds, invoked functions.
- firewall: resource caps on BOTH paths (query length, var-length depth cap,
  unbounded-* rejection, per-txn statement_timeout); semantic read-only rules
  (no writes / no CALL) on the safe path only.
- capabilities: dialect table keyed by AGE version. Verified 1.5.0 REJECT
  cells with actionable messages: datetime(), RETURN *, ORDER BY <alias>
  ('could not find rte'). Unverified cells (merge_on_set, where_label_check,
  multi_label, shortest_path) ship as TBD and pass through per the design.
- emit: cypher() envelope with collision-proof dollar-quoting, synthesized
  'AS (c0 agtype, …)' column list, SET LOCAL search_path/statement_timeout
  preamble (transaction-pooler safe), and PREPARE/EXECUTE/DEALLOCATE binding
  (AGE rejects inline ::agtype literals as the params argument — verified).
- decode: agtype -> plain Python via the vendored apache/age driver parser
  (upstream master @ 5a254d68, Apache-2.0; regenerated at 4.13.2; strict
  error listener instead of upstream's silent error recovery). The stale
  PyPI apache-age-python (0.0.7, hard-pins antlr 4.11.1) is deliberately
  NOT a dependency.
- errors: AgeTranslationError / AgeUnsupportedFeature / AgeFirewallRejected,
  all fail loud into the LLM repair loop.
- deps: antlr4-python3-runtime>=4.13.2,<4.14 via ai/common requirements;
  vendored dirs excluded from ruff (force-exclude so lefthook's explicit
  staged-file invocation honors it).

rocketride_graph node:
- GraphGlobalBase/InstanceBase subclass mirroring graph_neo4j; connection is
  the shared resolve_db_dsn seam (no connection fields). Safe reads run in a
  SET TRANSACTION READ ONLY transaction (server-side write block, verified);
  _validate_query EXPLAINs the translated envelope so Cypher syntax errors
  surface without executing; schema reflection = ag_catalog labels + bounded
  property/edge sampling; raw EXECUTE stays behind allow_execute and still
  obeys resource caps. Fails fast when the configured graph does not exist —
  create_graph ownership is an open question and the node does not decide it.

Tests: 38 layer unit tests (offline) + 15 node integration tests against a
container on the live pin (PG16 + AGE 1.5.0 + pgvector 0.8.0), covering
params round-trip, read-only enforcement, limit+1 truncation contract,
EXECUTE gating, EXPLAIN validation, and schema reflection.

Gate: ./builder nodes:test — 1730 passed, 0 failed. builder docs:build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r + env injection

Implements our half of the data-core contract locked with cloud/ops
(2026-07-23): POST /provision {tenant_id} -> {db_name, dsn}, idempotent,
password derived server-side, graph pre-created.

- AccountBase.resolve_db_dsn default implementation: env-gated call to the
  data-core provisioner (ROCKETRIDE_DB_BROKER_URL + ROCKETRIDE_DB_BROKER_TOKEN,
  Bearer auth, client_id passed verbatim as tenant_id, stdlib urllib in a
  worker thread — no new dependency). Unset env raises the same cloud-sign-in
  error as before, so OSS behavior is unchanged and the OSS subclass override
  is removed as redundant. Consequence: the extension.saas overlay needs NO
  code for these nodes — a cloud deployment sets two env vars.
- task_engine: resolve the DSN server-side at task start (the only process
  with SaaS account context — --saas never reaches node subprocesses) and
  inject ROCKETRIDE_DB_DSN into the node env alongside ROCKETRIDE_CLIENT_ID.
  Scoped to pipelines containing rocketride_sql/vector/graph so unrelated
  tasks never trigger provisioning; resolution failure is non-fatal (the node
  surfaces its own clear error).
- rocketride_db seam: read the injected ROCKETRIDE_DB_DSN first; account
  fallback unchanged. Stateless by design — the broker is idempotent with
  derived passwords, so no caching, persistence, or rotation-invalidation
  logic exists on this side; a rotated password flows through on the next
  task start.

Tests: 10 broker tests against a real localhost fake /provision (Bearer,
verbatim tenant_id, idempotent recall, 4xx/timeout/missing-dsn); 3 pipeline-
scoping tests; seam env-first units; and an integration test running
rocketride_sql end-to-end against the live container via an injected env DSN
with the account poisoned — the exact production delivery path.

Gate: ./builder nodes:test — 1732 passed, 0 failed. ai tests (account + task
engine): 65 passed under the engine runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Open-source builds can now use the three RocketRide cloud DB nodes by
pasting a personal rr_ API key into the new cloud_api_key config field
(password widget, all three nodes). The seam resolves credentials with a
strict precedence — injected ROCKETRIDE_DB_DSN env, then the ambient
account/broker path, then the config key exchanged at the cloud door
(POST api.rocketride.ai/db/dsn, Bearer key, empty body: the door derives
the tenant server-side), else a sign-in error naming the field. Ambient
platform identity deliberately outranks the pasted key so a stray key in
a hosted pipeline can never retarget writes to another tenant; broker
failures propagate untouched rather than being masked by the key path.

validateConfig surfaces the same guidance as a non-fatal warning (save
time runs pre-injection); enforcement stays at task start. Door URL is
env-overridable via ROCKETRIDE_DB_DOOR_URL for staging/tests.

11 new unit tests against a live localhost fake door (precedence, Bearer
passing, 401/500/missing-dsn/unreachable); gate 1743 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lets the sql/vector integration suite run against a provisioned tenant
DSN (t_<slug>_<hash> through a pooler) as well as the default local
container. Verified against both: 10/10 on the default :55432 container
and 10/10 against a real #381-provisioner tenant DSN via transaction-mode
PgBouncer with TLS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#381 shape

The provisioner shipped {database, role, dsn, created} (draft said db_name).
Resolver behavior is unchanged — only dsn is ever read; this updates the two
docstrings and the fake-broker/fake-door payloads to mirror the real response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed on 1.5.0)

Tested empirically against the exact pin container (PG 16.14 + AGE 1.5.0):
MERGE...ON CREATE/MATCH SET, WHERE label predicates (both forms), multi-labels
(n:A:B), and shortestPath() are all syntax-level failures, while plain MERGE on
the same graph succeeds (baseline control). Capability behavior is a property
of the AGE version, not the deployment, so the pin container is valid evidence.

Each cell now rejects pre-flight with an actionable alternative instead of
surfacing AGE's raw syntax error. Zero TBD cells remain in the 1.5.0 table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The client half (cloud_api_key field + door exchange) was built and tested, but
the server half — the door route on api.rocketride.ai and external pooler
exposure — has no owner yet, so shipping the field now would give OSS users a
credential input that cannot work. Deferred until the server side is scheduled;
the full implementation remains in branch history (8cf951b) to restore.

Hosted-path behavior is unchanged: env-injected DSN, else broker via the
signed-in account, else the sign-in error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added docs Documentation module:nodes Python pipeline nodes module:ai AI/ML modules labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds tenant-scoped RocketRide SQL, AGE graph, and pgvector nodes. It introduces broker-backed DSN resolution, a Cypher-to-AGE translation layer, PostgreSQL-backed storage and execution, service registrations, documentation, and unit/integration tests.

Changes

RocketRide cloud database integrations

Layer / File(s) Summary
Tenant DSN resolution and task wiring
packages/ai/src/ai/account/*, packages/ai/src/ai/common/rocketride_db.py, packages/ai/src/ai/modules/task/task_engine.py
Adds broker-based tenant DSN resolution, DSN normalization, and subprocess environment injection for RocketRide database nodes.
Cypher to AGE translation pipeline
packages/ai/src/ai/common/graph/age/*, nodes/test/test_age_translate.py
Adds parsing, firewall enforcement, AGE capability checks, SQL plan emission, agtype decoding, and translation tests.
RocketRide SQL node
nodes/src/nodes/rocketride_sql/*, nodes/test/test_rocketride_db*.py
Adds a DSN-backed SQL driver, service metadata, configuration, documentation, and SQL integration scenarios.
RocketRide AGE graph node
nodes/src/nodes/rocketride_graph/*, nodes/test/test_rocketride_graph_full.py
Adds AGE connection lifecycle, safe/raw query execution, validation, schema reflection, service wiring, and integration tests.
RocketRide pgvector node
nodes/src/nodes/rocketride_vector/*, nodes/test/test_rocketride_db*.py
Adds tenant-scoped vector storage, document operations, HNSW indexing, semantic search, service wiring, and unit/integration tests.
Shared repository support
packages/ai/src/ai/common/requirements.txt, packages/docs/content-static/integrations/neo4j.md, pyproject.toml, .gitattributes
Adds the ANTLR runtime dependency, updates Neo4j references, and excludes or marks vendored AGE grammars and decoders.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: jmaionchi, rod-christensen, stepmikhaylov, dsapandora

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.01% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main addition of RocketRide cloud database nodes across SQL, vector, and graph.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rocketride-db-nodes
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/rocketride-db-nodes

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.5)
nodes/src/nodes/rocketride_vector/services.json

File contains syntax errors that prevent linting: Line 2: Expected a property but instead found '//'.; Line 6: End of file expected; Line 6: End of file expected; Line 6: End of file expected; Line 6: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 45: End of file expected; Li

... [truncated 286 characters] ...

End of file expected; Line 55: End of file expected; Line 56: End of file expected; Line 56: End of file expected; Line 56: End of file expected; Line 56: End of file expected; Line 62: End of file expected; Line 62: End of file expected; Line 62: End of file expected; Line 62: End of file expected; Line 68: End of file expected; Line 68: End of file expected; Line 68: End of file expected; Line 71: End of file expected; Line 77: End of file expected; Line 77: End of file expected; Line 77: End of file expected; Line 90: End of file expected; Line 97: End of file expected; Line 97: End of file expected; Line 97: End of file expected; Line 175: End of file expected; Line 181: End of file expected; Line 181: End of file expected; Line 181: End of file expected; Line 193: End of file expected


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/rocketride_graph/IGlobal.py`:
- Around line 290-304: Update _sample_properties to build the SELECT statement
with psycopg2.sql.SQL and Identifier rather than interpolating
self._qualified(label) into an f-string passed to execute(). Preserve the
existing properties column, LIMIT 1 behavior, cursor/transaction handling, and
result processing.

In `@nodes/src/nodes/rocketride_sql/__init__.py`:
- Around line 27-32: Update the dependency declaration used by the rocketride
SQL node so requirements.txt includes a pinned SQLAlchemy dependency alongside
psycopg2-binary==2.9.12, keeping the install comment and depends(requirements)
flow in __init__.py accurate.

In `@nodes/src/nodes/rocketride_sql/requirements.txt`:
- Line 1: Add sqlalchemy>=2.0 to the rocketride_sql requirements alongside
psycopg2-binary, ensuring DatabaseGlobalBase can import and use create_engine
during configuration validation and runtime queries.

In `@nodes/src/nodes/rocketride_vector/rocketride_vector.py`:
- Around line 157-166: Update the runtime connection in the initialization flow
around resolve_rocketride_dsn and psycopg2.connect to include a 3-second connect
timeout, matching IGlobal.validateConfig. Preserve the existing DSN and
connection setup behavior while ensuring unreachable poolers do not wait for the
OS TCP timeout.
- Around line 557-588: Update render() to size the text buffer from the fetched
results rather than renderChunkSize, avoiding allocation of a massive list for
sparse batches. Preserve chunkId-to-index placement and callback assembly, while
ensuring the buffer covers the highest fetched index needed by lastIndex.
- Around line 143-151: The HNSW configuration handling in rocketride_vector.py
must enforce pgvector’s valid bounds: clamp hnsw_m to a minimum of 2 and
hnsw_ef_construction to its supported minimum while preserving invalid-value
fallbacks. In nodes/src/nodes/rocketride_vector/services.json:117-124, raise
rrvector.hnsw_m.minimum to 2.

In `@packages/ai/src/ai/common/graph/age/_agtype/builder.py`:
- Around line 31-45: Update parseAgeValue to avoid reusing the module-global
resultHandler across calls; instantiate an independent Antlr4ResultHandler for
each parse so mutable lexer, parser, and token-stream state remains isolated
between threads. Preserve the existing None handling and AGTypeError wrapping,
and leave newResultHandler unchanged unless needed to remove the shared
instance.
- Around line 108-117: The _stripStringDelimiters helper currently removes
quotes without decoding Agtype/JSON-style escapes. Update
_stripStringDelimiters, used by visitStringValue, to unescape sequences such as
escaped quotes, newlines, carriage returns, and Unicode escapes while preserving
the existing removal of only the surrounding delimiters; ensure visitStringValue
returns the decoded plain Python string.

In `@packages/ai/src/ai/common/graph/age/emit.py`:
- Around line 116-118: Update the parameter-binding logic in the surrounding
Cypher emission flow: validate that every name in facts.param_names has a
supplied value, including when params is None or empty, and select the
prepared-statement cypher() path whenever facts.param_names is non-empty rather
than based on bool(params). Preserve the existing error for supplied parameters
when the query references no parameters.

In `@packages/ai/src/ai/common/rocketride_db.py`:
- Around line 150-159: Update the DSN parsing flow around the parsed field
accesses to preserve best-effort behavior for malformed authorities: safely
retrieve hostname, port, username, and password, treating any urllib.parse
validation errors as empty or None cosmetic values instead of allowing
exceptions to escape. Keep valid DSN parsing unchanged and ensure node
construction continues for invalid ports or malformed authority data.

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Around line 1569-1587: The task subprocess environment currently inherits
broker credentials through subprocess_env. Update the RocketRide DB setup around
_pipeline_uses_rocketride_db and account.resolve_db_dsn so the parent resolves
and injects only ROCKETRIDE_DB_DSN, while explicitly removing
ROCKETRIDE_DB_BROKER_TOKEN and the broker URL variable from subprocess_env
before launching any child pipeline; preserve the existing non-fatal resolution
handling.

In `@packages/docs/content-static/integrations/neo4j.md`:
- Line 8: Update the Neo4j documentation examples to use the renamed graph_neo4j
node: change the JSON sample’s provider value to graph_neo4j and replace the old
provider/tool prefix in the tool examples with graph_neo4j., preserving the
surrounding example structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a72dcceb-8609-4add-9af6-990b10d3eb0c

📥 Commits

Reviewing files that changed from the base of the PR and between 1483794 and 5e732d2.

⛔ Files ignored due to path filters (13)
  • nodes/src/nodes/rocketride_graph/rocketride_graph.svg is excluded by !**/*.svg
  • nodes/src/nodes/rocketride_sql/rocketride_sql.svg is excluded by !**/*.svg
  • nodes/src/nodes/rocketride_vector/rocketride_vector.svg is excluded by !**/*.svg
  • packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeLexer.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeListener.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeParser.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeVisitor.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_agtype/gen/__init__.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_cypher/gen/CypherLexer.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_cypher/gen/CypherListener.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_cypher/gen/CypherParser.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_cypher/gen/CypherVisitor.py is excluded by !**/gen/**
  • packages/ai/src/ai/common/graph/age/_cypher/gen/__init__.py is excluded by !**/gen/**
📒 Files selected for processing (49)
  • nodes/src/nodes/rocketride_graph/IGlobal.py
  • nodes/src/nodes/rocketride_graph/IInstance.py
  • nodes/src/nodes/rocketride_graph/README.md
  • nodes/src/nodes/rocketride_graph/__init__.py
  • nodes/src/nodes/rocketride_graph/requirements.txt
  • nodes/src/nodes/rocketride_graph/services.json
  • nodes/src/nodes/rocketride_sql/IGlobal.py
  • nodes/src/nodes/rocketride_sql/IInstance.py
  • nodes/src/nodes/rocketride_sql/README.md
  • nodes/src/nodes/rocketride_sql/__init__.py
  • nodes/src/nodes/rocketride_sql/requirements.txt
  • nodes/src/nodes/rocketride_sql/services.json
  • nodes/src/nodes/rocketride_vector/IEndpoint.py
  • nodes/src/nodes/rocketride_vector/IGlobal.py
  • nodes/src/nodes/rocketride_vector/IInstance.py
  • nodes/src/nodes/rocketride_vector/README.md
  • nodes/src/nodes/rocketride_vector/__init__.py
  • nodes/src/nodes/rocketride_vector/requirements.txt
  • nodes/src/nodes/rocketride_vector/rocketride_vector.py
  • nodes/src/nodes/rocketride_vector/services.json
  • nodes/test/test_age_translate.py
  • nodes/test/test_rocketride_db.py
  • nodes/test/test_rocketride_db_full.py
  • nodes/test/test_rocketride_graph_full.py
  • packages/ai/src/ai/account/base.py
  • packages/ai/src/ai/account/oss/__init__.py
  • packages/ai/src/ai/common/graph/age/README.md
  • packages/ai/src/ai/common/graph/age/__init__.py
  • packages/ai/src/ai/common/graph/age/_agtype/Agtype.g4
  • packages/ai/src/ai/common/graph/age/_agtype/__init__.py
  • packages/ai/src/ai/common/graph/age/_agtype/builder.py
  • packages/ai/src/ai/common/graph/age/_agtype/exceptions.py
  • packages/ai/src/ai/common/graph/age/_agtype/models.py
  • packages/ai/src/ai/common/graph/age/_cypher/Cypher.g4
  • packages/ai/src/ai/common/graph/age/_cypher/__init__.py
  • packages/ai/src/ai/common/graph/age/analysis.py
  • packages/ai/src/ai/common/graph/age/capabilities.py
  • packages/ai/src/ai/common/graph/age/decode.py
  • packages/ai/src/ai/common/graph/age/emit.py
  • packages/ai/src/ai/common/graph/age/errors.py
  • packages/ai/src/ai/common/graph/age/firewall.py
  • packages/ai/src/ai/common/graph/age/translate.py
  • packages/ai/src/ai/common/requirements.txt
  • packages/ai/src/ai/common/rocketride_db.py
  • packages/ai/src/ai/modules/task/task_engine.py
  • packages/ai/tests/ai/account/test_resolve_db_dsn.py
  • packages/ai/tests/ai/modules/task/test_task_engine.py
  • packages/docs/content-static/integrations/neo4j.md
  • pyproject.toml

Comment thread nodes/src/nodes/rocketride_graph/IGlobal.py
Comment thread nodes/src/nodes/rocketride_sql/__init__.py
Comment thread nodes/src/nodes/rocketride_sql/requirements.txt
Comment thread nodes/src/nodes/rocketride_vector/rocketride_vector.py Outdated
Comment thread nodes/src/nodes/rocketride_vector/rocketride_vector.py
Comment thread packages/ai/src/ai/common/graph/age/_agtype/builder.py
Comment thread packages/ai/src/ai/common/graph/age/emit.py
Comment thread packages/ai/src/ai/common/rocketride_db.py Outdated
Comment thread packages/ai/src/ai/modules/task/task_engine.py
Comment thread packages/docs/content-static/integrations/neo4j.md
dylan-savage and others added 2 commits July 28, 2026 12:08
- graph: build the label-sample SELECT with psycopg2.sql.Identifier
  (drop hand-rolled _qualified); bound runtime connect at 3s
- vector: 3s connect_timeout on the runtime connection; size render()
  buffer from fetched rows, not the chunk window; clamp HNSW params to
  pgvector bounds (m in [2,100], ef_construction in [4,1000], >= 2*m)
  and raise services.json minimums to match
- agtype decoder: decode JSON escapes in STRING tokens via json.loads
  (escaped quotes/newlines/unicode previously survived as literal
  backslash sequences); deviation itemized in the vendoring banner
- emit: pre-flight error when the query references $parameters with no
  supplied values; key the PREPARE/EXECUTE branch on facts.param_names
- rocketride_db: parse_dsn_fields degrades per-field on malformed
  authorities (invalid port, bad IPv6 bracket) instead of raising
- task engine: scrub ROCKETRIDE_DB_BROKER_URL/_TOKEN from the node
  subprocess env — children only ever get the resolved DSN
- sql node: correct the stale SQLAlchemy comment (dep comes from
  ai.common's own requirements, same as the other DB nodes)
- docs: fix stale neo4jdb provider / tool prefix in neo4j integration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-nodes

# Conflicts:
#	packages/ai/tests/ai/modules/task/test_task_engine.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ai/src/ai/modules/task/task_engine.py (1)

1569-1594: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Clear inherited ROCKETRIDE_DB_DSN before launching the child.

Removing only the broker variables is insufficient: subprocess_env starts from os.environ, so every child can still inherit a parent-level DSN—including unrelated pipelines or the stale DSN when tenant resolution fails. Remove ROCKETRIDE_DB_DSN before the conditional and set it only after successful tenant-scoped resolution.

Proposed fix
 subprocess_env = os.environ.copy()
 subprocess_env['ROCKETRIDE_CLIENT_ID'] = self.client_id
+subprocess_env.pop('ROCKETRIDE_DB_DSN', None)
 subprocess_env.pop('ROCKETRIDE_DB_BROKER_URL', None)
 subprocess_env.pop('ROCKETRIDE_DB_BROKER_TOKEN', None)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/ai/modules/task/task_engine.py` around lines 1569 - 1594,
Clear ROCKETRIDE_DB_DSN from subprocess_env before the
_pipeline_uses_rocketride_db() conditional, alongside the broker credential
removals. Leave it absent when the pipeline does not use RocketRide DB nodes or
tenant-scoped resolve_db_dsn fails, and set it only after successful resolution.
♻️ Duplicate comments (1)
nodes/src/nodes/rocketride_vector/rocketride_vector.py (1)

574-586: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid allocating by the highest sparse chunk ID.

Line 582 can still allocate ~33.5 million entries when one fetched row has chunkId near the window end. Store fetched content by index and join sorted values instead.

Proposed fix
-            lastIndex = max(index for index, _ in rows)
-            text = [''] * (lastIndex + 1)
-            for index, content in rows:
-                text[index] = content
-
-            fullText = ''.join(text)
+            content_by_index = {index: content for index, content in rows}
+            fullText = ''.join(
+                content_by_index[index] for index in sorted(content_by_index)
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/rocketride_vector/rocketride_vector.py` around lines 574 -
586, Update the row assembly in the retrieval flow around rows, lastIndex, and
fullText so it does not allocate a list sized by the highest sparse index. Store
fetched content keyed by its nonnegative index, order entries by index, and join
the sorted content values while preserving the existing empty-results break
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/docs/content-static/integrations/neo4j.md`:
- Around line 103-104: Update the tool-prefix example in the connected-agent
documentation to use the configured node ID, such as `graph_1.get_data` or
`<node-id>.get_data`, instead of the provider ID `graph_neo4j.get_data`; clarify
that bare tool methods are automatically namespaced by the node ID.

---

Outside diff comments:
In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Around line 1569-1594: Clear ROCKETRIDE_DB_DSN from subprocess_env before the
_pipeline_uses_rocketride_db() conditional, alongside the broker credential
removals. Leave it absent when the pipeline does not use RocketRide DB nodes or
tenant-scoped resolve_db_dsn fails, and set it only after successful resolution.

---

Duplicate comments:
In `@nodes/src/nodes/rocketride_vector/rocketride_vector.py`:
- Around line 574-586: Update the row assembly in the retrieval flow around
rows, lastIndex, and fullText so it does not allocate a list sized by the
highest sparse index. Store fetched content keyed by its nonnegative index,
order entries by index, and join the sorted content values while preserving the
existing empty-results break behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b7414c78-ae00-493c-91b6-979cb8b02676

📥 Commits

Reviewing files that changed from the base of the PR and between ac68fcb and 06ab8c5.

📒 Files selected for processing (12)
  • nodes/src/nodes/rocketride_graph/IGlobal.py
  • nodes/src/nodes/rocketride_sql/__init__.py
  • nodes/src/nodes/rocketride_vector/rocketride_vector.py
  • nodes/src/nodes/rocketride_vector/services.json
  • nodes/test/test_age_translate.py
  • nodes/test/test_rocketride_db.py
  • packages/ai/src/ai/common/graph/age/_agtype/__init__.py
  • packages/ai/src/ai/common/graph/age/_agtype/builder.py
  • packages/ai/src/ai/common/graph/age/emit.py
  • packages/ai/src/ai/common/rocketride_db.py
  • packages/ai/src/ai/modules/task/task_engine.py
  • packages/docs/content-static/integrations/neo4j.md

Comment on lines 103 to +104
When connected to an agent, the node exposes three functions under the node's
prefix (e.g. `neo4j.get_data`):
prefix (e.g. `graph_neo4j.get_data`):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the configured node ID for the tool prefix.

The example config uses id: "graph_1", so the discoverable tool is graph_1.get_data (or <node-id>.get_data), not graph_neo4j.get_data. graph_neo4j is the provider ID. Based on learnings, tool discovery automatically namespaces bare tool methods with the node-ID prefix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/content-static/integrations/neo4j.md` around lines 103 - 104,
Update the tool-prefix example in the connected-agent documentation to use the
configured node ID, such as `graph_1.get_data` or `<node-id>.get_data`, instead
of the provider ID `graph_neo4j.get_data`; clarify that bare tool methods are
automatically namespaced by the node ID.

Source: Learnings

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

Labels

docs Documentation module:ai AI/ML modules module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant