ruvector/crates/ruvector-capgated
rUv 2176625403
docs(ruvector-capgated): add crate README for crates.io publish (#607)
Cargo.toml declares `readme = "README.md"` but the file was missing, which
blocks `cargo publish` (readme is only validated at package time, so CI was
green). Add a concise crate-level README covering the capability model, the
three variants, and measured results.

Co-authored-by: ruv <ruvnet@users.noreply.github.com>
2026-06-25 16:15:38 -04:00
..
src research(nightly): capability-gated-ann — per-vector read access control in ANN search (#604) 2026-06-25 14:05:34 -04:00
Cargo.toml research(nightly): capability-gated-ann — per-vector read access control in ANN search (#604) 2026-06-25 14:05:34 -04:00
README.md docs(ruvector-capgated): add crate README for crates.io publish (#607) 2026-06-25 16:15:38 -04:00

ruvector-capgated

Capability-gated approximate nearest-neighbour search — per-vector read access control baked into the retrieval engine, using 64-bit bitset capability tokens. Pure Rust, zero external dependencies, WASM-safe.

Most vector databases enforce access control at the collection level: everyone sharing an index can retrieve everyone else's vectors. For multi-tenant agent memory (thousands of agents on one index) that is a design gap, and one-collection-per-tenant does not scale. ruvector-capgated makes the access check part of the search itself: each vector carries a required CapMask, each query presents a held CapMask, and only vectors the querier is authorised for are ever returned.

This is the read-side complement to RuVector's proof-gated writes (ADR-227); see ADR-268.

Capability model

use ruvector_capgated::CapMask;

let required = CapMask::single(3);       // vector needs capability bit 3
let holder   = CapMask::single(3).union(CapMask::single(7));
assert!(holder.satisfies(required));      // (holder & required) == required

A capability check is one 64-bit bitwise AND — orders of magnitude cheaper than the f32 distance computation it guards.

Variants

All implement the CapGatedIndex trait:

Variant Strategy Recall Notes
PostFilter Score all vectors, filter after distance 100% Baseline; equivalent to current post-filtering SOTA
EagerMask Build authorised bitset first, skip distance for unauthorised 100% Latency scales with the authorised fraction, not corpus size
CapGraph k-NN graph walk with ef-bounded exploration ~90% Sub-linear node visits; traverses bridge nodes for connectivity

Measured results

cargo run --release -p ruvector-capgated --bin benchmark (5,000 × 64-dim, 200 queries, x86_64 Linux):

  • Low-access (12.5% authorised): EagerMask 17,548 QPS / 100% recall@10 — 7.9× faster than PostFilter.
  • High-access (37.5% authorised): EagerMask 5,728 QPS / 100% recall@10 — 2.8× faster than PostFilter.

EagerMask latency tracks authorised_fraction × full-scan latency, because unauthorised vectors never enter the distance loop.

Usage

use ruvector_capgated::{CapGatedIndex, CapMask};
// build an EagerMask index, insert (id, vector, required_mask), then:
// index.search(&query, k, holder_mask) -> Vec<SearchResult>
cargo test  -p ruvector-capgated          # 22 tests
cargo run   --release -p ruvector-capgated --bin benchmark

License

MIT