Skip to content

fix(ai): install AI test deps via depends() so engine constraints apply#1471

Merged
asclearuc merged 1 commit into
developfrom
fix/ai-tests
Jul 7, 2026
Merged

fix(ai): install AI test deps via depends() so engine constraints apply#1471
asclearuc merged 1 commit into
developfrom
fix/ai-tests

Conversation

@asclearuc

@asclearuc asclearuc commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ai:test installed test requirements with plain pip. pip is never handed the engine's compiled constraints file, so unpinned deps (e.g. pillow) resolved to the latest version instead of the engine-pinned one.
  • On Windows this broke test collection. pillow was pulled to a newer version, then depends() tried to downgrade it at import time and could not overwrite the already-loaded _imaging*.pyd (file locked). That left a half-written PIL package, so tests failed with ModuleNotFoundError: No module named 'PIL'.
  • Install the test requirements through depends() instead of pip. It runs uv pip install with the constraints file, so every dep (and each nested -r include) resolves to its pinned version. pillow now stays at the pinned 10.4.0.

Type

fix (test/build tooling)

Testing

  • Tests added or updated
  • Tested locally
  • ./builder test passes

The four previously-failing test files collect and pass (23 tests), and pillow resolves to the pinned 10.4.0. Full ./builder test not run.

Checklist

  • Commit messages follow conventional commits
  • No secrets or credentials included
  • Wiki updated (if applicable)
  • Breaking changes documented (if applicable)

Linked Issue

Fixes #

Summary by CodeRabbit

  • Chores
    • Updated the test setup to install Python dependencies through the standard engine-managed flow, helping keep test environments consistent and avoid unexpected dependency upgrades.

…1466)

The ai:test pre-install used plain pip, which is not handed the engine's
compiled constraints file. Unpinned deps (e.g. pillow) resolved to latest
instead of the engine-pinned version. On Windows this corrupted the install:
pillow was pulled to a newer version, then depends() tried to downgrade it at
import and could not overwrite the loaded _imaging*.pyd, leaving a half-written
PIL package (ModuleNotFoundError: No module named 'PIL').

Install the test requirements through depends() instead, so uv applies the
constraints and every dep resolves to its pinned version.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

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

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7dec2c5e-f213-4a23-b87d-1bb438e3a0bc

📥 Commits

Reviewing files that changed from the base of the PR and between a430793 and 7bd741a.

📒 Files selected for processing (1)
  • packages/ai/scripts/tasks.js

📝 Walkthrough

Walkthrough

The ai:run-pytest action in packages/ai/scripts/tasks.js now installs requirements by invoking the engine's depends() function via a Python snippet instead of calling pip install directly, with comments explaining the change ensures engine constraints apply and avoids unpinned dependency resolution.

Changes

Dependency Installation Change

Layer / File(s) Summary
Engine-based dependency install for pytest task
packages/ai/scripts/tasks.js
Replaced direct pip install -r requirements.txt with an engine-executed Python snippet calling depends(requirementsPath), adding comments on constraint behavior.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

Suggested labels: `ci/cd`

Suggested reviewers: `jmaionchi`, `Rod-Christensen`, `stepmikhaylov`

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: installing AI test dependencies through depends() so engine constraints are applied.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/ai-tests

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.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: private package registry requires authentication. Disable ESLint in CodeRabbit settings or use public packages.


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.

