feat(ruvector-rabitq-wasm): WASM bindings for RaBitQ#390
Open
feat(ruvector-rabitq-wasm): WASM bindings for RaBitQ#390
Conversation
Unblocks the 7 stacked PRs (#381-#387) and turns `main`'s CI green for the first time in days. Two issues fixed: ## Failure 1 — Security audit (was: 8 vulnerabilities) `cargo audit` is now exit 0. 4 of the 5 critical advisories were fixed by version bumps; only the unfixable one is ignored. **Dep-bumped:** - `rustls-webpki 0.101.7` + `0.103.10` → `0.103.13` via `cargo update -p rustls-webpki@0.103.10`. Patches: RUSTSEC-2026-0098 (URI name constraints) RUSTSEC-2026-0099 (wildcard name constraints) RUSTSEC-2026-0104 (CRL parsing panic) - `idna 0.5.0` → `1.1.0` via `validator 0.18 → 0.20` in `examples/scipix`. Patches RUSTSEC-2024-0421 (Punycode acceptance). - Bonus: `reqwest 0.11 → 0.12` (in `ruvector-core` + `examples/benchmarks`) and `hf-hub 0.3 → 0.4` (in `ruvector-core` + `ruvllm` + `ruvllm-cli`). Removes the entire legacy `rustls 0.21` / `rustls-webpki 0.101.7` subtree from the lockfile. **Ignored** (single advisory, with rationale): - `RUSTSEC-2023-0071` (rsa Marvin timing sidechannel) — no upstream fix available; we don't expose RSA decryption services. Documented in `.cargo/audit.toml`. **Unmaintained warnings** (16 total — proc-macro-error, derivative, instant, paste, bincode 1, pqcrypto-{kyber,dilithium}, rustls-pemfile 1, rusttype, wee_alloc, number_prefix, rand_os, core2, lru, pprof, rand) — each given a one-line justification in `.cargo/audit.toml` so CI stays green on them while the team decides whether to chase upstream replacements. ## Failure 2 — Tests timeout (was: 30-min job timeout cancellation) `.github/workflows/ci.yml` `test` job is now a `matrix` with `fail-fast: false` and `timeout-minutes: 45`. Six parallel shards under `cargo nextest run` (installed via `taiki-e/install-action@v2`) plus a separate `cargo test --doc` step (nextest doesn't run doctests): | Shard | Crates | |------------------|---------------------------------------------| | vector-index | rabitq, rulake, diskann, graph, gnn, cnn | | rvagent | 10 rvagent-* crates | | ruvix | 16 ruvix-* crates | | ruqu-quantum | 5 ruqu* crates | | ml-research | attention, mincut, scipix, fpga-transformer,| | | sparse-inference, sparsifier, solver, | | | graph-transformer, domain-expansion, | | | robotics | | core-and-rest | --workspace minus the above | `Swatinem/rust-cache@v2` is keyed per shard. Audit job switched to `taiki-e/install-action` for `cargo-audit` (faster than `cargo install --locked`). ## Verification cargo audit → exit 0 cargo build --workspace --exclude ruvector-postgres → clean cargo clippy --workspace --exclude ruvector-postgres --no-deps -- -D warnings → exit 0 cargo fmt --all --check → exit 0 ## Cargo.lock churn 166-line diff, net ~120 lines removed (more deletions than additions). Removed: `idna 0.5.0`, `rustls-webpki 0.101.7`, `validator 0.18`, `validator_derive 0.18`, `proc-macro-error 1.0.4`. Added: `rustls-webpki 0.103.13`, `validator 0.20`, `proc-macro-error2`, `hf-hub 0.4.3`, `reqwest 0.12.28`. No suspicious crates. ## Recommended merge order 1. **This PR first** — unblocks every other PR's CI. 2. After this lands and main is green, rebase the 7 open PRs (#381-#387) one at a time. The DiskANN stack (#383→#384→#385→#386) must merge in numeric order. #381 (Python SDK), #382 (research), #387 (graph property index) are independent and can merge in any order after their CI goes green on the rebase. Co-Authored-By: claude-flow <ruv@ruv.net>
Closes the WASM gap from `docs/research/rabitq-integration/` Tier 2
("WASM / edge: 32× compression makes on-device RAG feasible") and
ADR-157 ("VectorKernel WASM kernel as a Phase 2 goal"). Adds a
`ruvector-rabitq-wasm` sibling crate that exposes `RabitqIndex` to
JavaScript/TypeScript callers (browsers, Cloudflare Workers, Deno,
Bun) via wasm-bindgen.
```js
import init, { RabitqIndex } from "ruvector-rabitq";
await init();
const dim = 768;
const n = 10_000;
const vectors = new Float32Array(n * dim); // populate
const idx = RabitqIndex.build(vectors, dim, 42, 20);
const query = new Float32Array(dim);
const results = idx.search(query, 10); // [{id, distance}, ...]
```
## Surface
- `RabitqIndex.build(vectors: Float32Array, dim, seed, rerank_factor)`
- `idx.search(query: Float32Array, k) → SearchResult[]`
- `idx.len`, `idx.isEmpty`
- `version()` — crate version baked at build time
- `SearchResult { id: u32, distance: f32 }` — mirrors the Python SDK
(PR #381) shape so callers porting code between languages get
identical structures.
## Native compatibility tweak
`ruvector-rabitq` had one rayon call site in
`from_vectors_parallel_with_rotation`. WASM is single-threaded — gated
that path on `cfg(not(target_arch = "wasm32"))` with a sequential
`.into_iter()` fallback for wasm. Output is bit-identical because the
rotation matrix is deterministic (ADR-154); parallel ordering doesn't
affect bytes.
`rayon` is now `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`
so the wasm build doesn't pull it in. Native build behavior unchanged
(39 / 39 lib tests still pass).
## Crate layout
crates/ruvector-rabitq-wasm/
Cargo.toml cdylib + rlib, wasm-bindgen 0.2, abi-3-friendly
src/lib.rs ~150 LoC of bindings; tests gated to wasm32 via
wasm_bindgen_test (native test would panic in
wasm-bindgen 0.2.117's runtime stub).
## Testing strategy
Native tests of WASM bindings panic by design — `JsValue::from_str`
calls into a wasm-bindgen runtime stub that's `unimplemented!()` on
non-wasm32 targets (since 0.2.117). The right path is
`wasm-pack test --node` or `wasm-pack test --headless --chrome`,
which we'll wire into CI as a follow-up.
The numerical correctness is already covered by `ruvector-rabitq`'s
own test suite. This crate only adds the JS-facing surface.
## Verification (native)
cargo build --workspace → 0 errors
cargo build -p ruvector-rabitq-wasm → clean
cargo clippy -p ruvector-rabitq-wasm --all-targets --no-deps -- -D warnings → exit 0
cargo test -p ruvector-rabitq → 39 / 39 (unchanged)
cargo fmt --all --check → clean
WASM target build (`wasm32-unknown-unknown`) requires `rustup target
add wasm32-unknown-unknown` — not exercised in this PR; will be
covered by a follow-up CI job.
Refs: docs/research/rabitq-integration/ Tier 2, ADR-157
("Optional Accelerator Plane"), PR #381 (Python SDK shape mirror).
Co-Authored-By: claude-flow <ruv@ruv.net>
ruvnet
added a commit
that referenced
this pull request
Apr 26, 2026
PR #388's matrix-split CI exposed two pre-existing failures hidden by the previous 30-minute Tests-job timeout. Both have surprising root causes worth recording. ## Failure 1 — `rvagent-cli::a2a_cli::a2a_serve_discover_and_send_task` Symptom: `unrecognized subcommand 'a2a'` from the spawned `rvagent` binary; test panicked at the `expect(server closed before emitting listening line)` site. Root cause: **PR #380's `main.rs` and `Cargo.toml` changes were silently lost during merge.** The new `crates/rvAgent/rvagent-cli/src/a2a.rs` file landed, but: - `mod a2a;` was never added to `main.rs` - The `A2a(A2aCommand)` variant was never added to the `Commands` enum - The dispatch arm was never wired in - `Cargo.toml` was never updated with the new deps (`rvagent-a2a` path dep, `ed25519-dalek`, `rand_core`, `axum`, `reqwest`, `hex`, plus tokio's `signal`/`process`/`time`/`io-*` /`fs`/`net` features) So `rvagent` shipped with `a2a.rs` orphaned: the file compiled into the lib via `lib.rs` but the binary's `main.rs` never knew about it. Fix: - `main.rs`: add `mod a2a;`, add `A2a(a2a::A2aCommand)` variant, add `is_tui_mode` arm, add dispatch arm using `cli.command.take()` to own the variant (avoids needing to derive Clone on every clap struct in `a2a.rs`). - `Cargo.toml`: restore the deps and tokio features PR #380 intended. Diagnostic improvement: also extended the test to drain the server's stderr in the background and dump it on every panic path. Without that I'd never have seen `unrecognized subcommand 'a2a'` — the future-me debugging this would have spent hours. Verified locally: `cargo test -p rvagent-cli --test a2a_cli` → `1 passed; 0 failed`. ## Failure 2 — `ruqu-wasm::tests::test_circuit_rejects_too_many_qubits` Symptom: panic inside `wasm-bindgen-0.2.117/src/lib.rs:1280` ("function not implemented on non-wasm32 targets"). Root cause: the test module was `#[cfg(test)]` (runs on every `cargo test`) but called into wasm-bindgen-wrapped types (`WasmQuantumCircuit::new`), which since wasm-bindgen 0.2.117 panic when called from a non-wasm runtime. Fix: gate the tests module on `#[cfg(all(test, target_arch = "wasm32"))]`. WASM-binding tests run via `wasm-pack test`; the underlying `ruqu-core` numeric logic is already covered by its own native test suite. This is the same pattern PR #390 (RaBitQ WASM) used proactively. ## Verification cargo build -p rvagent-cli → clean cargo test -p rvagent-cli --test a2a_cli → 1/1 pass cargo build -p ruqu-wasm → clean cargo test -p ruqu-wasm → 0 native tests (wasm-only path) cargo clippy -p rvagent-cli -p ruqu-wasm --all-targets --no-deps -- -D warnings → exit 0 cargo fmt --all --check → exit 0 After this lands, PR #388's Tests (rvagent) and Tests (ruqu-quantum) shards should go green. Co-Authored-By: claude-flow <ruv@ruv.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the WASM gap called out in
docs/research/rabitq-integration/Tier 2 ("WASM / edge: 32× compression makes on-device RAG feasible") and ADR-157's Phase 2 goal. Newruvector-rabitq-wasmsibling crate exposesRabitqIndexto JavaScript/TypeScript callers via wasm-bindgen.Surface
RabitqIndex.build(vectors, dim, seed, rerank_factor)— flat Float32Array of lengthn * dim.idx.search(query, k)→SearchResult[].idx.len,idx.isEmpty.version()— crate version baked at build time.SearchResult { id: u32, distance: f32 }— mirrors the Python SDK M1 shape (PR feat(ruvector-py): Python SDK M1 — RaBitQ wheel #381) so callers porting between languages get identical structures.Native compatibility tweak
ruvector-rabitqhad one rayon call site infrom_vectors_parallel_with_rotation. Gated that oncfg(not(target_arch = "wasm32"))with a sequential.into_iter()fallback for WASM. Output is bit-identical because the rotation matrix is deterministic (ADR-154) — parallel ordering doesn't affect bytes.rayonis now[target.'cfg(not(target_arch = "wasm32"))'.dependencies]. Native behavior unchanged (39 / 39 lib tests still pass).Verification (native)
cargo build --workspace→ 0 errorscargo build -p ruvector-rabitq-wasm→ cleancargo clippy -p ruvector-rabitq-wasm --all-targets --no-deps -- -D warnings→ exit 0cargo test -p ruvector-rabitq→ 39 / 39 (unchanged)cargo fmt --all --check→ cleanWASM target build (
wasm32-unknown-unknown) requiresrustup target add wasm32-unknown-unknown— not exercised here; will be covered by a follow-up CI job.Testing strategy note
Native tests of WASM bindings panic by design —
JsValue::from_strcalls into a wasm-bindgen runtime stub that'sunimplemented!()on non-wasm32 targets (since 0.2.117). The right testing path iswasm-pack test --nodeorwasm-pack test --headless --chrome. Follow-up.The numerical correctness is already covered by
ruvector-rabitq's own test suite (39 tests, untouched).Refs
docs/research/rabitq-integration/Tier 2🤖 Generated with claude-flow