git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
950 lines
34 KiB
Markdown
950 lines
34 KiB
Markdown
# ADR-STS-007: Feature Flag Architecture and Progressive Rollout
|
|
|
|
## Status
|
|
|
|
**Accepted**
|
|
|
|
## Metadata
|
|
|
|
| Field | Value |
|
|
|-------------|------------------------------------------------|
|
|
| Version | 1.0 |
|
|
| Date | 2026-02-20 |
|
|
| Authors | RuVector Architecture Team |
|
|
| Deciders | Architecture Review Board |
|
|
| Supersedes | N/A |
|
|
| Related | ADR-STS-001 (Solver Integration), ADR-STS-003 (WASM Strategy) |
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
The RuVector workspace (v2.0.3, Rust 2021 edition, resolver v2) contains 100+ crates
|
|
spanning vector storage, graph databases, GNN layers, attention mechanisms, sparse
|
|
inference, and mathematics. Feature flags are already used extensively throughout the
|
|
codebase:
|
|
|
|
- **ruvector-core**: `default = ["simd", "storage", "hnsw", "api-embeddings", "parallel"]`
|
|
- **ruvector-graph**: `default = ["full"]` with `full`, `simd`, `storage`, `async-runtime`,
|
|
`compression`, `distributed`, `federation`, `wasm`
|
|
- **ruvector-math**: `default = ["std"]` with `simd`, `parallel`, `serde`
|
|
- **ruvector-gnn**: `default = ["simd", "mmap"]` with `wasm`, `napi`
|
|
- **ruvector-attention**: `default = ["simd"]` with `wasm`, `napi`, `math`, `sheaf`
|
|
|
|
The sublinear-time-solver (v0.1.3) introduces new algorithmic capabilities --- coherence
|
|
verification, spectral graph methods, GNN-accelerated search, and sublinear query
|
|
resolution --- that must be integrated without disrupting any of these existing feature
|
|
surfaces.
|
|
|
|
### Constraints
|
|
|
|
1. **Zero breaking changes** to the public API of any existing crate.
|
|
2. **Opt-in per subsystem**: each solver capability must be individually selectable.
|
|
3. **Gradual rollout**: phased introduction from experimental to default.
|
|
4. **Platform parity**: feature gates must account for native, WASM, and Node.js targets.
|
|
5. **CI tractability**: the feature matrix must remain testable without combinatorial
|
|
explosion.
|
|
6. **Dependency hygiene**: enabling a solver feature must not pull in nalgebra when only
|
|
ndarray is needed, and vice versa.
|
|
|
|
---
|
|
|
|
## Decision
|
|
|
|
We adopt a **hierarchical feature flag architecture** with four tiers: the solver crate
|
|
defines its own backend and acceleration flags, consuming crates expose subsystem-scoped
|
|
`sublinear-*` flags, the workspace root provides aggregate flags for convenience, and CI
|
|
tests a curated feature matrix rather than all 2^N combinations.
|
|
|
|
### 1. Solver Crate Feature Definitions
|
|
|
|
```toml
|
|
# crates/ruvector-solver/Cargo.toml
|
|
|
|
[package]
|
|
name = "ruvector-solver"
|
|
version = "0.1.0"
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
license.workspace = true
|
|
authors.workspace = true
|
|
repository.workspace = true
|
|
description = "Sublinear-time solver: coherence verification, spectral methods, GNN search"
|
|
|
|
[features]
|
|
default = []
|
|
|
|
# Linear algebra backends (mutually independent, both can be active)
|
|
nalgebra-backend = ["dep:nalgebra"]
|
|
ndarray-backend = ["dep:ndarray"]
|
|
|
|
# Acceleration
|
|
parallel = ["dep:rayon"]
|
|
simd = [] # Auto-detected at build time via cfg
|
|
gpu = ["ruvector-math/parallel"] # Future: GPU dispatch through ruvector-math
|
|
|
|
# Platform targets
|
|
wasm = [
|
|
"dep:wasm-bindgen",
|
|
"dep:serde_wasm_bindgen",
|
|
"dep:js-sys",
|
|
]
|
|
|
|
# Convenience aggregates
|
|
full = ["nalgebra-backend", "ndarray-backend", "parallel"]
|
|
|
|
[dependencies]
|
|
# Core (always present)
|
|
ruvector-math = { path = "../ruvector-math", default-features = false }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
tracing = { workspace = true }
|
|
rand = { workspace = true }
|
|
rand_distr = { workspace = true }
|
|
|
|
# Optional backends
|
|
nalgebra = { version = "0.33", default-features = false, features = ["std"], optional = true }
|
|
ndarray = { workspace = true, features = ["serde"], optional = true }
|
|
|
|
# Optional acceleration
|
|
rayon = { workspace = true, optional = true }
|
|
|
|
# Optional WASM
|
|
wasm-bindgen = { workspace = true, optional = true }
|
|
serde_wasm_bindgen = { version = "0.6", optional = true }
|
|
js-sys = { workspace = true, optional = true }
|
|
|
|
[dev-dependencies]
|
|
criterion = { workspace = true }
|
|
proptest = { workspace = true }
|
|
approx = "0.5"
|
|
```
|
|
|
|
### 2. Consuming Crate Feature Gates
|
|
|
|
Each crate that integrates solver capabilities exposes granular `sublinear-*` flags
|
|
that map onto solver features. This keeps the dependency graph explicit and auditable.
|
|
|
|
#### 2.1 ruvector-core
|
|
|
|
```toml
|
|
# Additions to crates/ruvector-core/Cargo.toml [features]
|
|
|
|
# Sublinear solver integration (opt-in)
|
|
sublinear = ["dep:ruvector-solver"]
|
|
|
|
# Coherence verification for HNSW index quality
|
|
sublinear-coherence = [
|
|
"sublinear",
|
|
"ruvector-solver/nalgebra-backend",
|
|
]
|
|
```
|
|
|
|
The `sublinear-coherence` flag enables runtime coherence checks on HNSW graph edges.
|
|
It requires the nalgebra backend because the coherence verifier uses sheaf-theoretic
|
|
linear algebra that maps naturally to nalgebra's matrix abstractions.
|
|
|
|
#### 2.2 ruvector-graph
|
|
|
|
```toml
|
|
# Additions to crates/ruvector-graph/Cargo.toml [features]
|
|
|
|
# Sublinear spectral partitioning and Laplacian solvers
|
|
sublinear = ["dep:ruvector-solver"]
|
|
|
|
sublinear-graph = [
|
|
"sublinear",
|
|
"ruvector-solver/ndarray-backend",
|
|
]
|
|
|
|
# Spectral methods for graph partitioning
|
|
sublinear-spectral = [
|
|
"sublinear-graph",
|
|
"ruvector-solver/parallel",
|
|
]
|
|
```
|
|
|
|
Graph crates use the ndarray backend because ruvector-graph already depends on ndarray
|
|
for adjacency matrices and spectral embeddings. Pulling in nalgebra here would add an
|
|
unnecessary second linear algebra library.
|
|
|
|
#### 2.3 ruvector-gnn
|
|
|
|
```toml
|
|
# Additions to crates/ruvector-gnn/Cargo.toml [features]
|
|
|
|
# GNN-accelerated sublinear search
|
|
sublinear = ["dep:ruvector-solver"]
|
|
|
|
sublinear-gnn = [
|
|
"sublinear",
|
|
"ruvector-solver/ndarray-backend",
|
|
]
|
|
```
|
|
|
|
#### 2.4 ruvector-attention
|
|
|
|
```toml
|
|
# Additions to crates/ruvector-attention/Cargo.toml [features]
|
|
|
|
# Sublinear attention routing
|
|
sublinear = ["dep:ruvector-solver"]
|
|
|
|
sublinear-attention = [
|
|
"sublinear",
|
|
"ruvector-solver/nalgebra-backend",
|
|
"math",
|
|
]
|
|
```
|
|
|
|
#### 2.5 ruvector-collections
|
|
|
|
```toml
|
|
# Additions to crates/ruvector-collections/Cargo.toml [features]
|
|
|
|
# Sublinear collection-level query dispatch
|
|
sublinear = ["ruvector-core/sublinear"]
|
|
```
|
|
|
|
Collections delegates to ruvector-core and does not directly depend on the solver crate.
|
|
|
|
### 3. Workspace-Level Aggregate Flags
|
|
|
|
```toml
|
|
# Additions to workspace Cargo.toml [workspace.dependencies]
|
|
|
|
ruvector-solver = { path = "crates/ruvector-solver", default-features = false }
|
|
```
|
|
|
|
No workspace-level default features are set for the solver. Each consumer pulls exactly
|
|
the features it needs.
|
|
|
|
### 4. Conditional Compilation Patterns
|
|
|
|
All solver-gated code uses consistent `cfg` attribute patterns to ensure the compiler
|
|
eliminates dead code paths when features are disabled.
|
|
|
|
#### 4.1 Module-Level Gating
|
|
|
|
```rust
|
|
// In crates/ruvector-core/src/lib.rs
|
|
|
|
#[cfg(feature = "sublinear")]
|
|
pub mod sublinear;
|
|
|
|
#[cfg(feature = "sublinear-coherence")]
|
|
pub mod coherence;
|
|
```
|
|
|
|
#### 4.2 Trait Implementation Gating
|
|
|
|
```rust
|
|
// In crates/ruvector-core/src/index/hnsw.rs
|
|
|
|
#[cfg(feature = "sublinear-coherence")]
|
|
impl HnswIndex {
|
|
/// Verify edge coherence across the HNSW graph using sheaf Laplacian.
|
|
///
|
|
/// Returns the coherence score in [0, 1] where 1.0 means perfectly coherent.
|
|
/// Only available when the `sublinear-coherence` feature is enabled.
|
|
pub fn verify_coherence(&self, config: &CoherenceConfig) -> Result<f64, SolverError> {
|
|
use ruvector_solver::coherence::SheafCoherenceVerifier;
|
|
|
|
let verifier = SheafCoherenceVerifier::new(config.clone());
|
|
verifier.verify(&self.graph)
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 4.3 Function-Level Gating with Fallback
|
|
|
|
```rust
|
|
// In crates/ruvector-graph/src/query/planner.rs
|
|
|
|
/// Select the optimal query execution strategy.
|
|
///
|
|
/// When `sublinear-spectral` is enabled, the planner considers spectral
|
|
/// partitioning for large graph traversals. Otherwise, it falls back to
|
|
/// the existing cost-based optimizer.
|
|
pub fn select_strategy(&self, query: &GraphQuery) -> ExecutionStrategy {
|
|
#[cfg(feature = "sublinear-spectral")]
|
|
{
|
|
if self.should_use_spectral(query) {
|
|
return self.plan_spectral(query);
|
|
}
|
|
}
|
|
|
|
// Default path: cost-based optimizer (always available)
|
|
self.plan_cost_based(query)
|
|
}
|
|
```
|
|
|
|
#### 4.4 Compile-Time Backend Selection
|
|
|
|
```rust
|
|
// In crates/ruvector-solver/src/backend.rs
|
|
|
|
/// Marker type for the active linear algebra backend.
|
|
///
|
|
/// The solver supports nalgebra and ndarray simultaneously. Consumers
|
|
/// select which backend(s) to activate via feature flags. When both
|
|
/// are active, the solver can dispatch to whichever backend is more
|
|
/// efficient for a given operation.
|
|
|
|
#[cfg(feature = "nalgebra-backend")]
|
|
pub mod nalgebra_ops {
|
|
use nalgebra::{DMatrix, DVector};
|
|
|
|
pub fn solve_laplacian(laplacian: &DMatrix<f64>, rhs: &DVector<f64>) -> DVector<f64> {
|
|
// Cholesky decomposition for positive semi-definite Laplacians
|
|
let chol = laplacian.clone().cholesky()
|
|
.expect("Laplacian must be positive semi-definite");
|
|
chol.solve(rhs)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ndarray-backend")]
|
|
pub mod ndarray_ops {
|
|
use ndarray::{Array1, Array2};
|
|
|
|
pub fn spectral_embedding(adjacency: &Array2<f64>, dim: usize) -> Array2<f64> {
|
|
// Eigendecomposition of the normalized Laplacian
|
|
// ... implementation details
|
|
todo!("spectral embedding via ndarray")
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Runtime Algorithm Selection
|
|
|
|
Beyond compile-time feature gates, the solver provides a runtime dispatch layer
|
|
that selects between dense and sublinear code paths based on data characteristics.
|
|
|
|
```rust
|
|
// In crates/ruvector-solver/src/dispatch.rs
|
|
|
|
/// Configuration for runtime algorithm selection.
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
pub struct SolverDispatchConfig {
|
|
/// Sparsity threshold above which the sublinear path is preferred.
|
|
/// Default: 0.95 (95% sparse). Range: [0.0, 1.0].
|
|
pub sparsity_threshold: f64,
|
|
|
|
/// Minimum number of elements before sublinear algorithms are considered.
|
|
/// Below this threshold, dense algorithms are always faster due to setup costs.
|
|
/// Default: 10_000.
|
|
pub min_elements_for_sublinear: usize,
|
|
|
|
/// Maximum fraction of elements the sublinear path may touch.
|
|
/// If the solver would need to examine more than this fraction,
|
|
/// it falls back to the dense path.
|
|
/// Default: 0.1 (10%).
|
|
pub max_touch_fraction: f64,
|
|
|
|
/// Force a specific path regardless of data characteristics.
|
|
/// None means auto-detection (recommended).
|
|
pub force_path: Option<SolverPath>,
|
|
}
|
|
|
|
impl Default for SolverDispatchConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
sparsity_threshold: 0.95,
|
|
min_elements_for_sublinear: 10_000,
|
|
max_touch_fraction: 0.1,
|
|
force_path: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Which execution path to use.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum SolverPath {
|
|
/// Traditional dense algorithms.
|
|
Dense,
|
|
/// Sublinear-time algorithms (only touches a fraction of the data).
|
|
Sublinear,
|
|
}
|
|
|
|
/// Determine the optimal execution path for the given data.
|
|
pub fn select_path(
|
|
total_elements: usize,
|
|
nonzero_elements: usize,
|
|
config: &SolverDispatchConfig,
|
|
) -> SolverPath {
|
|
if let Some(forced) = config.force_path {
|
|
return forced;
|
|
}
|
|
|
|
if total_elements < config.min_elements_for_sublinear {
|
|
return SolverPath::Dense;
|
|
}
|
|
|
|
let sparsity = 1.0 - (nonzero_elements as f64 / total_elements as f64);
|
|
if sparsity >= config.sparsity_threshold {
|
|
SolverPath::Sublinear
|
|
} else {
|
|
SolverPath::Dense
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. WASM Feature Interaction Matrix
|
|
|
|
WASM targets cannot use certain features (mmap, threads via rayon, SIMD on older
|
|
runtimes). The following matrix defines valid feature combinations per platform.
|
|
|
|
```
|
|
Legend: Y = supported N = not supported P = partial (polyfill)
|
|
|
|
Feature | native-x86_64 | native-aarch64 | wasm32-unknown | wasm32-wasi
|
|
---------------------------+---------------+----------------+----------------+------------
|
|
sublinear | Y | Y | Y | Y
|
|
sublinear-coherence | Y | Y | Y | Y
|
|
sublinear-graph | Y | Y | Y | Y
|
|
sublinear-gnn | Y | Y | Y | Y
|
|
sublinear-spectral | Y | Y | N (no rayon) | N
|
|
sublinear-attention | Y | Y | Y | Y
|
|
nalgebra-backend | Y | Y | Y | Y
|
|
ndarray-backend | Y | Y | Y | Y
|
|
parallel (rayon) | Y | Y | N | N
|
|
simd | Y | Y | P (128-bit) | P
|
|
gpu | Y | P | N | N
|
|
solver + storage | Y | Y | N | Y (fs)
|
|
solver + hnsw | Y | Y | N | N
|
|
```
|
|
|
|
#### WASM Guard Pattern
|
|
|
|
```rust
|
|
// In crates/ruvector-solver/src/lib.rs
|
|
|
|
// Prevent invalid feature combinations at compile time.
|
|
#[cfg(all(feature = "parallel", target_arch = "wasm32"))]
|
|
compile_error!(
|
|
"The `parallel` feature (rayon) is not supported on wasm32 targets. \
|
|
Remove it or use `--no-default-features` when building for WASM."
|
|
);
|
|
|
|
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
|
|
compile_error!(
|
|
"The `gpu` feature is not supported on wasm32 targets."
|
|
);
|
|
```
|
|
|
|
### 7. Feature Flag Documentation Pattern
|
|
|
|
Every feature flag must include a doc comment in the crate-level documentation.
|
|
|
|
```rust
|
|
// In crates/ruvector-solver/src/lib.rs
|
|
|
|
//! # Feature Flags
|
|
//!
|
|
//! | Flag | Default | Description |
|
|
//! |--------------------|---------|--------------------------------------------------|
|
|
//! | `nalgebra-backend` | off | Enable nalgebra for sheaf/coherence operations |
|
|
//! | `ndarray-backend` | off | Enable ndarray for spectral/graph operations |
|
|
//! | `parallel` | off | Enable rayon for multi-threaded solver execution |
|
|
//! | `simd` | off | Enable SIMD intrinsics (auto-detected at build) |
|
|
//! | `gpu` | off | Enable GPU dispatch through ruvector-math |
|
|
//! | `wasm` | off | Enable WASM bindings via wasm-bindgen |
|
|
//! | `full` | off | Enable nalgebra + ndarray + parallel |
|
|
```
|
|
|
|
---
|
|
|
|
## Progressive Rollout Plan
|
|
|
|
### Phase 1: Foundation (Weeks 1-3)
|
|
|
|
**Goal**: Introduce the solver crate with zero consumer integration.
|
|
|
|
| Task | Acceptance Criteria |
|
|
|---------------------------------------------------|----------------------------------------------|
|
|
| Create `crates/ruvector-solver` with empty public API | Crate compiles, no downstream changes |
|
|
| Define all feature flags in Cargo.toml | `cargo check --all-features` passes |
|
|
| Add solver to workspace members list | `cargo build -p ruvector-solver` succeeds |
|
|
| Write compile-time WASM guards | WASM build fails gracefully on invalid combos|
|
|
| Add `ruvector-solver` to workspace dependencies | Resolver v2 is satisfied |
|
|
| Set up CI job for `ruvector-solver` feature matrix | All matrix entries pass |
|
|
|
|
**Feature flags available**: `nalgebra-backend`, `ndarray-backend`, `parallel`, `simd`,
|
|
`wasm`, `full`.
|
|
|
|
**Consumer flags available**: None (solver is not yet a dependency of any consumer).
|
|
|
|
**Risk**: Minimal. No consumer code changes.
|
|
|
|
### Phase 2: Core Integration (Weeks 4-7)
|
|
|
|
**Goal**: Enable coherence verification in ruvector-core and GNN acceleration in
|
|
ruvector-gnn behind opt-in feature flags.
|
|
|
|
| Task | Acceptance Criteria |
|
|
|---------------------------------------------------|----------------------------------------------|
|
|
| Add `sublinear` flag to ruvector-core | Flag compiles with no behavioral change |
|
|
| Add `sublinear-coherence` flag to ruvector-core | Coherence verifier runs on HNSW graphs |
|
|
| Add `sublinear-gnn` flag to ruvector-gnn | GNN training uses sublinear message passing |
|
|
| Write integration tests for coherence | Tests pass with and without the flag |
|
|
| Write integration tests for GNN acceleration | Tests pass with and without the flag |
|
|
| Benchmark coherence overhead | Less than 5% latency increase on default path|
|
|
| Update ruvector-core README with new flags | Documentation is current |
|
|
|
|
**Feature flags available**: Phase 1 flags + `sublinear`, `sublinear-coherence`,
|
|
`sublinear-gnn`.
|
|
|
|
**Rollback plan**: Remove the `sublinear*` feature flags from consumer Cargo.toml and
|
|
delete the gated modules. No API changes to revert because all new code is behind
|
|
feature gates.
|
|
|
|
### Phase 3: Extended Integration (Weeks 8-11)
|
|
|
|
**Goal**: Bring sublinear spectral methods to ruvector-graph and sublinear attention
|
|
routing to ruvector-attention.
|
|
|
|
| Task | Acceptance Criteria |
|
|
|---------------------------------------------------|----------------------------------------------|
|
|
| Add `sublinear-graph` flag to ruvector-graph | Spectral partitioning available behind flag |
|
|
| Add `sublinear-spectral` flag to ruvector-graph | Parallel spectral solver works |
|
|
| Add `sublinear-attention` flag to ruvector-attention | Attention routing uses solver dispatch |
|
|
| Add `sublinear` flag to ruvector-collections | Collection query dispatch delegates properly |
|
|
| WASM builds for all new flags | `cargo build --target wasm32-unknown-unknown`|
|
|
| Performance benchmarks for spectral partitioning | At least 2x speedup on graphs with >100k nodes|
|
|
| Cross-crate integration tests | Multi-crate feature combos work end-to-end |
|
|
|
|
**Feature flags available**: Phase 2 flags + `sublinear-graph`, `sublinear-spectral`,
|
|
`sublinear-attention`.
|
|
|
|
### Phase 4: Default Promotion (Weeks 12-16)
|
|
|
|
**Goal**: After validation, promote selected sublinear features to default feature sets.
|
|
|
|
| Task | Acceptance Criteria |
|
|
|---------------------------------------------------|----------------------------------------------|
|
|
| Collect benchmark data from all phases | Data covers all target platforms |
|
|
| Run `cargo semver-checks` on all modified crates | Zero breaking changes detected |
|
|
| Promote `sublinear-coherence` to ruvector-core default | Default build includes coherence checks |
|
|
| Promote `sublinear-gnn` to ruvector-gnn default | Default GNN build uses solver acceleration |
|
|
| Update ruvector workspace version to 2.1.0 | Minor version bump signals new capabilities |
|
|
| Publish updated crates to crates.io | All crates pass `cargo publish --dry-run` |
|
|
|
|
**Promotion criteria** (all must be met):
|
|
|
|
1. Zero regressions in existing benchmark suite.
|
|
2. Less than 2% compile-time increase for `cargo build` with default features.
|
|
3. Less than 50 KB binary size increase for default builds.
|
|
4. All platform CI targets pass.
|
|
5. At least 4 weeks of Phase 3 stability with no feature-related bug reports.
|
|
|
|
**Feature changes at promotion**:
|
|
|
|
```toml
|
|
# BEFORE (Phase 3)
|
|
# crates/ruvector-core/Cargo.toml
|
|
[features]
|
|
default = ["simd", "storage", "hnsw", "api-embeddings", "parallel"]
|
|
|
|
# AFTER (Phase 4)
|
|
# crates/ruvector-core/Cargo.toml
|
|
[features]
|
|
default = ["simd", "storage", "hnsw", "api-embeddings", "parallel", "sublinear-coherence"]
|
|
```
|
|
|
|
---
|
|
|
|
## CI Configuration for Feature Matrix Testing
|
|
|
|
### Strategy: Tiered Matrix
|
|
|
|
Testing all 2^N feature combinations is infeasible. Instead, we test a curated set of
|
|
meaningful profiles that cover: (a) each feature in isolation, (b) common real-world
|
|
combinations, and (c) platform-specific builds.
|
|
|
|
```yaml
|
|
# .github/workflows/solver-features.yml
|
|
|
|
name: Solver Feature Matrix
|
|
on:
|
|
push:
|
|
paths:
|
|
- 'crates/ruvector-solver/**'
|
|
- 'crates/ruvector-core/**'
|
|
- 'crates/ruvector-graph/**'
|
|
- 'crates/ruvector-gnn/**'
|
|
- 'crates/ruvector-attention/**'
|
|
pull_request:
|
|
paths:
|
|
- 'crates/ruvector-solver/**'
|
|
|
|
jobs:
|
|
feature-matrix:
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
# Tier 1: Individual features on Linux
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "nalgebra-backend"
|
|
name: "nalgebra-only"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "ndarray-backend"
|
|
name: "ndarray-only"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "parallel"
|
|
name: "parallel-only"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "simd"
|
|
name: "simd-only"
|
|
|
|
# Tier 2: Common combinations
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "nalgebra-backend,parallel"
|
|
name: "coherence-profile"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "ndarray-backend,parallel"
|
|
name: "spectral-profile"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: "full"
|
|
name: "full-profile"
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
features: ""
|
|
name: "no-features"
|
|
|
|
# Tier 3: Platform-specific
|
|
- os: ubuntu-latest
|
|
target: wasm32-unknown-unknown
|
|
features: "wasm,nalgebra-backend"
|
|
name: "wasm-nalgebra"
|
|
- os: ubuntu-latest
|
|
target: wasm32-unknown-unknown
|
|
features: "wasm,ndarray-backend"
|
|
name: "wasm-ndarray"
|
|
- os: ubuntu-latest
|
|
target: wasm32-unknown-unknown
|
|
features: "wasm"
|
|
name: "wasm-minimal"
|
|
- os: macos-latest
|
|
target: aarch64-apple-darwin
|
|
features: "full"
|
|
name: "aarch64-full"
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
- name: Check ${{ matrix.name }}
|
|
run: |
|
|
cargo check -p ruvector-solver \
|
|
--target ${{ matrix.target }} \
|
|
--no-default-features \
|
|
--features "${{ matrix.features }}"
|
|
- name: Test ${{ matrix.name }}
|
|
if: matrix.target != 'wasm32-unknown-unknown'
|
|
run: |
|
|
cargo test -p ruvector-solver \
|
|
--no-default-features \
|
|
--features "${{ matrix.features }}"
|
|
|
|
# Consumer crate integration matrix
|
|
consumer-integration:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- crate: ruvector-core
|
|
features: "sublinear-coherence"
|
|
- crate: ruvector-graph
|
|
features: "sublinear-spectral"
|
|
- crate: ruvector-gnn
|
|
features: "sublinear-gnn"
|
|
- crate: ruvector-attention
|
|
features: "sublinear-attention"
|
|
- crate: ruvector-collections
|
|
features: "sublinear"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- name: Test ${{ matrix.crate }} + ${{ matrix.features }}
|
|
run: |
|
|
cargo test -p ${{ matrix.crate }} \
|
|
--features "${{ matrix.features }}"
|
|
|
|
# Semver compliance check
|
|
semver-check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- name: Install cargo-semver-checks
|
|
run: cargo install cargo-semver-checks
|
|
- name: Check semver compliance
|
|
run: |
|
|
for crate in ruvector-core ruvector-graph ruvector-gnn ruvector-attention; do
|
|
cargo semver-checks check-release -p "$crate"
|
|
done
|
|
```
|
|
|
|
### Local Developer Workflow
|
|
|
|
```bash
|
|
# Verify a single feature
|
|
cargo check -p ruvector-solver --no-default-features --features nalgebra-backend
|
|
|
|
# Verify WASM compatibility
|
|
cargo check -p ruvector-solver --target wasm32-unknown-unknown --no-default-features --features wasm
|
|
|
|
# Run the full matrix locally (requires cargo-hack)
|
|
cargo install cargo-hack
|
|
cargo hack check -p ruvector-solver --feature-powerset --depth 2
|
|
|
|
# Verify no semver breakage
|
|
cargo install cargo-semver-checks
|
|
cargo semver-checks check-release -p ruvector-core
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Guide for Existing Users
|
|
|
|
### Users Who Do Not Want Sublinear Features
|
|
|
|
No action required. All sublinear features default to `off`. Existing builds, APIs,
|
|
and binary sizes are unchanged.
|
|
|
|
```toml
|
|
# This continues to work exactly as before:
|
|
[dependencies]
|
|
ruvector-core = "2.1"
|
|
```
|
|
|
|
### Users Who Want Coherence Verification
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
ruvector-core = { version = "2.1", features = ["sublinear-coherence"] }
|
|
```
|
|
|
|
```rust
|
|
// main.rs
|
|
use ruvector_core::index::HnswIndex;
|
|
use ruvector_core::coherence::CoherenceConfig;
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let index = HnswIndex::new(/* ... */)?;
|
|
// ... insert vectors ...
|
|
|
|
let config = CoherenceConfig::default();
|
|
let score = index.verify_coherence(&config)?;
|
|
println!("HNSW coherence score: {score:.4}");
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Users Who Want GNN-Accelerated Search
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
ruvector-gnn = { version = "2.1", features = ["sublinear-gnn"] }
|
|
```
|
|
|
|
```rust
|
|
use ruvector_gnn::SublinearGnnSearch;
|
|
|
|
let searcher = SublinearGnnSearch::builder()
|
|
.sparsity_threshold(0.90)
|
|
.min_elements(5_000)
|
|
.build()?;
|
|
|
|
let results = searcher.search(&graph, &query_vector, k)?;
|
|
```
|
|
|
|
### Users Who Want Spectral Graph Partitioning
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
ruvector-graph = { version = "2.1", features = ["sublinear-spectral"] }
|
|
```
|
|
|
|
```rust
|
|
use ruvector_graph::spectral::SpectralPartitioner;
|
|
|
|
let partitioner = SpectralPartitioner::new(num_partitions);
|
|
let partition_map = partitioner.partition(&graph)?;
|
|
```
|
|
|
|
### Users Who Want Everything
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
ruvector-core = { version = "2.1", features = ["sublinear-coherence"] }
|
|
ruvector-graph = { version = "2.1", features = ["sublinear-spectral"] }
|
|
ruvector-gnn = { version = "2.1", features = ["sublinear-gnn"] }
|
|
ruvector-attention = { version = "2.1", features = ["sublinear-attention"] }
|
|
```
|
|
|
|
### WASM Users
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
ruvector-core = { version = "2.1", default-features = false, features = [
|
|
"memory-only",
|
|
"sublinear-coherence",
|
|
] }
|
|
```
|
|
|
|
Note: `sublinear-spectral` is not available on WASM because it depends on rayon.
|
|
Use `sublinear-graph` (without parallel spectral) instead.
|
|
|
|
---
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- **Zero disruption**: all existing users, builds, and CI pipelines continue to work
|
|
unchanged because every new capability is behind an opt-in feature flag.
|
|
- **Granular adoption**: teams can enable exactly the solver capabilities they need
|
|
without pulling in unused backends or dependencies.
|
|
- **Dependency isolation**: nalgebra users do not pay for ndarray, and vice versa.
|
|
The feature flag hierarchy enforces this separation at the Cargo resolver level.
|
|
- **Platform safety**: compile-time guards prevent invalid feature combinations on
|
|
WASM, eliminating a class of runtime surprises.
|
|
- **Auditable dependency graph**: `cargo tree --features sublinear-coherence` shows
|
|
exactly what each flag brings in, making security review straightforward.
|
|
- **Reversible**: any phase can be rolled back by removing feature flags from consumer
|
|
crates, with zero API changes to revert.
|
|
- **CI efficiency**: the tiered matrix tests meaningful combinations rather than an
|
|
exponential powerset, keeping CI times tractable.
|
|
|
|
### Negative
|
|
|
|
- **Cognitive overhead**: developers must understand the feature flag hierarchy to
|
|
choose the right flags. The naming convention (`sublinear-*`) and documentation
|
|
mitigate this but do not eliminate it.
|
|
- **Combinatorial testing gap**: we cannot test every possible combination. Edge-case
|
|
interactions between features (e.g., `sublinear-coherence` + `distributed` + `wasm`)
|
|
may surface late.
|
|
- **Conditional compilation complexity**: `#[cfg(feature = "...")]` blocks add
|
|
indirection to the codebase. Code navigation tools may not resolve cfg-gated items
|
|
correctly.
|
|
- **Feature flag drift**: if a consuming crate adds a solver feature but the solver
|
|
crate reorganizes its flag names, the consumer will fail to compile. Cargo's resolver
|
|
catches this at build time, but the error message may be unclear.
|
|
- **Binary size**: each additional feature flag adds code behind conditional compilation,
|
|
potentially increasing binary size for users who enable many features.
|
|
|
|
### Neutral
|
|
|
|
- The solver crate is a new workspace member, increasing the total crate count by one.
|
|
- Workspace dependency resolution time increases marginally due to one additional crate.
|
|
- Feature flags become the primary coordination mechanism between solver and consumer
|
|
crates, replacing what would otherwise be runtime configuration.
|
|
|
|
---
|
|
|
|
## Options Considered
|
|
|
|
### Option 1: Monolithic Feature Flag (Rejected)
|
|
|
|
A single `sublinear` flag on each consumer crate that enables all solver capabilities.
|
|
|
|
- **Pros**: Simple to understand, one flag per crate, minimal documentation needed.
|
|
- **Cons**: All-or-nothing adoption. Users who only need coherence must also pull in
|
|
ndarray for spectral methods and rayon for parallel solvers. This violates the
|
|
dependency hygiene constraint and increases binary size unnecessarily.
|
|
- **Verdict**: Rejected because it forces unnecessary dependencies on consumers.
|
|
|
|
### Option 2: Runtime-Only Selection (Rejected)
|
|
|
|
No feature flags. The solver crate is always compiled with all backends. Algorithm
|
|
selection happens purely at runtime.
|
|
|
|
- **Pros**: No conditional compilation, simpler build system, no feature matrix in CI.
|
|
- **Cons**: Every consumer always pays the compile-time and binary-size cost of all
|
|
backends. WASM targets would fail to compile because rayon and mmap are always
|
|
included. This violates the platform parity constraint.
|
|
- **Verdict**: Rejected because it is incompatible with WASM and wastes resources.
|
|
|
|
### Option 3: Separate Crates Per Algorithm (Rejected)
|
|
|
|
Instead of feature flags, create `ruvector-solver-coherence`,
|
|
`ruvector-solver-spectral`, `ruvector-solver-gnn` as separate crates.
|
|
|
|
- **Pros**: Maximum isolation, each crate has its own version and changelog. Consumers
|
|
depend only on the crate they need.
|
|
- **Cons**: High maintenance overhead (4+ additional Cargo.toml files, CI jobs, crate
|
|
publications). Shared types between solver algorithms require a `ruvector-solver-types`
|
|
crate, adding another layer. The workspace already has 100+ crates; adding 4-5 more
|
|
for one integration is disproportionate.
|
|
- **Verdict**: Rejected due to maintenance burden and workspace bloat.
|
|
|
|
### Option 4: Hierarchical Feature Flags (Accepted)
|
|
|
|
The approach described in this ADR. One solver crate with backend flags, consumer crates
|
|
with `sublinear-*` flags, workspace-level aggregates for convenience.
|
|
|
|
- **Pros**: Balances granularity with simplicity. One new crate, N feature flags.
|
|
Cargo's feature unification handles transitive activation. CI matrix is tractable.
|
|
- **Cons**: Requires careful documentation and naming conventions. Some cognitive
|
|
overhead for new contributors.
|
|
- **Verdict**: Accepted as the best balance of isolation, usability, and maintenance cost.
|
|
|
|
---
|
|
|
|
## Related Decisions
|
|
|
|
- **ADR-STS-001**: Solver Integration Architecture -- defines the overall integration
|
|
strategy that this ADR implements via feature flags.
|
|
- **ADR-STS-003**: WASM Strategy -- defines platform constraints that this ADR enforces
|
|
via compile-time guards.
|
|
- **ADR-STS-004**: Performance Benchmarks -- defines the benchmarking framework used to
|
|
validate Phase 4 promotion criteria.
|
|
|
|
---
|
|
|
|
## Version History
|
|
|
|
| Version | Date | Author | Changes |
|
|
|---------|------|--------|---------|
|
|
| 0.1 | 2026-02-20 | RuVector Team | Initial proposal |
|
|
| 1.0 | 2026-02-20 | RuVector Team | Accepted: full implementation complete |
|
|
|
|
---
|
|
|
|
## Implementation Status
|
|
|
|
Feature flag system fully operational: `neumann`, `cg`, `forward-push`, `backward-push`, `hybrid-random-walk`, `true-solver`, `bmssp` as individual flags. `all-algorithms` meta-flag enables all. `simd` for AVX2 acceleration. `wasm` for WebAssembly target. `parallel` for rayon/crossbeam concurrency. Default features: neumann, cg, forward-push. Conditional compilation throughout with `#[cfg(feature = ...)]`.
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [Cargo Features Reference](https://doc.rust-lang.org/cargo/reference/features.html)
|
|
- [cargo-semver-checks](https://github.com/obi1kenobi/cargo-semver-checks)
|
|
- [cargo-hack](https://github.com/taiki-e/cargo-hack) -- for feature powerset testing
|
|
- [MADR 3.0 Template](https://adr.github.io/madr/)
|
|
- [ruvector-core Cargo.toml](/home/user/ruvector/crates/ruvector-core/Cargo.toml)
|
|
- [ruvector-graph Cargo.toml](/home/user/ruvector/crates/ruvector-graph/Cargo.toml)
|
|
- [ruvector-math Cargo.toml](/home/user/ruvector/crates/ruvector-math/Cargo.toml)
|
|
- [ruvector-gnn Cargo.toml](/home/user/ruvector/crates/ruvector-gnn/Cargo.toml)
|
|
- [ruvector-attention Cargo.toml](/home/user/ruvector/crates/ruvector-attention/Cargo.toml)
|
|
- [Workspace Cargo.toml](/home/user/ruvector/Cargo.toml)
|