feat(nodes,ai): RocketRide cloud DB nodes — sql, vector, graph - #1713
feat(nodes,ai): RocketRide cloud DB nodes — sql, vector, graph#1713dylan-savage wants to merge 12 commits into
Conversation
…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>
🤖 Internal: Discord sync markerAuto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete. |
📝 WalkthroughWalkthroughThe 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. ChangesRocketRide cloud database integrations
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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.jsonFile 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. Comment |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (13)
nodes/src/nodes/rocketride_graph/rocketride_graph.svgis excluded by!**/*.svgnodes/src/nodes/rocketride_sql/rocketride_sql.svgis excluded by!**/*.svgnodes/src/nodes/rocketride_vector/rocketride_vector.svgis excluded by!**/*.svgpackages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeLexer.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeListener.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeParser.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeVisitor.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_agtype/gen/__init__.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_cypher/gen/CypherLexer.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_cypher/gen/CypherListener.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_cypher/gen/CypherParser.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_cypher/gen/CypherVisitor.pyis excluded by!**/gen/**packages/ai/src/ai/common/graph/age/_cypher/gen/__init__.pyis excluded by!**/gen/**
📒 Files selected for processing (49)
nodes/src/nodes/rocketride_graph/IGlobal.pynodes/src/nodes/rocketride_graph/IInstance.pynodes/src/nodes/rocketride_graph/README.mdnodes/src/nodes/rocketride_graph/__init__.pynodes/src/nodes/rocketride_graph/requirements.txtnodes/src/nodes/rocketride_graph/services.jsonnodes/src/nodes/rocketride_sql/IGlobal.pynodes/src/nodes/rocketride_sql/IInstance.pynodes/src/nodes/rocketride_sql/README.mdnodes/src/nodes/rocketride_sql/__init__.pynodes/src/nodes/rocketride_sql/requirements.txtnodes/src/nodes/rocketride_sql/services.jsonnodes/src/nodes/rocketride_vector/IEndpoint.pynodes/src/nodes/rocketride_vector/IGlobal.pynodes/src/nodes/rocketride_vector/IInstance.pynodes/src/nodes/rocketride_vector/README.mdnodes/src/nodes/rocketride_vector/__init__.pynodes/src/nodes/rocketride_vector/requirements.txtnodes/src/nodes/rocketride_vector/rocketride_vector.pynodes/src/nodes/rocketride_vector/services.jsonnodes/test/test_age_translate.pynodes/test/test_rocketride_db.pynodes/test/test_rocketride_db_full.pynodes/test/test_rocketride_graph_full.pypackages/ai/src/ai/account/base.pypackages/ai/src/ai/account/oss/__init__.pypackages/ai/src/ai/common/graph/age/README.mdpackages/ai/src/ai/common/graph/age/__init__.pypackages/ai/src/ai/common/graph/age/_agtype/Agtype.g4packages/ai/src/ai/common/graph/age/_agtype/__init__.pypackages/ai/src/ai/common/graph/age/_agtype/builder.pypackages/ai/src/ai/common/graph/age/_agtype/exceptions.pypackages/ai/src/ai/common/graph/age/_agtype/models.pypackages/ai/src/ai/common/graph/age/_cypher/Cypher.g4packages/ai/src/ai/common/graph/age/_cypher/__init__.pypackages/ai/src/ai/common/graph/age/analysis.pypackages/ai/src/ai/common/graph/age/capabilities.pypackages/ai/src/ai/common/graph/age/decode.pypackages/ai/src/ai/common/graph/age/emit.pypackages/ai/src/ai/common/graph/age/errors.pypackages/ai/src/ai/common/graph/age/firewall.pypackages/ai/src/ai/common/graph/age/translate.pypackages/ai/src/ai/common/requirements.txtpackages/ai/src/ai/common/rocketride_db.pypackages/ai/src/ai/modules/task/task_engine.pypackages/ai/tests/ai/account/test_resolve_db_dsn.pypackages/ai/tests/ai/modules/task/test_task_engine.pypackages/docs/content-static/integrations/neo4j.mdpyproject.toml
- 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
There was a problem hiding this comment.
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 winClear inherited
ROCKETRIDE_DB_DSNbefore launching the child.Removing only the broker variables is insufficient:
subprocess_envstarts fromos.environ, so every child can still inherit a parent-level DSN—including unrelated pipelines or the stale DSN when tenant resolution fails. RemoveROCKETRIDE_DB_DSNbefore 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 winAvoid allocating by the highest sparse chunk ID.
Line 582 can still allocate ~33.5 million entries when one fetched row has
chunkIdnear 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
📒 Files selected for processing (12)
nodes/src/nodes/rocketride_graph/IGlobal.pynodes/src/nodes/rocketride_sql/__init__.pynodes/src/nodes/rocketride_vector/rocketride_vector.pynodes/src/nodes/rocketride_vector/services.jsonnodes/test/test_age_translate.pynodes/test/test_rocketride_db.pypackages/ai/src/ai/common/graph/age/_agtype/__init__.pypackages/ai/src/ai/common/graph/age/_agtype/builder.pypackages/ai/src/ai/common/graph/age/emit.pypackages/ai/src/ai/common/rocketride_db.pypackages/ai/src/ai/modules/task/task_engine.pypackages/docs/content-static/integrations/neo4j.md
| 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`): |
There was a problem hiding this comment.
📐 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
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 databaserocketride_vector— pgvector store with HNSW indexing (cosine/L2/IP,m/ef_constructionconfigurable)rocketride_graph— Cypher queries translated to Apache AGE, safe path runs in server-sideREAD ONLYtransactionsai/common/rocketride_db.py) — one place that answers "which database is yours", with strict precedence:ROCKETRIDE_DB_DSNinjected by the server at task start (hosted path)Account.resolve_db_dsn(broker → provisioner)AccountBase.resolve_db_dsncalls thedata-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).ai/common/graph/age/) — vendored openCypher M23parser (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 withPREPARE/EXECUTEparam binding (transaction-pooling safe), vendored agtype decoder.apache-age-pythondeliberately 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:
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.graph/age/_agtype/— vendored agtype decoder fromapache/age(Apache-2.0). PyPI'sapache-age-pythonis 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.graph/age/— hand-written translation layer: analysis, resource firewall, capability table, envelope emission, decode glue.nodes/src/nodes/rocketride_{sql,vector,graph}/— the three nodes.packages/ai/core — resolver seam,Account.resolve_db_dsnbroker, task-start DSN injection. Smallest and highest-leverage: this is the part wired into every task start.So the real review surface is ~6,200 lines, and the 288 in
packages/aideserve themost 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:
SELECT * FROM cypher('graph', $$ ... $$) AS (c0 agtype, c1 agtype, ...)— theASclause must match the query's
RETURNprojection exactly. You cannot know how manycolumns a query returns without parsing it.
PREPARE/EXECUTE. AGE rejects inline::agtypeparameterliterals; 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.
SET TRANSACTION READ ONLY; deciding a query is read-only requires structure, notstring matching (
CREATEappearing in a property value must not trip it).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
The tenant is derived server-side from the authenticated connection's
AccountInfoand isnever read from pipeline config (no tenant retargeting).
"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 fullybuilt + tested, but is deferred with its server side — see Deferred.
How it's verified
precedence, broker client against a real fake-HTTP server, translation layer).
(PG 16.14 + AGE 1.5.0 + pgvector 0.8.0), incl. planner proof that HNSW indexes are used.
local stack (template-seeded extensions, pgbouncer 1.25.2 transaction mode + TLS +
auth_query): provision → idempotent recall → pooled tenant connect → full node workload.
and pooler — provision slow path, idempotency, TLS connect, public-schema DDL, HNSW+knn,
ag_graphpresence, cypherPREPARE/EXECUTEbound-param through transaction pooling,READ ONLYwrite-block. Also landed as a permanent CI test in the saas repo (Implement co-sponsor API integrations for Mar 30 hackathon #414).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/)
cloud_api_keyfield exchanging a personalrr_key at a cloud door — was fully builtand tested on this branch (
8cf951ba), then deliberately reverted (5e732d22): itsserver half (an authenticated door route on api.rocketride.ai + external pooler
exposure with
verify-fullDSNs) has no owner yet, and shipping a credential fieldthat cannot work would mislead OSS users. Restore point: revert the revert when the
server side is scheduled.
EMULATErewrites (the capability table has the hook; no emulations are implementedand none are planned — unsupported constructs simply reject with guidance).
🤖 Generated with Claude Code
Summary by CodeRabbit