fix: add patches README and fix rust formatting

- Add README.md to patches/ explaining the critical hnsw_rs patch
- Run cargo fmt on ruvector-postgres to fix formatting issues

The patches/hnsw_rs directory is REQUIRED for builds as it provides
a WASM-compatible version of hnsw_rs (using rand 0.8 instead of 0.9).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rUv 2025-12-30 15:41:45 +00:00
parent 1f7d8e6001
commit b1ff59da22
9 changed files with 363 additions and 189 deletions

View file

@ -6,15 +6,18 @@ use pgrx::prelude::*;
#[pg_extern]
fn dag_analyze_plan(
query_text: &str,
) -> TableIterator<'static, (
name!(node_id, i32),
name!(operator_type, String),
name!(criticality, f64),
name!(bottleneck_score, f64),
name!(estimated_cost, f64),
name!(parent_ids, Vec<i32>),
name!(child_ids, Vec<i32>),
)> {
) -> TableIterator<
'static,
(
name!(node_id, i32),
name!(operator_type, String),
name!(criticality, f64),
name!(bottleneck_score, f64),
name!(estimated_cost, f64),
name!(parent_ids, Vec<i32>),
name!(child_ids, Vec<i32>),
),
> {
// Parse and plan the query using PostgreSQL's EXPLAIN
let plan_json = Spi::connect(|client| {
let query = format!("EXPLAIN (FORMAT JSON) {}", query_text);
@ -47,13 +50,16 @@ fn dag_analyze_plan(
#[pg_extern]
fn dag_critical_path(
query_text: &str,
) -> TableIterator<'static, (
name!(path_position, i32),
name!(node_id, i32),
name!(operator_type, String),
name!(accumulated_cost, f64),
name!(attention_weight, f64),
)> {
) -> TableIterator<
'static,
(
name!(path_position, i32),
name!(node_id, i32),
name!(operator_type, String),
name!(accumulated_cost, f64),
name!(attention_weight, f64),
),
> {
// Analyze query and compute critical path
// This would use topological attention mechanism
let results = vec![
@ -70,23 +76,45 @@ fn dag_critical_path(
fn dag_bottlenecks(
query_text: &str,
threshold: default!(f64, 0.7),
) -> TableIterator<'static, (
name!(node_id, i32),
name!(operator_type, String),
name!(bottleneck_score, f64),
name!(impact_estimate, f64),
name!(suggested_action, String),
)> {
) -> TableIterator<
'static,
(
name!(node_id, i32),
name!(operator_type, String),
name!(bottleneck_score, f64),
name!(impact_estimate, f64),
name!(suggested_action, String),
),
> {
// Analyze query for bottlenecks
// This would identify nodes with high cost relative to their position
let all_results = vec![
(0, "SeqScan".to_string(), 0.85, 85.0, "Consider adding index on scanned column".to_string()),
(1, "HashJoin".to_string(), 0.65, 45.0, "Check join selectivity".to_string()),
(3, "Sort".to_string(), 0.72, 60.0, "Increase work_mem or add index".to_string()),
(
0,
"SeqScan".to_string(),
0.85,
85.0,
"Consider adding index on scanned column".to_string(),
),
(
1,
"HashJoin".to_string(),
0.65,
45.0,
"Check join selectivity".to_string(),
),
(
3,
"Sort".to_string(),
0.72,
60.0,
"Increase work_mem or add index".to_string(),
),
];
// Filter by threshold
let filtered: Vec<_> = all_results.into_iter()
let filtered: Vec<_> = all_results
.into_iter()
.filter(|r| r.2 >= threshold)
.collect();
@ -97,13 +125,16 @@ fn dag_bottlenecks(
#[pg_extern]
fn dag_mincut_analysis(
query_text: &str,
) -> TableIterator<'static, (
name!(cut_id, i32),
name!(source_nodes, Vec<i32>),
name!(sink_nodes, Vec<i32>),
name!(cut_capacity, f64),
name!(parallelization_opportunity, bool),
)> {
) -> TableIterator<
'static,
(
name!(cut_id, i32),
name!(source_nodes, Vec<i32>),
name!(sink_nodes, Vec<i32>),
name!(cut_capacity, f64),
name!(parallelization_opportunity, bool),
),
> {
// Compute min-cut analysis to identify parallelization opportunities
// This would use the mincut-gated attention mechanism
let results = vec![
@ -118,28 +149,47 @@ fn dag_mincut_analysis(
#[pg_extern]
fn dag_suggest_optimizations(
query_text: &str,
) -> TableIterator<'static, (
name!(suggestion_id, i32),
name!(category, String),
name!(description, String),
name!(expected_improvement, f64),
name!(confidence, f64),
)> {
) -> TableIterator<
'static,
(
name!(suggestion_id, i32),
name!(category, String),
name!(description, String),
name!(expected_improvement, f64),
name!(confidence, f64),
),
> {
// Generate optimization suggestions using learned patterns
// This would query the SONA engine's learned patterns
let results = vec![
(0, "index".to_string(),
"Add B-tree index on users(created_at) for time-range queries".to_string(),
0.35, 0.85),
(1, "join_order".to_string(),
"Reorder joins: filter users first, then join with orders".to_string(),
0.25, 0.78),
(2, "statistics".to_string(),
"Run ANALYZE on 'orders' table - statistics are 7 days old".to_string(),
0.15, 0.92),
(3, "work_mem".to_string(),
"Increase work_mem to 16MB for this session to avoid disk sorts".to_string(),
0.18, 0.70),
(
0,
"index".to_string(),
"Add B-tree index on users(created_at) for time-range queries".to_string(),
0.35,
0.85,
),
(
1,
"join_order".to_string(),
"Reorder joins: filter users first, then join with orders".to_string(),
0.25,
0.78,
),
(
2,
"statistics".to_string(),
"Run ANALYZE on 'orders' table - statistics are 7 days old".to_string(),
0.15,
0.92,
),
(
3,
"work_mem".to_string(),
"Increase work_mem to 16MB for this session to avoid disk sorts".to_string(),
0.18,
0.70,
),
];
TableIterator::new(results)
@ -149,12 +199,15 @@ fn dag_suggest_optimizations(
#[pg_extern]
fn dag_estimate(
query_text: &str,
) -> TableIterator<'static, (
name!(metric, String),
name!(postgres_estimate, f64),
name!(neural_estimate, f64),
name!(confidence, f64),
)> {
) -> TableIterator<
'static,
(
name!(metric, String),
name!(postgres_estimate, f64),
name!(neural_estimate, f64),
name!(confidence, f64),
),
> {
// Compare PostgreSQL's estimates with neural predictions
// This would use the SONA engine to predict actual runtime
let results = vec![
@ -169,11 +222,7 @@ fn dag_estimate(
/// Compare actual execution with predictions and update learning
#[pg_extern]
fn dag_learn_from_execution(
query_text: &str,
actual_time_ms: f64,
actual_rows: i64,
) -> String {
fn dag_learn_from_execution(query_text: &str, actual_time_ms: f64, actual_rows: i64) -> String {
// Record actual execution metrics for learning
// This would update the SONA engine's patterns
@ -185,7 +234,9 @@ fn dag_learn_from_execution(
format!(
"Recorded execution: {}ms, {} rows. Pattern updated. Estimated improvement: {:.1}%",
actual_time_ms, actual_rows, improvement * 100.0
actual_time_ms,
actual_rows,
improvement * 100.0
)
}

View file

@ -7,44 +7,34 @@ use pgrx::prelude::*;
fn dag_attention_scores(
query_text: &str,
mechanism: default!(&str, "auto"),
) -> TableIterator<'static, (
name!(node_id, i32),
name!(attention_weight, f64),
)> {
) -> TableIterator<'static, (name!(node_id, i32), name!(attention_weight, f64))> {
// Validate mechanism
let valid = [
"topological", "causal_cone", "critical_path",
"mincut_gated", "hierarchical_lorentz",
"parallel_branch", "temporal_btsp", "auto"
"topological",
"causal_cone",
"critical_path",
"mincut_gated",
"hierarchical_lorentz",
"parallel_branch",
"temporal_btsp",
"auto",
];
if !valid.contains(&mechanism) {
pgrx::error!("Invalid attention mechanism: '{}'. Valid: {:?}", mechanism, valid);
pgrx::error!(
"Invalid attention mechanism: '{}'. Valid: {:?}",
mechanism,
valid
);
}
// Compute attention scores based on the selected mechanism
// This would integrate with ruvector-attention crate
let results = match mechanism {
"topological" => vec![
(0, 0.45),
(1, 0.35),
(2, 0.20),
],
"causal_cone" => vec![
(0, 0.50),
(1, 0.30),
(2, 0.20),
],
"critical_path" => vec![
(0, 0.60),
(1, 0.25),
(2, 0.15),
],
_ => vec![
(0, 0.40),
(1, 0.35),
(2, 0.25),
],
"topological" => vec![(0, 0.45), (1, 0.35), (2, 0.20)],
"causal_cone" => vec![(0, 0.50), (1, 0.30), (2, 0.20)],
"critical_path" => vec![(0, 0.60), (1, 0.25), (2, 0.15)],
_ => vec![(0, 0.40), (1, 0.35), (2, 0.25)],
};
TableIterator::new(results)
@ -52,10 +42,7 @@ fn dag_attention_scores(
/// Get attention matrix for visualization (node-to-node attention)
#[pg_extern]
fn dag_attention_matrix(
query_text: &str,
mechanism: default!(&str, "auto"),
) -> Vec<Vec<f64>> {
fn dag_attention_matrix(query_text: &str, mechanism: default!(&str, "auto")) -> Vec<Vec<f64>> {
// Compute full attention matrix (NxN where N is number of nodes)
// Each entry [i,j] represents attention from node i to node j
@ -109,7 +96,8 @@ fn dag_attention_visualize(
],
"mechanism": mechanism,
"critical_path": [0, 1, 2, 3]
}).to_string()
})
.to_string()
}
"ascii" => {
// ASCII art for terminal display
@ -133,7 +121,8 @@ Query Plan with Attention Weights (topological)
(users) (High Attention)
Legend: Higher numbers = More critical to optimize
"#.to_string()
"#
.to_string()
}
"mermaid" => {
// Mermaid syntax for markdown rendering
@ -147,20 +136,21 @@ graph BT
style B fill:#feca57,stroke:#333,stroke-width:2px
style C fill:#48dbfb,stroke:#333,stroke-width:1.5px
style D fill:#1dd1a1,stroke:#333,stroke-width:1px
```"#.to_string()
```"#
.to_string()
}
_ => {
pgrx::error!("Invalid format: '{}'. Use 'dot', 'json', 'ascii', or 'mermaid'", format);
pgrx::error!(
"Invalid format: '{}'. Use 'dot', 'json', 'ascii', or 'mermaid'",
format
);
}
}
}
/// Configure attention hyperparameters for a specific mechanism
#[pg_extern]
fn dag_attention_configure(
mechanism: &str,
params: pgrx::JsonB,
) {
fn dag_attention_configure(mechanism: &str, params: pgrx::JsonB) {
let params_value = params.0;
// Validate and extract parameters based on mechanism
@ -205,18 +195,24 @@ fn dag_attention_configure(
// Store configuration
crate::dag::state::DAG_STATE.set_attention_params(mechanism, params_value);
pgrx::notice!("Configured attention mechanism '{}' with provided parameters", mechanism);
pgrx::notice!(
"Configured attention mechanism '{}' with provided parameters",
mechanism
);
}
/// Get attention mechanism statistics
#[pg_extern]
fn dag_attention_stats() -> TableIterator<'static, (
name!(mechanism, String),
name!(invocations, i64),
name!(avg_latency_us, f64),
name!(hit_rate, f64),
name!(improvement_ratio, f64),
)> {
fn dag_attention_stats() -> TableIterator<
'static,
(
name!(mechanism, String),
name!(invocations, i64),
name!(avg_latency_us, f64),
name!(hit_rate, f64),
name!(improvement_ratio, f64),
),
> {
// Get statistics from state
// This would track performance of different attention mechanisms
let results = vec![
@ -235,18 +231,25 @@ fn dag_attention_stats() -> TableIterator<'static, (
fn dag_attention_benchmark(
query_text: &str,
iterations: default!(i32, 100),
) -> TableIterator<'static, (
name!(mechanism, String),
name!(avg_time_us, f64),
name!(min_time_us, f64),
name!(max_time_us, f64),
name!(std_dev_us, f64),
)> {
) -> TableIterator<
'static,
(
name!(mechanism, String),
name!(avg_time_us, f64),
name!(min_time_us, f64),
name!(max_time_us, f64),
name!(std_dev_us, f64),
),
> {
// Benchmark each attention mechanism
let mechanisms = [
"topological", "causal_cone", "critical_path",
"mincut_gated", "hierarchical_lorentz",
"parallel_branch", "temporal_btsp"
"topological",
"causal_cone",
"critical_path",
"mincut_gated",
"hierarchical_lorentz",
"parallel_branch",
"temporal_btsp",
];
let mut results = Vec::new();

View file

@ -28,15 +28,21 @@ fn dag_set_learning_rate(rate: f64) {
#[pg_extern]
fn dag_set_attention(mechanism: &str) {
let valid_mechanisms = [
"topological", "causal_cone", "critical_path",
"mincut_gated", "hierarchical_lorentz",
"parallel_branch", "temporal_btsp", "auto"
"topological",
"causal_cone",
"critical_path",
"mincut_gated",
"hierarchical_lorentz",
"parallel_branch",
"temporal_btsp",
"auto",
];
if !valid_mechanisms.contains(&mechanism) {
pgrx::error!(
"Invalid attention mechanism '{}'. Valid options: {:?}",
mechanism, valid_mechanisms
mechanism,
valid_mechanisms
);
}
@ -54,16 +60,25 @@ fn dag_configure_sona(
) {
// Validation
if !(1..=4).contains(&micro_lora_rank) {
pgrx::error!("micro_lora_rank must be between 1 and 4, got {}", micro_lora_rank);
pgrx::error!(
"micro_lora_rank must be between 1 and 4, got {}",
micro_lora_rank
);
}
if !(4..=16).contains(&base_lora_rank) {
pgrx::error!("base_lora_rank must be between 4 and 16, got {}", base_lora_rank);
pgrx::error!(
"base_lora_rank must be between 4 and 16, got {}",
base_lora_rank
);
}
if ewc_lambda < 0.0 {
pgrx::error!("ewc_lambda must be non-negative, got {}", ewc_lambda);
}
if !(10..=1000).contains(&pattern_clusters) {
pgrx::error!("pattern_clusters must be between 10 and 1000, got {}", pattern_clusters);
pgrx::error!(
"pattern_clusters must be between 10 and 1000, got {}",
pattern_clusters
);
}
// Store in state
@ -129,7 +144,10 @@ mod tests {
#[pg_test]
fn test_dag_set_attention() {
dag_set_attention("topological");
assert_eq!(crate::dag::state::DAG_STATE.get_attention_mechanism(), "topological");
assert_eq!(
crate::dag::state::DAG_STATE.get_attention_mechanism(),
"topological"
);
}
#[pg_test]

View file

@ -1,13 +1,13 @@
//! SQL function implementations for neural DAG learning
pub mod config;
pub mod analysis;
pub mod attention;
pub mod status;
pub mod config;
pub mod qudag;
pub mod status;
pub use config::*;
pub use analysis::*;
pub use attention::*;
pub use status::*;
pub use config::*;
pub use qudag::*;
pub use status::*;

View file

@ -182,11 +182,7 @@ fn qudag_create_proposal(
/// Vote on proposal
#[pg_extern]
fn qudag_vote(
proposal_id: &str,
vote_choice: &str,
stake_weight: f64,
) -> pgrx::JsonB {
fn qudag_vote(proposal_id: &str, vote_choice: &str, stake_weight: f64) -> pgrx::JsonB {
let choice = match vote_choice.to_lowercase().as_str() {
"for" | "yes" => "for",
"against" | "no" => "against",

View file

@ -24,12 +24,15 @@ fn dag_status() -> pgrx::JsonB {
/// Run comprehensive health check on all components
#[pg_extern]
fn dag_health_check() -> TableIterator<'static, (
name!(component, String),
name!(status, String),
name!(last_check, pgrx::TimestampWithTimeZone),
name!(message, String),
)> {
fn dag_health_check() -> TableIterator<
'static,
(
name!(component, String),
name!(status, String),
name!(last_check, pgrx::TimestampWithTimeZone),
name!(message, String),
),
> {
let now = pgrx::TimestampWithTimeZone::now();
let state = &crate::dag::state::DAG_STATE;
@ -40,25 +43,30 @@ fn dag_health_check() -> TableIterator<'static, (
"sona_engine".to_string(),
"healthy".to_string(),
now,
"Operating normally with 1024 learned patterns".to_string()
"Operating normally with 1024 learned patterns".to_string(),
),
(
"attention_cache".to_string(),
if cache_hit_rate > 0.7 { "healthy" } else { "degraded" }.to_string(),
if cache_hit_rate > 0.7 {
"healthy"
} else {
"degraded"
}
.to_string(),
now,
format!("{:.1}% hit rate", cache_hit_rate * 100.0)
format!("{:.1}% hit rate", cache_hit_rate * 100.0),
),
(
"trajectory_buffer".to_string(),
"healthy".to_string(),
now,
format!("{} trajectories stored", state.get_trajectory_count())
format!("{} trajectories stored", state.get_trajectory_count()),
),
(
"pattern_store".to_string(),
"healthy".to_string(),
now,
format!("{} patterns in memory", state.get_pattern_count())
format!("{} patterns in memory", state.get_pattern_count()),
),
];
@ -67,13 +75,16 @@ fn dag_health_check() -> TableIterator<'static, (
/// Get latency breakdown by component
#[pg_extern]
fn dag_latency_breakdown() -> TableIterator<'static, (
name!(component, String),
name!(p50_us, f64),
name!(p95_us, f64),
name!(p99_us, f64),
name!(max_us, f64),
)> {
fn dag_latency_breakdown() -> TableIterator<
'static,
(
name!(component, String),
name!(p50_us, f64),
name!(p95_us, f64),
name!(p99_us, f64),
name!(max_us, f64),
),
> {
// Return latency percentiles for each component
// In a real implementation, this would track actual measurements
let results = vec![
@ -81,7 +92,13 @@ fn dag_latency_breakdown() -> TableIterator<'static, (
("pattern_lookup".to_string(), 1450.0, 2850.0, 4800.0, 9500.0),
("micro_lora".to_string(), 48.0, 78.0, 92.0, 98.0),
("embedding".to_string(), 125.0, 280.0, 450.0, 750.0),
("total_overhead".to_string(), 1580.0, 3100.0, 5200.0, 10500.0),
(
"total_overhead".to_string(),
1580.0,
3100.0,
5200.0,
10500.0,
),
];
TableIterator::new(results)
@ -89,17 +106,30 @@ fn dag_latency_breakdown() -> TableIterator<'static, (
/// Get memory usage by component
#[pg_extern]
fn dag_memory_usage() -> TableIterator<'static, (
name!(component, String),
name!(allocated_bytes, i64),
name!(used_bytes, i64),
name!(peak_bytes, i64),
)> {
fn dag_memory_usage() -> TableIterator<
'static,
(
name!(component, String),
name!(allocated_bytes, i64),
name!(used_bytes, i64),
name!(peak_bytes, i64),
),
> {
// Return memory usage statistics
// In a real implementation, this would track actual allocations
let results = vec![
("attention_cache".to_string(), 10_485_760, 8_912_384, 10_223_616),
("pattern_store".to_string(), 52_428_800, 44_040_192, 50_331_648),
(
"attention_cache".to_string(),
10_485_760,
8_912_384,
10_223_616,
),
(
"pattern_store".to_string(),
52_428_800,
44_040_192,
50_331_648,
),
("trajectory_buffer".to_string(), 1_048_576, 439_296, 996_147),
("embeddings".to_string(), 26_214_400, 23_068_672, 25_690_112),
("sona_weights".to_string(), 4_194_304, 4_194_304, 4_194_304),
@ -110,19 +140,38 @@ fn dag_memory_usage() -> TableIterator<'static, (
/// Get general statistics
#[pg_extern]
fn dag_statistics() -> TableIterator<'static, (
name!(metric, String),
name!(value, f64),
name!(unit, String),
)> {
fn dag_statistics() -> TableIterator<
'static,
(
name!(metric, String),
name!(value, f64),
name!(unit, String),
),
> {
let state = &crate::dag::state::DAG_STATE;
let results = vec![
("queries_analyzed".to_string(), 12847.0, "count".to_string()),
("patterns_learned".to_string(), state.get_pattern_count() as f64, "count".to_string()),
("trajectories_recorded".to_string(), state.get_trajectory_count() as f64, "count".to_string()),
("avg_improvement".to_string(), state.get_avg_improvement(), "ratio".to_string()),
("cache_hit_rate".to_string(), state.get_cache_hit_rate(), "ratio".to_string()),
(
"patterns_learned".to_string(),
state.get_pattern_count() as f64,
"count".to_string(),
),
(
"trajectories_recorded".to_string(),
state.get_trajectory_count() as f64,
"count".to_string(),
),
(
"avg_improvement".to_string(),
state.get_avg_improvement(),
"ratio".to_string(),
),
(
"cache_hit_rate".to_string(),
state.get_cache_hit_rate(),
"ratio".to_string(),
),
("learning_cycles".to_string(), 58.0, "count".to_string()),
("avg_query_speedup".to_string(), 1.15, "ratio".to_string()),
];
@ -142,13 +191,16 @@ fn dag_reset_stats() -> String {
#[pg_extern]
fn dag_performance_history(
time_window_minutes: default!(i32, 60),
) -> TableIterator<'static, (
name!(timestamp, pgrx::TimestampWithTimeZone),
name!(queries_per_minute, f64),
name!(avg_improvement, f64),
name!(cache_hit_rate, f64),
name!(patterns_learned, i32),
)> {
) -> TableIterator<
'static,
(
name!(timestamp, pgrx::TimestampWithTimeZone),
name!(queries_per_minute, f64),
name!(avg_improvement, f64),
name!(cache_hit_rate, f64),
name!(patterns_learned, i32),
),
> {
// Return historical performance data
// In a real implementation, this would query a time-series buffer
let now = pgrx::TimestampWithTimeZone::now();

View file

@ -3,7 +3,7 @@
//! This module integrates the SONA (Scalable On-device Neural Adaptation) engine
//! with PostgreSQL's query planner to provide learned query optimization.
pub mod state;
pub mod functions;
pub mod state;
pub use state::{DAG_STATE, DagState, DagConfig};
pub use state::{DagConfig, DagState, DAG_STATE};

View file

@ -3,9 +3,9 @@
//! This module manages the global state for the neural DAG learning system,
//! including configuration, metrics, and statistics.
use std::sync::{Arc, Mutex};
use once_cell::sync::Lazy;
use serde_json::Value;
use std::sync::{Arc, Mutex};
/// Global DAG state singleton
pub static DAG_STATE: Lazy<DagState> = Lazy::new(DagState::default);
@ -91,8 +91,13 @@ impl DagState {
}
/// Configure SONA parameters
pub fn configure_sona(&self, micro_lora_rank: i32, base_lora_rank: i32,
ewc_lambda: f64, pattern_clusters: i32) {
pub fn configure_sona(
&self,
micro_lora_rank: i32,
base_lora_rank: i32,
ewc_lambda: f64,
pattern_clusters: i32,
) {
let mut inner = self.inner.lock().unwrap();
inner.micro_lora_rank = micro_lora_rank;
inner.base_lora_rank = base_lora_rank;
@ -133,7 +138,9 @@ impl DagState {
/// Set attention parameters for a mechanism
pub fn set_attention_params(&self, mechanism: &str, params: Value) {
self.inner.lock().unwrap()
self.inner
.lock()
.unwrap()
.attention_params
.insert(mechanism.to_string(), params);
}

47
patches/README.md Normal file
View file

@ -0,0 +1,47 @@
# Patches Directory
**CRITICAL: Do not delete this directory or its contents!**
This directory contains patched versions of external crates that are necessary for building RuVector.
## hnsw_rs
The `hnsw_rs` directory contains a patched version of the [hnsw_rs](https://crates.io/crates/hnsw_rs) crate.
### Why this patch exists
The official hnsw_rs crate uses `rand 0.9` which is **incompatible with WebAssembly (WASM)** builds. This patched version:
1. Uses `rand 0.8` instead of `rand 0.9` for WASM compatibility
2. Uses Rust edition 2021 (not 2024) for stable Rust toolchain compatibility
### How it's used
The patch is applied via `Cargo.toml` at the workspace root:
```toml
[patch.crates-io]
hnsw_rs = { path = "./patches/hnsw_rs" }
```
This ensures all workspace crates that depend on `hnsw_rs` use this patched version.
### What depends on it
- `ruvector-core` (with `hnsw` feature enabled by default)
- `ruvector-graph` (with `hnsw_rs` feature)
- All native builds (Node.js bindings, CLI tools)
### Consequences of deletion
If this directory is deleted:
- **All CI builds will fail** (Build Native Modules, PostgreSQL Extension CI, etc.)
- `cargo build` will fail with "failed to load source for dependency `hnsw_rs`"
- The project cannot be compiled
### Updating the patch
If you need to update hnsw_rs:
1. Download the new version from crates.io
2. Apply the rand 0.8 compatibility changes from the current patch
3. Test WASM and native builds before committing