Follow-up to #403. Addresses the runtime-side issues from #400 (`ruvector
demo` modes) and #402 §A/§B (VectorDB CRUD + GNN/attention typed-array
errors) that needed binding-surface investigation.
## Changes
### `VectorDBWrapper`: normalize distance metric (§A root cause)
`@ruvector/core`'s `JsDistanceMetric` enum is PascalCase
(`Euclidean | Cosine | DotProduct | Manhattan`), but every CLI call site
passes lowercase shorthand (`'cosine'`, `'euclidean'`, `'dot'`). The
native binding rejects lowercase with:
value `"cosine"` does not match any variant of enum `JsDistanceMetric`
on JsDbOptions.distanceMetric
Add a `normalizeMetric()` helper in `src/index.ts` that maps both casing
*and* common aliases (`l2`, `dot`, `dotproduct`, `innerproduct`, `l1`)
to the enum variant. Also accept `metric` as a constructor alias for
`distanceMetric` so the CLI's existing `{metric: ...}` shape works
without changing every call site.
### `demo --basic`: realign with current `VectorDb` API (§A surface)
Old code:
db.insert('vec1', [1.0, 0.0, 0.0, 0.0], { label: 'x-axis' });
const r = db.search([0.8, 0.6, 0, 0], 3);
Current `VectorDBWrapper` (and the underlying `@ruvector/core` binding)
takes a single object:
await db.insert({ id: 'vec1', vector: new Float32Array([...]),
metadata: { label: 'x-axis' } });
const r = await db.search({ vector: new Float32Array([...]), k: 3 });
Updated all four insert calls + the search call accordingly. Verified
locally — vec4 (closest to [0.8, 0.6]) is correctly returned first.
### `demo --gnn`: Float32Array + binding-bug surfaceability
Two issues:
1. CLI passed plain `number[]`; binding requires `Float32Array`. Fixed.
2. `@ruvector/gnn-linux-x64-gnu@0.1.25` has a published-binding
regression where every method (`differentiableSearch`,
`RuvectorLayer.forward`, `TensorCompress.compress`) throws
`Given napi value is not an array` regardless of input shape —
verified with both `Array<Float32Array>` and `number[][]`. This is
a binary-side bug, not fixable from the CLI.
Added `reportGnnBindingError()` helper that detects the error pattern
and surfaces a pointer at #402 so users don't waste time debugging
their own install. Wired it into all three GNN command error handlers
(`gnn layer --test`, `gnn compress`, `gnn search`) and the demo.
Also fixed `result.attention_weights` → `result.weights` (the wrapper
shape; `attention_weights` was the older binding shape) with a fallback
that handles both.
### `demo --graph`: real round-trip via `GraphDatabase`
Was a stub printing "Full graph demo coming soon". `@ruvector/graph-node`
exposes a `GraphDatabase` class with `createNode({ id, embedding,
properties })`, `createEdge({ from, to, description, embedding,
confidence })`, and `stats()` — all async. Implemented a tiny
Alice -[:KNOWS]-> Bob round-trip using the actual API surface.
### `demo --benchmark`: real inline benchmark (with workaround)
Was redirecting to `npx ruvector benchmark`. Implemented an inline
1000-vector / 100-query mini-benchmark. Pinned to `dim=4` because
`ruvector-core-linux-x64-gnu@0.1.29` has a regression where the
`dimensions` constructor arg is ignored — every `VectorDb` instance
reports `expected 4` regardless of what's passed (verified by
constructing fresh instances with various dims). Tracked at #402.
Once that binding is rebuilt, `dim` can scale up.
### `attention compute`: align with current `compute()` surface (§B)
The CLI's old switch invoked `attn.forward([query], keys, values)`,
but every current `@ruvector/attention` class exposes `compute(query,
keys, values)` instead — `forward` doesn't exist. Also the query
must be a flat `Float32Array`, not `[query]` matrix.
Reproduces the user's `Failed to convert napi value Undefined into
rust type u32` and `Get TypedArray info failed` errors directly.
Replaced all five branches (`dot | multi-head | flash | hyperbolic |
linear`) with the correct `compute()` invocation + Float32Array
conversion. Verified locally:
$ node bin/cli.js attention compute -q "[1,0,0,0]" -k keys.json -t dot
✔ Attention computed (dot)
Output: [0.6225, 0.3775, 0, 0...]
### `gnn layer --test` / `gnn compress` / `gnn search`: typed-array conversion
All three commands previously passed plain `number[]` where the binding
needs `Float32Array`. Converted at the call sites + added the
`reportGnnBindingError` hook so users see the upstream pointer when
they hit the binding-side regression.
## Verification
```
$ node bin/cli.js demo --basic
Searching for nearest to [0.8, 0.6, 0, 0]:
1. vec4 (score: 0.0101) ✓ correct nearest
2. vec1 (score: 0.2000)
3. vec2 (score: 0.4000)
Demo complete!
$ node bin/cli.js demo --gnn
GNN demo failed: Given napi value is not an array
Note: this is a known regression in the @ruvector/gnn native binding…
https://github.com/ruvnet/ruvector/issues/402
$ node bin/cli.js demo --graph
✓ GraphDatabase instance created
✓ Created nodes: Alice (alice), Bob (bob)
✓ Created edge Alice -[:KNOWS]-> Bob (uuid)
Graph demo complete!
$ node bin/cli.js demo --benchmark
✓ Inserted 1000 vectors in 126ms (0.13ms/vec)
✓ 100× top-10 search in 51ms (0.51ms/query)
$ node bin/cli.js attention compute -q "[1,0,0,0]" -k keys.json -t dot
✔ Attention computed (dot)
$ npm run verify-dist
verify-dist: 13 dist path(s) present.
```
Version bumped 0.2.24 → 0.2.25.
## Out of scope (binding-side rebuilds needed)
- `@ruvector/gnn` published bindings throw on every call (binding bug).
- `@ruvector/core` published bindings ignore `dimensions` constructor
arg (binding bug).
Both need a rebuild from current source — the Rust source in this repo
shows correct independent state, but the published `.node` files have
the regression. Rebuild and republish are tracked separately.
Addresses release-hygiene gaps in the published `ruvector` npm CLI
(0.2.23) reported in #399, #400, #401, #402.
## Changes
### `prepublishOnly` build-output verification (#399, #402 §C)
`0.2.23` was published without a `dist/` directory at all. tsc was
supposed to run via the existing `prepublishOnly` hook, but the hook
either didn't fire or failed silently — the published tarball shipped
no `dist/` and `bin/cli.js`'s 13 distinct `require('../dist/...')`
sites all crashed (`ruvector doctor`, `embed`, `rvf` subsystems).
Add `scripts/verify-dist.js` that scans `bin/cli.js` for every
`require('../dist/...')` path and asserts the file exists in the
local tree. Wire it into `prepublishOnly` after `npm run build` so
publish itself fails loudly if the artifact is incomplete:
"prepublishOnly": "npm run build && npm run verify-dist"
This is a structural gate — independent of whether the publisher
remembered to run the build manually.
### Router help-text package name (#401 §2)
The CLI claimed `router` "requires ruvector-router-core", which is the
Rust crate name and isn't on npm. Users following the hint hit
`npm error 404 'ruvector-router-core@*' is not in this registry`.
The actual npm package is `@ruvector/router` (already used by the
internal wrapper at `src/core/router-wrapper.ts:16`). Replace the
three user-facing strings:
- `program.command('router').description(...)`
- the `--info` help block's "Rust crate available:" line
- the install-hint section in `info` (kept the Rust hint for the
Rust workflow, added a parallel npm hint)
### `optimize` graceful failure (#401 §1)
`bin/cli.js` requires `'../src/optimizer/index.js'`, but the
optimizer module was never implemented — there's no `index.js` in
`src/optimizer/` (only three orphan helpers: `context.js`,
`settings-generator.js`, `tool-schemas.js`), and the listProfiles /
getProfile / detectTaskType functions the CLI calls don't exist
anywhere. Hard-coded `process.exit(1)` after a generic stack-trace
made it look like the package was broken on the user's machine.
Replace with a friendly "not yet shipped" message that points at the
tracking issue.
### Out of scope (deferred)
These need binding-side investigation and are larger:
- #400: `ruvector demo` API drift (`db.insert is not a function`)
- #402 §A: `VectorDB` CRUD lifecycle (`insertBatch is not a function`)
- #402 §B: GNN/attention typed-array marshalling
The structural fixes here unblock #399 and #402 §C entirely; the
remaining items are tracked separately.
## Verification
```
$ npm run build
$ npm run verify-dist
verify-dist: 13 dist path(s) present.
$ node bin/cli.js optimize --list
ruvector optimize: not yet shipped in this release.
...track at .../issues/401
$ node bin/cli.js router --info
Status: Coming Soon
npm package: npm install @ruvector/router
Rust crate: cargo add ruvector-router-core
$ node bin/cli.js --help | grep router
router [options] AI semantic router operations (requires
@ruvector/router)
$ npm pack --dry-run | grep -E "dist/(index|core/onnx-embedder|core/rvf-wrapper)"
14.3kB dist/core/onnx-embedder.js
2.6kB dist/core/rvf-wrapper.js
7.8kB dist/index.js
```
Version bumped 0.2.23 → 0.2.24.
Co-authored-by: ruvnet <ruvnet@gmail.com>
* feat(ruvector-rabitq-wasm): WASM bindings for RaBitQ via wasm-bindgen
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>
* feat(acorn): add ruvector-acorn crate — ACORN predicate-agnostic filtered HNSW
Implements the ACORN algorithm (Patel et al., SIGMOD 2024, arXiv:2403.04871)
as a standalone Rust crate. ACORN solves filtered vector search recall collapse
at low predicate selectivity by expanding ALL graph neighbors regardless of
predicate outcome, combined with a γ-augmented graph (γ·M neighbors/node).
Three index variants:
- FlatFilteredIndex: post-filter brute-force baseline
- AcornIndex1: ACORN with M=16 standard edges
- AcornIndexGamma: ACORN with 2M=32 edges (γ=2)
Measured (n=5K, D=128, release): ACORN-γ achieves 98.9% recall@10 at 1%
selectivity. cargo build --release and cargo test (12/12) both pass.
https://claude.ai/code/session_0173QrGBttNDWcVXXh4P17if
* perf(acorn): bounded beam, parallel build, flat data, unrolled L2²
Five linked optimizations to ruvector-acorn (≈50% smaller search
working set, ≈6× faster build on 8 cores, comparable or better
recall at every selectivity):
1. **Fix broken bounded-beam eviction in `acorn_search`.**
The previous implementation admitted that its `else` branch was
"wrong" (the comment literally said "this is wrong") and pushed
every neighbor into `candidates` unconditionally, growing the
frontier to O(n). Replace with a correct max-heap eviction:
when `|candidates| >= ef`, only admit a neighbor if it improves
on the farthest pending candidate, evicting that one. This gives
the documented O(ef) memory bound and stops wasted neighbor
expansions at the prune cutoff.
2. **Parallelize the O(n²·D) graph build with rayon.**
The forward pass (each node finds its M nearest predecessors) is
embarrassingly parallel — `into_par_iter` over rows. Back-edge
merge stays serial behind a `Mutex<Vec<u32>>` per node so the
merge is deterministic. ~6× faster on an 8-core box for 5K×128.
3. **Flat row-major vector storage.**
`data: Vec<Vec<f32>>` → `data: Vec<f32>` (length n·dim) with a
`row(i)` accessor. Eliminates the per-vector heap indirection,
keeps the L2² inner loop on contiguous memory the compiler can
vectorize, and trims index size by ~one allocation per row.
4. **`Vec<bool>` for `visited` instead of `HashSet<u32>`.**
O(1) lookup with no hashing or allocator pressure on the hot path.
5. **Hand-unroll L2² by 4.**
Four independent accumulators give LLVM enough room to issue
AVX2/SSE/NEON FMA chains on contemporary x86_64 / aarch64.
3-5× faster for D ≥ 64 in microbenchmarks.
Other:
- `exact_filtered_knn` parallelizes across data via rayon (recall
measurement only — needs `+ Sync` on the predicate).
- `benches/acorn_bench.rs` switches `SmallRng` → `StdRng` (the
workspace doesn't enable rand's `small_rng` feature so the bench
failed to compile).
- `cargo fmt` applied across the crate; CI's Rustfmt check was the
blocking failure on the original PR.
Demo run on x86_64, n=5000, D=128, k=10:
Build: ACORN-γ ≈ 23 ms (was 1.8 s)
Recall: 96.0% @ 1% selectivity (paper: ~98%)
92.0% @ 5% selectivity
79.7% @ 10% selectivity
34.5% @ 50% selectivity (predicate dilutes top-k truth)
QPS: 18 K @ 1% sel, 65 K @ 50% sel
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(acorn): clippy clean-up — sort_by_key, is_empty, redundant closures
CI's `Clippy (deny warnings)` flagged three lints introduced by the
previous optimization commit:
- `unnecessary_sort_by` (graph.rs:158, 176) → use `sort_by_key`
- `len_without_is_empty` (graph.rs) → add `AcornGraph::is_empty`
and `if graph.is_empty()` in search.rs
- `redundant_closure` (main.rs:65, 159, 160) → pass the predicate
directly to `recall_at_k` instead of `|id| pred(id)`
No semantic change.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(wasm): publish @ruvector/rabitq-wasm and @ruvector/acorn-wasm to npm
Two new WASM packages (both v0.1.0, MIT OR Apache-2.0, scoped under
@ruvector). Mirrors the existing @ruvector/graph-wasm packaging
pattern so release tooling treats all three uniformly.
- ADR-161: @ruvector/rabitq-wasm — RaBitQ 1-bit quantized vector
index. 32× embedding compression with deterministic rotation.
Wraps the existing crates/ruvector-rabitq-wasm crate.
- ADR-162: @ruvector/acorn-wasm — ACORN predicate-agnostic filtered
HNSW. 96% recall@10 at 1% selectivity with arbitrary JS predicates.
Adds crates/ruvector-acorn-wasm (new), wrapping the ruvector-acorn
crate from PR #391.
Each crate ships with:
- `build.sh` that runs `wasm-pack build` for web / nodejs / bundler
targets, emitting into npm/packages/{rabitq,acorn}-wasm/{,node/,bundler/}.
- A canonical scoped package.json (kept under git as
package.scoped.json because wasm-pack regenerates package.json from
Cargo metadata on every build).
- A README.md with install + usage for browser, Node.js, and bundler
contexts.
- A `.gitignore` that excludes the wasm-pack-generated artifacts
(.wasm + .js + .d.ts) so only canonical source lives in the repo.
Build sanity:
- `cargo check -p ruvector-acorn-wasm -p ruvector-rabitq-wasm` clean
- `cargo clippy -- -D warnings` clean for both
- `wasm-pack build` succeeds for all three targets on both crates
Published:
- @ruvector/rabitq-wasm@0.1.0 — 40 KB tarball, 71 KB wasm
- @ruvector/acorn-wasm@0.1.0 — 49 KB tarball, ~85 KB wasm
Root README updated with both packages in the npm packages table.
Note: this branch also carries cherry-picks of PR #391's `ruvector-acorn`
crate (commits b90af9caa, 0b4eab11f, eb88176bd, f5913b783) and PR
#391's predecessor commit a674d6eba for `ruvector-rabitq-wasm` itself,
because both base crates are required to build the new WASM wrappers.
Co-Authored-By: claude-flow <ruv@ruv.net>
---------
Co-authored-by: ruvnet <ruvnet@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Node.js ESM resolution requires explicit file extensions in relative imports.
The bare `./index`, `./dag`, and `./storage` specifiers in src/index.ts and
src/node.ts cause ERR_MODULE_NOT_FOUND when the package is consumed from an
ESM context with `"type": "module"`.
Fixes: https://github.com/ruvnet/ruvector/issues (reported via @nwj patch-package workaround)
0.1.28 and 0.1.29 were already published with stale optionalDependencies.
0.1.30 ensures all platform packages + main package are in sync.
Co-Authored-By: claude-flow <ruv@ruv.net>
Extracted into separate modules for clarity:
- subcategories.js: 47 categories (tools/*, core/*, auth/*, mcp/*, etc.)
- statement-parser.js: parseTopLevelStatements() with proper depth tracking
- module-tree.js: agglomerative clustering for folder hierarchy
Note: keyword-based classification captures ~0.2% of minified code.
The Rust Louvain partitioner (1,029 modules from reference graph) is
the correct approach for real decompilation. Node.js pipeline should
shell out to the Rust binary for graph-based splitting.
Co-Authored-By: claude-flow <ruv@ruv.net>
Folder structure emerges from the dependency graph — not hardcoded keywords.
tree.rs (362 lines):
- Agglomerative clustering on inter-module edge weights
- TF-IDF naming: most discriminative strings name each folder
- Recursive depth control (configurable max_depth, min_folder_size)
inferrer.rs: infer_folder_name() with TF-IDF scoring
types.rs: ModuleTree struct, hierarchical config options
run_on_cli.rs: --output-dir prints folder tree to disk
module-splitter.js: JS-side tree builder with same approach
Key principle: tightly-coupled code shares a folder,
MinCut boundaries become folder boundaries, names from context.
59 tests passing, zero warnings.
Co-Authored-By: claude-flow <ruv@ruv.net>
Completes #274 by adding the load path for SONA state persistence:
- ReasoningBank.insert_pattern(): directly insert a pattern (for restore)
- LoopCoordinator.load_state(json): deserialize and restore patterns
- NAPI loadState(stateJson): binding for Node.js
- TypeScript loadState(stateJson): wrapper with return count
Full save/load cycle now works:
const state = engine.saveState(); // serialize patterns to JSON
// ... restart ...
const restored = engine.loadState(state); // restore N patterns
serialize_state() now includes full pattern data (centroids, quality,
cluster sizes) not just counts.
#273: trajectoriesRecorded always returns 0
Root cause: Rust CoordinatorStats serializes as trajectories_buffered
but TypeScript expects trajectoriesRecorded. Added trajectories_recorded
field and mapped snake_case → camelCase in TypeScript wrapper.
#274: Save/load learned state for persistence across restarts
Added serialize_state() to LoopCoordinator and saveState() to
NAPI + TypeScript wrapper.
Stop writing empty mcpServers: {} to settings.json which could trigger
Claude Code to regenerate .mcp.json, stripping user-added fields like
autoStart. Doctor --fix now cleans up stale empty mcpServers entries.
Co-Authored-By: claude-flow <ruv@ruv.net>
Bridge the gap between "stores knowledge" and "learns from knowledge":
- Background training loop (tokio::spawn, 5 min interval) runs SONA
force_learn + domain evolve_population when new data arrives
- POST /v1/train endpoint for on-demand training cycles
- `ruvector brain train` CLI command with --json support
- `brain_train` MCP tool for agent-triggered training
- Vote dedup: 24h TTL on ip_votes entries, author exemption from IP check
- ADR-082 updated, ADR-083 created
Results: Pareto frontier grew 0→24 after 3 cycles. SONA activates
after 100+ trajectory threshold (natural search/share usage).
Publish ruvector@0.2.11.
* feat: proxy-aware fetch + brain API improvements — publish v0.2.7
Add proxyFetch() wrapper to cli.js and mcp-server.js that detects
HTTPS_PROXY/HTTP_PROXY/ALL_PROXY env vars, uses undici ProxyAgent
(Node 18+) or falls back to curl. Handles NO_PROXY patterns.
Replaced all 17 fetch() call sites with timeouts (15-30s).
Brain server API:
- Search returns similarity scores via ScoredBrainMemory
- List supports pagination (offset/limit), sorting (updated_at/quality/votes), tag filtering
- Transfer response includes warnings, source/target memory counts
- New POST /v1/verify endpoint with 4 verification methods
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: brain server bug fixes, GET /v1/pages, 9 MCP page/node tools — v0.2.10
Fix proxyFetch curl fallback to capture real HTTP status instead of
hardcoding 200, add 204 guards to brainFetch/fetchBrainEndpoint/MCP
handler, fix brain_list schema (missing offset/sort/tags), fix
brain_sync direction passthrough, add --json to share/vote/delete/sync.
Add GET /v1/pages route with pagination, status filter, sort.
Add 9 MCP tools: brain_page_list/get/create/update/delete,
brain_node_list/get/publish/revoke (previously SSE-only).
Polish: delete --json returns {deleted:true,id} not {}, page get
unwraps .memory wrapper for formatted display.
112 MCP tools, 69/69 tests pass. Published v0.2.10 to npm.
Co-Authored-By: claude-flow <ruv@ruv.net>
Brain commands now use direct pi.ruv.io fetch (PR #233), so
@ruvector/pi-brain is no longer needed as a peer dependency.
Co-Authored-By: claude-flow <ruv@ruv.net>
Replace requirePiBrain() + PiBrainClient with direct fetch() calls to pi.ruv.io.
All 13 brain CLI commands and 11 brain MCP tools now work out of the box with
zero extra dependencies. Includes 30s timeout on all brain API calls.
- Sanitize shell arguments in hooks_init, hooks_pretrain, hooks_build_agents,
and workers_status handlers to prevent OS command injection (CWE-78)
- Strengthen sanitizeShellArg to also strip quotes, newlines, and backslashes
- Add sanitizeNumericArg helper for numeric-typed arguments
- Improve validateRvfPath with symlink resolution (realpathSync) and cwd confinement
- Add schema validation and prototype pollution guard to hooks_import handler
- Replace permissive CORS (CorsLayer::permissive) with localhost-only origin
policy in Rust SSE transport
Co-Authored-By: claude-flow <ruv@ruv.net>