fix(test/mocks): correct three Python truthiness/isinstance traps - #748
Conversation
isinstance(p, int) is True for bool values in Python (bool subclasses int). The two LIMIT-detection sites in the psycopg2 mock — _handle_semantic_search line 184 and _extract_limit line 308 — were matching boolean params (e.g. False from `isDeleted = %s`) and assigning them as the SQL LIMIT, silently breaking WHERE filtering. Add `not isinstance(p, bool)` guard to both checks. Refs rocketride-org#747
`if limit and ...` treated limit=0 as "no limit" because 0 is falsy in Python, so calling get(limit=0) returned all rows instead of zero. Use `is not None` so that None means unlimited and 0 means zero. Refs rocketride-org#747
The sort key collapsed 0.0 cosine distance (exact match) into the 1.0 fallback because 0.0 is falsy in Python, ranking exact matches at the bottom of nearest-neighbor results instead of the top. Use `is not None` so only missing distances get the fallback. Refs rocketride-org#747
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThree test-mock fixes: ChromaDB now respects ChangesTest Mock Bug Fixes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
|
No description provided. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
nodes/test/mocks/chromadb/__init__.py (1)
186-203:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
limit=0still returns one row.The new check runs after appending, so a zero limit still leaks the first item before the loop breaks. Move the limit guard before the append, or special-case
0up front.🐛 Proposed fix
count = 0 for id, data in items: metadata = data.get("metadata", {}) document = data.get("document", "") @@ if count < offset: count += 1 continue + + if limit is not None and len(results["ids"]) >= limit: + break results["ids"].append(id) if "metadatas" in include: results["metadatas"].append(self._serialize_metadata(metadata)) @@ if "embeddings" in include: results["embeddings"].append(data.get("embedding")) count += 1 - - # Apply limit - if limit is not None and len(results["ids"]) >= limit: - break🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nodes/test/mocks/chromadb/__init__.py` around lines 186 - 203, The loop appends an item before enforcing the limit, so passing limit=0 still returns one row; update the loop in nodes/test/mocks/chromadb/__init__.py (the block that manipulates count, results, ids, include, metadata, document, data and calls self._serialize_metadata) to guard against the limit before appending (or handle limit==0 at the top of the method) — i.e., check if limit is not None and len(results["ids"]) >= limit (or limit == 0) and break/return prior to pushing id/metadatas/documents/embeddings to results so no item is emitted when limit is zero.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@nodes/test/mocks/chromadb/__init__.py`:
- Around line 186-203: The loop appends an item before enforcing the limit, so
passing limit=0 still returns one row; update the loop in
nodes/test/mocks/chromadb/__init__.py (the block that manipulates count,
results, ids, include, metadata, document, data and calls
self._serialize_metadata) to guard against the limit before appending (or handle
limit==0 at the top of the method) — i.e., check if limit is not None and
len(results["ids"]) >= limit (or limit == 0) and break/return prior to pushing
id/metadatas/documents/embeddings to results so no item is emitted when limit is
zero.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c3ec8356-5940-4c92-9656-770c6b16cd42
📒 Files selected for processing (3)
nodes/test/mocks/chromadb/__init__.pynodes/test/mocks/psycopg2/__init__.pynodes/test/mocks/weaviate/__init__.py
The previous fix correctly distinguished None from 0 for the limit parameter, but the break check still ran AFTER appending, so limit=0 returned one item instead of zero. Caught by CodeRabbit on rocketride-org#748. Move the check ahead of the append so the loop short-circuits before emitting the first row when the cap is already reached. Verified the three cases trace through correctly: limit=0 returns 0 items, limit=2 returns 2 items, limit=None returns all items. Refs rocketride-org#747
|
Good catch — the v1 fix correctly distinguished |
asclearuc
left a comment
There was a problem hiding this comment.
Thanks for tackling these, @TrishaReddygari — three real bugs in one tightly-scoped PR, with clear per-commit messages and a good follow-up after CodeRabbit caught the chromadb append-ordering subtlety. Nice cleanup.
Value Assessment
These are mock-only bugs, but mock-only bugs are the worst kind: tests pass against the mock, real behavior diverges in production, and the gap is invisible until something breaks live. Catching the entire bool ⊂ int / 0 is falsy family across all three vector-store mocks at once is the right call — the maintenance cost of three one-liners is trivial, and bundling them avoids three round-trip reviews of the same class of fix.
…mocks Three test mocks mishandled falsy values, causing mock tests to pass while real-service tests would fail: psycopg2: bool is a subclass of int in Python, so isinstance(False, int) returns True. SQL WHERE params like isDeleted=False were incorrectly matched as LIMIT values. Fixed by adding not isinstance(p, bool) guards in _handle_semantic_search and _extract_limit. chromadb: 'if limit and ...' treated limit=0 as falsy, returning all rows instead of zero rows. Fixed with explicit 'limit is not None' check. weaviate: sort key 'distance if distance else 1.0' collapsed 0.0 (exact match, best possible distance) to 1.0, ranking perfect matches last. Fixed with 'distance if distance is not None else 1.0'. Resolves #748
|
Hi @TrishaReddygari — thanks for tackling this! Your fix correctly identified all three truthiness/isinstance traps, and the chromadb restructuring (moving the limit check before the append) was a thoughtful touch. We've merged #780 which addresses the same three bugs with a minimal diff. Closing this one in its favor, but appreciate the contribution! |
* add gemini vision node * clear cache to prevent bleed * error handling + README * fix non-symmetric cache + empty frame guard * bug fixes + CR comments * add openai vision node * update README + update models * fix: use chunk reference in _processFullTables to resolve chunkId scoping bug In the second phase of _processFullTables, the code incorrectly used `doc.metadata.chunkId` when initializing a new table entry. `doc` was a stale variable from the outer `for docKey, doc in documents.items()` loop, not the current chunk being processed. Additionally, Doc() was called with invalid constructor params (objectId, chunk) that don't exist on the Pydantic model. Fix mirrors the pattern used in _processFullDocuments: reset page_content on the chunk itself and store it directly, so the subsequent append builds the correct concatenated table content. Resolves #776 * fix: address CodeRabbit review - score assignment and first-chunk aliasing Two follow-up fixes from code review: 1. Line 157: tableDocs[tableKey] = doc.score replaced the Doc object with a raw float, crashing Phase 2 with AttributeError when page_content was accessed. Fixed to tableDocs[tableKey].score = doc.score. 2. Lines 184-189: tableDocs[tableKey] and chunk aliased the same object, so clearing chunk.page_content then appending chunk.page_content accumulated empty string, silently dropping the first chunk's text. Fixed by saving original_content before clearing, restoring it after assignment, and continuing to skip the duplicate append below. * fix: guard against falsy-value traps in psycopg2, chromadb, weaviate mocks Three test mocks mishandled falsy values, causing mock tests to pass while real-service tests would fail: psycopg2: bool is a subclass of int in Python, so isinstance(False, int) returns True. SQL WHERE params like isDeleted=False were incorrectly matched as LIMIT values. Fixed by adding not isinstance(p, bool) guards in _handle_semantic_search and _extract_limit. chromadb: 'if limit and ...' treated limit=0 as falsy, returning all rows instead of zero rows. Fixed with explicit 'limit is not None' check. weaviate: sort key 'distance if distance else 1.0' collapsed 0.0 (exact match, best possible distance) to 1.0, ranking perfect matches last. Fixed with 'distance if distance is not None else 1.0'. Resolves #748 * fix(client-python): break drain cycle causing RecursionError on disconnect ## Summary <!-- Describe your changes in 1-3 bullet points --> - Fix `RecursionError: maximum recursion depth exceeded` in `_GatheringFuture.cancel` during `/task/service` WebSocket teardown by breaking the cycle between an in-flight message-handler task and the disconnect-drain gather it waits on. - Fix C — `dap_client.request()`'s `ConnectionError` handler now uses fire-and-forget `self._transport.disconnect()` instead of `await self.disconnect()`. Develop's `disconnect()` is sync-returns-`Task`, matching the documented carve-out at `transport_websocket.py:516-517`. - Fix A — `_drain_message_tasks` walks each candidate's `_fut_waiter` chain and excludes tasks that transitively await the drainer itself, so the gather can never close back on itself even if a future caller awaits disconnect from a handler context. ## Type <!-- What kind of change is this? (feature, fix, refactor, docs, chore, etc.) --> fix ## Testing - [x] Tests added or updated - [x] Tested locally - [ ] `./builder test` passes `packages/client-python/tests/test_drain_cycle_regression.py` adds two regression tests, one pinning each fix: - `test_drain_cycle_does_not_recurse` — builds the smallest task graph that exhibits the cycle (a handler awaiting `transport.disconnect()` while it is itself in `_message_tasks`); fails on pre-fix code via deadlock + recursion, passes after Fix A. - `test_request_does_not_await_self_disconnect_on_connection_error` — asserts `dap_client.request()` does not call `self.disconnect()` on `ConnectionError`; fails pre-fix, passes after Fix C. The pytest teardown after a pre-fix run reproduces the exact `tasks.py:721` recursion as the production trace, confirming the unit test covers the production bug rather than a similar-looking variant. `./builder test` reports one unrelated failure on `test_node_cases[llm_openai:services:test2:openai-4o-mini]` — a live OpenAI call (`avoidMocks: true`, `requires: ['ROCKETRIDE_OPENAI_KEY']`) where the LLM produced no answer for `"What is 5*5?"`. Pre-existing flakiness in the real-API path, unrelated to these changes (transport-layer fixes don't touch LLM provider calls). ## Checklist - [x] Commit messages follow [conventional commits](https://www.conventionalcommits.org/) - [x] No secrets or credentials included - [ ] Wiki updated (if applicable) - [ ] Breaking changes documented (if applicable) ## Linked Issue <!-- REQUIRED: Every PR must be linked to an issue. Use one of: --> <!-- Fixes #123 / Closes #123 / Resolves #123 --> Fixes #791 * chore(deps)(deps): update requests requirement in /nodes/src/nodes Updates the requirements on [requests](https://github.com/psf/requests) to permit the latest version. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](psf/requests@v2.34.0...v2.34.2) --- updated-dependencies: - dependency-name: requests dependency-version: 2.34.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * feat(shell-ui): apply saved theme on init to prevent tan loading flash - index.html: inline script sets --rr-bg-default and body background synchronously before React renders, so the page starts dark for users who have a dark preference saved in localStorage (rr:home:theme). - createShellConfig onInit: reads rr:home:theme then rr:theme to apply the user's saved theme instead of always defaulting to rocketride-light, eliminating the tan background during the 1.5s OAuth code-exchange loading window. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(shell-ui): apply saved theme on init to prevent tan loading flash (#878) - index.html: inline script sets --rr-bg-default and body background synchronously before React renders, so the page starts dark for users who have a dark preference saved in localStorage (rr:home:theme). - createShellConfig onInit: reads rr:home:theme then rr:theme to apply the user's saved theme instead of always defaulting to rocketride-light, eliminating the tan background during the 1.5s OAuth code-exchange loading window. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore(deps): pin cryptography==46.0.7 in text_output (#884) Previously unpinned, leaving the resolved version subject to PyPI latest. Three open Dependabot alerts all clear at 46.0.7: - GHSA-r6ph-v2qm-q3c2 (high) subgroup attack on SECT curves - GHSA-p423-j2cm-9vmq (medium) buffer overflow on non-contiguous buffers - GHSA-m959-cc7f-wv43 (low) incomplete DNS name constraint enforcement Exact pin matches the sibling node db_mysql/requirements.txt (also pins ==46.0.7) for reproducible builds and consistent dependency policy across nodes. Future bumps will arrive via Dependabot PRs. * chore(deps): patch minimatch ReDoS across all four major lines via overrides (#887) Resolves 6 high-severity Dependabot alerts on root pnpm-lock.yaml. (3 additional alerts on packages/shared-ui/pnpm-lock.yaml clear when #886 lands, which deletes that dead sub-lockfile.) Adds four bounded pnpm.overrides entries — one per major — so each transitive caller stays on the API surface it expects, just patched: minimatch@<3.1.4 → >=3.1.4 <4 was 3.1.2, fix 3.1.4* minimatch@5 → >=5.1.8 <6 was 5.1.6, fix 5.1.8 minimatch@9 → >=9.0.7 <10 was 9.0.5, fix 9.0.7 minimatch@10 → >=10.2.3 <11 was 10.1.1, fix 10.2.3 * 3.1.4 covers GHSA-23c5-xmqv-rm74 in the 3.x line (Dependabot only flagged 3.1.3 for GHSA-7r86-cg39-jmmj; the tighter bound pre-empts the second advisory too). Post-install resolves to minimatch@3.1.5, 5.1.9, 9.0.9, 10.2.5. Advisories cleared (all high): GHSA-3ppc-4f35-3m26 minimatch ReDoS: repeated wildcards with non-matching literal in pattern (5.x) GHSA-23c5-xmqv-rm74 minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regex (3.x, 5.x) GHSA-7r86-cg39-jmmj minimatch ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments (3.x, 5.x, 9.x, 10.x) A single high pin (e.g. minimatch>=10.2.3) was considered and rejected: minimatch had breaking API changes across majors (default vs named exports between 3→5→7); forcing all callers to 10.x risks breaking deps that still rely on 3.x/5.x semantics. The bounded approach matches the existing repo convention (handlebars: >=4.7.9 <5; protobufjs: >=7.5.5 <8). * chore(deps): bump dompurify to 3.4.x in shared-ui, drop dead sub-lockfile (#886) Resolves all 16 open Dependabot alerts on dompurify (8 unique GHSAs, all medium severity) ranging in fix versions 3.3.2 → 3.4.0. Changes: - packages/shared-ui/package.json: peerDependency dompurify ~3.2.5 → ~3.4.0 - package.json: add "dompurify": ">=3.4.0" to root pnpm.overrides, matching the existing security-bump convention (cross-spawn, glob, handlebars, js-yaml, postcss, protobufjs, qs). - packages/shared-ui/pnpm-lock.yaml: deleted. This file was a dead artifact — last (and only) touched in the initial commit (8197744), zero references in CI, build scripts, or publish workflows. Its sole effect was duplicating dompurify alerts since Dependabot scans every pnpm-lock.yaml independently. - pnpm-lock.yaml: regenerated. dompurify resolves to 3.4.3 (latest 3.4.x available); no 3.2.x/3.3.x copies remain. Advisories cleared (all medium): GHSA-cj63-jhhr-wcxv GHSA-cjmm-f4jc-qw8r GHSA-h8r8-wccr-v5f2 GHSA-v2wj-7wpq-c8vv GHSA-39q2-94rc-95cp GHSA-crv5-9vww-q3g8 GHSA-h7mw-gpvr-xq4m GHSA-v9jr-rg53-9pgp Call site (packages/shared-ui/src/components/canvas/util/helpers.tsx:228) uses DOMPurify.sanitize(input, { ADD_ATTR: [...] }) — stable across the 3.x line; no API breakage. * docs(agents): add observability & tracing integration guide (#716) * docs(agents): add observability & tracing integration guide Documents how external services consume runtime logs, lifecycle events, and pipeline traces over the WebSocket DAP channel so integrators have a single source of truth on what the server actually exposes (no OTel, Prometheus, or audit-log DB). Wires the new doc into the .rocketride/docs sync list and lists it in every agent stub (Claude Code, CLAUDE.md, AGENTS.md, Cursor, Windsurf, Copilot) so installed workspaces surface it alongside the existing README/quickstart/API references. Co-Authored-By: Claude <noreply@anthropic.com> * docs(agents): tag observability doc websocket fence with language Adds the `text` language identifier to the WebSocket URL fenced code block so markdownlint MD040 stops flagging it, per CodeRabbit review on PR #716. Co-Authored-By: Claude <noreply@anthropic.com> * docs(agents): remove HTTP transport from observability guide Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> * fix(cli): implement signal handlers for graceful shutdown (RR-655) (#891) This is from an already approved PR Rebased onto current develop, dropping changes already merged separately (redaction regex, CSS comment fix, PageStatus UI, upload math fix). Only the signal handler work remains: - SIGINT/SIGTERM handlers with proper exit codes (130/143) - Double-signal force exit, 5s cleanup timeout - Idempotent cleanupClient via dedup promise - isCancelled guards on command actions to prevent exit races - run()/main() shutdown awareness via awaitShutdown() Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(deps): patch undici + lodash family ReDoS/RCE via overrides (#888) Resolves 16 open Dependabot alerts (6 undici, 5 lodash, 5 lodash-es) in a single PR. Affected manifests: root pnpm-lock.yaml (8 alerts) and packages/shared-ui/pnpm-lock.yaml (8 alerts, all clear once #886 lands deleting that dead sub-lockfile). Changes: - package.json: add three bounded entries to pnpm.overrides "lodash": ">=4.18.0 <5" was 4.17.23 "lodash-es": ">=4.18.0 <5" was 4.17.23 "undici": ">=7.24.0 <8" was 7.20.0 - packages/shared-ui/package.json: bump lodash peerDep ~4.17.21 → ~4.18.0 (declaration alignment; shared-ui has zero direct lodash imports in src/, so this is purely a doc-correctness fix). Post-install resolves to lodash@4.18.1, lodash-es@4.18.1, undici@7.25.0. Advisories cleared: undici (all in 7.0.0..<7.24.0): GHSA-f269-vfmq-vjvj (high) WebSocket 64-bit length parser overflow / crash GHSA-v9p9-hfj2-hcw8 (high) WebSocket unhandled exception via server_max_win GHSA-vrm6-8vpv-qv8q (high) WebSocket permessage-deflate unbounded memory GHSA-2mjp-6q6p-2qxm (medium) HTTP request/response smuggling GHSA-phc3-fgpg-7m6h (medium) DeduplicationHandler unbounded memory GHSA-4992-7rv2-5pvq (medium) CRLF injection via `upgrade` option lodash + lodash-es (<= 4.17.23): GHSA-r5fr-rjxr-66jc (high) Code injection via _.template imports key names GHSA-f23m-r3pf-42rh (medium) Prototype pollution via _.unset / array path bypass Note: GHSA-xxjr-mmjv-4gpg (medium, fix=4.17.23) was already covered by the prior dependabot-bumped 4.17.23 and will auto-close when Dependabot rescans. Bounded ranges match the existing repo convention (handlebars: ">=4.7.9 <5", protobufjs: ">=7.5.5 <8") and avoid pulling in unreleased lodash 5.x or undici 8.x majors that would carry breaking changes. * chore(deps): bump shared-ui devDependencies - @rollup/plugin-commonjs 28.0.6 → 28.0.9 - @rollup/plugin-replace 6.0.2 → 6.0.3 - @rollup/plugin-typescript 12.1.4 → 12.3.0 - @types/react 18.2.21 → 18.3.28 - @types/react-dom 18.2.7 → 18.3.7 - rollup 4.59.0 → 4.60.4 - rollup-plugin-dts 6.2.3 → 6.4.1 Replaces Dependabot PR #881 which failed due to stale pnpm-lock.yaml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(database): add QuestionType.EXECUTE for direct SQL/Cypher execution Add a new QuestionType.EXECUTE value plus a `client.database.query()` namespace on both the Python and TypeScript SDKs so trusted callers can issue raw SQL or Cypher against database pipelines without paying the LLM-translation tax. db_postgres / db_mysql / db_neo4j skip both the LLM call and the is_sql_safe / _is_cypher_safe gating when the question arrives with type=EXECUTE. End-to-end verified against local Postgres + Neo4j with the matching SDK on both languages (8/8 cases pass; nodes contract suite 733/733). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(database): gate EXECUTE path, bound results, validate SDK inputs Addresses CodeRabbit review on PR #782: - Gate QuestionType.EXECUTE behind opt-in allow_execute config (default off) on both SQL (DatabaseGlobalBase) and Neo4j (db_neo4j.IGlobal) backends so arbitrary write/admin SQL/Cypher cannot run without explicit operator consent. - Bound _executeRawQuery SELECT results with fetchmany(max_execute_rows+1) to avoid memory blowups on large reads; normalize negative rowcount to 0. - Wrap EXECUTE output blocks (SQL + Neo4j) in try/except so formatting and lane writes can't escape writeQuestions; serialize with default=str and sanitize SQL rows before json.dumps to handle non-serializable DB values. - Validate non-empty token/sql in client.database.query() for both Python and TypeScript SDKs; raise ValueError / throw Error instead of dispatching. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(database): bound Neo4j EXECUTE rows, parse allow_execute strictly Addresses second CodeRabbit review on PR #782: - _run_query_raw now caps result collection at max_execute_rows (default 25000), mirroring the SQL path, so a large EXECUTE MATCH...RETURN cannot exhaust the worker. Overflow raises ValueError, caught at the IInstance handler. - allow_execute config now parses strings explicitly: only '1', 'true', 'yes', 'on' (case-insensitive) enable EXECUTE. Plain bool() would have let 'false' or '0' weaken the opt-in gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(database): hoist max_execute_rows default to a single constant per module Replace the three repeated 25000 literals in each of db_global_base.py and db_neo4j/IGlobal.py with a module-level DEFAULT_MAX_EXECUTE_ROWS constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(database): reference DEFAULT_MAX_EXECUTE_ROWS in get_data limits Replace the remaining 25000 literals in the get_data tool description and _clamp_limit / inline limit clamp with the existing DEFAULT_MAX_EXECUTE_ROWS constant. Single source of truth across the EXECUTE path and the non-EXECUTE get_data path within each module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(database): add QuestionType.DIALECT for engine discovery Lets SDK callers ask a database pipeline which engine it's connected to so applications can branch on dialect-specific behavior (SQL syntax, type coercion) or assert they aren't pointed at the wrong kind of database. - New QuestionType.DIALECT in both Python and TypeScript client schemas. - DatabaseInstanceBase: new abstract _db_dialect() method + DIALECT branch in writeQuestions that emits {'dialect': '...'} on the answers lane. No gate (the value is metadata, not data). - db_postgres / db_mysql implement _db_dialect() -> 'postgres' / 'mysql'. - db_neo4j handles DIALECT in writeQuestions and emits 'neo4j'. - Python SDK: client.database.dialect(token=...) returns DatabaseDialect enum. - TypeScript SDK: client.database.dialect({token}) returns DatabaseDialect enum; re-export from client/index.ts so callers can import from the package root. - services.json: each db node now exposes a UI-toggleable allow_execute boolean (default false) so operators can opt-in to the EXECUTE path from the Dropper UI instead of hand-editing pipe JSON. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(client-python): clean pending DAP request on send failure (#736) Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> * chore: removed obsolote input section from 72 service configuration files. Input array in services*.jsons were duplicate of lane. Closes #629 (#728) Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ryan-t-christensen <ryan.christensen@rocketride.ai> Co-authored-by: koushik sreevatsa <koushiksreevatsa100@gmail.com> Co-authored-by: Alexandru Sclearuc <asclearuc@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Anand Ray <anandray@users.noreply.github.com> Co-authored-by: Joshua Phillips <J.dspears@yahoo.com> Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> Co-authored-by: Dylan Savage <dylanmsavage@hotmail.com> Co-authored-by: wdwd720 <mihirmodi808@gmail.com> Co-authored-by: Nechar KC <126724860+SpeedRelativity@users.noreply.github.com>
* add gemini vision node * clear cache to prevent bleed * error handling + README * fix non-symmetric cache + empty frame guard * bug fixes + CR comments * add openai vision node * update README + update models * fix: use chunk reference in _processFullTables to resolve chunkId scoping bug In the second phase of _processFullTables, the code incorrectly used `doc.metadata.chunkId` when initializing a new table entry. `doc` was a stale variable from the outer `for docKey, doc in documents.items()` loop, not the current chunk being processed. Additionally, Doc() was called with invalid constructor params (objectId, chunk) that don't exist on the Pydantic model. Fix mirrors the pattern used in _processFullDocuments: reset page_content on the chunk itself and store it directly, so the subsequent append builds the correct concatenated table content. Resolves #776 * fix: address CodeRabbit review - score assignment and first-chunk aliasing Two follow-up fixes from code review: 1. Line 157: tableDocs[tableKey] = doc.score replaced the Doc object with a raw float, crashing Phase 2 with AttributeError when page_content was accessed. Fixed to tableDocs[tableKey].score = doc.score. 2. Lines 184-189: tableDocs[tableKey] and chunk aliased the same object, so clearing chunk.page_content then appending chunk.page_content accumulated empty string, silently dropping the first chunk's text. Fixed by saving original_content before clearing, restoring it after assignment, and continuing to skip the duplicate append below. * fix: guard against falsy-value traps in psycopg2, chromadb, weaviate mocks Three test mocks mishandled falsy values, causing mock tests to pass while real-service tests would fail: psycopg2: bool is a subclass of int in Python, so isinstance(False, int) returns True. SQL WHERE params like isDeleted=False were incorrectly matched as LIMIT values. Fixed by adding not isinstance(p, bool) guards in _handle_semantic_search and _extract_limit. chromadb: 'if limit and ...' treated limit=0 as falsy, returning all rows instead of zero rows. Fixed with explicit 'limit is not None' check. weaviate: sort key 'distance if distance else 1.0' collapsed 0.0 (exact match, best possible distance) to 1.0, ranking perfect matches last. Fixed with 'distance if distance is not None else 1.0'. Resolves #748 * fix(client-python): break drain cycle causing RecursionError on disconnect ## Summary <!-- Describe your changes in 1-3 bullet points --> - Fix `RecursionError: maximum recursion depth exceeded` in `_GatheringFuture.cancel` during `/task/service` WebSocket teardown by breaking the cycle between an in-flight message-handler task and the disconnect-drain gather it waits on. - Fix C — `dap_client.request()`'s `ConnectionError` handler now uses fire-and-forget `self._transport.disconnect()` instead of `await self.disconnect()`. Develop's `disconnect()` is sync-returns-`Task`, matching the documented carve-out at `transport_websocket.py:516-517`. - Fix A — `_drain_message_tasks` walks each candidate's `_fut_waiter` chain and excludes tasks that transitively await the drainer itself, so the gather can never close back on itself even if a future caller awaits disconnect from a handler context. ## Type <!-- What kind of change is this? (feature, fix, refactor, docs, chore, etc.) --> fix ## Testing - [x] Tests added or updated - [x] Tested locally - [ ] `./builder test` passes `packages/client-python/tests/test_drain_cycle_regression.py` adds two regression tests, one pinning each fix: - `test_drain_cycle_does_not_recurse` — builds the smallest task graph that exhibits the cycle (a handler awaiting `transport.disconnect()` while it is itself in `_message_tasks`); fails on pre-fix code via deadlock + recursion, passes after Fix A. - `test_request_does_not_await_self_disconnect_on_connection_error` — asserts `dap_client.request()` does not call `self.disconnect()` on `ConnectionError`; fails pre-fix, passes after Fix C. The pytest teardown after a pre-fix run reproduces the exact `tasks.py:721` recursion as the production trace, confirming the unit test covers the production bug rather than a similar-looking variant. `./builder test` reports one unrelated failure on `test_node_cases[llm_openai:services:test2:openai-4o-mini]` — a live OpenAI call (`avoidMocks: true`, `requires: ['ROCKETRIDE_OPENAI_KEY']`) where the LLM produced no answer for `"What is 5*5?"`. Pre-existing flakiness in the real-API path, unrelated to these changes (transport-layer fixes don't touch LLM provider calls). ## Checklist - [x] Commit messages follow [conventional commits](https://www.conventionalcommits.org/) - [x] No secrets or credentials included - [ ] Wiki updated (if applicable) - [ ] Breaking changes documented (if applicable) ## Linked Issue <!-- REQUIRED: Every PR must be linked to an issue. Use one of: --> <!-- Fixes #123 / Closes #123 / Resolves #123 --> Fixes #791 * chore(deps)(deps): update requests requirement in /nodes/src/nodes Updates the requirements on [requests](https://github.com/psf/requests) to permit the latest version. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](psf/requests@v2.34.0...v2.34.2) --- updated-dependencies: - dependency-name: requests dependency-version: 2.34.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * feat(shell-ui): apply saved theme on init to prevent tan loading flash - index.html: inline script sets --rr-bg-default and body background synchronously before React renders, so the page starts dark for users who have a dark preference saved in localStorage (rr:home:theme). - createShellConfig onInit: reads rr:home:theme then rr:theme to apply the user's saved theme instead of always defaulting to rocketride-light, eliminating the tan background during the 1.5s OAuth code-exchange loading window. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(shell-ui): apply saved theme on init to prevent tan loading flash (#878) - index.html: inline script sets --rr-bg-default and body background synchronously before React renders, so the page starts dark for users who have a dark preference saved in localStorage (rr:home:theme). - createShellConfig onInit: reads rr:home:theme then rr:theme to apply the user's saved theme instead of always defaulting to rocketride-light, eliminating the tan background during the 1.5s OAuth code-exchange loading window. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore(deps): pin cryptography==46.0.7 in text_output (#884) Previously unpinned, leaving the resolved version subject to PyPI latest. Three open Dependabot alerts all clear at 46.0.7: - GHSA-r6ph-v2qm-q3c2 (high) subgroup attack on SECT curves - GHSA-p423-j2cm-9vmq (medium) buffer overflow on non-contiguous buffers - GHSA-m959-cc7f-wv43 (low) incomplete DNS name constraint enforcement Exact pin matches the sibling node db_mysql/requirements.txt (also pins ==46.0.7) for reproducible builds and consistent dependency policy across nodes. Future bumps will arrive via Dependabot PRs. * chore(deps): patch minimatch ReDoS across all four major lines via overrides (#887) Resolves 6 high-severity Dependabot alerts on root pnpm-lock.yaml. (3 additional alerts on packages/shared-ui/pnpm-lock.yaml clear when #886 lands, which deletes that dead sub-lockfile.) Adds four bounded pnpm.overrides entries — one per major — so each transitive caller stays on the API surface it expects, just patched: minimatch@<3.1.4 → >=3.1.4 <4 was 3.1.2, fix 3.1.4* minimatch@5 → >=5.1.8 <6 was 5.1.6, fix 5.1.8 minimatch@9 → >=9.0.7 <10 was 9.0.5, fix 9.0.7 minimatch@10 → >=10.2.3 <11 was 10.1.1, fix 10.2.3 * 3.1.4 covers GHSA-23c5-xmqv-rm74 in the 3.x line (Dependabot only flagged 3.1.3 for GHSA-7r86-cg39-jmmj; the tighter bound pre-empts the second advisory too). Post-install resolves to minimatch@3.1.5, 5.1.9, 9.0.9, 10.2.5. Advisories cleared (all high): GHSA-3ppc-4f35-3m26 minimatch ReDoS: repeated wildcards with non-matching literal in pattern (5.x) GHSA-23c5-xmqv-rm74 minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regex (3.x, 5.x) GHSA-7r86-cg39-jmmj minimatch ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments (3.x, 5.x, 9.x, 10.x) A single high pin (e.g. minimatch>=10.2.3) was considered and rejected: minimatch had breaking API changes across majors (default vs named exports between 3→5→7); forcing all callers to 10.x risks breaking deps that still rely on 3.x/5.x semantics. The bounded approach matches the existing repo convention (handlebars: >=4.7.9 <5; protobufjs: >=7.5.5 <8). * chore(deps): bump dompurify to 3.4.x in shared-ui, drop dead sub-lockfile (#886) Resolves all 16 open Dependabot alerts on dompurify (8 unique GHSAs, all medium severity) ranging in fix versions 3.3.2 → 3.4.0. Changes: - packages/shared-ui/package.json: peerDependency dompurify ~3.2.5 → ~3.4.0 - package.json: add "dompurify": ">=3.4.0" to root pnpm.overrides, matching the existing security-bump convention (cross-spawn, glob, handlebars, js-yaml, postcss, protobufjs, qs). - packages/shared-ui/pnpm-lock.yaml: deleted. This file was a dead artifact — last (and only) touched in the initial commit (8197744), zero references in CI, build scripts, or publish workflows. Its sole effect was duplicating dompurify alerts since Dependabot scans every pnpm-lock.yaml independently. - pnpm-lock.yaml: regenerated. dompurify resolves to 3.4.3 (latest 3.4.x available); no 3.2.x/3.3.x copies remain. Advisories cleared (all medium): GHSA-cj63-jhhr-wcxv GHSA-cjmm-f4jc-qw8r GHSA-h8r8-wccr-v5f2 GHSA-v2wj-7wpq-c8vv GHSA-39q2-94rc-95cp GHSA-crv5-9vww-q3g8 GHSA-h7mw-gpvr-xq4m GHSA-v9jr-rg53-9pgp Call site (packages/shared-ui/src/components/canvas/util/helpers.tsx:228) uses DOMPurify.sanitize(input, { ADD_ATTR: [...] }) — stable across the 3.x line; no API breakage. * docs(agents): add observability & tracing integration guide (#716) * docs(agents): add observability & tracing integration guide Documents how external services consume runtime logs, lifecycle events, and pipeline traces over the WebSocket DAP channel so integrators have a single source of truth on what the server actually exposes (no OTel, Prometheus, or audit-log DB). Wires the new doc into the .rocketride/docs sync list and lists it in every agent stub (Claude Code, CLAUDE.md, AGENTS.md, Cursor, Windsurf, Copilot) so installed workspaces surface it alongside the existing README/quickstart/API references. Co-Authored-By: Claude <noreply@anthropic.com> * docs(agents): tag observability doc websocket fence with language Adds the `text` language identifier to the WebSocket URL fenced code block so markdownlint MD040 stops flagging it, per CodeRabbit review on PR #716. Co-Authored-By: Claude <noreply@anthropic.com> * docs(agents): remove HTTP transport from observability guide Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> * fix(cli): implement signal handlers for graceful shutdown (RR-655) (#891) This is from an already approved PR Rebased onto current develop, dropping changes already merged separately (redaction regex, CSS comment fix, PageStatus UI, upload math fix). Only the signal handler work remains: - SIGINT/SIGTERM handlers with proper exit codes (130/143) - Double-signal force exit, 5s cleanup timeout - Idempotent cleanupClient via dedup promise - isCancelled guards on command actions to prevent exit races - run()/main() shutdown awareness via awaitShutdown() Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(deps): patch undici + lodash family ReDoS/RCE via overrides (#888) Resolves 16 open Dependabot alerts (6 undici, 5 lodash, 5 lodash-es) in a single PR. Affected manifests: root pnpm-lock.yaml (8 alerts) and packages/shared-ui/pnpm-lock.yaml (8 alerts, all clear once #886 lands deleting that dead sub-lockfile). Changes: - package.json: add three bounded entries to pnpm.overrides "lodash": ">=4.18.0 <5" was 4.17.23 "lodash-es": ">=4.18.0 <5" was 4.17.23 "undici": ">=7.24.0 <8" was 7.20.0 - packages/shared-ui/package.json: bump lodash peerDep ~4.17.21 → ~4.18.0 (declaration alignment; shared-ui has zero direct lodash imports in src/, so this is purely a doc-correctness fix). Post-install resolves to lodash@4.18.1, lodash-es@4.18.1, undici@7.25.0. Advisories cleared: undici (all in 7.0.0..<7.24.0): GHSA-f269-vfmq-vjvj (high) WebSocket 64-bit length parser overflow / crash GHSA-v9p9-hfj2-hcw8 (high) WebSocket unhandled exception via server_max_win GHSA-vrm6-8vpv-qv8q (high) WebSocket permessage-deflate unbounded memory GHSA-2mjp-6q6p-2qxm (medium) HTTP request/response smuggling GHSA-phc3-fgpg-7m6h (medium) DeduplicationHandler unbounded memory GHSA-4992-7rv2-5pvq (medium) CRLF injection via `upgrade` option lodash + lodash-es (<= 4.17.23): GHSA-r5fr-rjxr-66jc (high) Code injection via _.template imports key names GHSA-f23m-r3pf-42rh (medium) Prototype pollution via _.unset / array path bypass Note: GHSA-xxjr-mmjv-4gpg (medium, fix=4.17.23) was already covered by the prior dependabot-bumped 4.17.23 and will auto-close when Dependabot rescans. Bounded ranges match the existing repo convention (handlebars: ">=4.7.9 <5", protobufjs: ">=7.5.5 <8") and avoid pulling in unreleased lodash 5.x or undici 8.x majors that would carry breaking changes. * chore(deps): bump shared-ui devDependencies - @rollup/plugin-commonjs 28.0.6 → 28.0.9 - @rollup/plugin-replace 6.0.2 → 6.0.3 - @rollup/plugin-typescript 12.1.4 → 12.3.0 - @types/react 18.2.21 → 18.3.28 - @types/react-dom 18.2.7 → 18.3.7 - rollup 4.59.0 → 4.60.4 - rollup-plugin-dts 6.2.3 → 6.4.1 Replaces Dependabot PR #881 which failed due to stale pnpm-lock.yaml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(database): add QuestionType.EXECUTE for direct SQL/Cypher execution Add a new QuestionType.EXECUTE value plus a `client.database.query()` namespace on both the Python and TypeScript SDKs so trusted callers can issue raw SQL or Cypher against database pipelines without paying the LLM-translation tax. db_postgres / db_mysql / db_neo4j skip both the LLM call and the is_sql_safe / _is_cypher_safe gating when the question arrives with type=EXECUTE. End-to-end verified against local Postgres + Neo4j with the matching SDK on both languages (8/8 cases pass; nodes contract suite 733/733). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(database): gate EXECUTE path, bound results, validate SDK inputs Addresses CodeRabbit review on PR #782: - Gate QuestionType.EXECUTE behind opt-in allow_execute config (default off) on both SQL (DatabaseGlobalBase) and Neo4j (db_neo4j.IGlobal) backends so arbitrary write/admin SQL/Cypher cannot run without explicit operator consent. - Bound _executeRawQuery SELECT results with fetchmany(max_execute_rows+1) to avoid memory blowups on large reads; normalize negative rowcount to 0. - Wrap EXECUTE output blocks (SQL + Neo4j) in try/except so formatting and lane writes can't escape writeQuestions; serialize with default=str and sanitize SQL rows before json.dumps to handle non-serializable DB values. - Validate non-empty token/sql in client.database.query() for both Python and TypeScript SDKs; raise ValueError / throw Error instead of dispatching. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(database): bound Neo4j EXECUTE rows, parse allow_execute strictly Addresses second CodeRabbit review on PR #782: - _run_query_raw now caps result collection at max_execute_rows (default 25000), mirroring the SQL path, so a large EXECUTE MATCH...RETURN cannot exhaust the worker. Overflow raises ValueError, caught at the IInstance handler. - allow_execute config now parses strings explicitly: only '1', 'true', 'yes', 'on' (case-insensitive) enable EXECUTE. Plain bool() would have let 'false' or '0' weaken the opt-in gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(database): hoist max_execute_rows default to a single constant per module Replace the three repeated 25000 literals in each of db_global_base.py and db_neo4j/IGlobal.py with a module-level DEFAULT_MAX_EXECUTE_ROWS constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(database): reference DEFAULT_MAX_EXECUTE_ROWS in get_data limits Replace the remaining 25000 literals in the get_data tool description and _clamp_limit / inline limit clamp with the existing DEFAULT_MAX_EXECUTE_ROWS constant. Single source of truth across the EXECUTE path and the non-EXECUTE get_data path within each module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(database): add QuestionType.DIALECT for engine discovery Lets SDK callers ask a database pipeline which engine it's connected to so applications can branch on dialect-specific behavior (SQL syntax, type coercion) or assert they aren't pointed at the wrong kind of database. - New QuestionType.DIALECT in both Python and TypeScript client schemas. - DatabaseInstanceBase: new abstract _db_dialect() method + DIALECT branch in writeQuestions that emits {'dialect': '...'} on the answers lane. No gate (the value is metadata, not data). - db_postgres / db_mysql implement _db_dialect() -> 'postgres' / 'mysql'. - db_neo4j handles DIALECT in writeQuestions and emits 'neo4j'. - Python SDK: client.database.dialect(token=...) returns DatabaseDialect enum. - TypeScript SDK: client.database.dialect({token}) returns DatabaseDialect enum; re-export from client/index.ts so callers can import from the package root. - services.json: each db node now exposes a UI-toggleable allow_execute boolean (default false) so operators can opt-in to the EXECUTE path from the Dropper UI instead of hand-editing pipe JSON. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(client-python): clean pending DAP request on send failure (#736) Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> * chore: removed obsolote input section from 72 service configuration files. Input array in services*.jsons were duplicate of lane. Closes #629 (#728) Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> * chore: bump minor version on all release packages Bump all package versions to the next minor release (patch reset to 0): - rocketride-server: 3.1.2 → 3.2.0 - chat-ui, dropper-ui: 2.0.1 → 2.1.0 - All other packages: 1.0.x → 1.1.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style(nodes): ruff format llm_vision_gemini + llm_vision_openai PRs #672 (gemini-vision) and #677 (openai-vision) merged with `ruff format --check` failures on three files, leaving develop CI red and gating the post-CI nightly publish workflow_run. Pure whitespace changes — line wraps long tuple unpacks and except-clause type tuples per ruff's default style — no semantic edits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nodes): remove llm_vertex, fix llm_gemini key handling, migrate vision nodes to LLMBase - Remove deprecated llm_vertex node (Vertex AI now supported via llm_gemini) - Fix llm_gemini API key validation: drop strict startsWith('AI') check, add sub-key fallback for old pipe files where sub-key is profile.split('_',1)[1] - Fix llm_gemini validateConfig to early-return when apikey is empty (secure fields are not decrypted at validate time) - Migrate llm_vision_gemini and llm_vision_openai IInstance from deprecated IInstanceGenericLLM to ai.common.llm_base.LLMBase - Remove startsWith('AI') key format check from gemini_vision.py - Remove vertex icon from UI icon map and asset bundle - Hide modelSource field in services.common.llm.json (was readonly) - Bump client-typescript SDK version to 1.1.0 - Update test mocks to remove langchain_google_vertexai mock Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(task): add backward-compat fallback for token in DAP request root get_task_token() now checks arguments.token first, then falls back to request.token so older clients and debug command token injection still work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): prioritize URL auth param over sessionStorage token The URL param carries the freshly-minted key for the current task and must not be shadowed by a stale sessionStorage value from a previous task on the same origin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): remove redundant auth check in dropper-ui to match chat-ui Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ryan-t-christensen <ryan.christensen@rocketride.ai> Co-authored-by: koushik sreevatsa <koushiksreevatsa100@gmail.com> Co-authored-by: Alexandru Sclearuc <asclearuc@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Anand Ray <anandray@users.noreply.github.com> Co-authored-by: Joshua Phillips <J.dspears@yahoo.com> Co-authored-by: Dmitrii Karataev <dmitry.karataev@gmail.com> Co-authored-by: Dylan Savage <dylanmsavage@hotmail.com> Co-authored-by: wdwd720 <mihirmodi808@gmail.com> Co-authored-by: Nechar KC <126724860+SpeedRelativity@users.noreply.github.com> Co-authored-by: Dmitrii Kataraev <dmitrii.kataraev@rocketride.ai>
Summary
Fixes three pre-existing Python truthiness /
isinstancebugs in test mocks, identified by CodeRabbit during review of #744. All three are the same class of bug, applied as one commit per file:nodes/test/mocks/psycopg2/__init__.py— booleans were matchingisinstance(p, int)and getting assigned as a SQLLIMIT. Fixed by addingnot isinstance(p, bool)at both call sites (_handle_semantic_searchline 184 and_extract_limitline 308).nodes/test/mocks/chromadb/__init__.py—if limit and ...treatedlimit=0as "no limit," returning all rows instead of zero. Fixed withis not None.nodes/test/mocks/weaviate/__init__.py— sort key collapsed0.0cosine distance (exact match) into the1.0fallback, ranking exact matches last. Fixed withis not None.Why this matters
Mock-only bugs are sneaky: tests pass against the mock, real behavior diverges in production, and the gap is hard to spot. Catching the truthiness trap class across all three mocks at once seemed worth doing as one PR.
Testing
Each fix is a one-liner with no behavior change in the cases that previously worked. Existing test suite should remain green; the bugs only manifested in edge cases that no current test exercises.
Breaking changes
None.
Fixes #747
Summary by CodeRabbit