@asclearuc
asclearuc enabled auto-merge (squash) July 7, 2026 08:57
@asclearuc
asclearuc merged commit d37d27a into develop Jul 7, 2026
22 checks passed
@asclearuc
asclearuc deleted the fix/ai-tests branch July 7, 2026 13:34
Rod-Christensen added a commit that referenced this pull request Jul 13, 2026
* docs(readme): add RocketRide Cloud links and section (#1444)

Add a "Two ways to run RocketRide" section, surface RocketRide Cloud
in the Features table and Quick Start deploy step, and add a footer
call-to-action. All links point to https://cloud.rocketride.ai/.

Closes #1401

Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MBP.attlocal.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(explorer): File Explorer app with rich media viewers, Monaco editor, hex viewer, and Open with… menu (#1356)

Reviewed and resolved all issues from Alexandrus review

* feat(explorer): add File Explorer app with rich media viewers, drag-drop, and download

Introduces the explorer-ui micro-frontend — a multi-tab file browser and viewer
for the RocketRide server store. Previously, files stored on the server could
only be accessed programmatically via the SDK. This gives users a visual way to
browse, preview, upload, download, and manage their store files directly from
the RocketRide web UI.

## What it does

### Media type system (mediaTypes.ts)
Three content modes determine how each file type is loaded and displayed:
- `inline` — text read as a string (plain text, markdown, JSON)
- `link` — presigned URL for native browser streaming with Range/seek (video, audio)
- `blob` — data fetched via presigned URL, served as a local blob: URL so
  cross-origin content works in iframes and img elements (images, PDF, docx, xlsx)

### Viewer components (viewers/)
Each file type has a self-contained viewer in its own file:
- VideoViewer / AudioViewer — call fsGetUrl directly for streaming URLs
- ImageViewer / PdfViewer — render from blob URLs (no CORS issues)
- DocxViewer — Word document rendering via docx-preview library
- SpreadsheetViewer — Excel/CSV rendering via SheetJS (xlsx) library
- MarkdownViewer — shared MarkdownRenderer with GFM, syntax highlighting
- JsonViewer — pretty-printed with syntax highlighting via MarkdownRenderer
- TextViewer — editable textarea with monospace font
- BinaryViewer — fallback for unsupported types

### Drag-and-drop (shared Explorer component)
- Drag files/folders within the tree to move them between directories
- Drop files from the OS to upload (chunked 4MB writes via fsOpen/fsWrite/fsClose)
- Visual drop-target highlighting (brand-colored outline) on directories
- Fully backward-compatible: new optional onMove/onUpload props on IExplorerProps;
  existing hosts (VS Code, pipe-builder) that don't pass them are unaffected

### Download
- Download menu item with BxDownload icon in the file kebab menu
- Injected via the existing fileActions prop so it only appears in explorer-ui
- Uses fsGetUrl to get a presigned URL, triggers browser download via <a> element

## Why

### Backend (fsGetUrl / fs_geturl DAP command)
- New DAP command `fs_geturl` returns a time-limited URL for accessing a store file
- S3 backend: presigned URL via generate_presigned_url
- Azure backend: SAS token URL
- Local filesystem backend: JWT-signed URL pointing at a new /task/fetch endpoint
- RR_SIGNING_KEY env var added to .env.template for the JWT signing secret
- Python SDK: new fs_get_url method on the store mixin
- TypeScript SDK: new fsGetUrl method on RocketRideClient

### Architecture decisions
- Viewers are self-contained components in a viewers/ subdirectory, keeping
  ExplorerApp.tsx as a thin dispatcher (~30 lines in FilePane)
- Video/audio viewers own their URL lifecycle (call fsGetUrl directly) so they
  get native browser streaming with Range request support for seeking
- Blob-mode files (images, PDF, docx, xlsx) are fetched by the VFS store layer
  and served as local blob: URLs to avoid cross-origin restrictions in iframes
- The shared Explorer component gains drag-drop via optional props, preserving
  backward compatibility with all existing hosts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(explorer): add Monaco editor, hex viewer, and "Open with…" menu

Add Monaco code editor with theme-aware syntax highlighting for 80+
file types, a streaming hex viewer with range-based fetching for large
files, and an "Open with…" submenu on the file context menu to switch
between compatible viewers per file type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): add fs_geturl to task command dispatch table test

The fs_geturl subcommand was added to TaskCommands but the test's
expected command set was not updated, causing CI to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(deps): remove cryptography upper-bound pin causing CI version conflict

The <47 ceiling on cryptography fought with the 49.x installed by the
base build, making uv race to downgrade a locked .pyd on Windows CI.
Drop the ceiling, keep the >=46.0.7 security floor, and relax exact
pins in node-level requirements to >= so they no longer conflict.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(explorer): address CodeRabbit review feedback across explorer stack

Security:
- Remove hardcoded RR_SIGNING_KEY default from .config
- Validate and clamp expires_in (max 1h) in file_store.get_url()
- Fail fast when RR_BASE_URL is unset instead of falling back to localhost
- Defer RR_BASE_URL when port=0 until the real port is resolved
- Check fetch response.ok before creating blob URLs in store.ts
- Only cache HTTP 206 responses in HexViewer range fetcher
- Reject absolute paths in TS SDK validateStorePath (match Python SDK)

Stability:
- Reset viewer state (url/error/html) on file switch in Audio, Video,
  Docx, Spreadsheet, and Hex viewers to prevent stale content flashing
- Handle empty files in HexViewer instead of permanent loading spinner
- Close upload handles on failure via try/finally in ExplorerSidebar
- Ref-count shared blob URLs so closing one pane doesn't revoke another's
- Guard against moving a directory into itself in Explorer handleDrop

Quality:
- Remove unused vfs state/effect and createStoreVfs import in sidebar
- Add binary category mappings for common binary extensions in mediaTypes
- Use registry default (HexViewer) for binary files instead of hardcoded
- Add wrap="off" to TextViewer textarea for horizontal scrolling
- Make "Open with…" submenu keyboard-accessible (focus/blur, aria attrs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(build): pin cryptography<47 in setup-pip to match node constraints

Transitive deps (mcp, huggingface_hub) pull cryptography 49.x during
server:setup-pip. Later, nodes/requirements.txt (>=46.0.7,<47) forces
a downgrade — which fails on Windows when the .pyd is locked by the
running engine. Fix by installing cryptography<47 upfront so no
downgrade is needed. Restore the <47 ceiling in node requirements
and bump state key to V9 to force re-install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(explorer): address PR #1356 review feedback (security, correctness)

Resolves the still-open review comments from asclearuc and CodeRabbit on
the File Explorer PR. Each fix and its rationale:

apps/explorer-ui/src/store.ts
- [High] Infinite re-fetch loop: read() returned '' on blob load failure,
  but '' is falsy, so the FilePane revert effect (contentMode==='blob' &&
  !doc.content) fired revertDocument -> bumped doc.version -> effect
  re-ran -> forever, hammering the server. revertDocument() already bails
  on null, so return null (not '') to distinguish "load failed" from
  "empty content" and break the loop.

apps/explorer-ui/src/ExplorerSidebar.tsx
- [Major] Upload buffered the whole file via file.arrayBuffer(), defeating
  chunked upload. Stream via file.slice(offset, offset+chunk) per 4MB chunk.
  (try/finally around fsClose was already present.)
- [Major] Drag-move didn't sync open tabs: moving an open file left its
  editor at the old path; moving a directory left descendant tabs stale.
  After fsRename, re-point the moved file's tab and all descendant tabs
  (prefix match) to their new paths, mirroring the existing rename handler.
- [Low] Documented that the <a download> filename hint is honored only for
  same-origin /task/fetch, ignored for cross-origin S3/Azure presigned URLs.

apps/explorer-ui/src/mediaTypes.ts
- [Major] Legacy .doc mapped to category 'docx', but docx-preview only
  handles OOXML .docx (not the old Compound File Binary .doc). Route .doc
  to the binary/hex path instead so it no longer fails silently in DocxViewer.

apps/explorer-ui/src/viewers/JsonViewer.tsx
- [Minor] On JSON parse failure the raw content was injected into a fenced
  markdown block, so content containing a fence could break out of the code
  block. Render the raw fallback via a plain <pre> element (no fence).

apps/explorer-ui/src/viewers/SpreadsheetViewer.tsx (+ package.json, lockfile)
- [Major/Security] sheet_to_html output was injected via dangerouslySetInnerHTML
  without sanitization (SheetJS can emit javascript: links -> XSS, CVE-2026-44549).
  Pass { sanitizeLinks: true } and run the HTML through DOMPurify.sanitize()
  before rendering. Adds dompurify (~3.4.0) to explorer-ui deps.

apps/explorer-ui/src/viewers/HexViewer.tsx
- Name the 206 status literal HTTP_PARTIAL_CONTENT with a comment: a
  successful ranged fetch is exactly 206; a 200 (server ignored Range and
  sent the whole file) must not be cached under a single chunk index. (401/403
  left as meaningful literals per maintainer note.)

packages/ai/src/ai/account/store_providers/azure/azure.py
- [Medium] SAS URL was built from a hardcoded blob.core.windows.net host with
  a raw, unencoded blob name -> wrong host for Azurite/sovereign/custom
  endpoints and malformed URLs for names with spaces. Derive the base URL
  from blob_client.url (correct scheme/host, encoded path) + SAS token.

packages/ai/src/ai/modules/task/fetch.py
- [Major/Security] jwt.decode accepted tokens missing exp -> non-expiring
  fetch URLs. Add options={'require': ['exp']}. Also guard file_store._full_path
  against a malformed path claim, returning 400 instead of a 500.

.config
- Reworded the RR_SIGNING_KEY block: it is REQUIRED for the filesystem store
  backend (downloads/streams fail at runtime if unset); left intentionally
  empty so no shared signing secret ships in source (never commit a real key).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(sdk): document fs_get_url / fsGetUrl in the client guides

The store's fsGetUrl (TS) / fs_get_url (Python) method — added in this PR
for the File Explorer's presigned-URL access — was missing from both SDK
guide docs (CodeRabbit review note on PR #1356).

Adds a "Store (file access)" section with the method signature, return
type, and an example to both single-file guides
(client-typescript/docs/guide/index.md and client-python/docs/index.md),
placed after the Data section to mirror the existing table layout. Notes
that cloud backends return a presigned/SAS URL while the local filesystem
backend returns a JWT-signed /task/fetch URL, and documents the relative
path + expires_in semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(sdk): document the full fs_* store API in both client guides

Expands the "Store (file access)" section (added for fsGetUrl) to cover the
entire file-store surface in both SDKs, so users have a complete reference
for reading/writing/managing server-side store files — not just an orphaned
fsGetUrl entry.

Both guides now document, grouped for clarity:
- Handle I/O (low-level binary): fsOpen / fsRead / fsWrite / fsClose, with the
  open -> read/write -> close lifecycle and 4 MB chunk default noted.
- Convenience wrappers (handle managed internally): fsReadString / fsWriteString
  / fsReadJson / fsWriteJson.
- Directory & metadata: fsListDir / fsStat / fsMkdir / fsRmdir / fsRename / fsDelete.
- Direct URL: fsGetUrl (presigned/SAS for cloud, JWT-signed /task/fetch for local).

Each entry has signature, return type, and description, plus runnable examples
(strings/JSON, browsing, streamed chunked upload, presigned URL). Both docs stay
parallel (TS fsCamelCase / Python fs_snake_case). Notes the relative-path
requirement (absolute-like paths rejected).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(store): server-side Content-Disposition for cross-origin downloads

Fixes asclearuc's [Low] review note on the Explorer download button: the
`<a download>` filename hint is ignored by browsers for cross-origin URLs,
so S3/Azure presigned downloads used the object key instead of a friendly
name (and could open in-tab). There is no reliable client-side fix, so we
control the filename server-side by baking Content-Disposition into the URL.

New optional `download_name` (Python `fs_get_url` / TS `fsGetUrl`) threads
through to each backend:
- S3: `ResponseContentDisposition` param on the presigned URL.
- Azure: `content_disposition` signed into the SAS.
- Local: carried in the JWT claim; `/task/fetch` sets the header.

Semantics: `download_name` set -> `Content-Disposition: attachment;
filename="<name>"` (forces a download with that name, cross-origin safe).
Omitted (default) -> served **inline**, so media viewers keep streaming.
Note this makes the local `/task/fetch` default inline (was attachment);
the download button now passes `download_name`, so downloads still work and
inline streaming (video/audio/pdf viewers) is now correct.

Filenames are sanitized (`_sanitize_download_name`) — CR/LF, quotes, and
backslashes stripped — so they can't inject into the header/quoted token.

Files:
- file_store.get_url: build + sanitize disposition; pass to cloud backends;
  add download_name JWT claim for local.
- store.get_url (base) + s3/azure get_url: accept `content_disposition`.
- cmd_task._store_fs_geturl: parse `download_name` arg.
- fetch.py: attachment when claim present, else inline.
- Python/TS SDKs: `download_name` / `downloadName` param on fs_get_url/fsGetUrl.
- ExplorerSidebar download handler: pass the basename as download_name
  (keeps `a.download` as a same-origin belt-and-suspenders).
- Both client guides: document the param + inline-vs-download examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(store): extract file-store commands into StoreCommands mixin

Move the fs_* file-storage DAP commands out of TaskCommands (cmd_task.py)
into a dedicated StoreCommands mixin (cmd_store.py), mirroring the module
layout already present on feat/alb.

Why:
  feat/alb relocated these same fs_* handlers from cmd_task.py into a new
  cmd_store.py as part of its larger pod/RequestContext refactor. Because
  feat/fileexplorer kept them in cmd_task.py and extended them (Explorer's
  download support), every rebase between the two branches produced a large
  add-block/delete-block conflict over the entire fs_* section. Aligning the
  file layout here shrinks that recurring conflict to just the ctx-parameter
  lines, which are small and uniform.

What changed:
  - cmd_store.py (new): StoreCommands(DAPConn) owning on_rrext_store, the
    _store_subcommand_handlers dispatch table, _get_file_store, and all
    _store_fs_* handlers. This is feat/alb's StoreCommands with the alb-only
    RequestContext threading removed, since feat/fileexplorer's dispatch layer
    calls handlers with (request) only:
      * verify_permission('task.store', ctx) -> verify_permission('task.store')
      * _get_file_store(ctx) -> _get_file_store() scoped via self._account_info.userId
      * dropped the alb-only RequestContext/DAPRequest/DAPResponse imports
  - _store_fs_geturl retains Explorer's download_name passthrough (alb's copy
    predates it), so get_url still forwards download_name for the
    Content-Disposition: attachment path. Verified end to end (client SDKs ->
    dispatch -> file_store.get_url -> S3/Azure content_disposition and local
    /task/fetch header).
  - cmd_task.py: removed the moved on_rrext_store/_get_file_store/_store_fs_*
    code and the dispatch-table population in __init__ (now `pass`). No
    imports orphaned.
  - task_conn.py: wired StoreCommands into TaskConn at feat/alb's positions
    (import, base list, and the required StoreCommands.__init__ call that
    builds the dispatch table).
  - test_cmd_task.py: repointed the moved symbols from TaskCommands to
    StoreCommands, bound _get_file_store onto the __init__-bypassed stub via
    MethodType, and moved the constructor test to StoreCommands.__init__.
    17/17 tests pass.

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

* test(ai): declare pillow for direct PIL imports in test modules

Four test modules import ``from PIL import Image`` at module scope:
  tests/ai/common/image/test_image.py
  tests/ai/common/models/test_caption.py
  tests/ai/common/models/test_detection.py
  tests/ai/common/models/test_pose.py

Pillow was not listed in tests/requirements.txt, and none of that file's
-r includes (ai, ai/common, ai/web requirements) pull it. It is declared
only by the source packages (ai/common/image, vision/*), which install it
lazily via depends() when the source module is first imported — and the
vision variants are skip-install on the PR lane.

Because the tests import PIL at the TOP of the file, the import runs during
pytest collection, before any source depends() executes. In a fresh engine
env that yields `ModuleNotFoundError: No module named 'PIL'`, and pytest
aborts the whole run on the collection error — so `builder ai:test` fails
before any test executes.

Rule: a package imported directly at module scope in a test must be declared
in that test target's requirements, not left to a source module's lazy
depends(). Add pillow (unpinned, matching ai/common/image/requirements.txt's
own declaration). Verified: the 4 modules now collect and pass (23 tests).

A sweep of packages/ai/tests for other top-level third-party imports found no
further instances of this class — the only other undeclared roots (depends,
rocketride, numpy) are first-party/base-engine deps guaranteed by the build.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(vscode): wire native file dialog for embedded-app Browse button (#1011) (#1235)

The Dropper "Browse" button posts {type:'requestFileDialog'}, which the
openLink webview bridge forwards to the extension host. But the host's
onDidReceiveMessage only implemented clipboard paste/copy — it never
handled requestFileDialog. So the request arrived, no native dialog
opened, and nativeFilesSelected was never posted back: the button did
nothing, while clicking elsewhere in the drop zone (the in-iframe
<input type=file>) still worked. That matches the report exactly.

Add the missing requestFileDialog case: open vscode.window.showOpenDialog,
read the selected files, and post them back as nativeFilesSelected (buffers
as number[] so they survive webview message serialization). Mirrors the
existing clipboard and drag-drop bridges in the same handler.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(anonymize): configurable entity types + token redaction style (#1447)

* feat(anonymize): configurable PII entity types

Add an editable `entityTypes` field to the Anonymize node so pipelines can
choose which PII / entity types GLiNER detects and masks, instead of the
previously hardcoded default set. The field is seeded with the 15 common
types and is fully open (zero-shot), so user-written custom labels work too.

- services.json: new `entityTypes` array field, seeded with common defaults,
  wired into every model profile (profile-scoped so edits survive config merge)
- glinerRecognizer: owns DEFAULT_PII_LABELS, reads/sanitizes `entityTypes`,
  falls back to defaults when unset or empty
- IInstance: anonymize using the configured labels in the standalone path

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

* feat(anonymize): add anonymize_tokens for placeholder-token replacement

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

* feat(anonymize): branch recognizer on redactionStyle (mask|token)

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

* feat(anonymize): expose redactionStyle field across all profiles

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

* fix(anonymize): correct token-redaction path + harden entityTypes config

Addresses review findings on the token (redactionStyle) and configurable
entityTypes features:

- Token merge collapsed *adjacent* distinct spans (`<=`), dropping the second
  entity's label; now merges only true overlaps (`<`) so back-to-back entities
  keep their own tags.
- Generic [REDACTED] classification fallbacks were listed before NER token
  matches, so the stable offset sort kept [REDACTED] over specific labels on
  equal-offset overlaps; specific tokens now come first and win.
- Multi-word entity tokens rendered with underscores ([PHONE_NUMBER]); a new
  format_token helper renders clean tags ([PHONE NUMBER]).
- entityTypes from config is now validated as a list before cleaning, so a
  stray string can't be iterated into per-character labels (which would
  silently disable detection); empty -> defaults fallback preserved.
- anonymize_tokens rebuilt the whole string per match (O(n*k)); now a single
  left-to-right join (O(n)), matching the mask path.
- Token span extraction now delegates to convert_ner_results_to_matches
  (single source of truth for offset/length).
- DEFAULT_PII_LABELS moved into anonymize.py; a unit test asserts it stays in
  sync with services.json's entityTypes default.
- README documents the new redactionStyle and entityTypes fields.

Adds nodes/test/test_anonymize_logic.py (17 server-free unit tests).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(nodes,vscode): add Gmail tool node + Google user-OAuth broker (RR-1055, RR-1142) (#1334)

* feat(billing): promo codes — SDK methods, CheckoutModal promo box, host wiring (#1475)

* feat(billing): promo codes — SDK methods, CheckoutModal promo box, host wiring

Shared/SDK half of the Stripe-native promo code feature (server half lives
in the saas repo). Two code kinds: checkout discount codes and hackathon
credit-grant codes ($0 subscription + one-off token grant, no card).

SDKs (TS + Python, kept in sync):
- createCheckoutSession gains optional promotionCode; return widened to
  {clientSecret: string | null, subscriptionId, status} — clientSecret is
  null when a 100%-off code makes the first invoice $0 (subscription is
  already active, no payment step)
- new validatePromoCode(orgId, code, priceId?) → PromoValidation
  (read-only; unknown codes return {valid:false, reason}, never throw)
- new redeemPromoCode(orgId, code) → PromoRedemption
  ({redeemed, mode: 'subscribed'|'credits_only', appId, status?, credits})
- PromoValidation / PromoRedemption types exported from both packages

shared-ui CheckoutModal:
- "Promo Code" box under the plan cards (rendered when the host wires
  onValidatePromoCode/onRedeemPromoCode)
- grant codes (identified by appId metadata) redeem immediately — no plan
  selection, no Elements; success block shows the granted credits
- discount codes show an applied chip with the discounted first payment,
  flow into checkout via onCreateCheckout(priceId, code); payment recap
  shows struck-through list price + renew note for duration==='once'
- clientSecret === null path calls onConfirmPending best-effort then
  onSuccess() — Stripe Elements is never mounted for $0 invoices
- CheckoutModal + UpgradeModal hide plans with metadata.kind='promo_base'
  (hidden $0 base plans only reachable via promo-code redemption)

Hosts:
- shell-ui CheckoutFlow forwards promotionCode and wires the two promo
  callbacks to the billing SDK
- vscode AccountProvider/AccountWebview add checkout:validatePromo /
  checkout:redeemPromo resolver pairs (topupResolverRef pattern), forward
  promotionCode in checkout:createSession, refresh billing after redeem
- shell-ui BoxIcon: new BxPurchaseTag (used by the saas repo's admin UI)

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

* fix(billing): address PR #1475 review — promo state consistency + typed payloads

CodeRabbit review fixes:
- AccountWebview: forward `status` when resolving checkout:sessionResult
  (the widened callback type promised it; the resolver dropped it)
- views/types.ts: type checkout:validatePromoResult / redeemPromoResult
  payloads as PromoValidation / PromoRedemption instead of unknown;
  AccountWebview drops the casts and rejects on null results
- CheckoutModal:
  - discountedCents prefers the server-computed discountedAmountCents,
    local percent/amount math is only the fallback
  - preserve the entered code when validation omits a canonical `code`
    (handleContinue sends appliedPromo.code to the server)
  - switching plans clears an applied discount — validation is
    price-specific, so stale amounts can no longer flow into checkout
  - Continue is disabled while a promo is validating/redeeming so a
    just-entered code can't be silently dropped
- checkout/types.ts: promo callbacks are now paired at the type level
  (both or neither) — providing only onValidatePromoCode would have sent
  grant codes down the discount path with no error
- UpgradeModal: preselectedPriceId now goes through the same visibility
  filter as the picker grid, so hidden promo_base plans can't be reached
  via preselection

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(shell-ui): define REACT_APP_OAUTH_ROOT_URL (ReferenceError: process is not defined) (#1486)

shared-ui/config/oauth.ts (added by #1334) reads process.env.REACT_APP_OAUTH_ROOT_URL
at module top level. shell-ui bundles shared-ui from source (eager MF 'shared'),
so shared-ui's rslib define never applies and this token survived unreplaced ->
the shared factory threw 'process is not defined' in the browser. Define it in
shell-ui's rsbuild config like rslib/vscode already do; e() falls back to '' so
it's not hard-required (oauth.ts has its own https://oauth2.rocketride.ai default).

* fix(build): linux build improvement  (#1473)

* fix(build): fail the build when Linux dependency install fails (#1465)

check_dependencies ignored the exit code of dep_install and always
printed "Dependencies installed successfully". A failed dnf/apt install
was silently accepted, and the missing runtime libraries only surfaced
later at runtime as "libc++.so.1: cannot open shared object file".

Make a failed install fatal with a clear message, and re-check every
package after the install so a partial install (package manager exits 0
without installing everything) is not accepted either. Also make
dep_install return non-zero on the first failed package so the check
still works when it runs inside an if-condition.

* fix(build): make cc/c++ symlinking idempotent and fail gracefully without sudo (#1469)

On Fedora, the --autoinstall path unconditionally ran `sudo ln -sf` to point
cc/c++ at clang, even when the symlinks were already correct. The builder spawns
this script with stdio captured and no controlling terminal, so sudo could not
prompt for a password and died with "sudo: a password is required" — failing an
otherwise fully-provisioned build with a bare, contextless error.

- Add link_points_to() and guard the dnf symlinking so a box whose cc/c++
  already resolve to clang skips the privileged step entirely (no sudo).
- Add run_privileged() wrapping the cc/c++ symlink (dnf) and update-alternatives
  (apt) calls: uses sudo only when not root, and on failure prints how to
  recover (run ./scripts/compiler-unix.sh --autoinstall standalone in a
  terminal, pre-auth with `sudo -v`, or grant passwordless sudo) instead of
  aborting the build with sudo's raw error.

apt already guarded this via NEED_CC_ALTERNATIVES, so behavior there is
unchanged aside from the friendlier failure message.

* fix(weaviate): pin grpcio-health-checking<1.80 to match protobuf runtime (#1472)

grpcio-health-checking 1.82.0 regenerated grpc_health/v1/health_pb2 with
protobuf gencode 7.35.0, so it requires protobuf runtime >= 7.35.0. Its
metadata floor was left at protobuf>=6.33.5, so it co-installs with older
runtimes. weaviate-client caps protobuf<7, so the resolver lands on protobuf
6.33.6, and importing weaviate raises VersionError (gencode 7.35.0 / runtime
6.33.6).

The node left grpcio-health-checking unpinned, so it floated to 1.82.0.
weaviate-client already requires grpcio<1.80; pin grpcio-health-checking to
the same range so the stub gencode stays on the protobuf 6.x line.

* fix(ai): install test deps via depends() so engine constraints apply (#1466) (#1471)

The ai:test pre-install used plain pip, which is not handed the engine's
compiled constraints file. Unpinned deps (e.g. pillow) resolved to latest
instead of the engine-pinned version. On Windows this corrupted the install:
pillow was pulled to a newer version, then depends() tried to downgrade it at
import and could not overwrite the loaded _imaging*.pyd, leaving a half-written
PIL package (ModuleNotFoundError: No module named 'PIL').

Install the test requirements through depends() instead, so uv applies the
constraints and every dep resolves to its pinned version.

* fix(ai): migrate GPU import guard to find_spec/exec_module (Python 3.12+) (#1460)

## Summary

- Migrate `ai.common.models.gpu_guard._GPUImportBlocker` from the legacy import-hook
  protocol (`find_module`/`load_module`, **removed in Python 3.12**) to the modern PEP 451
  protocol (`find_spec` + `create_module`/`exec_module`).
- Without this, on Python 3.12+ the hook silently becomes a no-op and `--modelserver` mode
  stops blocking direct `torch`/`tensorflow`/`onnxruntime`/`cupy` imports — a fail-open
  isolation/billing regression with no error.
- Relocate the test to the mirrored path (`tests/ai/common/models/`), rewrite it for the
  new protocol, and add an end-to-end regression test that drives a real `import` through
  the machinery so a future silent no-op is caught.

## Type

fix

## Testing

- [x] Tests added or updated
- [x] Tested locally
- [ ] `./builder test` passes <!-- ran targeted `ai:run-pytest` equivalent: 8 passed on Python 3.12.13 via the engine interpreter; full ./builder test not yet run -->

Verification detail:
- `ruff check` + `ruff format --check` clean on both changed files.
- `engine.exe -m pytest tests/ai/common/models/test_gpu_guard.py` → **8 passed** on
  Python 3.12.13 (the version where the old protocol silently failed).
- Repo-wide audit confirmed `gpu_guard.py` is the only hard 3.12 break in first-party code.

## Checklist

- [x] Commit messages follow [conventional commits](https://www.conventionalcommits.org/)
- [x] No secrets or credentials included
- [ ] Wiki updated (if applicable)
- [x] Breaking changes documented (if applicable) <!-- CHANGELOG.md → Unreleased → Fixed -->

## Linked Issue

Fixes #1459

* feat(events-ui): real-time DAP event monitor micro-frontend (#1484)

* feat(events-ui): real-time DAP event monitor micro-frontend

Web UI equivalent of the Python CLI `rocketride events` command.
Registers for server events via the SDK's addMonitor/removeMonitor API,
captures them in memory, and displays them in a scrolling list with
expandable JSON inspection and JSON file download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(events-ui): address CodeRabbit review findings

- Move nextId counter into component ref, reset on clear
- Cancel pending rAF on unmount to avoid dangling callbacks
- Amortize event buffer trim (1.2x overshoot before slicing)
- Log addMonitor/removeMonitor errors instead of swallowing
- Auto-scroll uses last event ID instead of filtered.length
- Append download anchor to DOM before click for browser compat
- Remove direct commonStyles import from Toolbar, use styles re-exports
- Add aria-pressed to event type toggle chips
- Add .catch() to AppDescriptor dynamic import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(events-ui): cap events at 1,000 and drop unnecessary useCallback

Reduce MAX_EVENTS from 10,000 to 1,000 to keep the DOM manageable
without adding a virtualization dependency. 1,000 is still 40x the
Python CLI's 25-event window — plenty for debugging.

Remove the useCallback wrapper on the useShellEvent handler since
useShellEvent already stores the handler in a ref internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore(nodes): classify FalkorDB as store (classType ["store","tool"]) (#1474)

FalkorDB was the only DB/store node tagged classType ["tool"] while the
vector stores use ["store"]/["store","tool"] and the SQL/graph DBs use
["database","tool"]. Per Rod, add "store" to classType so the node lands
in the right canvas category regardless of its source folder name.

* feat(test-ui): stress/chaos testing app for RocketRide backend (#1462)

* feat(test-ui): stress/chaos testing app for RocketRide backend

Adds apps/test-ui, a Module Federation app for stress/chaos testing the
RocketRide backend (scenario runner, swarm/latency/metrics panels, API-coverage
tracking, live event stream). It consumes the shell-ui shell and the rocketride
TS SDK like the other MF remotes.

This app was originally developed on feat/alb but was entangled with unrelated
dashboard/decoupling work there, bloating that PR by ~7.8k lines. It is a
self-contained leaf (nothing imports it) and depends only on shell-ui / SDK
surface that already exists on develop, so it is split out here onto its own
branch off develop for independent review. Removed from feat/alb in the
companion commit.

Registers the app in pnpm-workspace.yaml. Verified with
`./builder test-ui:clean test-ui:build` against the develop base — the MF
bundle (remoteEntry.js) builds and registers cleanly.

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

* fix(test-ui): address CodeRabbit review feedback on PR #1462

Resolves all 14 review threads. Correctness and stability fixes:

- engine: chunk crypto.getRandomValues() into 64 KiB blocks (new
  randomBytes() helper). The Web Crypto spec throws QuotaExceededError
  above 65,536 bytes, so 1 MB/100 MB binary payloads previously crashed
  payload generation before any data reached the server.
- engine: give the ping heartbeat client a 5s requestTimeout instead of
  the 300s default. The catch-block already classified >5000ms as "Ping
  timeout", but a stalled ping could block the 100ms heartbeat loop for
  5 minutes, defeating its purpose of catching event-loop stalls fast.
- engine: add getPipelineClient() that resolves a pipeline's recorded
  clientIdx as a stable slot into clientPool. getPoolClient() re-filters
  connected clients, so when an earlier pool client dropped, later sends/
  terminates could silently re-map onto a different WebSocket than the
  one that owns the pipeline token. Round-robin getPoolClient() remains
  for initial assignment and "any client" chaos phases.
- monitor: cap per-method latencies at 500 samples (rolling window via
  new pushLatency(), also used by the ping loop in engine.ts). send/ping
  run thousands-to-millions of times in long stress runs; the unbounded
  array bloated memory and slowed percentile recomputation in panels.

Maintainability/UX fixes:

- engine: reset() now delegates to clear() — bodies were byte-identical
  with misleading docstrings (settings are view-owned; TestSessionView
  restores config itself on reset).
- store: new createExternalStore() factory owning the listener-Set /
  emit / useSyncExternalStore boilerplate previously re-implemented in
  navigation.ts, connections.ts, and actions.ts.
- TestSessionView: lazy engine ref init — useRef(createTestEngine())
  constructed and discarded a fresh engine on every rAF-driven render.
- SwarmPanel: header now shows "showing 64/N" when pipeline count
  exceeds the 64 grid slots instead of silently truncating.
- ConnectionManagerView: FormState.mode is a discriminated union
  ({type:'add'} | {type:'edit';id}) — 'add'|string collapsed to string
  and could collide with a connection id; handleSave builds the new
  connection object once instead of duplicating the field set; form
  labels are paired to inputs via htmlFor/id for a11y.
- TestSidebar: type handleOpenConnection with SavedConnection, not any.
- package.json: move @module-federation/rsbuild-plugin to
  devDependencies (build-time only, same role as @rsbuild/*).
- pnpm-workspace.yaml: restore alphabetical order of apps/* entries.
- license headers: add the full MIT header to files edited here that
  were missing it (TestSessionView, SwarmPanel) or had a placeholder
  (actions.ts), per repo file standards.

Verified with ./builder test-ui:clean test-ui:build (MF bundle builds
and registers cleanly); no new tsc errors in touched files.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(vscode): repoint dead example links to awesome-rocketride (#1422) (#1448)

The extension's "example pipelines" block linked to
docs.rocketride.org/examples/{advanced-rag-pipeline,video-key-frame-grabber,audio-transcription-simple},
which no longer resolve, so a new builder hits a dead end on the first
click. Repoint to the awesome-rocketride demos-and-examples collection.

Both tracked sources carried the block: apps/vscode/docs/index.md
(docs-site surface) and docs/README-vscode.md (source for the generated,
gitignored marketplace apps/vscode/README.md).

Closes #1422

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add HydraDB database node (db_hydradb) (#1499)

Adds the db_hydradb node (database + tool) with store_memory / recall_memory / query_graph / get_schema over the HydraDB v2 REST API. Ingest uses multipart form-data (live-verified). Shared post_with_retry gains data=/files= passthrough plus get_with_retry.

* feat(ci): migrate Discord notifier workflows to forum channels (tags + auto-archive) (#1510)

* feat(ci): migrate Discord notifier workflows to forum channels (tags + auto-archive)

Convert the three Discord notification workflows (issues, PR, discussions) to
target Discord forum channels:

- Forum posts: the webhook create POST sends thread_name and captures/stores
  the new thread_id in the sync marker; PATCH/DELETE are thread-scoped; POST is
  at-most-once (--no-retry-5xx) to avoid duplicate threads.
- Tags (webhook-only): applied_tags resolved on create from a committed,
  non-secret config (discord-forum-tags.json) mapping GitHub state/labels (and,
  for discussions, answer status + category) -> Discord tag IDs.
- Archiving (bot token, optional): discord_archive_thread archives closed/merged
  threads and unarchives reopened ones. Only place a bot token is used; no-op if
  DISCORD_GITHUB_BOT_TOKEN is unset.

Webhook secrets renamed to DISCORD_*_FORUM_WEBHOOK_URL for a zero-downtime
cutover; bot token secret is DISCORD_GITHUB_BOT_TOKEN. Channel/tag IDs are
committed (not secrets).

Closes #1509

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): make Discord notifiers degrade against the default-branch helper

These notifiers source discord-helper.sh from the default branch (fork-safety),
so the PR that first introduces new helper functions runs the new workflow YAML
against the OLD helper and aborts with "command not found" (exit 127) at the
first new-helper call.

Add no-op fallback shims for discord_applied_tags / extract_discord_thread /
discord_archive_thread right after sourcing the helper, so the workflow degrades
gracefully (no tags / no archive) instead of failing. The real implementations
take over automatically once this merges to the default branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ci): two-message forum posts — link card + detailed embed

Restructure each notifier into the two-message forum layout:

1. Starter (root) message: a plain "**#N  title**" + URL. Discord unfurls it
   into the GitHub card, which becomes the forum grid-view image. thread_name
   and applied_tags go on this create POST.
2. Detail embed: posted as the 2nd message in the thread and tracked in the sync
   marker (msg id + thread id). It now carries the issue/PR/discussion body as
   the embed description (HTML-comment-stripped, trimmed, capped at 3800), an
   "opened this … on <date>" author line, and a "repo · #N" footer. Follow-up
   events PATCH this embed (thread-scoped); the starter link stays put.

Both POSTs are at-most-once (--no-retry-5xx); if the detail POST fails the just-
created thread is deleted so the next event retries cleanly. Race guard deletes
the whole duplicate thread (via its root message) and patches the winner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): address review comments on Discord notifiers

- Clear DISCORD_THREAD_ID in the 404 repost branch (issues/pr/discussions) so
  the archive step targets the recreated thread, not the deleted one.
- discord_applied_tags: resolve in priority order and dedupe preserving order
  before the 5-tag cap, so a state tag is never dropped past 5 tags.
- Paginate the issue/PR comment lookups (main + race-guard re-check) so the sync
  marker is found on repos where a PR/issue has many comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ci): add good-first-issue forum tag (issues + PR)

Created on the issues (after open/closed) and PR (after open/closed/merged)
forum channels; mapped from the "good first issue" GitHub label in the tag
config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(ci): drop good-first-issue tag from PR channel

Keep it on the issues channel only; remove the PR-channel tag and its config
label/id references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MBP.attlocal.net>

* feat(nodes): add currency_convert_explicit node (#1497)

* feat(nodes): add currency_convert_explicit node

Add `currency_convert_explicit`, a filter node (answers -> answers) that
converts monetary facts from a source to a target currency at an explicitly
stated rate and date supplied in config. Part of the audit-grade financial
extraction node suite (#1432). Mirrors anonymize/guardrails and the suite
siblings reconcile (#1457) / authoritative_overlay (#1485).

- Opt-in / never implicit: the rate is stated in config; nothing is fetched
  from a live FX service (no network, no credentials). Only facts tagged with
  the configured source_currency are converted; everything else passes through.
- Non-destructive: preserves the original amount+currency, adds a `converted`
  block, and appends a `provenance` entry {rate, rate_date, source->target}.
  Exact Decimal math with half-up rounding; standard library only.
- Configurable JSON fact convention (amount_field/currency_field) so the node
  ships standalone while normalize_facts (#1427) is still unbuilt.

Tests: 17 unit tests + 2 service test-block cases; ruff check/format clean;
full contract suite (pytest nodes/test/test_contracts.py) 288 passed. Committed
with --no-verify (lefthook gitleaks/ruff not on PATH in sandbox); ruff run
manually.

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

* fix(nodes): emit currency_convert_explicit answers via preventDefault + closing

The live-engine dynamic test (test_dynamic.py) revealed the node double-drove
the answers lane: writeAnswers emitted a new answer without calling
preventDefault, so the engine ALSO auto-forwarded the original — producing two
malformed answers (Results: {'answers': ['', '']}) instead of one converted
record.

Adopt the proven anonymize pattern: take ownership of the lane with
preventDefault() in writeAnswers, collect the converted payload (extracted
immediately so nothing depends on the engine reusing the Answer object), and
re-emit each record exactly once in closing(). Behaviour is unchanged —
non-destructive conversion of source-currency facts, verbatim pass-through of
everything else. Pure convert.py logic and its 17 unit tests are untouched.

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

* fix(nodes): drop infeasible dynamic test block for currency_convert_explicit

The dynamic node-test harness injects lane input through data_conn, which
builds answers with `Answer.model_validate_json(...)`. The Answer schema's
`prohibit_answer_on_creation` field validator raises if `answer` is set at
construction, so a webhook-injected `answers` input can only ever be empty
(verified: model_validate_json can populate expectJson but never answer
content). An answers-consuming node therefore always receives an empty answer
under the harness, and an engine-level dynamic test can never deliver a
populated fact — the earlier test block failed for this reason
(Results: {'answers': ['']}), not because of the conversion logic.

Remove the test block (matching anomaly_detector and other nodes that ship
without one). Coverage is retained by the 17 unit tests in
nodes/test/test_currency_convert_explicit.py and the contract suite
(service-contract + module-exists). The preventDefault + closing emit fix from
the previous commit is kept — it is the correct single-emit behaviour for when
a real upstream node (e.g. normalize_facts) feeds populated answers.

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

---------

Co-authored-by: kgarg2468 <181051779+kgarg2468@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(nodes): add n8n workflow-automation node (#1231)

* feat(nodes): add n8n workflow-automation node

Add `tool_n8n`, a dual node (pipeline step + agent tool) that triggers
self-hosted n8n workflows and consumes their results. Mirrors tool_github
(multi-fn REST + sibling client module) and db_postgres (dual classType).

- RR->n8n: webhook trigger -- sync (Respond-to-Webhook) or async polling of
  executions via a correlation id; public REST API to list/inspect workflows
  and executions; activate/deactivate gated by a read-only toggle.
- Rich I/O: simple or structured payloads (documents + metadata); files/binary
  both ways via multipart (image/audio/video lanes); table output lane.
- Safety/DX: SSRF containment (agent never picks the host); none/header/basic/
  bearer/JWT webhook auth; TLS-verify toggle; deploy-aware reachability
  preflight (host.docker.internal hint); 404->activate and ack->Respond-node
  guidance; configurable sync/async timeouts; n8n execution deep-links.
- n8n->RR + round-trips documented (docs/README-n8n.md) with importable
  templates under examples/n8n/ (fan-out, agent, round-trip, dispatcher).

Verified: ruff clean, 283 unit + contract tests, 15/15 live e2e against a real
n8n (sync / async / sequential / round-trip / binary / non-webhook reach).

Closes #1230

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

* chore(ci): allow digits in gitleaks env-var placeholder allowlist

The pipe-file/services-json API-key rules allowlist `${VAR}` placeholders,
but the regex `[A-Z_]+` excluded digits — so `${ROCKETRIDE_N8N_KEY}` and
`${ROCKETRIDE_N8N_URL}` (the `8` in n8n) were flagged as secrets while
digit-free names like `${ROCKETRIDE_OPENAI_KEY}` were not. Widen the
allowlist to `[A-Z0-9_]+` so digit-containing env-var names are recognized.

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

* fix(nodes): address review feedback on tool_n8n

Resolve the 9 actionable items from the PR #1231 review:
- SSRF: _safe_path rejects path traversal, query/fragment, and backslashes
- Normalize tool-face results (binary -> descriptor, JSON scalars -> string)
  to the advertised object/array/string/null schema
- Compare async execution timestamps as tz-aware datetimes, not strings
- Pin idna>=3.10 (GHSA-65pc-fj4g-8rjx) in node + shared requirements
- Advertise the table lane on image/audio/video routes
- Smoke test requires ROCKETRIDE_N8N_WORKFLOW so a workflow is configured
- Sync the README config table (Sync timeout; bearer/jwt webhook auth)
- MD040: add a language to the README/example fenced diagrams
- Extend the unit suite (path guard, result normalization, timestamp compare)

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

* fix(nodes): address n8n review nits

---------

Co-authored-by: Krish Garg <krishgarg@Krishs-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nodes): warn that parse/llamaparse are not provenance-preserving (RR-1406) (#1507)

The native parse and the llamaparse node flatten documents to Markdown,
dropping page boundaries, bbox/polygon coordinates, and the table-HTML cell
grid, so cell-level provenance cannot be reconstructed for audit-grade
extraction.

Document this limitation prominently on both nodes (READMEs + catalog
description) and emit a one-time runtime warning from the LlamaParse node
steering users to the structure-preserving datalab_parse node (#1425). No
parsing/output behavior change; the structural lossless parser is tracked
separately by #1425, which Epic #1432 names as the structural fix for this bug.

Fixes #1406

Co-authored-by: kgarg2468 <181051779+kgarg2468@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nodes): friendlier chroma port + splitter-profile config (RR-1416) (#1496)

* fix(nodes): friendlier chroma port + splitter-profile config (RR-1416)

Two strict-schema config-friction papercuts from Aparavi's field feedback
(issue #1416, reports I-9 / I-11). The engine owns the raw AJV-style
validation text, so this fixes the friction declaratively (schema + node
coercion + docs) without any engine change.

chroma port (I-11): env-var interpolation resolves to a string, but the
port field inherited type:number from the shared core vector schema, so
`${ROCKETRIDE_CHROMA_PORT}` / "8000" failed validation with no useful
message. Fixed with a chroma-LOCAL override (shared
core/services.common.vector.json is intentionally left untouched to avoid
blast radius across every vector node): the port field now accepts
number-or-string, and Store._coercePort normalizes int / numeric string /
whole-number float, falling back to the default for an unresolved
placeholder, bool, or non-numeric input rather than crashing the client.

splitter const-lock (I-9): selecting MarkdownTextSplitter under the
`default` profile fails with ".default.splitter must be equal to
constant". The const design is intentional (each profile pins its
splitter), so this adds title/description guidance on the profile selector
and every const-locked splitter field naming the right profile, plus a
README note. No behavior change.

Regenerated the GENERATED:PARAMS doc tables from services.json via
nodes:docs-generate. Adds nodes/test/chroma/test_coerce_port.py pinning
every _coercePort branch.

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

* fix(nodes): address review feedback on RR-1416 (port bounds + custom-splitter docs)

CodeRabbit review on #1496 raised two valid items; both addressed.

1. chroma _coercePort: add a TCP port-range guard (1-65535). CodeRabbit's
   suggested patch only guarded the string branch, but a raw int/float
   (-1, 99999, 99999.0) would also bypass it, so every numeric branch (int,
   whole-float, string) now funnels through one `1 <= parsed <= 65535` check
   that falls back to the default with a debug log. Added regression tests for
   out-of-range int/string/float and the 1 / 65535 / 443.0 boundaries.

2. preprocessor_langchain custom-splitter docs: custom.splitter is const-locked
   to RecursiveCharacterTextSplitter, but the README implied it accepted a
   user-selectable class. Corrected the intro, the profile table row, and the
   Custom section, and added custom to the "each profile locks its class" note.
   For consistency with the other six splitter fields, gave custom.splitter the
   same locked title/description in services.json and regenerated the
   GENERATED:PARAMS table. Docs/schema-text only, no behavior change (the const
   is unchanged; unlocking the custom profile is left as a follow-up).

Verified: ./builder nodes:test -> 1405 passed, 48 skipped; ruff clean.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): read full marker body in Discord issue/PR notifiers (#1530)

The sync-marker lookup piped the comment body through `head -1`, but the marker
lives inside a multi-line <details> block, so only "<details>" was kept and the
msg/thread ids never resolved. The notifier then treated every event as a fresh
post and created a duplicate forum thread each time.

Read the first matching comment's full body instead (via jq `first(...)`), so
extract_discord_marker / extract_discord_thread see the marker line. The `.id |
head -1` lookups in the 404 branch are unchanged (`.id` is single-line).

Closes #1529

Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MBP.attlocal.net>

* docs(readme): revamp landing README with Cloud + On-Prem sections (#1528)

* docs(readme): revamp landing README with Cloud + On-Prem sections

- Lead with a cloud-first 'Two ways to run' table (Cloud | On-Prem) and align its CTAs
- Add a dedicated RocketRide Cloud section (4 value pillars, connect snippet, CTAs)
- Add a parallel On-Prem section (control/MIT/anywhere/same engine)
- Swap the CI badge to a shields.io GitHub-Actions status badge (renders reliably)
- Add branded visuals (pipeline, SDK, install) as in-repo images/ assets
- Remove emojis; rename self-host -> on-prem

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(readme): use poster banner as the hero image

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: mark RocketRide Cloud as available in use-cases

Reconcile the stale 'coming soon' line in evaluate/use-cases.mdx with the
live status shown across the README, cloud.md, and cloud.rocketride.ai.
Addresses CodeRabbit review on PR #1528.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(readme): rename Self-Hosting CTA label to On-Prem

Retire the last 'Self-Hosting' visible label (href unchanged) for
consistency with the On-Prem terminology. Addresses CodeRabbit review on PR #1528.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(readme): restore native GitHub Actions CI badge

The workflow badge renders correctly on the repo README; the shields.io
swap is unnecessary. Reverting to the native badge.svg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(readme): make Runtime badge track the latest server release

Replace the hardcoded Runtime v3.1.0 badge with a shields.io dynamic
github release badge filtered to server-v* tags, so it always reflects
the latest server release (currently server-v3.3.1). Addresses
@joshuadarron review on PR #1528.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MBP.attlocal.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MacBook-Pro.local>

* fix(depends): pin bootstrap tools and pydantic to stop downgrade churn (#1536)

* fix(depends): pin bootstrap tools and pydantic to stop downgrade churn

Base modules were installed unpinned and picked up 'latest', then a later
constrained install had to downgrade them. Downgrading a package with a loaded
binary (.pyd/.so) locks/crashes cross-platform — worst on Windows, where
parallel pytest workers race to overwrite the already-loaded pydantic-core /
cryptography DLL and the suite fails.

Cure the immediate wound by pinning the things that float to 'latest': the
bootstrap tools (uv is the resolver itself, wheel/setuptools are the build
backend, so they install outside any constraint) and pydantic (the base module
Rod flagged). An exact pin, not a floor — a floor still resolves to newest.

- depends.py: add _BOOTSTRAP_TOOL_VERSIONS (platform-overridable) + _tool_spec();
  _ensure_wheel/_ensure_setuptools/_ensure_uv now install uv==0.11.25,
  wheel==0.47.0, setuptools==82.0.1 (the known-good versions already shipped)
- tasks.js: build-tools install pinned to the same versions, in lockstep
- nodes + ai requirements.txt: pydantic==2.12.5 (exact; cross-platform safe,
  proven compatible with the full requirement set)

* refactor(build): rename test-requirements.txt to build-requirements.txt

The file also carries build and model-server deps, so the "test" name misleads.

* fix(auth): register /auth/vscode/google outside standardEndpoints gate (#1543)

The route was only registered under `if register_standard_endpoints:`, which
eaas.py disables in cloud (standardEndpoints=False), so the public OAuth-broker
bounce was dark in cloud and 401'd — the Gmail tool's Google login never
completed. Move it to the always-on section next to /version.

* feat(server, nodes): add json lane (#1297)

* feat(shared-ui): pipeline TTL settings — toolbar cog + modal, VS Code support (RR-309) (#1521)

* feat(shared-ui): add pipeline settings (cog) button to canvas toolbar

Adds an optional onOpenSettings callback threaded through
ProjectView -> Canvas -> FlowContainer -> FlowProvider -> FlowProjectContext,
rendered as a Settings (cog) ToolbarButton in the floating canvas toolbar.
Hosts that omit the callback (e.g. VS Code, readonly status views) see no
button. Used by the SaaS Pipeline Builder to open the idle-timeout (TTL)
settings modal (RR-309).

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

* feat(shared-ui): own TTL settings modal in ProjectView; wire ttl through VS Code host

Moves the idle-timeout (TTL) settings modal from the SaaS host into
shared-ui so every host gets it:

- New modules/project/TtlSettingsDialog.tsx (portal modal; presets +
  custom integer minutes).
- ProjectView owns the dialog + value: persisted per-pipeline in prefs
  (pipelineTtl map keyed by project_id), riding the existing
  onPrefsChange channel. Supplies its own onOpenSettings to the canvas,
  so the toolbar cog no longer needs a host prop; onOpenSettings is
  removed from IProjectViewProps.
- onPipelineAction gains optional options.ttl (run/restart), backward
  compatible for hosts that ignore it.
- VS Code host forwards ttl through the status:pipelineAction message
  into client.use; prefs already persist via workspaceState.

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

* fix(shared-ui): gate settings cog on !isLocked; add dialog ARIA semantics

Review feedback (CodeRabbit, PR #1521):
- Settings cog now hidden while the canvas is locked, matching the
  onSave/onExport toolbar pattern.
- TtlSettingsDialog gets role=dialog, aria-modal, aria-label so screen
  readers announce the modal.

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

* fix(shared-ui): trap Tab focus in TTL dialog; restore opener focus on close

Review feedback (CodeRabbit, PR #1521): keyboard users could tab out of
the modal, and closing it dropped focus instead of returning to the
settings button.

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

* fix(vscode): address CodeRabbit review on #1521 (openExternal scheme allowlist)

- add ttl?: number to the status:pipelineAction ProjectWebviewToHost variant
  so the webview's run message compiles against the contract
- capture the dialog opener synchronously via a useRef initializer so focus
  is restored to the settings button (not the unmounted select) on close
- handle Escape on the TTL dialog wrapper so it closes from any focused
  element; drop the duplicate Escape checks on the select/input
- document the Pipeline Settings idle timeout and the ttl message field in
  apps/vscode/docs/usage.md
- add shared isAllowedExternalUrl helper and gate the WelcomeProvider
  openExternal handler behind an https/http/mailto scheme allowlist

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

* revert(vscode): drop openExternal allowlist unrelated to RR-309

The isAllowedExternalUrl helper and WelcomeProvider gating were not part of
any review comment on this PR; they belong to a separate hardening effort.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: mithileshgau <55141824+mithileshgau@users.noreply.github.com>
Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MBP.attlocal.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Rod-Christensen <98939082+Rod-Christensen@users.noreply.github.com>
Co-authored-by: Meetpatel06 <65815199+meetp06@users.noreply.github.com>
Co-authored-by: Dylan Savage <130109357+dylan-savage@users.noreply.github.com>
Co-authored-by: Joshua Phillips <J.dspears@yahoo.com>
Co-authored-by: Alexandru Sclearuc <asclearuc@gmail.com>
Co-authored-by: Charlie Gillet <102723287+charliegillet@users.noreply.github.com>
Co-authored-by: Poushali Deb Purkayastha <65014940+Poushali0202@users.noreply.github.com>
Co-authored-by: Mithilesh Gaurihar <mithileshgaurihar@Mithileshs-MacBook-Pro.local>
Co-authored-by: Krish Garg <kgarg@chapman.edu>
Co-authored-by: kgarg2468 <181051779+kgarg2468@users.noreply.github.com>
Co-authored-by: Krish Garg <krishgarg@Krishs-MacBook-Pro.local>
Co-authored-by: Ariel Vernaza <ariel@lazyracoon.tech>
Co-authored-by: stepmik <stepmikhaylov@yandex.ru>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants