diff --git a/.gitignore b/.gitignore index 9c5d908e0..7988b4fb9 100644 --- a/.gitignore +++ b/.gitignore @@ -145,7 +145,9 @@ bench_data/ # whatever cwd it's invoked from, even with --output-dir set. Always # transient so any tree they land in should ignore them. acceleras.log -hailo_sdk.client.log +hailo_sdk*.log +hailort.log +logs/ # Iter 228 — per-crate Cargo.lock files for the hailo workspace members # (post iter-219 workspace rejoin). The parent workspace's Cargo.lock @@ -155,3 +157,7 @@ crates/ruvector-hailo/Cargo.lock crates/ruvector-hailo-cluster/Cargo.lock crates/hailort-sys/Cargo.lock crates/ruvector-mmwave/Cargo.lock + +# Python virtual environments (hailo toolchain) +venv-hailo/ +venv-hailo-dfc/ diff --git a/Cargo.lock b/Cargo.lock index 75fccc774..1a42126d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -883,7 +883,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -892,7 +892,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -1306,7 +1306,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -1341,7 +1341,7 @@ dependencies = [ "criterion 0.5.1", "libm", "proptest", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -2418,7 +2418,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -2886,7 +2886,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -3855,7 +3855,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -4466,7 +4466,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -4963,7 +4963,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -5047,12 +5047,12 @@ dependencies = [ "ruvector-consciousness", "ruvector-delta-core", "ruvector-domain-expansion", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "ruvector-nervous-system", "ruvector-solver", "ruvector-sona 0.2.0", "ruvector-sparsifier", - "ruvllm 2.2.0", + "ruvllm 2.2.1", "rvf-crypto", "rvf-federation", "rvf-runtime", @@ -5404,7 +5404,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -6391,7 +6391,7 @@ dependencies = [ "ruqu-algorithms", "ruvector-attention", "ruvector-cluster", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-delta-core", "ruvector-filter", "ruvector-gnn", @@ -6445,7 +6445,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -7054,11 +7054,11 @@ dependencies = [ "rkyv", "roaring", "ruvector-attention", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-gnn", "ruvector-graph", "ruvector-hyperbolic-hnsw", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "ruvector-nervous-system", "ruvector-raft", "ruvector-sona 0.2.0", @@ -7983,7 +7983,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -8070,7 +8070,7 @@ dependencies = [ "ndarray 0.16.1", "rand 0.8.5", "rand_distr 0.4.3", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -8314,7 +8314,7 @@ dependencies = [ [[package]] name = "ruqu" -version = "2.2.0" +version = "2.2.1" dependencies = [ "blake3", "cognitum-gate-tilezero 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -8580,7 +8580,7 @@ dependencies = [ [[package]] name = "ruvector-acorn" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "rand 0.8.5", @@ -8603,7 +8603,7 @@ dependencies = [ [[package]] name = "ruvector-attention" -version = "2.2.0" +version = "2.2.1" dependencies = [ "approx", "criterion 0.5.1", @@ -8618,7 +8618,7 @@ dependencies = [ [[package]] name = "ruvector-attention-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", @@ -8650,7 +8650,7 @@ dependencies = [ [[package]] name = "ruvector-attention-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -8665,7 +8665,7 @@ dependencies = [ [[package]] name = "ruvector-attn-mincut" -version = "2.2.0" +version = "2.2.1" dependencies = [ "serde", "serde_json", @@ -8674,7 +8674,7 @@ dependencies = [ [[package]] name = "ruvector-bench" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "byteorder", @@ -8695,8 +8695,8 @@ dependencies = [ "rayon", "ruvector-cognitive-container", "ruvector-coherence", - "ruvector-core 2.2.0", - "ruvector-mincut 2.2.0", + "ruvector-core 2.2.1", + "ruvector-mincut 2.2.1", "serde", "serde_json", "statistical", @@ -8725,7 +8725,7 @@ dependencies = [ "rand_distr 0.4.3", "rayon", "reqwest 0.12.28", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "rvf-crypto", "rvf-types", "rvf-wire", @@ -8742,7 +8742,7 @@ dependencies = [ [[package]] name = "ruvector-cli" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "assert_cmd", @@ -8767,7 +8767,8 @@ dependencies = [ "predicates", "prettytable-rs", "rand 0.8.5", - "ruvector-core 2.2.0", + "reqwest 0.12.28", + "ruvector-core 2.2.1", "ruvector-gnn", "ruvector-graph", "serde", @@ -8800,7 +8801,7 @@ dependencies = [ "rand_distr 0.4.3", "rayon", "ruvector-attention", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-gnn", "ruvector-graph", "serde", @@ -8816,7 +8817,7 @@ dependencies = [ [[package]] name = "ruvector-cluster" -version = "2.2.0" +version = "2.2.1" dependencies = [ "async-trait", "bincode 2.0.1", @@ -8825,7 +8826,7 @@ dependencies = [ "futures", "parking_lot 0.12.5", "rand 0.8.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -8836,7 +8837,7 @@ dependencies = [ [[package]] name = "ruvector-cnn" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "fastrand", @@ -8864,7 +8865,7 @@ dependencies = [ [[package]] name = "ruvector-cognitive-container" -version = "2.2.0" +version = "2.2.1" dependencies = [ "proptest", "serde", @@ -8874,7 +8875,7 @@ dependencies = [ [[package]] name = "ruvector-coherence" -version = "2.2.0" +version = "2.2.1" dependencies = [ "serde", "serde_json", @@ -8882,14 +8883,14 @@ dependencies = [ [[package]] name = "ruvector-collections" -version = "2.2.0" +version = "2.2.1" dependencies = [ "bincode 2.0.1", "chrono", "criterion 0.5.1", "dashmap 6.1.0", "parking_lot 0.12.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -8898,7 +8899,7 @@ dependencies = [ [[package]] name = "ruvector-consciousness" -version = "2.2.0" +version = "2.2.1" dependencies = [ "approx", "criterion 0.5.1", @@ -8910,7 +8911,7 @@ dependencies = [ "ruvector-cognitive-container", "ruvector-coherence", "ruvector-math", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "ruvector-solver", "ruvector-sparsifier", "serde", @@ -8920,7 +8921,7 @@ dependencies = [ [[package]] name = "ruvector-consciousness-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "getrandom 0.2.17", "js-sys", @@ -8986,7 +8987,7 @@ dependencies = [ [[package]] name = "ruvector-core" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "bincode 2.0.1", @@ -9027,7 +9028,7 @@ dependencies = [ "approx", "ruvector-attention", "ruvector-gnn", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "serde", "serde_json", "thiserror 1.0.69", @@ -9035,7 +9036,7 @@ dependencies = [ [[package]] name = "ruvector-dag" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "crossbeam", @@ -9047,7 +9048,7 @@ dependencies = [ "pqcrypto-kyber", "proptest", "rand 0.8.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "sha2 0.10.9", @@ -9072,7 +9073,7 @@ dependencies = [ [[package]] name = "ruvector-decompiler" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "memchr", @@ -9081,7 +9082,7 @@ dependencies = [ "ort", "rayon", "regex", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "serde", "serde_json", "sha3", @@ -9090,7 +9091,7 @@ dependencies = [ [[package]] name = "ruvector-decompiler-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -9194,7 +9195,7 @@ dependencies = [ [[package]] name = "ruvector-diskann" -version = "2.2.0" +version = "2.2.1" dependencies = [ "bincode 2.0.1", "bytemuck", @@ -9211,7 +9212,7 @@ dependencies = [ [[package]] name = "ruvector-diskann-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", @@ -9232,7 +9233,7 @@ dependencies = [ [[package]] name = "ruvector-domain-expansion" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "proptest", @@ -9275,7 +9276,7 @@ dependencies = [ [[package]] name = "ruvector-exotic-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -9291,12 +9292,12 @@ dependencies = [ [[package]] name = "ruvector-filter" -version = "2.2.0" +version = "2.2.1" dependencies = [ "chrono", "dashmap 6.1.0", "ordered-float", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -9342,7 +9343,7 @@ dependencies = [ [[package]] name = "ruvector-gnn" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "criterion 0.5.1", @@ -9358,7 +9359,7 @@ dependencies = [ "rand 0.8.5", "rand_distr 0.4.3", "rayon", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "tempfile", @@ -9367,7 +9368,7 @@ dependencies = [ [[package]] name = "ruvector-gnn-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", @@ -9378,7 +9379,7 @@ dependencies = [ [[package]] name = "ruvector-gnn-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -9393,7 +9394,7 @@ dependencies = [ [[package]] name = "ruvector-graph" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "bincode 2.0.1", @@ -9433,7 +9434,7 @@ dependencies = [ "rkyv", "roaring", "ruvector-cluster", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-raft", "ruvector-replication", "serde", @@ -9454,14 +9455,14 @@ dependencies = [ [[package]] name = "ruvector-graph-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "futures", "napi", "napi-build", "napi-derive", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-graph", "serde", "serde_json", @@ -9473,14 +9474,14 @@ dependencies = [ [[package]] name = "ruvector-graph-transformer" -version = "2.2.0" +version = "2.2.1" dependencies = [ "proptest", "rand 0.8.5", "ruvector-attention", "ruvector-coherence", "ruvector-gnn", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "ruvector-solver", "ruvector-verified", "serde", @@ -9489,7 +9490,7 @@ dependencies = [ [[package]] name = "ruvector-graph-transformer-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", @@ -9501,7 +9502,7 @@ dependencies = [ [[package]] name = "ruvector-graph-transformer-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "js-sys", "serde", @@ -9513,7 +9514,7 @@ dependencies = [ [[package]] name = "ruvector-graph-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "console_error_panic_hook", @@ -9522,7 +9523,7 @@ dependencies = [ "js-sys", "parking_lot 0.12.5", "regex", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-graph", "serde", "serde-wasm-bindgen", @@ -9547,7 +9548,7 @@ dependencies = [ "criterion 0.5.1", "hailort-sys", "proptest", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde_json", "sha2 0.10.9", "thiserror 2.0.18", @@ -9567,9 +9568,10 @@ dependencies = [ "prost", "protoc-bin-vendored", "rcgen", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-hailo", "ruvector-mmwave", + "ruvllm 2.2.1", "serde", "serde_json", "sha2 0.10.9", @@ -9634,7 +9636,7 @@ dependencies = [ [[package]] name = "ruvector-math" -version = "2.2.0" +version = "2.2.1" dependencies = [ "approx", "criterion 0.5.1", @@ -9649,7 +9651,7 @@ dependencies = [ [[package]] name = "ruvector-math-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -9667,7 +9669,7 @@ dependencies = [ [[package]] name = "ruvector-metrics" -version = "2.2.0" +version = "2.2.1" dependencies = [ "chrono", "lazy_static", @@ -9722,7 +9724,7 @@ dependencies = [ [[package]] name = "ruvector-mincut" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "criterion 0.5.1", @@ -9736,7 +9738,7 @@ dependencies = [ "rand 0.8.5", "rayon", "roaring", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-graph", "serde", "serde_json", @@ -9781,24 +9783,24 @@ dependencies = [ [[package]] name = "ruvector-mincut-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", "napi-derive", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "serde", "serde_json", ] [[package]] name = "ruvector-mincut-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", "js-sys", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", "serde", "serde-wasm-bindgen", "serde_json", @@ -9812,7 +9814,7 @@ version = "0.0.1" [[package]] name = "ruvector-nervous-system" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "approx", @@ -9846,14 +9848,14 @@ dependencies = [ [[package]] name = "ruvector-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "napi", "napi-build", "napi-derive", "ruvector-collections", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-filter", "ruvector-metrics", "serde", @@ -9865,7 +9867,7 @@ dependencies = [ [[package]] name = "ruvector-profiler" -version = "2.2.0" +version = "2.2.1" dependencies = [ "serde", "serde_json", @@ -9874,7 +9876,7 @@ dependencies = [ [[package]] name = "ruvector-rabitq" -version = "2.2.0" +version = "2.2.1" dependencies = [ "criterion 0.5.1", "rand 0.8.5", @@ -9901,7 +9903,7 @@ dependencies = [ [[package]] name = "ruvector-raft" -version = "2.2.0" +version = "2.2.1" dependencies = [ "bincode 2.0.1", "chrono", @@ -9909,7 +9911,7 @@ dependencies = [ "futures", "parking_lot 0.12.5", "rand 0.8.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -9920,7 +9922,7 @@ dependencies = [ [[package]] name = "ruvector-replication" -version = "2.2.0" +version = "2.2.1" dependencies = [ "bincode 2.0.1", "chrono", @@ -9928,7 +9930,7 @@ dependencies = [ "futures", "parking_lot 0.12.5", "rand 0.8.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -9963,7 +9965,7 @@ dependencies = [ [[package]] name = "ruvector-router-cli" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "chrono", @@ -9978,7 +9980,7 @@ dependencies = [ [[package]] name = "ruvector-router-core" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "bincode 2.0.1", @@ -10005,7 +10007,7 @@ dependencies = [ [[package]] name = "ruvector-router-ffi" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "chrono", @@ -10020,7 +10022,7 @@ dependencies = [ [[package]] name = "ruvector-router-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "js-sys", "ruvector-router-core", @@ -10034,7 +10036,7 @@ dependencies = [ [[package]] name = "ruvector-rulake" -version = "2.2.0" +version = "2.2.1" dependencies = [ "hex", "rand 0.8.5", @@ -10049,7 +10051,7 @@ dependencies = [ [[package]] name = "ruvector-scipix" -version = "2.2.0" +version = "2.2.1" dependencies = [ "ab_glyph", "anyhow", @@ -10122,12 +10124,12 @@ dependencies = [ [[package]] name = "ruvector-server" -version = "2.2.0" +version = "2.2.1" dependencies = [ "axum 0.7.9", "dashmap 6.1.0", "parking_lot 0.12.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -10140,13 +10142,13 @@ dependencies = [ [[package]] name = "ruvector-snapshot" -version = "2.2.0" +version = "2.2.1" dependencies = [ "async-trait", "bincode 2.0.1", "chrono", "flate2", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "sha2 0.10.9", @@ -10157,7 +10159,7 @@ dependencies = [ [[package]] name = "ruvector-solver" -version = "2.2.0" +version = "2.2.1" dependencies = [ "approx", "criterion 0.5.1", @@ -10176,7 +10178,7 @@ dependencies = [ [[package]] name = "ruvector-solver-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "napi", "napi-build", @@ -10189,7 +10191,7 @@ dependencies = [ [[package]] name = "ruvector-solver-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "getrandom 0.2.17", "js-sys", @@ -10239,7 +10241,7 @@ dependencies = [ [[package]] name = "ruvector-sparse-inference" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "byteorder", @@ -10262,7 +10264,7 @@ dependencies = [ [[package]] name = "ruvector-sparsifier" -version = "2.2.0" +version = "2.2.1" dependencies = [ "approx", "criterion 0.5.1", @@ -10280,7 +10282,7 @@ dependencies = [ [[package]] name = "ruvector-sparsifier-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "console_error_panic_hook", "getrandom 0.2.17", @@ -10295,11 +10297,11 @@ dependencies = [ [[package]] name = "ruvector-temporal-tensor" -version = "2.2.0" +version = "2.2.1" [[package]] name = "ruvector-tiny-dancer-core" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "bytemuck", @@ -10329,7 +10331,7 @@ dependencies = [ [[package]] name = "ruvector-tiny-dancer-node" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "chrono", @@ -10346,7 +10348,7 @@ dependencies = [ [[package]] name = "ruvector-tiny-dancer-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "js-sys", "ruvector-tiny-dancer-core", @@ -10367,7 +10369,7 @@ dependencies = [ "proptest", "ruvector-cognitive-container", "ruvector-coherence", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "serde", "serde_json", "thiserror 2.0.18", @@ -10389,7 +10391,7 @@ dependencies = [ [[package]] name = "ruvector-wasm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "base64 0.22.1", @@ -10402,7 +10404,7 @@ dependencies = [ "parking_lot 0.12.5", "rand 0.8.5", "ruvector-collections", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-filter", "serde", "serde-wasm-bindgen", @@ -10416,6 +10418,84 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ruview-cluster-sdk" +version = "0.1.0" +dependencies = [ + "futures", + "prost", + "protoc-bin-vendored", + "thiserror 2.0.18", + "tokio", + "tonic", + "tonic-build", + "tracing", +] + +[[package]] +name = "ruview-ruvllm-h10" +version = "0.1.0" +dependencies = [ + "async-stream", + "axum 0.7.9", + "futures-core", + "prost", + "protoc-bin-vendored", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ruview-ruvllm-router" +version = "0.1.0" +dependencies = [ + "async-stream", + "axum 0.7.9", + "futures-core", + "prost", + "protoc-bin-vendored", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ruview-vitals-worker" +version = "0.1.0" +dependencies = [ + "async-stream", + "axum 0.7.9", + "futures-core", + "prost", + "protoc-bin-vendored", + "reqwest 0.12.28", + "ruvector-hailo", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", + "wifi-densepose-vitals", +] + [[package]] name = "ruvix-aarch64" version = "0.1.0" @@ -10634,7 +10714,7 @@ dependencies = [ [[package]] name = "ruvllm" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "async-trait", @@ -10664,7 +10744,7 @@ dependencies = [ "rayon", "regex", "ruvector-attention", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-gnn", "ruvector-graph", "ruvector-sona 0.2.0", @@ -10684,7 +10764,7 @@ dependencies = [ [[package]] name = "ruvllm-cli" -version = "2.2.0" +version = "2.2.1" dependencies = [ "anyhow", "assert_cmd", @@ -10704,7 +10784,7 @@ dependencies = [ "predicates", "prettytable-rs", "rustyline", - "ruvllm 2.2.0", + "ruvllm 2.2.1", "serde", "serde_json", "tempfile", @@ -11019,7 +11099,7 @@ dependencies = [ "rand_distr 0.4.3", "ruvector-attention", "ruvector-collections", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-dag", "ruvector-filter", "ruvector-gnn", @@ -11133,7 +11213,7 @@ dependencies = [ "js-sys", "once_cell", "parking_lot 0.12.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "rvf-runtime", "rvf-types", "serde", @@ -11224,7 +11304,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -11233,7 +11313,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -11372,7 +11452,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -11381,7 +11461,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -11999,7 +12079,7 @@ name = "subpolynomial-time-mincut-demo" version = "0.1.0" dependencies = [ "rand 0.8.5", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -12222,7 +12302,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -12915,7 +12995,7 @@ name = "train-discoveries" version = "0.1.0" dependencies = [ "rand 0.8.5", - "ruvector-core 2.2.0", + "ruvector-core 2.2.1", "ruvector-solver", "serde", "serde_json", @@ -13335,7 +13415,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -13601,7 +13681,7 @@ version = "0.1.0" dependencies = [ "rand 0.8.5", "ruvector-coherence", - "ruvector-mincut 2.2.0", + "ruvector-mincut 2.2.1", ] [[package]] @@ -13815,6 +13895,13 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +[[package]] +name = "wifi-densepose-vitals" +version = "0.3.0" +dependencies = [ + "tracing", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 49a498254..72504285c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,17 @@ members = [ "crates/ruvector-hailo", "crates/ruvector-mmwave", "crates/ruvector-hailo-cluster", + # ADR-183 Tier 1 — per-Pi WiFi-CSI vital-signs worker. Owns the + # ADR-018 frame parser, sliding window, vitals DSP, gRPC :50054 + # service, and brain POST shim. Path-dep on RuView is opt-in via + # `--features ruview-integration` (default off). + "crates/ruview-vitals-worker", + # ADR-183: gRPC client SDK for cluster-wide vitals aggregation (Tier 1/2 fusion). + "crates/ruview-cluster-sdk", + # ADR-184: ruvllm LLM serving on cognitum-cluster-3 Hailo-10H (AI HAT+ 2). + "crates/ruview-ruvllm-h10", + # ADR-185: multi-backend LLM router — load-balances across all H10H nodes. + "crates/ruview-ruvllm-router", "examples/refrag-pipeline", "examples/scipix", "examples/google-cloud", diff --git a/crates/ruvector-cli/Cargo.toml b/crates/ruvector-cli/Cargo.toml index 53500656f..acfd7ea1a 100644 --- a/crates/ruvector-cli/Cargo.toml +++ b/crates/ruvector-cli/Cargo.toml @@ -67,6 +67,9 @@ ndarray = { workspace = true } colored = "2.1" prettytable-rs = "0.10" +# HTTP client — used by `ruvector csi sink` to poll the brain +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "blocking"] } + # HTTP for MCP SSE transport hyper = { version = "1.5", features = ["full"] } hyper-util = { version = "0.1", features = ["full"] } diff --git a/crates/ruvector-cli/src/cli/csi.rs b/crates/ruvector-cli/src/cli/csi.rs new file mode 100644 index 000000000..e164f643e --- /dev/null +++ b/crates/ruvector-cli/src/cli/csi.rs @@ -0,0 +1,328 @@ +//! `ruvector csi` subcommands — ADR-183 Tier 3 iter 16. +//! +//! `ruvector csi sink` — poll brain for `spatial-csi-embedding` memories +//! and index them into an HNSW VectorDB. +//! `ruvector csi search` — k-NN cosine search over the CSI embedding index. + +use anyhow::{Context, Result}; +use clap::Subcommand; +use colored::*; +use ruvector_core::{ + types::{DbOptions, DistanceMetric, HnswConfig, SearchQuery, VectorEntry}, + VectorDB, +}; +use serde::Deserialize; +use std::collections::HashMap; +use std::path::PathBuf; + +/// Default HNSW database path on cognitum-v0. +pub const DEFAULT_CSI_DB: &str = "/var/lib/ruvector-vectors/csi-embeddings.db"; + +/// Default brain URL on cognitum-v0. +pub const DEFAULT_BRAIN_URL: &str = "http://127.0.0.1:9876"; + +/// CSI embedding dimension (must match `ruvector_hailo::CSI_EMBED_DIM`). +pub const CSI_DIM: usize = 128; + +#[derive(Subcommand)] +pub enum CsiCommands { + /// Poll the brain for spatial-csi-embedding memories and insert them + /// into a 128-dim cosine HNSW index at `--db`. + Sink { + /// Brain HTTP base URL. + #[arg(long, default_value = DEFAULT_BRAIN_URL)] + brain: String, + + /// Path to the HNSW index file. + #[arg(long, default_value = DEFAULT_CSI_DB)] + db: PathBuf, + + /// Poll once then exit (default: run continuously every 30 s). + #[arg(long)] + once: bool, + + /// Polling interval in seconds (ignored with --once). + #[arg(long, default_value = "30")] + interval: u64, + }, + + /// Search the CSI embedding index for the K nearest neighbours of a + /// query embedding. + Search { + /// Path to the HNSW index file. + #[arg(long, default_value = DEFAULT_CSI_DB)] + db: PathBuf, + + /// 128 comma-separated f32 values (the query embedding). + #[arg(long)] + embedding: Option, + + /// Number of results. + #[arg(short = 'k', long, default_value = "5")] + top_k: usize, + + /// Print full 128-dim vectors. + #[arg(long)] + show_vectors: bool, + }, +} + +/// Brain `/memories` response shape. +#[derive(Debug, Deserialize)] +struct BrainResponse { + memories: Vec, +} + +#[derive(Debug, Deserialize)] +struct BrainMemory { + category: String, + content: String, +} + +/// Parse `node_id=N node=X embedding=[f32,…]` content string. +/// Returns `(id_string, embedding)` on success. +fn parse_csi_embedding(content: &str) -> Option<(String, Vec)> { + // Extract node_id + let node_id = content + .split_whitespace() + .find(|t| t.starts_with("node_id="))? + .strip_prefix("node_id=")? + .to_string(); + let node = content + .split_whitespace() + .find(|t| t.starts_with("node=")) + .and_then(|t| t.strip_prefix("node=")) + .unwrap_or("unknown"); + + // Extract embedding JSON array + let emb_start = content.find("embedding=[")?; + let array_str = &content[emb_start + "embedding=".len()..]; + let close = array_str.find(']')?; + let values: Vec = array_str[1..close] + .split(',') + .filter_map(|s| s.trim().parse().ok()) + .collect(); + if values.len() != CSI_DIM { + return None; + } + let id = format!("csi:{node}:{node_id}"); + Some((id, values)) +} + +/// Open (or create) the 128-dim cosine HNSW index at `path`. +fn open_csi_db(path: &PathBuf) -> Result { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("create db dir {}", parent.display()))?; + } + let opts = DbOptions { + dimensions: CSI_DIM, + distance_metric: DistanceMetric::Cosine, + storage_path: path.to_string_lossy().into_owned(), + hnsw_config: Some(HnswConfig { + m: 16, + ef_construction: 100, + ef_search: 50, + max_elements: 100_000, + }), + quantization: None, + }; + VectorDB::new(opts).context("open CSI VectorDB") +} + +/// Fetch all `spatial-csi-embedding` memories from the brain and ingest +/// any that are not already in the index. +fn ingest_once(brain_url: &str, db: &mut VectorDB) -> Result { + let url = format!("{}/memories", brain_url.trim_end_matches('/')); + let resp = reqwest::blocking::get(&url) + .with_context(|| format!("GET {url}"))? + .json::() + .context("parse brain response")?; + + let memories: Vec = { + let raw = resp + .get("memories") + .and_then(|v| v.as_array()) + .cloned() + .unwrap_or_default(); + raw.into_iter() + .filter_map(|v| serde_json::from_value(v).ok()) + .collect() + }; + + let csi_mems: Vec<_> = memories + .iter() + .filter(|m| m.category == "spatial-csi-embedding") + .collect(); + + let mut inserted = 0usize; + for mem in &csi_mems { + let Some((id, vec)) = parse_csi_embedding(&mem.content) else { + continue; + }; + let mut meta = HashMap::new(); + meta.insert( + "content".to_string(), + serde_json::Value::String(mem.content.clone()), + ); + let entry = VectorEntry { + id: Some(id), + vector: vec, + metadata: Some(meta), + }; + if db.insert(entry).is_ok() { + inserted += 1; + } + } + Ok(inserted) +} + +pub fn run_csi_sink( + brain: &str, + db_path: &PathBuf, + once: bool, + interval_secs: u64, +) -> Result<()> { + println!("{}", format!("CSI sink — brain: {brain} db: {}", db_path.display()).cyan()); + + let mut db = open_csi_db(db_path)?; + loop { + match ingest_once(brain, &mut db) { + Ok(n) => { + if n > 0 { + println!("{}", format!(" +{n} spatial-csi-embedding entries indexed").green()); + } else { + println!(" no new embeddings"); + } + } + Err(e) => eprintln!("{}", format!(" ingest error: {e}").red()), + } + if once { + break; + } + std::thread::sleep(std::time::Duration::from_secs(interval_secs)); + } + Ok(()) +} + +/// Open the CSI index for read-only search. When the live DB is locked by +/// the sink process, copies it to a temp file and reads the snapshot. +fn open_for_search(db_path: &PathBuf) -> Result { + match open_csi_db(db_path) { + Ok(db) => Ok(db), + Err(_) if db_path.exists() => { + // DB is locked by the running sink — snapshot it. + let tmp = std::env::temp_dir().join("csi-search-snapshot.db"); + std::fs::copy(db_path, &tmp) + .context("snapshot locked CSI db for search")?; + open_csi_db(&tmp) + } + Err(e) => Err(e), + } +} + +pub fn run_csi_search( + db_path: &PathBuf, + embedding_arg: Option<&str>, + top_k: usize, + show_vectors: bool, +) -> Result<()> { + let mut db = open_for_search(db_path)?; + + let query_vec: Vec = if let Some(s) = embedding_arg { + let v: Vec = s + .trim_matches(|c| c == '[' || c == ']') + .split(',') + .filter_map(|x| x.trim().parse().ok()) + .collect(); + anyhow::ensure!(v.len() == CSI_DIM, "embedding must be {CSI_DIM} floats, got {}", v.len()); + v + } else { + // No query provided — list the most recently inserted entries instead. + println!("{}", "No --embedding provided; listing most-recent entries:".yellow()); + let results = db.search(SearchQuery { + vector: vec![0.0_f32; CSI_DIM], + k: top_k, + filter: None, + ef_search: Some(200), + })?; + for (i, r) in results.iter().enumerate() { + let meta_str = r + .metadata + .as_ref() + .and_then(|m| m.get("content")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .get(..80) + .unwrap_or(""); + println!(" {}: id={} score={:.4} {}", i + 1, r.id.cyan(), r.score, meta_str); + } + return Ok(()); + }; + + let results = db.search(SearchQuery { + vector: query_vec, + k: top_k, + filter: None, + ef_search: None, + })?; + + println!("{}", format!("Top-{} CSI embedding matches:", top_k).bold()); + for (i, r) in results.iter().enumerate() { + let meta_str = r + .metadata + .as_ref() + .and_then(|m| m.get("content")) + .and_then(|v| v.as_str()) + .map(|s| s.get(..80).unwrap_or(s)) + .unwrap_or(""); + println!(" {}: id={} score={:.4} {}", i + 1, r.id.cyan(), r.score, meta_str); + if show_vectors { + if let Some(ref vec) = r.vector { + let preview: Vec = vec.iter().take(8).map(|v| format!("{v:.3}")).collect(); + println!(" vec: [{}…]", preview.join(", ")); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_valid_csi_embedding() { + let content = format!( + "node_id=3 node=cognitum-v0 embedding=[{}]", + vec!["0.125000"; 128].join(",") + ); + let (id, vec) = parse_csi_embedding(&content).unwrap(); + assert!(id.starts_with("csi:"), "id should start with csi: prefix: {id}"); + assert!(id.contains("cognitum-v0"), "id should include node name: {id}"); + assert_eq!(vec.len(), 128); + assert!((vec[0] - 0.125).abs() < 1e-5); + } + + #[test] + fn parse_wrong_dim_returns_none() { + let content = "node_id=1 node=x embedding=[0.1,0.2,0.3]"; + assert!(parse_csi_embedding(content).is_none()); + } + + #[test] + fn parse_missing_embedding_returns_none() { + let content = "node_id=1 node=x"; + assert!(parse_csi_embedding(content).is_none()); + } + + #[test] + fn hailo_embedder_config_variants() { + // Verify the ADR-183 iter 14 config types are accessible from CLI + // (they live in ruvector-hailo, but we want to confirm the CLI + // can construct them without pulling the full hailo dep). + // Just test the constants we re-define here match expectations. + assert_eq!(CSI_DIM, 128); + assert_eq!(DEFAULT_CSI_DB, "/var/lib/ruvector-vectors/csi-embeddings.db"); + } +} diff --git a/crates/ruvector-cli/src/cli/mod.rs b/crates/ruvector-cli/src/cli/mod.rs index 9d24c6e63..6ae0a8627 100644 --- a/crates/ruvector-cli/src/cli/mod.rs +++ b/crates/ruvector-cli/src/cli/mod.rs @@ -1,6 +1,7 @@ //! CLI module for Ruvector pub mod commands; +pub mod csi; pub mod format; pub mod graph; pub mod hooks; diff --git a/crates/ruvector-cli/src/main.rs b/crates/ruvector-cli/src/main.rs index f49b59e7c..a7beb110f 100644 --- a/crates/ruvector-cli/src/main.rs +++ b/crates/ruvector-cli/src/main.rs @@ -9,6 +9,7 @@ mod cli; mod config; use crate::cli::commands::*; +use crate::cli::csi::CsiCommands; use crate::config::Config; #[derive(Parser)] @@ -142,6 +143,15 @@ enum Commands { #[command(subcommand)] action: cli::hooks::HooksCommands, }, + + /// WiFi-CSI embedding sink and search (ADR-183 Tier 3) + /// + /// `ruvector csi sink` — ingest brain spatial-csi-embedding memories into HNSW + /// `ruvector csi search` — cosine k-NN over the 128-dim CSI index + Csi { + #[command(subcommand)] + action: CsiCommands, + }, } #[tokio::main] @@ -367,6 +377,20 @@ async fn main() -> Result<()> { HooksCommands::CacheStats => cli::hooks::cache_stats(&config), } } + Commands::Csi { action } => match action { + CsiCommands::Sink { + brain, + db, + once, + interval, + } => cli::csi::run_csi_sink(&brain, &db, once, interval), + CsiCommands::Search { + db, + embedding, + top_k, + show_vectors, + } => cli::csi::run_csi_search(&db, embedding.as_deref(), top_k, show_vectors), + }, }; // Handle errors diff --git a/crates/ruvector-hailo-cluster/deploy/rebirth-clone.sh b/crates/ruvector-hailo-cluster/deploy/rebirth-clone.sh new file mode 100755 index 000000000..ba1c59f6b --- /dev/null +++ b/crates/ruvector-hailo-cluster/deploy/rebirth-clone.sh @@ -0,0 +1,261 @@ +#!/usr/bin/env bash +# Rebirth a freshly-cloned cognitum SD card into a new cluster node. +# +# Run on the host that did the dd clone (NOT on the Pi). Operates on +# the cloned SD card before its first boot, scrubbing identity from +# the source so the new Pi joins the tailnet as a separate node. +# +# What it does (in order): +# 1. growpart + resize2fs partition 2 to fill the device +# 2. mount partition 2 as rootfs +# 3. set /etc/hostname + /etc/hosts to the new name +# 4. disable cloud-init's manage_etc_hosts/hostname (else it reverts step 3) +# 5. enable persistent journald (so first-boot failures are debuggable) +# 6. seed RUVECTOR_REBIRTH_PUBKEY into ~genesis/.ssh/authorized_keys +# 7. clear /etc/machine-id (systemd regenerates on first boot) +# 8. delete /etc/ssh/ssh_host_* (sshd regenerates on first boot) +# 9. clear /var/lib/tailscale/tailscaled.state* (re-auths as new node) +# 10. clear /root/.bash_history, ~genesis/.bash_history +# 11. clear /var/log/journal/*, /var/log/wtmp, /var/log/btmp +# 12. sync + unmount +# +# Idempotent: re-runnable on the same card. +# +# Usage: +# sudo bash rebirth-clone.sh +# +# Optional env vars: +# RUVECTOR_REBIRTH_PUBKEY="ssh-ed25519 AAAA... operator@host" +# Seed an SSH pubkey into ~genesis/.ssh/authorized_keys so you can +# SSH the node from a known operator host the moment it joins WiFi. +# +# Example: +# RUVECTOR_REBIRTH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \ +# sudo -E bash rebirth-clone.sh /dev/sdd cognitum-v1 + +set -euo pipefail + +if [[ $EUID -ne 0 ]]; then + echo "must run as root (use sudo)" >&2; exit 1 +fi +if [[ $# -lt 2 ]]; then + echo "usage: $0 " >&2 + echo "example: $0 /dev/sdd cognitum-v1" >&2 + exit 1 +fi + +DEV="$1" +NEW_HOSTNAME="$2" + +# ---- sanity checks ---------------------------------------------------------- + +if [[ ! -b "$DEV" ]]; then + echo "not a block device: $DEV" >&2; exit 1 +fi + +# refuse to scribble on the host's own root or boot disk +HOST_ROOT_DEV=$(findmnt -no SOURCE / | sed 's/[0-9]*$//') +HOST_BOOT_DEV=$(findmnt -no SOURCE /boot 2>/dev/null | sed 's/[0-9]*$//' || true) +if [[ "$DEV" == "$HOST_ROOT_DEV" || "$DEV" == "$HOST_BOOT_DEV" ]]; then + echo "refusing to operate on host's own disk ($DEV)" >&2; exit 1 +fi + +# ensure it looks like a freshly-dd'd Pi card: p1 vfat boot, p2 ext4 root +P1="${DEV}1" +P2="${DEV}2" +if [[ ! -b "$P1" || ! -b "$P2" ]]; then + echo "expected ${P1} and ${P2} to exist (Pi layout: vfat boot + ext4 root)" >&2 + echo "did you run partprobe $DEV after dd?" >&2 + exit 1 +fi + +# unmount anything auto-mounted from this device (GNOME) +for m in $(mount | awk -v d="$DEV" '$1 ~ "^"d {print $1}'); do + echo "unmounting $m" + umount "$m" || true +done + +# validate hostname +if [[ ! "$NEW_HOSTNAME" =~ ^[a-z][a-z0-9-]{0,62}$ ]]; then + echo "invalid hostname: $NEW_HOSTNAME" >&2 + echo "must match [a-z][a-z0-9-]{0,62} (RFC 1123 subset)" >&2 + exit 1 +fi + +# ---- step 1: grow rootfs partition + filesystem ----------------------------- + +echo "==> growpart $DEV 2" +# growpart returns 1 if no growth needed; that's fine on re-runs +growpart "$DEV" 2 || true +partprobe "$DEV" +sleep 1 + +echo "==> e2fsck -f $P2" +# e2fsck exit codes: 0=clean, 1=errors corrected (still success), +# 2=corrected but reboot required (also success for our offline use), +# >=4 are real failures. +set +e +e2fsck -fy "$P2" +fsck_rc=$? +set -e +if (( fsck_rc > 2 )); then + echo "e2fsck failed with rc=$fsck_rc" >&2 + exit "$fsck_rc" +fi + +echo "==> resize2fs $P2" +resize2fs "$P2" + +# ---- step 2: mount rootfs --------------------------------------------------- + +MNT=$(mktemp -d -t cognitum-rebirth.XXXXXX) +trap 'umount "$MNT/boot/firmware" 2>/dev/null || true; umount "$MNT" 2>/dev/null || true; rmdir "$MNT" 2>/dev/null || true' EXIT + +echo "==> mount $P2 -> $MNT" +mount "$P2" "$MNT" + +# also mount bootfs in case we want to write to /boot/firmware later +if [[ -d "$MNT/boot/firmware" ]]; then + echo "==> mount $P1 -> $MNT/boot/firmware" + mount "$P1" "$MNT/boot/firmware" +fi + +# ---- step 3: hostname ------------------------------------------------------- + +OLD_HOSTNAME=$(cat "$MNT/etc/hostname" 2>/dev/null | tr -d '\n' || echo "") +echo "==> hostname: $OLD_HOSTNAME -> $NEW_HOSTNAME" +echo "$NEW_HOSTNAME" > "$MNT/etc/hostname" + +# replace OLD_HOSTNAME wherever it appears in /etc/hosts +if [[ -n "$OLD_HOSTNAME" && -f "$MNT/etc/hosts" ]]; then + sed -i "s/\b${OLD_HOSTNAME}\b/${NEW_HOSTNAME}/g" "$MNT/etc/hosts" +fi +# guarantee a 127.0.1.1 line for the new hostname +if ! grep -qE "^127\.0\.1\.1\s+${NEW_HOSTNAME}\b" "$MNT/etc/hosts" 2>/dev/null; then + echo "127.0.1.1 ${NEW_HOSTNAME}" >> "$MNT/etc/hosts" +fi + +# ---- step 3.5: cloud-init ----------------------------------------------------- +# Pi OS Bookworm/Trixie ships cloud-init. By default it has +# manage_etc_hosts: true and preserve_hostname: false, which means it +# rewrites /etc/hostname and /etc/hosts on EVERY boot from cached +# instance metadata — undoing step 3. We disable it two ways: +# 1. drop a cloud.cfg.d override (preserves hostname even if cloud-init +# gets re-enabled later) +# 2. touch /etc/cloud/cloud-init.disabled (skips cloud-init entirely) + +if [[ -d "$MNT/etc/cloud" ]]; then + echo "==> disable cloud-init hostname management" + mkdir -p "$MNT/etc/cloud/cloud.cfg.d" + cat > "$MNT/etc/cloud/cloud.cfg.d/99-rebirth-clone.cfg" <<'EOF' +# rebirth-clone.sh: stop cloud-init from re-applying source-image hostname +preserve_hostname: true +manage_etc_hosts: false +EOF + touch "$MNT/etc/cloud/cloud-init.disabled" +fi + +# ---- step 3.6: persistent journald -------------------------------------------- +# default Pi OS journald is volatile (Storage=auto, no /var/log/journal), +# so first-boot failures leave no logs. Enable persistent storage. + +echo "==> enable persistent journald" +mkdir -p "$MNT/etc/systemd/journald.conf.d" "$MNT/var/log/journal" +cat > "$MNT/etc/systemd/journald.conf.d/persistent.conf" <<'EOF' +[Journal] +Storage=persistent +EOF + +# ---- step 3.7: seed authorized_keys -------------------------------------------- +# RUVECTOR_REBIRTH_PUBKEY env var lets you inject a pubkey at rebirth +# time so the new Pi is reachable from a known host immediately +# (without needing console + tailscale-up). Useful when you are +# bringing up many nodes from one operator workstation. + +if [[ -n "${RUVECTOR_REBIRTH_PUBKEY:-}" ]]; then + echo "==> seed RUVECTOR_REBIRTH_PUBKEY into ~genesis/.ssh/authorized_keys" + GEN_HOME="$MNT/home/genesis" + if [[ -d "$GEN_HOME" ]]; then + GEN_UID=$(stat -c %u "$GEN_HOME") + GEN_GID=$(stat -c %g "$GEN_HOME") + install -d -m 0700 -o "$GEN_UID" -g "$GEN_GID" "$GEN_HOME/.ssh" + if ! grep -qF "$RUVECTOR_REBIRTH_PUBKEY" "$GEN_HOME/.ssh/authorized_keys" 2>/dev/null; then + echo "$RUVECTOR_REBIRTH_PUBKEY" >> "$GEN_HOME/.ssh/authorized_keys" + fi + chmod 600 "$GEN_HOME/.ssh/authorized_keys" + chown "$GEN_UID:$GEN_GID" "$GEN_HOME/.ssh/authorized_keys" + else + echo "warning: $GEN_HOME not present, skipping pubkey seed" >&2 + fi +fi + +# ---- step 4: machine-id ----------------------------------------------------- + +echo "==> clear /etc/machine-id (systemd will regenerate)" +: > "$MNT/etc/machine-id" +# /var/lib/dbus/machine-id is usually a symlink; if not, clear it too +if [[ -f "$MNT/var/lib/dbus/machine-id" && ! -L "$MNT/var/lib/dbus/machine-id" ]]; then + : > "$MNT/var/lib/dbus/machine-id" +fi + +# ---- step 5: ssh host keys -------------------------------------------------- +# IMPORTANT: don't just delete and rely on the Pi OS one-shot regen +# service — on a cloned image that service has already marked itself +# completed and was disabled. So missing host keys = sshd refuses to +# start = no remote shell on first boot. Instead, regenerate the keys +# directly here so the new node has unique keys AND sshd works. + +echo "==> regenerate SSH host keys (unique to this clone)" +rm -fv "$MNT"/etc/ssh/ssh_host_* +ssh-keygen -A -f "$MNT" +ls "$MNT/etc/ssh/" | grep ssh_host + +# ---- step 6: tailscale state ------------------------------------------------ + +if [[ -d "$MNT/var/lib/tailscale" ]]; then + echo "==> clear tailscale state (forces re-auth as new node)" + rm -fv "$MNT/var/lib/tailscale/tailscaled.state" + rm -fv "$MNT/var/lib/tailscale/tailscaled.log"* + # keep the tailscaled binary; only state is identity-bearing +fi + +# ---- step 7: bash history --------------------------------------------------- + +echo "==> clear bash histories" +rm -fv "$MNT/root/.bash_history" 2>/dev/null || true +for u in "$MNT"/home/*; do + [[ -d "$u" ]] || continue + rm -fv "$u/.bash_history" 2>/dev/null || true +done + +# ---- step 8: logs ----------------------------------------------------------- + +echo "==> truncate logs" +rm -rfv "$MNT"/var/log/journal/* 2>/dev/null || true +: > "$MNT/var/log/wtmp" 2>/dev/null || true +: > "$MNT/var/log/btmp" 2>/dev/null || true +: > "$MNT/var/log/lastlog" 2>/dev/null || true +# don't touch syslog/auth.log/dpkg.log — useful breadcrumbs after first boot + +# ---- step 9: optional ruvector worker reset --------------------------------- +# the cloned card will keep cognitum-v0's worker config + models. that's +# fine — the worker has no host-specific state. but clear cached metrics. +if [[ -d "$MNT/var/lib/ruvector-hailo" ]]; then + echo "==> clear ruvector worker runtime state (keep models)" + find "$MNT/var/lib/ruvector-hailo" \ + -mindepth 1 -maxdepth 1 \ + -not -name models -not -name '.*' \ + -exec rm -rfv {} + 2>/dev/null || true +fi + +# ---- finalize --------------------------------------------------------------- + +sync +echo +echo "rebirth complete: ${OLD_HOSTNAME:-(unknown)} -> $NEW_HOSTNAME on $DEV" +echo "next steps:" +echo " 1. eject the card: sudo eject $DEV" +echo " 2. boot it on the new Pi" +echo " 3. on the new Pi: sudo tailscale up (re-auth as new node)" +echo " 4. approve the new node in https://login.tailscale.com/admin/machines" +echo " 5. verify worker: sudo systemctl status ruvector-hailo-worker" diff --git a/crates/ruvector-hailo/Cargo.toml b/crates/ruvector-hailo/Cargo.toml index 579f92eed..e46ea0b39 100644 --- a/crates/ruvector-hailo/Cargo.toml +++ b/crates/ruvector-hailo/Cargo.toml @@ -25,7 +25,7 @@ hailo = ["hailort-sys/hailo"] # `HailoEmbedder::open` falls back to `CpuEmbedder` if no model.hef is # found (and only the safetensors / tokenizer.json artifacts are # present). Net: real semantic vectors today, NPU stays idle until HEF. -cpu-fallback = ["candle-core", "candle-nn", "candle-transformers", "tokenizers", "serde_json"] +cpu-fallback = ["candle-core", "candle-nn", "candle-transformers", "tokenizers"] # Iter 219 — rejoined the parent workspace. Closes ADR-178 Gap E # (folded into Gap B). The iter-218 ruvector-core path dep made @@ -58,7 +58,7 @@ candle-core = { version = "0.8", optional = true, default-features = fal candle-nn = { version = "0.8", optional = true, default-features = false } candle-transformers = { version = "0.8", optional = true, default-features = false } tokenizers = { version = "0.20", optional = true, default-features = false, features = ["onig"] } -serde_json = { version = "1", optional = true } +serde_json = { version = "1" } [dev-dependencies] anyhow = "1" diff --git a/crates/ruvector-hailo/deploy/compile-csi-encoder-hef.py b/crates/ruvector-hailo/deploy/compile-csi-encoder-hef.py new file mode 100644 index 000000000..ec8dce4fe --- /dev/null +++ b/crates/ruvector-hailo/deploy/compile-csi-encoder-hef.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +""" +compile-csi-encoder-hef.py — compile the RuView CSI contrastive encoder +to a Hailo HEF for NPU inference on the Hailo-8 AI HAT+. + +ADR-183 Tier 3: WifiCsi128d variant. + +Architecture (from ruv/ruview model.safetensors): + Input: [batch, 8] — 8 aggregate CSI features from sliding window + FC1: Linear(8→64) + ReLU + FC2: Linear(64→128) + Output: [batch, 128] — contrastive embedding (L2-normalised by caller) + +Usage: + venv-hailo/bin/python deploy/compile-csi-encoder-hef.py \ + [--weights model.safetensors] [--out csi-encoder.hef] + +Deps (all in venv-hailo): + hailo_dataflow_compiler, safetensors, torch, onnx +""" + +import argparse +import struct +import os +import sys +import tempfile +import numpy as np +import torch +import torch.nn as nn +import onnx + +# ── model architecture ──────────────────────────────────────────────────────── + +class CsiEncoder(nn.Module): + """2-layer FC CSI encoder matching ruv/ruview model.safetensors.""" + + def __init__(self): + super().__init__() + self.fc1 = nn.Linear(8, 64) + self.fc2 = nn.Linear(64, 128) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = torch.relu(self.fc1(x)) + x = self.fc2(x) + return x # caller applies L2-normalise; Hailo HEF is linear-only + + +def load_weights_from_safetensors(model: CsiEncoder, path: str): + """Parse the safetensors file and load weights manually.""" + with open(path, "rb") as f: + header_size = struct.unpack("