mirror of
https://github.com/ruvnet/RuView
synced 2026-06-18 11:43:19 +00:00
c84ea39e62d14dcafe61fc80d357dfe0349462cd
61 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20ad75f30c |
feat(ADR-131): HOMECORE-UI dashboard + BFF gateway — review-fixed (supersedes #1082) (#1099)
* feat(ADR-131): HOMECORE-UI operational dashboard + BFF gateway Complete two-tier Cognitum operator dashboard (ADR-131), served by homecore-server at /homecore, plus the single-origin BFF gateway that wires it to real backends. Front-end (zero-dep vanilla TS/JS + CSS, exact Cognitum design tokens): - All 10 panels (§4.1-4.10): dashboard, SEED fleet + detail, fleet map, entities (live WS subscribe_events, never polls), rooms, COGs, calibration wizard, events + automation builder, witness/audit, settings. - §6 UX invariants in code: first-class provenance, prominent stale/veto/ fragility, null(not-trained) vs withheld vs error, --mono everywhere, Hailo vs CPU COG distinction. - api.js calls the gateway routes in production; mock demoted to a dev-only ?demo=1 fixture (no mock in prod); typed error states. - Tests under plain node: import-graph, boot, render-smoke (22), interaction (3), prod-errors (13) — 5 files green; bundle ~137 KB (~37x smaller than HA), <2 ms/cold-render. BFF gateway (homecore-server/src/gateway.rs, compiled + tested on Rust 1.89): - /api/cal/* reverse-proxy to the calibration API (ADR-151). - GET /api/homecore/rooms with the RoomState adapter (breathing->breathing_bpm, heartbeat:null->heart_bpm:null, injected anomaly.threshold/room_id). - GET /api/homecore/cogs supervisor over /var/lib/cognitum/apps/. - GET /api/homecore/appliance from /proc + TCP service probes. - SEED-device/appliance routes return typed 503 upstream_unavailable. - cargo test -p homecore-server = 12/12; run live (curl-verified); fixed a real double-v1 proxy-URL bug found during live testing. Honest scope: W1/W2/W4/W6-appliance functional; W3/W5/W6-Hailo/federation return typed 503 (depend on services/hardware not in this repo). Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-ui): resolve code-review findings — SSRF guard, CORS/trace coverage, §6 honesty, crash guards Addresses the high-effort review of PR #1082: - SECURITY: cal_proxy rejects path-traversal/confused-deputy SSRF (`.`/`..` segments, backslash, %2e%2e/%2f, absolute) on raw+decoded forms → 400, before attaching the server-side calibration bearer. - CORRECTNESS: /api/homecore/* + /api/cal/* now covered by the shared CORS allowlist (build_cors_layer, exported from homecore-api) + TraceLayer — previously merged outside router()'s layers (no CORS, no tracing). - §6 HONESTY (no fabricated data): dashboard renders '—' for null metrics (not "null%"/"null°C"); cogs Hailo pill reflects the REAL appliance probe (not hardcoded "connected"); room anomaly threshold passed through / null, not a fabricated 0.5. - ROBUSTNESS: cogs asArray(hef) guards a non-array manifest field; calibration progress guards target<=0 (no NaN%/Infinity%); restart clears the poll timer. - CLEANUP: mock.js is now a cached DYNAMIC import (demo-only) — never bundled in production (§2.2). - New ui/tests/unit-fixes.mjs pins the above; ADR-131 + CHANGELOG updated. Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: Nick Ruest <127058086+nicholas-ruest@users.noreply.github.com> |
||
|
|
df617145d6 |
feat(ADR-262 P3): live /api/field + /ws/field — RuView sensing speaks RuField (fail-closed egress) (#1071)
* feat(ADR-262 P3): live RuField surface — RuView sensing speaks RuField on /api/field + /ws/field Wire the P1 `wifi-densepose-rufield` bridge into the live `wifi-densepose-sensing-server` so the governed sensing cycle emits real signed RuField `FieldEvent`s on two additive endpoints. - Cargo: add the `wifi-densepose-rufield` path dep (the single coupling point, ADR-262 §5.4 — no new RuView-internal coupling). - New `src/rufield_surface.rs` (kept out of the 8k-line main.rs): `FieldSurface` holds a dedicated ed25519 `Signer` + a bounded ring of recent events + the `/ws/field` broadcast topic; `GET /api/field` and `GET /ws/field` handlers; a standalone `router()` for isolated testing. - Signer (defers the P2 key decision, ADR-262 §8 Q1): a STANDALONE dev/sensing key from `WDP_RUFIELD_SIGNING_SEED`, else a deterministic dev default with a logged WARN. Reusing the `cog-ha-matter` Ed25519 key is the deferred P2 call — P3 does not pre-empt it. - Tap: at the ESP32 governed-trust cycle (`main.rs` ~5886 observe_cycle / ~5938 SensingUpdate build), `emit_rufield_event` joins the cycle's features/classification/signal_field with the engine's effective_class/demoted trust state into a `SensingSnapshot` and surfaces it via the bridge. Existing endpoints (`/ws/sensing` etc.) are unchanged — purely additive. - Privacy egress: `network_egress_allowed` is fail-closed for an unattended live surface — only P1/P2 leave the box; P0 raw and P3/P4/P5 (identity/biometric/aggregate) are held edge-local. A `Derived` cycle maps to P4/P5 and never surfaces. - No-phantom: `emit` drops no-presence cycles (no fabricated events). Gates (tests/rufield_surface_test.rs, tower::oneshot, 4/0): well-formed signed event (WifiCsi, P2 not P1, is_fusable, real timestamp); empty cycle → no phantom; Derived trust never surfaces; mixed stream surfaces only egress-safe events. Honesty (ADR-262 §0/§6): real plumbing on a live endpoint, NOT accuracy. Single-link CSI with its existing caveats (no validated room-coordinate accuracy); dedicated dev signing key pending the P2 ownership decision; no accuracy claim. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(ADR-262 P3): mark P1+P3 implemented; document /api/field + /ws/field; CHANGELOG - ADR-262 Status → "P1 + P3 implemented"; add a P3 implementation-status block (tap site, endpoints, dedicated dev signer deferring the §8 Q1 key decision, fail-closed egress, gates). Keep the honesty framing: real plumbing on a live endpoint, not accuracy. - CHANGELOG [Unreleased]: add the ADR-262 P3 entry. - user-guide: add `/api/field` to the REST table + a "RuField surface (ADR-262 P3)" section covering `/api/field` + `/ws/field`, the fail-closed P1/P2-only egress, the WDP_RUFIELD_SIGNING_SEED dev key, and the no-accuracy honesty note. Co-Authored-By: claude-flow <ruv@ruv.net> * ci: checkout submodules everywhere + Dockerfile copies vendor/rufield Making wifi-densepose-rufield (ADR-262 bridge) a v2 workspace member means EVERY cargo-on-workspace context must have the vendor/rufield submodule present (cargo loads all member manifests). P1 only fixed the rust-tests job; this adds `submodules: recursive` to all workflow checkouts that run cargo (mqtt-integration was failing on the missing submodule manifest), and makes Dockerfile.rust COPY vendor/rufield/ to /vendor/rufield (matches the bridge's ../../../vendor/rufield path-dep under the collapsed Docker layout). update-submodules.yml left alone (it manages submodules itself). Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: ruv <ruvnet@gmail.com> |
||
|
|
f250149e94 |
feat(ADR-262 P1): wifi-densepose-rufield bridge — RuView sensing → signed RuField FieldEvents (fail-closed privacy map) (#1070)
* feat(rufield): ADR-262 P1 — wifi-densepose-rufield anti-corruption bridge New v2 workspace member that converts RuView WiFi-CSI sensing output into signed RuField FieldEvents. Path-deps the vendor/rufield submodule crates (rufield-core/-provenance/-privacy/-fusion); single coupling point between RuView and the standalone RuField MFS spec (ADR-262 §5.4). - SensingSnapshot: owned primitives mirroring SensingUpdate + TrustedOutput (no dependency on wifi-densepose-sensing-server). - snapshot_to_field_event(): builds a WifiCsi FieldTensor + Observation, derives a real position from the signal-field peak (never fabricated), real sha256 provenance + ed25519 signature (synthetic=false). - map_privacy() (§3.3 crux): maps by information content, NEVER byte value — Derived (byte 1) → P4/P5, never P1; fail-closed demotion floor to P2. P1 gates (tests/p1_gates.rs): round-trip serde, is_fusable verified receipt, RuFieldFusion::ingest accept + infer runs, privacy-safety (Derived never P1), full §3.3 table, fail-closed demotion, determinism, no-fabricated-position. 15 tests pass (5 unit + 9 integration + 1 doc), 0 failed. Honesty: P1 plumbing (tested conversion + safe privacy mapping), NOT wired into the live server (P3) and NOT an accuracy claim. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-262): mark P1 implemented + CI submodules:recursive + CHANGELOG/CLAUDE - ADR-262 Status → "Proposed — P1 implemented"; add §0.1 Implementation status (the bridge crate + the five P1 gates that pass; defers the provenance-carrier reuse, P3 live wiring, and P4 multi-modality). - ci.yml: add `submodules: recursive` to the rust-tests checkout so the new crate's `vendor/rufield` path-deps resolve in CI (they fail otherwise even though the workspace build passes locally with the submodule present). - CHANGELOG [Unreleased]: P1 bridge entry (kept alongside the upstream ADR-262 research entry). - CLAUDE.md: crate table row for `wifi-densepose-rufield`. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
865f9dee77 |
perf(beyond-sota): ADR-154 M2 — FFT planner hoist (1.84x, bit-identical) + 3 honest perf nulls + boundary tests (#1055)
* perf(signal): hoist FFT planner across subcarriers (ADR-154 §7.4 #20) compute_multi_subcarrier_spectrogram called compute_spectrogram once per subcarrier, and each call built a fresh FftPlanner + re-planned the same length-window_size FFT. Hoist the plan + window out of the per-subcarrier loop via a new compute_spectrogram_with_plan core that takes a pre-planned Arc<dyn Fft> and pre-built window. compute_spectrogram delegates to it (unchanged behaviour); the multi-subcarrier path plans once and reuses. MEASURED-HOT (dsp_perf_bench, this box): at 56 subcarriers, window 128, fresh-planner-per-subcarrier 467.88 µs -> hoisted-plan 254.75 µs = 1.84x; window 256: 627.27 µs -> 448.39 µs = 1.40x. Plan-forward cost alone is ~1.86 µs (w128), x56 subcarriers ~= the removed delta. Output is bit-identical: multi_subcarrier_hoisted_plan_bit_identical compares f64::to_bits of every spectrogram value + freq/time resolution against the per-call fresh-planner path across all 4 window functions x {power,magnitude} on a 56-subcarrier matrix. The numeric STFT body is the old loop verbatim; only plan/window construction is lifted. Co-Authored-By: claude-flow <ruv@ruv.net> * test(signal): boundary/tolerance tests for ADR-154 §7.4 #14 #16 #19 Three "+ test" backlog gaps closed — pure additions, no behaviour change (phase_align refactor is internal: estimate_phase_offsets still returns the identical offset vector; a counted core is split out only to observe the iteration count). #14 cir.rs fft_operator — fft_operator_within_tolerance_of_dense_canonical56: the opt-in FFT Φ/Φᴴ path changes the witness hash, so pin it numerically CLOSE to the dense path (not silently divergent). Asserts the full Cir output (every tap within 1e-2·dominant, dominant idx/ratio, active_tap_count, ranging_valid, rms_delay_spread) on the production canonical-56 config across τ ∈ {20,50,90} ns. Extends the existing HT20/single-τ test. #16 phase_align.rs — refinement_terminates_at_iteration_cap_when_not_converging: forces non-convergence (tolerance=0.0, unreachable) and asserts the loop runs exactly max_iterations then returns — proving the cap, not convergence, bounds the loop (no infinite spin). Companion refinement_converges_before_cap_on_easy_input proves the cap is an upper bound, not the only exit. #19 csi_ratio.rs — ratio_finite_at_and_below_1e_12_epsilon: the module implements the CSI ratio as the conjugate product H_i·conj(H_j) (no division), so it is finite even at/below the 1e-12 magnitude boundary a naive H_i/H_j division would need an epsilon to guard. Pins finiteness + bit-exact conjugate product at the boundary (zero target → zero, never inf/NaN), through the amplitude/phase extraction. cargo test -p wifi-densepose-signal --no-default-features --lib: 447 passed, 0 failed; --features cir --lib: 447 passed, 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-154): record Milestone-2 P2-perf verdicts + boundary tests (§7.4) §7.4: #20 MEASURED-HOT (1.40–1.84× spectrogram FFT-plan hoist, bit-identical); #5/#6/#7 MEASURED-NULL (benched, not hot, left as-is — sub-µs / stack-only / alloc-once); #8 MEASUREMENT-ONLY (per-call 56×56 eigh cost; eigenvalue/BLAS backend un-buildable on this Windows host, number deferred to a BLAS box, NOT fabricated; also corrects the finding — extract_perturbation reuses cached modes, the recompute is in estimate_occupancy). #14/#16/#19 RESOLVED (tolerance / convergence-cap / epsilon-boundary tests). Updated §7.4 intro + Horizon-ledger (deferred count 41→36). CHANGELOG [Unreleased] entry added. Co-Authored-By: claude-flow <ruv@ruv.net> * bench(signal): committed P2 bench-first benches (ADR-154 §7.4 #5/#6/#7/#8/#20) New dsp_perf_bench.rs backs every Milestone-2 perf verdict with a committed criterion bench — no speedup claimed without a before/after number here, and a benched NULL is the proof a micro-opt was unnecessary (the §5.x "already amortized" pattern). Registered in Cargo.toml [[bench]]. MEASURED (this box, criterion medians): #20 spectrogram_multi_subcarrier (fresh vs hoisted plan): MEASURED-HOT — 467.88→254.75 µs (1.84x) @ sc56/w128; 627.27→448.39 µs (1.40x) @ sc56/w256. Optimized in the prior commit. #5 multistatic_attention/weights: MEASURED-NULL — 181 ns (2 nodes) .. 848 ns (8 nodes); sub-µs, no hot-path alloc — left as-is. #6 tomography_reconstruct/solve: MEASURED-NULL — 47.5 µs (16 links) / 60.4 µs (32 links) for a full 50-iter ISTA solve; the 2 per-solve voxel buffers (~4 KB) are negligible vs O(iters·links·voxels) compute, and reconstruct(&self) reuses them across iterations already — left as-is. #7 pose_kalman_update/cycles: MEASURED-NULL — 150 ns (17 kpts) / 2.82 µs (170); the Kalman "gain matrices" are fixed-size STACK arrays ([[f32;3];6]), zero heap — nothing to reuse — left as-is. #8 field_model_occupancy (eigenvalue feature): MEASUREMENT-ONLY — quantifies the per-call n×n eigendecomposition cost; incremental SVD is a sized future project, not attempted (number recorded in ADR-154 §7.4). Reproduce: cargo bench -p wifi-densepose-signal --no-default-features --bench dsp_perf_bench cargo bench -p wifi-densepose-signal --bench dsp_perf_bench # adds #8 Cargo.lock: dev-dep (criterion/clap) graph + crate version bumps from the build; no runtime-dependency change. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
7c13ec6a00 |
bench(cogs): steady-state CPU infer latency benches (ADR-163 T2)
Criterion benches over InferenceEngine::infer for cog-person-count and cog-pose-estimation, on Device::Cpu with the real shipped safetensors weights (asserts candle backend so the stub is never silently benched), over a fixed CSI window after a warm-up forward. HOST-MEASURED steady-state medians (idle box): ~305us each. This is the recurring per-frame cost and is explicitly NOT the pose manifest's cold_start_ms_avg=5.4 (a different measurement, weight-load included, taken on ruvultra/RTX 5080) -- the two are labelled and not conflated. Closes the ADR-159/160 deferred cog inference-latency item. No production- code behavior change. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
0ca903b497 |
feat(homecore-plugins): enforce plugin signature + capability isolation (ADR-162 P4/P5)
ADR-161 honestly relabelled the manifest's wasm_module_hash / wasm_module_sig / publisher_key as "(P4 — not yet enforced)" and the homecore_permissions claims as deferred P5 authority isolation. This makes both real and tested. P4 (signature/integrity verification, SECURITY): - New `verify` module: SHA-256 module-hash check + Ed25519 signature verification over the digest against publisher_key, with a PluginPolicy trust allowlist and an explicit AllowUnsigned dev escape hatch (loud warn). Secure default rejects unsigned / unknown-publisher / tampered modules. - Reuses the in-repo cog-ha-matter::witness_signing Ed25519 pattern; sha2 is a workspace dep, ed25519-dalek/hex/base64 already in the lock — no new external dep tree (only new edges in homecore-plugins). - WasmtimeRuntime::load_plugin verifies before instantiation; legacy load_wasm retained for trusted/test modules. P5 (authority/capability isolation, SECURITY): - New `permissions` module: PermissionSet distilled from homecore_permissions (state:write:<glob> or bare entity glob). hc_state_set now consults it and returns a typed -3 to the guest on an undeclared write (no host panic). Tests (fail on old code, which had no load_plugin/verify and an unchecked hc_state_set): tampered module rejected; valid sig from trusted key loads; valid sig from untrusted key rejected; unsigned rejected by default and loads only under AllowUnsigned; light.* plugin writes light.kitchen but is denied lock.front_door; no-permission plugin can write nothing. Real deterministic keypair signs real bytes. Manifest doc updated: P4/P5 now ENFORCED (was "not yet enforced"). homecore-plugins --features wasmtime: 32 passed (lib 23, integration 9), 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
9d52d49c0b |
fix(homecore-api): close WS auth bypass + reply-theater, harden dev bin (ADR-161 A1/A2/A8)
A1 (CRITICAL): the /api/websocket handshake accepted any non-empty token, ignoring the LongLivedTokenStore whitelist the REST path enforces — a full WS auth bypass. Now validates via state.tokens().is_valid() before auth_ok; wrong tokens get auth_invalid + close. A2 (HIGH): WS command replies were pushed into an mpsc whose only consumer logged and discarded them — no result/pong/event reached the client. Split the socket with futures StreamExt::split; a dedicated writer task drains the response channel onto the wire. A8 (HIGH): the homecore-api dev bin bound 0.0.0.0 with unconditional allow-any auth and no env path. Wired the HOMECORE_TOKENS env path (dev fallback warn-logged when unset) and defaulted the bind to 127.0.0.1 (HOMECORE_BIND to opt into LAN). Tests (fail on old source): - ws_handshake::wrong_token_is_rejected (old → auth_ok) - ws_handshake::result_reply_is_received / ping_pong_reply_is_received (old → timeout) - server_bin_auth::provisioned_bin_rejects_wrong_bearer / from_env_path_enforces_whitelist Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
d79c22e03a |
fix(homecore-assist): exact in-memory cosine k-NN, drop fragile :memory: HNSW
The semantic recognizer built a ruvector-core VectorDB at ":memory:"; under full-workspace feature unification the file-storage backend is enabled and ":memory:" is an invalid Windows filename (os error 123), panicking via .expect(). Replace the external index with an exact in-memory cosine k-NN over the enrolled exemplars (embeddings are L2-normalised, so cosine = dot product). For HOMECORE's small intent vocabularies this is faster, fully deterministic, and removes the storage backend + cross-crate feature coupling entirely. ruvector-core dropped from the crate (only used here). Workspace 3122 passed/0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
2754af804e |
feat(occworld): real conv encoder/decoder forward pass + honesty flag
Replace the `Tensor::randn` stubs in occworld-candle's VQVAE encoder (`encode_occupancy`) and decoder (`decode_to_logits`) with a real, deterministic, input-dependent convolutional forward pass. Previously `predict()` emitted trajectory waypoints + confidence that were a function of RANDOM NOISE, independent of the input and silently presented as model output — the exact "AI slop" the project must eliminate. occworld-candle: - New `cnn.rs`: `Encoder2D` (3× Conv2d + GELU, interpolate2d to pin the token grid) and `Decoder2D` (upsample_nearest2d + Conv2d + 1×1 head). Both are deterministic functions of the input — same input → identical output; different input → different output. No randn in any forward path. - Deterministic weight init (`det_fill`, seeded xorshift64*) across all `dummy()` constructors (encoder/decoder, VQ codebook, quant-convs, transformer), so untrained engines are bit-for-bit reproducible. - `InferenceOutput.weights_trained: bool` — honest disclosure flag. `false` for `dummy()` (real but untrained net), `true` only after `load()` reads a real checkpoint. Priors are always from the real forward pass, never faked. - VQ codebook + quant/post-quant convs kept and wired encoder→VQ→decoder. - Centerpiece tests in `tests/predict_honesty.rs` (input-dependence, run-to-run + cross-engine determinism, untrained flag). All three FAIL on the old randn stub (verified by temporarily reinstating randn). pointcloud: - Optimize `to_gaussian_splats` hot path: 9 separate `.iter().sum()` passes per voxel → 2 fused accumulation passes. Bit-identical output. - `benches/splats_bench.rs` (criterion) measures old 9-pass vs new 2-pass with a parity guard. ~1.3× faster on representative cloud sizes. - Confirmed: no `randn`/placeholder in any claimed production path. The remaining synthetic generators (`send_test_frames`, `demo_depth_cloud`) and honestly-flagged heuristics (`heuristic_pose_from_amplitude`, luminance pseudo-depth fallback) are explicitly disclosed, not faked output. DATA-GATED: a trained checkpoint. An untrained-but-real net is the honest deliverable; accuracy is flagged via `weights_trained`, never claimed. Tests: occworld 16 unit + 3 integration + 2 doc, pointcloud 18 — all pass (CPU `Device::Cpu`; CUDA feature is GPU-gated and untouched). Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
a0e72eef50 |
feat(wifiscan,sensing): native wlanapi.dll FFI + real Matter manual code
wifiscan (Tier 2 wlanapi adapter ONLY): - Real native wlanapi.dll BSS-list FFI (new adapter/wlanapi_native.rs): WlanOpenHandle -> WlanEnumInterfaces -> WlanGetNetworkBssList -> WlanFreeMemory/WlanCloseHandle via windows-sys 0.59 (already in lock tree). Per-BSSID RSSI(dBm)/channel/band/radio-type/SSID + CSI-capable filter. #[cfg(windows)] real path; #[cfg(not(windows))] returns typed WifiScanError::Unsupported (honest, never fabricated). - wlanapi_scanner now native-first with documented netsh fallback, native_scans metric, scan_native()/scan_native_csi_capable(), and a benchmark() that MEASURES real Hz (no hardcoded "10x" claim). - MEASURED 9.74 Hz native on ruvzen (30 iters, Native backend) vs netsh ~2 Hz baseline. Live measurement kept as an #[ignore] test. - Cargo.toml: unsafe_code forbid->deny so only the audited wlan_ffi module opts into unsafe; all unsafe confined + null-checked + freed. sensing-server (Matter commissioning): - Replaced the lossy modulo placeholder in matter/commissioning.rs with the real Matter Core Spec 1.3 §5.1.4.1.1 field-packing. Canonical vector (20202021, 3840) now encodes to the published 34970112332. - Added ManualPairingCode::decode + DecodedManualCode proving the code is real/lossless (passcode round-trips bit-for-bit; short discriminator = top 4 bits) with Verhoeff integrity, incl. proptest. Tests: wifi-densepose-wifiscan 145 passed (real FFI exercised on Windows); wifi-densepose-sensing-server 614 passed. 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
17471e93ff |
ADR-152: WiFi-Pose SOTA 2026 intake — WiFlow-STD benchmark, Rust integrations, ADR-153 802.11bf layer, efficiency frontier (#1008)
* feat(calibration): NodeGeometry transceiver-geometry recording (ADR-152 §2.1.1) PerceptAlign-motivated geometry capture at enrollment: per-node optional records (position, antenna orientation, inter-node distances, acquisition method) — recorded when known, never required. Event-sourced via EnrollmentEvent::GeometryRecorded (latest recording wins); persisted on SpecialistBank with serde defaults so pre-ADR-152 bank JSON loads cleanly (fixture-proven, and geometry-free banks serialize byte-shape-identical to the old schema); threaded through MultiNodeMixture as data only — the learned geometry embeddings and algorithmic fusion use are §2.1.2, deliberately deferred until the ADR-151 P6 LoRA heads exist. Geometry recorded from now on means banks captured today remain usable for layout-conditioned training later — you can't retroactively add geometry to data you didn't record. 8 new tests (3 geometry, 2 anchor, 2 bank, 1 multistatic) + full-loop extension (2-node geometry, one tape-measured + one unknown, surviving the bank JSON round-trip the runtime loads from). 50/50 calibration (both feature configs) + 23 CLI tests green. Co-Authored-By: RuFlo <ruv@ruv.net> * feat(training): two-checkerboard camera↔room calibration for ADR-079 labels (ADR-152 §2.1.3) Defends the camera-supervised pipeline against PerceptAlign's "coordinate overfitting": MediaPipe keypoints were emitted in raw camera coordinates with no shared frame and no transceiver-geometry metadata — the exact label shape that memorizes deployment layout and collapses cross-layout. - scripts/calibrate-camera-room.py + calibration_lib.py: OpenCV two-checkerboard calibration → versioned bundle JSON (intrinsics, camera→room extrinsics, checkerboard spec, transceiver geometry, sha256 calibration_id). Intrinsics resolve from file > cache > multi-view computation > loud-warning 2-view fallback. - collect-ground-truth.py --calibration <bundle>: every sample gains keypoints_room (unit bearing rays from the camera center in the room frame — documented projective alignment; raw image coords preserved so training chooses), camera_origin_room, calibration_id, and the transceiver geometry stamp. Without the flag, output is byte-identical to before (tested) + a one-line ADR-152 warning. Design finding (recorded for ADR-152): a single planar checkerboard's corner grid is centrosymmetric — the reversed corner ordering fits a ghost camera pose with IDENTICAL reprojection error, so per-board flip disambiguation is mathematically ill-posed. solve_two_board_extrinsics solves the joint wall+floor set over all 4 flip combinations, where the minimum is unique — an independent reason the TWO-checkerboard method is required, beyond what PerceptAlign states. 15 headless pytest tests green (synthetic corners: extrinsics recovery incl. ghost resolution, bundle round-trip + hash stability, ray transforms w/ distortion + cross-resolution, no-calibration byte identity). Co-Authored-By: RuFlo <ruv@ruv.net> * feat(benchmarks): WiFlow-STD reproduction harness + measurement (a) results (ADR-152 §2.2) Shipped checkpoint REFUTED (0.08% PCK@20, wrong keypoint normalization); 6 reproducibility defects documented (broken imports, corrupted dataset tail with float32-max garbage that NaN-poisons fp16 BatchNorm, unreachable test phase). After repairs, retraining with upstream defaults reproduces 96.09% PCK@20 full-test / 96.61% corruption-free (published 97.25%) on RTX 5080. Claims graded MEASURED-EQUIVALENT; 2.23M params + ~0.055 GFLOPs verified. Third-party code/weights/data stay out of tree (gitignored). Co-Authored-By: claude-flow <ruv@ruv.net> * feat: ADR-152 Rust integrations + ADR-153 802.11bf protocol model - calibration: GeometryEmbedding — 32-slot permutation-invariant NodeGeometry featurization for future LoRA-head conditioning (ADR-152 §2.1.2); derived SpecialistBank::geometry_embedding() accessor; 59 tests - train: MaePretrainConfig + patchify/random-mask with UNSW measured recipe (80% masking, (30,3) patches; ADR-152 §2.3, arXiv 2511.18792); strict no-truncate/no-NaN policy; proptest properties - train: WiFlowStdModel — tch-gated port of the verified ~96%-PCK@20 WiFlow-STD architecture (ADR-152 §2.2 beyond-SOTA); ungated param formula pinned to 2,225,042; 15/17-keypoint support; 239 crate tests - hardware: ieee80211bf forward-compatibility protocol model (ADR-153): SpecProfile gates, SensingCapabilities negotiation, required ConsentMode, session FSM, SensingTransport + SimTransport + OpportunisticCsiBridge; full acceptance checklist covered; 156+4 tests - deps: ruvector bumps per ADR-152 §2.6 survey (mincut/solver 2.0.6, attention 2.1.0, gnn 2.2.0); vendor/ruvector synced to a083bd77f - docs: ADR-153 accepted; ADR-152 §2.2 status, §2.4 amendment, §2.6 added Workspace: 162 test suites green (--no-default-features); Python proof PASS. Known pre-existing flake: homecore-api env_empty_falls_back_to_defaults (unserialized env-var mutation) — untouched, follow-up. Co-Authored-By: claude-flow <ruv@ruv.net> * docs: CHANGELOG + CLAUDE.md entries for ADR-152 integrations and ADR-153 Co-Authored-By: claude-flow <ruv@ruv.net> * fix(train): repair tch-backend bit-rot — gated path compiles and tests run again Mechanical API refresh against current tch: Vec::from(Tensor) -> try_from (+ explicit flatten), numel() usize cast, Rem/div ops -> remainder() / divide_scalar_mode(floor) — the latter fixed a silent true-division bug in heatmap argmax decoding; clamp(1.0, f64::MAX) -> clamp_min (torch 2.x scalar overflow panic); petgraph EdgeRef import; missing EvalMetrics and verify_checkpoint_dir APIs that tests documented. wiflow_std roundtrip test uses safetensors (.pt _save_parameters roundtrip broken in torch 2.11 Windows). Gated: 349 passed (incl. all 20 wiflow_std); ungated: unchanged. Known pre-existing: gaussian-heatmap convention mismatch (2 tests), proof seed race under parallel threads — documented, deliberate follow-ups. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(train): WiFlow-STD PyTorch->tch weight import + numerical parity proof export_to_safetensors.py maps the retrained checkpoint (295 tensors -> 248 mapped, param sum exactly 2,225,042; num_batches_tracked dropped) into a tch-loadable safetensors plus a deterministic parity fixture. Gated #[ignore] integration test loads it strictly and asserts forward-pass agreement: max abs diff 1.192e-7 on the seed-42 fixture. dump_variable_names test makes the tch name layout authoritative. Zero architecture discrepancies found. Co-Authored-By: claude-flow <ruv@ruv.net> * fix: workflow-review findings — BN gamma init, ThresholdParams serde, init docs Concurrent validation workflow (2 review lanes + adversarial verification, 13 agents): 5 confirmed findings, 3 refuted. Fixes: - wiflow_std: pin BatchNorm gamma to 1.0 (tch default draws Uniform(0,1) — silently halves activations in from-scratch training; loaded checkpoints unaffected, parity re-verified after the change) - wiflow_std: document the conv-init divergences vs the reference's effective kaiming_normal(fan_out) re-init (from-scratch dynamics only) - ieee80211bf: ThresholdParams deserialization validates via try_from so the <=100 invariant holds for untrusted payloads (+ rejection test) Benchmarks (release, ruvzen): GeometryEmbedding 1.84us/call (542k/s), MAE tokenization 7.38us/window (135k/s), 802.11bf FSM 8.9M events/s — nothing suspicious. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr): ADR-152 §2.1.4 gate resolved — PerceptAlign repo MIT, dataset on HF Co-Authored-By: claude-flow <ruv@ruv.net> * feat(benchmarks): edge optimization measured + measurement (b) blocked + 92.9% retraction Edge optimization (ADR-152 optimize track): ONNX Runtime fp32 is the CPU latency win (3.2 ms/window, ~3.4x faster than torch, parity 2.4e-7); ORT dynamic int8 reaches 2.44 MB (paper's ~2.2 MB claim plausible only via conv-capable toolchains; -0.16pt PCK@20, +18% MPJPE, 2x slower); torch dynamic quant converts 0% of this conv-only model; fp16 halves storage free but is slower on CPU. Measurement (b) BLOCKED-ON-DATA: only 1,077 paired ESP32 windows exist (stop rule <2k). Forensic recheck of the surviving April holdout RETRACTS the ADR-079 '92.9% PCK@20' figure: constant-output model, absolute (not torso) threshold, 69 near-static frames — mean predictor scores 100% under that protocol; torso-PCK@20 is 19.1%. Corroborates PR #535. Stale citations removed from user-guide, readme-details, ADR-152 §2.1.3; no-citation rule extended to ADR-079 accuracy claims. Unblock: >=2k-window multi-pose paired session + torso-PCK re-baseline. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(user-guide): corrected camera-supervised collection tutorial Step 0 CSI-rate check + session-length math (window yield = frames/20 — the May session's 8x under-delivery was a ~12 Hz CSI rate, not an aligner bug); two-checkerboard calibration step (ADR-152 §2.1.3); pose-variety and confidence guidance; torso-normalized PCK + temporal-split + pred-variance eval protocol (lessons from the 92.9% retraction); scale presets re-keyed to realistic window counts. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(benchmarks): static PTQ int8 (calibrated) results + overnight capture script Conv-only static QDQ beats dynamic int8 on accuracy (PCK@20 96.61-96.63% vs 96.52%, MPJPE +10% vs +18% over fp32) at ~equal size/latency; all-ops QDQ strictly worse (int8 activations through attention glue). Entropy calibration verified bit-identical to MinMax on this data. Deployment: ONNX fp32 for speed (3.2ms), static conv-only QDQ for smallest (2.53MB). Also: scripts/overnight-empty-capture.py — segmented UDP CSI recorder for empty-room baselines (no glob collisions, detach-safe). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(benchmarks): measurement (b) MEASURED — optimization transfer only, mean-pose baseline wins WiFlow-STD fine-tuned on 2,046 fresh single-room ESP32 paired windows (temporal 70/15/15, 70->540 adapter, K=17): pretrained-init 65% PCK@20 vs scratch 0% (optimization transfer) but frozen-trunk ~0% (no feature transfer), and NOTHING beats the mean-pose baseline (95.9% PCK@20 — single subject, near-static normalized coords). Honesty gates held: pred std 0.0113 (non-constant model) but mean-baseline dominance means no citable CSI->pose capability from this data. ADR-152 open question 1 answered partially; definitive answer needs multi-subject/position data. Two new aligner findings: heterogeneous csi_shape with silent zero-padding (~20%), and extractCsiMatrix's transposed shape label (frame-major data, [nSc, nFrames] label) — fixes pending. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(benchmarks): efficiency sweep MEASURED — half model dominates full reference Compact WiFlow-STD variants on the same data/split/protocol: half (843,834 params, 0.38x) strictly dominates the 2.23M reference (PCK@20 96.62 vs 96.61, PCK@50 99.47 vs 99.11, MPJPE 0.00898 vs 0.0094) — the published architecture is over-parameterized for its own benchmark. quarter (338k) 96.05%; tiny (56,290 params, 1/39.5) holds 94.11% — a ~220KB fp32 edge candidate. In-domain caveats recorded; cross-domain untested. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(train): compact WiFlow-STD presets in Rust + tiny edge artifact (ADR-152) WiFlowStdConfig gains half()/quarter()/tiny() mirroring the overnight sweep exactly: TcnGroupsMode (Fixed/Gcd/Depthwise), input_pw_groups, derived stride schedule and decoder-mid (all default to upstream behavior; legacy serde JSON unaffected). Param formulas pin to trained ground truth first try: 843,834 / 338,600 / 56,290; default 2,225,042 pin and 1.192e-7 parity unchanged. 248 tests green. Tiny edge artifact (tiny_edge_bench.py): ONNX fp32 = 295 KB, 0.66 ms/win (~1,500/s CPU), 94.11% PCK@20 (matches sweep clean-test exactly; parity 1.49e-7). Static int8 is a bad trade at this scale (-1.43pt, +19% MPJPE, -16% size, slower) — recorded as negative result. Export note: width-16 breaks AdaptiveAvgPool((15,1)) TorchScript export; replaced by exact mean+matmul equivalent, proven by parity. Co-Authored-By: claude-flow <ruv@ruv.net> * fix: resolve all 10 confirmed code-review findings (7-angle review, 20/20 verified) wiflow_std: min_feature_width (default 15) replaces the keypoints->stride coupling — for_keypoints(17) now provably builds the trained [2,2,2,2] graph and pools 15->17, matching the validated Python protocol (pinned by tests); param_count() total on invalid configs; random_mask returns Result and rejects non-finite/out-of-range ratios; trainer checkpoints switched to safetensors (.pt VarStore roundtrip broken on Windows torch 2.11). ieee80211bf: SBP proxy now re-triggers instances and relays reports via Action::RelaySbpReport -> SensingFrame::SbpReport (clients consume via their existing path); missed_instances reset on success = consecutive semantics; SessionTable gains a guarded SBP entry point + unknown-id drop counter; initiator-role sessions reject inbound setup/SBP requests (RejectedNotSupported) closing the idle hijack; StartSetup/StartSbp outside Idle return InvalidStateForCommand; SBP validation unified through evaluate_setup with a 1:1 SetupStatus->SbpStatus mapping. events.rs split out to honor the 500-line cap. calibration/cli: enrollment geometry now actually reaches trained banks — both production call sites attach .with_geometry; --geometry flag on train-room and POST /enroll/geometry + train-body geometry on calibrate-serve give production a recording surface; geometry-free banks log the ADR-152 §2.1.2 note. benchmarks: corruption masks committed as ground truth (unregenerable after in-place cleaning; verified bit-identical regeneration from the pristine copy) + generate_corruption_masks.py producer; _bench_common.py dedups the 5x-copied shim/evaluate/seed/remap (post-refactor PCK@20 re-verified equal to the last digit); remote scripts get the mmap patch; tiny_edge --calib validated multiple-of-64; onnx_bench --help no longer executes (and overwrote) the export — artifact restored byte-exact. Workspace: 2,963 tests passed, 0 failed; Python proof PASS. Co-Authored-By: claude-flow <ruv@ruv.net> * ci: build workspace tests without debuginfo — runner disk exhaustion The combined 38-crate debug target exceeds the GitHub runner's disk ('final link failed: No space left on device'); the same tree measured 151GB locally with full debuginfo. CARGO_PROFILE_{DEV,TEST}_DEBUG=0 shrinks the target ~5-10x; debuginfo serves no purpose in CI test runs. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
29de574e63 |
Beyond-SOTA engine/signal/train improvements: mesh partition guard, FFT CIR solver, canonical frame decoder, falsifiable occupancy benchmark, governed streaming, adapter provenance (#1018)
* docs(research): add RuView beyond-SOTA system review (00) First document of the beyond-SOTA research series: capability audit of the current RuView engine with role-to-crate maturity matrix, ruvsense module inventory, gap analysis, and risk register. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * docs(research): add beyond-SOTA architecture design (02, in progress) https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * docs(research): finalize beyond-SOTA architecture (02) https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * docs(research): add benchmark/validation methodology snapshot (03) https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * docs(research): add beyond-SOTA series index with validation results; changelog README index ties the 5 research docs together with the session's measured validation evidence: 2,797 workspace tests / 0 failed, Python proof PASS (bit-exact), and paired pre/post criterion CIR benchmarks. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * perf(signal): precompute CIR warm-start system; hoist tomography solver allocs Exact, determinism-safe optimizations (bit-identical float results): - cir.rs: diag(PhiH Phi)+lambda*I and its CSR matrix depend only on Phi and lambda (fixed at CirEstimator::new) but were rebuilt every frame (O(K*G) pass + CSR allocation). Now built once in new() via build_warm_start_system; summation order unchanged. - tomography.rs: ISTA gradient buffer hoisted out of the 100-iteration loop (fill(0.0) reset) and the Frobenius Lipschitz bound moved from per-reconstruct to construction. Verified: signal 456 tests green; engine 11/11 green including cycle_is_deterministic and witness-stability tests. Criterion paired pre/post: cir_estimate/he40 -3.9% (p<0.01), multiband -1.2/-1.4%. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * fix(worldgraph): bound SemanticState growth with deterministic retention StreamingEngine::process_cycle appended one SemanticState belief per cycle with no eviction — ~1.7M nodes/day at 20 Hz (beyond-SOTA roadmap finding #6). Add WorldGraph::prune_semantic_states(max): deterministic eviction of the oldest beliefs by (valid_from_unix_ms, id); structural nodes (rooms, zones, sensors, anchors, tracks, events) are never eligible. Wire it into the engine after each belief append (DEFAULT_SEMANTIC_RETENTION = 7,200, ~6 min at 20 Hz; set_semantic_retention to tune). The WorldGraph holds current beliefs; durable history is the recorder's job, so no audit data is lost. 3 new tests: end-to-end bounded growth, oldest-only eviction, deterministic equal-timestamp tie-break. Workspace gate: 2,865 passed, 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(sensing-server): route live frames through the governed StreamingEngine Closes the live-trust-path gap (ADR-136 section 8, beyond-SOTA system review): the running server fused live CSI with the bare MultistaticFuser, while the privacy/provenance/witness control plane (ADR-135..146) only ever ran on synthetic in-test frames. The privacy control plane was therefore bypassable on the real path. New engine_bridge module drives StreamingEngine::process_cycle from the server's live NodeState map, reusing the existing NodeState -> MultiBandCsiFrame conversion. It lazily wires each contributing node as a WorldGraph sensor (idempotent), bounds belief growth via the retention cap, and forwards explicit timestamps/calibration ids so the path stays deterministic and replayable. Wired additively into both live ESP32/WiFi fusion sites in main.rs via a split-borrow off the write guard, so person-count behavior is unchanged; the latest BLAKE3 witness is stored on AppState. Every published belief now carries evidence + model + calibration + privacy decision and a deterministic witness. Adds wifi-densepose-engine/-worldgraph/-bfld/-geo deps. 6 new bridge tests (witnessed belief with full provenance, cross-run determinism, idempotent node registration, retention bound, privacy-mode propagation). sensing-server suite 430+128 green; workspace gate 2,904 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(train): falsifiable occupancy benchmark with anti-overfitting gate Makes the presence/person-count "beyond SOTA" claim falsifiable in code instead of aspirational (the unfalsifiability gap from the beyond-SOTA system review). occupancy_bench grades predictions vs ground truth and gates a SOTA claim behind one claim_allowed invariant requiring ALL of: - DataProvenance::Measured — synthetic/mock data is scorable for regression but never claimable (anti-mock-contamination; the CLAUDE.md Kconfig-bug lesson made structural). - A leak-free EvalSplit — validate() refuses any split where a subject OR environment id appears in both train and test (subject leakage / per-environment overfitting). - n_test >= min_test_samples (small-N guard). - Presence F1 whose bootstrap-CI lower bound (deterministic seeded splitmix64) clears the threshold — not the point estimate. - Count MAE within threshold. The claim string is unreadable except through the gate (NO_CLAIM otherwise), same discipline as the ruview-gamma acceptance gate. What remains is data, not method: a frozen, SHA-pinned, subject/environment-disjoint measured replay set turns the claim into a passing/failing test. Lives in wifi-densepose-train (the eval bounded context, alongside ablation/ eval/metrics). 10 tests cover each refusal path; warning-clean under the crate's missing_docs lint. Workspace gate 2,914 passed / 0 failed. Doc 03 updated. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(engine): per-room adapter provenance + drift-to-recalibration advisor Closes the trust-chain gap where an ~11 KB per-room LoRA adapter (ADR-150 section 3.4) could silently change inference without the witness noticing: provenance carried only "rfenc-v<N>" with no notion of adapter identity. - StreamingEngine::set_room_adapter(AdapterInfo): pins the adapter's content-derived id into provenance model_version ("rfenc-v1+adapter:<id>") — and therefore into the BLAKE3 witness — so swapping or clearing adapter weights always shifts the witness. Engine test proves base -> adapter -> other-adapter -> cleared all witness differently and cleared == base. - RecalibrationAdvisor: recommends re-running the ADR-135 empty-room baseline / refitting the room adapter on sustained low fusion coherence (streak threshold, default 60 cycles ~ 3 s at 20 Hz) or an ADR-142 change-point. Surfaced as TrustedOutput::recalibration_recommended, stored on the sensing-server AppState alongside the witness at both live fusion sites. - Bridge plumbing: EngineBridge::{set_room_adapter, clear_room_adapter} + live-path test that the adapter id flows into the live witness. Scope note (honest): this is the deployable provenance/trigger half of the "retrained model" roadmap item. Fitting the adapter itself runs in the existing external calibration service (aether-arena/calibration/); a trained RF-encoder checkpoint still does not exist in-tree. Engine 15 tests, bridge 7 tests. Workspace gate: 2,918 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * fix(mat): gate api module behind its feature — standalone no-default-features builds pub mod api was unconditional while its only dependency, serde, is optional behind the 'api' feature, so any build without default features failed with 101 unresolved-serde errors (masked in --workspace runs by feature unification). The api module and its create_router/AppState re-export are now cfg(feature = "api")-gated with docsrs annotations. All combos compile: bare --no-default-features (was 101 errors, now 0), --no-default-features --features api, and full default (177 tests pass). Workspace gate: 2,918 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * perf(signal): opt-in FFT operator for the CIR ISTA solver (8-14x measured) Phi is a sub-DFT, so each ISTA mat-vec can run as one length-G FFT (O(G log G)) instead of a dense O(K*G) product — the dominant-latency-hazard finding from the beyond-SOTA optimization roadmap. New CirConfig::fft_operator, default FALSE: the dense path stays the bit-exact witness default. The FFT evaluates the same sums in a different order, so enabling it shifts float results in the last bits and requires regenerating any pinned witness — strictly opt-in per deployment. FftOperator (rustfft, planned once at CirEstimator::new, scratch buffers reused across the ISTA loop) dispatches inside ista_solve: Phi x = scale * forward-FFT(x) sampled at bins (k_idx mod G) Phi^H v = scale * unnormalised inverse-FFT of v scattered into those bins Warm-start and Lipschitz estimation stay dense at construction. Measured (criterion, same run, same machine): ht20: 2.22 ms -> 265 us (8.4x) ht40: 10.26 ms -> 717 us (14.3x) The real HE40 grid (K=484, G=1452) scales further per the O(K*G)/O(G log G) ratio. 3 new tests: FFT<->dense matvec equivalence to float tolerance on ht20 and he40 grids; end-to-end dominant-tap agreement on a single-path frame; all default configs keep FFT off. New cir_estimate_fft bench group. Workspace gate: 2,921 passed / 0 failed (default path bit-exact, witnesses unchanged). https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(core): canonical frame decoder — capture-to-claim replay (ADR-136) The encode half of the ADR-136 frame contract existed (ComplexSample, to_canonical_bytes, witness_hash) but there was no decoder: a captured canonical frame could be witnessed but never reconstructed, blocking replay-from-capture. CsiFrame::from_canonical_bytes is the exact inverse: same id, metadata, complex payload, and witness hash (tested as the round-trip law AC7 — the replayed frame re-encodes byte-identically). Amplitude/phase are recomputed from the payload (projections, not independent state). Every malformed-input class fails closed (AC8): header truncation -> Truncated, payload truncation -> PayloadMismatch, unknown discriminants, non-UTF-8 device id, trailing bytes. Nil calibration uuid decodes as None per the documented encoding. Core: 36 tests pass. Workspace gate: 2,937 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(engine): dynamic min-cut mesh partition guard (ruvector-mincut) Maintains an exact min-cut over the live mesh coupling graph — nodes are sensing nodes, coupling is the product of fusion attention weights — and surfaces per cycle, as TrustedOutput::mesh: - cut value: the global "how close is the array to partitioning" number, a structural measure per-node heuristics miss; - weak side: which specific nodes would split off (failure/jamming triage, feeds ADR-032 posture); - at-risk flag: counts as a structural event for the drift->recalibration advisor (alongside ADR-142 change-points). Degenerate cases fail toward risk: a node with zero coupling is reported as already partitioned (cut 0, that node as the weak side). Measured cost policy (criterion, 12-node mesh — the honest part): - weights quantized (1/64) + change-gated: steady-state cycles do ZERO graph work and reuse the cached cut (~7.3 us, ~23x cheaper than building); - on any real change a full exact rebuild (~171 us) is used, because ONE DynamicMinCut delete+insert measured ~240 us — the subpolynomial machinery amortizes on much larger graphs, so rebuild-on-change is the measured optimum at mesh scale (one-edge case -28% after switching policy); - full process_cycle with the guard: ~33 us for 4 nodes vs the 50 ms budget. 9 mesh_guard tests (weak-node detection, steady-state zero updates, sub-quantum gating, join/drop rebuild, determinism, disconnection) + an engine-level wiring test (down-weighted node -> weak side -> recalibration). Engine 24 tests; workspace gate 2,946 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * feat(engine): mesh partition risk demotes privacy + enters the witness (ADR-032) Completes the mesh-guard integration: its at_risk signal was advisory-only (fed the recalibration advisor). It now also contributes to the ADR-141 privacy demotion alongside fusion- and array-level contradictions — a mesh close to partitioning makes the fused belief less trustworthy, so the cycle emits at a more restricted class (monotonic; information only removed). Because effective_class feeds the BLAKE3 witness, a fragmenting array now shifts the witness: partition risk is auditable, not just logged. The mesh computation moved ahead of the demotion step in process_cycle; mesh_guard_mut exposes risk-threshold tuning. Test: a forced-risk 3-node cycle demotes PrivateHome Anonymous->Restricted and shifts the witness vs a clean baseline. Engine 25 tests; workspace gate 2,947 passed / 0 failed. https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH * fix: public-PR review findings — privacy-path honesty, gate holes, mesh-guard cliff - sensing-server: engine errors logged+counted (no silent swallow), trust state exposed via status surface, privacy-demotion claims aligned with the actual parallel-audit-path behavior - occupancy_bench: vacuous-F1 hole closed (degenerate test sets fail with their own criterion); CI-lower-bound test made probative - mesh_guard: quantization scaled to observed coupling range — >=65-node balanced meshes no longer permanently at_risk (regression test) - engine: both wiring tests made probative (same-topology witness compare, deterministic risk-crossing fixture) - mat: axum/tokio optional behind api; real serde feature (api enables it) - core: canonical decoder strict (non-zero reserved bytes and nil UUID rejected — injective on accepted domain, forged-bytes tests) - CHANGELOG: un-spliced the FFT/adapter bullet mangle Co-Authored-By: claude-flow <ruv@ruv.net> * chore: strip private-track references for public PR Reword the occupancy-benchmark changelog bullet to drop a cross-reference to the private research track, and restore the WorldGraph retention bullet header that was glued onto the preceding MAT bullet. Co-Authored-By: claude-flow <ruv@ruv.net> * chore: lockfile refresh for cherry-picked feature set Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2a307138f2 |
feat: per-room calibration system (ADR-151) + cognitum-v0 appliance integration spec (#989)
* docs(adr): ADR-151 — Per-Room Calibration & Specialized Model Training Room-first calibration -> bank of small specialised ruVector models (breathing, heartbeat, restlessness, posture, presence, anomaly) distilled from the frozen Hugging-Face-published RF Foundation Encoder (ADR-150). Four-stage local-first pipeline: baseline (ADR-135 environmental fingerprint) -> guided enrollment (NEW EnrollmentProtocol, clean anchors not hours) -> feature extraction (reuse signal_features + ruvsense) -> specialist bank training (rapid_adapt LoRA heads, RVF storage, HNSW prototypes). Invariants: specialisation over scale; local heads over a shared public base; honest STALE degradation on baseline drift. Indexes ADR-149/150/151. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(cli): calibration HTTP API for UI-driven baseline capture (ADR-135/151) Adds `wifi-densepose calibrate-serve` — an Axum HTTP API that wraps the ADR-135 CalibrationRecorder so a UI (or any client) can drive an empty-room baseline capture remotely. Stage 1 ("teach the room") of the ADR-151 room calibration & training pipeline. A single background task owns the UDP socket (ESP32 0xC511_0001 frames) and the optional active recorder; HTTP handlers talk to it over an mpsc command channel and read a shared status snapshot, keeping the &mut recorder lock-free. CORS permissive so a browser UI can call it. Endpoints (/api/v1/calibration/*): GET /health liveness + UDP ingest stats (frames_seen, streaming) POST /start { tier?, duration_s?, room_id?, min_frames? } GET /status live progress (state, frames, progress, z, eta) — poll for UI POST /stop finalize the current session early GET /result finalized baseline summary (amp/phase-dispersion averages) GET /baselines list persisted baseline .bin files Reuses the existing calibrate.rs ESP32 wire parser (made pub(crate)); honest abort when <10 frames arrive in the window (e.g. ESP32 not streaming). Verified end-to-end over loopback: start -> 300 replayed HT20 frames -> state=complete, 52-subcarrier baseline, phase_dispersion_avg=0.00096 (concentrated/valid), persisted to disk; all 6 endpoints exercised. CLI: 19 tests pass; crate builds clean. Co-Authored-By: claude-flow <ruv@ruv.net> * test(cli): firewall-free CSI UDP relay for local Windows ESP32 testing Windows Defender blocks inbound LAN UDP to a freshly-built binary without an admin allow-rule; python.exe is already allowed. This relay binds the public CSI port and forwards each datagram verbatim to a loopback port where `calibrate-serve --udp-bind 127.0.0.1 --udp-port 5006` listens (loopback is firewall-exempt). No admin required. Validated: ESP32-format 0xC5110001 frames -> :5005 -> relay -> :5006 -> calibrate-serve -> state=complete, 52-subcarrier baseline, phase_dispersion_avg=0.00098 (clean). Completes the no-admin live-test path. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(changelog): record ADR-151 calibration API (calibrate-serve) Co-Authored-By: claude-flow <ruv@ruv.net> * feat(calibration): ADR-151 Stages 2–5 — enrollment, extraction, specialist bank, runtime New crate wifi-densepose-calibration implementing the per-room pipeline beyond Stage-1 baseline: - anchor.rs: guided-anchor sequence + event-sourced EnrollmentSession (Stage 2) - enrollment.rs: AnchorQualityGate + AnchorRecorder — gates anchors against the ADR-135 baseline deviation (presence/motion), re-prompts bad captures - extract.rs: Features + AnchorFeature — autocorrelation periodicity (breathing/ HR bands), variance/motion (Stage 3) - specialist.rs: 6 small room-calibrated models — presence (learned threshold), posture (nearest-prototype), breathing/heartbeat (band periodicity), restlessness (calm/active normalization), anomaly (novelty vs anchors) (Stage 4) - bank.rs: SpecialistBank — train/persist + baseline-drift STALE invalidation - runtime.rs: MixtureOfSpecialists — presence short-circuit + anomaly veto + stale flagging (Stage 5) Statistical heads make the pipeline runnable/validatable today; the ADR-150 HF RF Foundation Encoder backbone is the documented upgrade path. 29 unit tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(cli): wire ADR-151 enroll / train-room / room-status / room-watch Integrates the wifi-densepose-calibration crate into the CLI as four subcommands driving the full Stage 2–5 pipeline against a live ESP32 raw-CSI stream (edge_tier=0): - enroll: walks the guided anchor sequence, gates each capture against the ADR-135 baseline deviation (re-prompts bad anchors), writes labelled features - train-room: fits the SpecialistBank from the enrollment, persists JSON - room-status: prints a trained bank's summary - room-watch: live mixture-of-specialists readout (presence/posture/breathing/ heart/restless) over a rolling window, with anomaly veto + STALE flagging Per-frame scalar is the mean CSI amplitude (carries presence/motion + breathing modulation). Validated end-to-end on the live ESP32 (COM8, edge_tier=0): the real parser → feature extraction → runtime detected breathing (~16–31 BPM) on hardware. Full multi-anchor enrollment accuracy requires the operator to perform the poses; phase-based breathing extraction is a noted refinement. 48 tests pass (29 calibration + 19 CLI). Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-151): mark Stages 1–5 implemented; expand CHANGELOG Co-Authored-By: claude-flow <ruv@ruv.net> * fix(cli): keep proven mean-amplitude carrier for room features The max-variance-subcarrier carrier locked onto motion artifacts (not breathing) and also had an out-of-bounds bug on variable CSI subcarrier counts. Reverted to the mean-amplitude carrier, which is validated live to detect breathing. Phase-based extraction on a stable subcarrier remains the proper higher-SNR refinement (ADR-151 §4). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(calibration): multistatic fusion of co-located nodes (ADR-029/151) MultiNodeMixture fuses several co-located nodes (each with its own room-calibrated SpecialistBank) into one RoomState: - presence: OR across nodes (any node seeing a person wins) - posture/breathing/heartbeat: highest-confidence node (best viewpoint) - restlessness/anomaly: max across nodes - veto: any node's physically-implausible signal vetoes the room's vitals (anti-hallucination, same as single-node runtime) + presence short-circuit - stale: any node's STALE flag propagates Same-room multistatic only; cross-room is federation (ADR-105), not fusion. 6 unit tests (presence OR, best-confidence breathing, single-node veto, staleness). 35 calibration tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(cli): multistatic room-watch — fuse co-located nodes (ADR-029/151) `room-watch --node-bank N:path` (repeatable) groups live CSI frames by node_id and fuses per-node banks via MultiNodeMixture. Validated live on COM8 (node 9, edge_tier=0): frames grouped + fused end-to-end. True 2-node fusion is covered by unit tests; a second raw-CSI node is the hardware blocker. 54 tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(integration): calibration → cognitum-v0 appliance integration overview Detailed cross-repo integration spec for cognitum-one/v0-appliance: data contracts (CSI wire format, ADR-135 baseline binary, enrollment/bank/RoomState JSON schemas), calibrate-serve HTTP API, public crate API, Pi5+Hailo tiering, and a 5-step appliance integration plan. Grounded in the verified cognitum-v0 inventory (aarch64, cargo 1.96, HAILO10H, ruview-vitals-worker:50054). Co-Authored-By: claude-flow <ruv@ruv.net> * fix(calibration): address PR review — aarch64 decouple, API auth, path traversal, throttle Resolves the review on #989: - **Cross-compile (the appliance blocker):** make wifi-densepose-mat optional and feature-gate it (`mat`), so `cargo build -p wifi-densepose-cli --no-default-features` excludes the mat→nn→ort(ONNX)→openssl-sys chain. Verified: `cargo tree --no-default-features` shows 0 ort/openssl deps → calibration cross-compiles clean for the Pi. - **Security (must-fix before LAN):** - `--token` / CALIBRATE_TOKEN bearer-auth middleware on every route; warns if bound non-loopback without a token. - sanitize client-supplied `room_id` to [A-Za-z0-9_-] (≤64) before it reaches the baseline write path — kills the `../` file-write primitive. + test. - **Perf:** stop locking shared status + cloning SessionStatus on every UDP frame — counters/snapshot flush on the 200 ms tick instead (no CPU starvation under flood). finalize write moved to async `tokio::fs::write`. - **Docs:** ADR-151 STALE wording matches the impl (baseline-id change; drift-threshold = P6 refinement); integration doc gets the `--no-default-features` build + auth/sanitize notes. 35 calibration + 15 CLI tests (no-default) / 20 CLI (default) pass. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(worldgraph,worldmodel): add crates.io READMEs Plain-language overviews + feature lists, comparison tables (symbolic graph vs predictive occupancy; graph vs grid vs event-log), usage, and technical details. Adds readme = "README.md" to both manifests so they render on crates.io on the next release. Co-Authored-By: claude-flow <ruv@ruv.net> * release: worldgraph & worldmodel 0.3.1 (READMEs on crates.io) Co-Authored-By: claude-flow <ruv@ruv.net> * docs: precise calibration validation scope (capture+API+auth proven; clean enroll→train→infer not yet on-target) Aligns ADR-151 §7 + the appliance integration doc with the PR #989 scope clarification: nothing has run a clean baseline → enroll → train → infer on live CSI; the live breathing read used the stateless head, not a trained bank. Adds --source-format adr018v6 to the backlog. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(calibrate-serve): live GET /room/state endpoint (mixture over CSI window) Adds a live RoomState readout over HTTP — the appliance UI's main need. The ingest task maintains a rolling per-frame scalar window (flushed on the 200 ms tick, no per-frame lock); the handler loads a bank (resolved as a sanitized name under output_dir — same path-traversal defense as room_id), runs the MixtureOfSpecialists over the window, returns RoomState JSON. Validated live (ESP32-S3 via relay): breathing 14-19 BPM over HTTP; a bank=../../etc/passwd query is neutralized to 'etcpasswd' (no traversal). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(calibrate-serve): POST /room/train + fix AnchorLabel JSON to snake_case - POST /api/v1/room/train: { room_id, baseline_id, anchors[] } → trains a SpecialistBank and persists it as <output_dir>/<room_id>.json (path-sanitized), readable via /room/state?bank=<room_id>. Completes the HTTP train→infer loop. - Fix data-contract bug: AnchorLabel serialized as PascalCase variant names (serde default) while as_str() + the integration doc used snake_case. Added #[serde(rename_all = "snake_case")] so the JSON wire format matches the documented contract (empty/stand_still/…). Locked with a roundtrip test. Validated live (ESP32-S3): POST train (4 anchors → 6 specialists, persisted) → GET /room/state returns RoomState with the trained presence/restlessness; the synthetic-vs-real scale mismatch correctly triggers the anomaly veto. 36 calibration tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(calibrate-serve): live enroll-over-HTTP (POST /enroll/anchor + /enroll/status) Closes the last HTTP gap — the appliance can now drive the ENTIRE calibration pipeline over HTTP without the CLI: baseline (start/stop) -> enroll/anchor x8 -> room/train -> room/state - POST /enroll/anchor { room_id, baseline, label, duration_s? }: the ingest task loads the baseline (sanitized name under output_dir), captures the anchor for the duration against it (AnchorRecorder + per-frame series), runs the quality gate, and on completion replies with the verdict + accumulates the AnchorFeature in an in-server enrollment map keyed by room_id. Re-prompts on rejection. - GET /enroll/status?room=<id>: accepted anchors, next, complete. - POST /room/train now falls back to the in-server enrollment when anchors[] is omitted. Validated live (ESP32-S3): capture baseline -> enroll stand_still (271 frames, 6s) -> gate correctly rejects "no person detected (presence_z 0.90 < 1.50)" relative to a same-occupancy baseline (a clean empty-room baseline is the documented on-target prerequisite). Builds clean; CLI tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * test(calibrate-serve): HTTP integration tests for the room/enroll endpoints Factor the router into build_router() (shared by execute + tests) and add tower-oneshot integration tests (no network/ingest needed): - health + descriptor → 200 - POST /room/train persists the bank; GET /room/state → 200; train with no anchors/enrollment → 400 - path-traversal: /room/state?bank=../../etc/passwd → 404 (sanitized, never reads outside output_dir) - enroll/status empty; /enroll/anchor with an unknown label → 400 CI regression coverage for the endpoints added this session. 18 CLI tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(mat): make serde non-optional — unblocks `cargo test --workspace --no-default-features` Making wifi-densepose-mat optional in the CLI (for the aarch64/ort decouple) exposed a latent feature bug: mat's `api` module compiles unconditionally and uses serde, but `serde` was an optional dep enabled only via the `api`/`serde` features. Previously the CLI's *unconditional* mat dependency enabled those features transitively, so `--workspace --no-default-features` still got serde; once mat became optional+gated, the workspace build lost it → `error[E0432]: unresolved import serde` across mat's api/* (CI red). mat already pulls serde_json + axum unconditionally, so making `serde` non-optional has no real cost and restores the workspace build. Does NOT affect the aarch64 CLI build (mat isn't built there at all): verified `cargo tree -p wifi-densepose-cli --no-default-features` still shows 0 ort/openssl deps, and `cargo test --workspace --no-default-features` compiles clean. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(claude.md): add wifi-densepose-calibration to crate table (pre-merge) Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr): ADR-152 — WiFi-pose SOTA 2026 intake (geometry-conditioned calibration, external benchmarks, encoder recipe) Records the 2026-06-10 deep-research run (22 sources, 110 claims, 25 adversarially verified: 24 confirmed / 1 refuted) and the decisions it implies: - §2.1 ACCEPTED: geometry-condition the ADR-151 calibration system — NodeGeometry at enrollment, geometry embeddings for future LoRA heads, PerceptAlign-style two-checkerboard camera↔WiFi alignment for the ADR-079 supervised path. PerceptAlign (MobiCom'26) names the failure mode ("coordinate overfitting") that matches our own ADR-150 cross- subject collapse. - §2.2 ACCEPTED: benchmark protocol vs external "WiFlow-STD (DY2434)" (claimed 97.25% PCK@20, Apache-2.0 weights+dataset) with a no-citation rule until measured on our 17-keypoint ESP32 eval set. Name collision with our internal WiFlow is disambiguated. - §2.3 ACCEPTED: amend ADR-150 training recipe per UNSW MAE study — 80% masking, (30,3) patches, data-over-capacity priority (log-linear, unsaturated at 1.3M samples). - §2.4 watch items: IEEE 802.11bf-2025 published 2025-09-26; esp_wifi_sensing as external presence baseline (drop-in claim REFUTED 0-3); ZTECSITool 160MHz/512-subcarrier anchor node (procurement-gated). - §2.5 NOT adopted: non-WiFi "foundation model" papers; DensePose-UV (no 2025-2026 work does UV regression from commodity WiFi). Every number is evidence-graded CLAIMED vs MEASURED in the source register. Re-check horizon 2026-12. Co-Authored-By: RuFlo <ruv@ruv.net> * test(calibration): full-loop integration test — baseline→enroll→train→infer proven in-process (ADR-151 §7 gap, software half) Closes the software half of PR #989's headline validation gap: the complete calibration loop had never run end-to-end anywhere, even in-process. tests/full_loop.rs (412 lines, deterministic xorshift32 room simulator, HT20/52-subcarrier/20Hz, same fingerprint family as the ADR-135 roundtrip test) now drives the CLI's exact stage order through the public API: 1. baseline — 600 static frames, zero motion flags post-warmup, calibration_uuid() exactly as the CLI derives it 2. enroll — all 8 AnchorLabel::SEQUENCE anchors through AnchorQualityGate::default(), session is_complete() 3. extract — AnchorFeature::from_series recovers injected 0.25Hz and 0.125Hz breathing within ±0.04Hz 4. train — SpecialistBank::train fits all 6 specialists; JSON round-trip and the runtime consumes the RELOADED bank 5. infer — positive: never-enrolled 0.30Hz subject reads present, 18±2 BPM; negative: empty window reads absent; degradation: foreign baseline_id flags STALE Seed-robust (5 seeds), passes with and without default features: 36 unit + 1 integration green. Validation docs updated (ADR-151 §7 + integration doc §7 matrix): what remains is strictly the on-target hardware session (real CSI, physically empty room, operator performing the guided anchors). Three behavioral findings from building the test are recorded for pre-session triage: z-band squeeze between baseline motion flagging (z>2.0) and the still- anchor gate (presence_z≥1.5) — likeliest on-hardware enroll failure; variance-only PresenceSpecialist missing motionless-person mean shift; ungated breathing_hz/heart_hz in noise-window embeddings. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(calibration): close all four ADR-152 behavioral findings pre-hardware-session The full-loop integration test surfaced three findings; fixing the third exposed a fourth. All four are fixed and regression-guarded: 1. z-band squeeze (enrollment.rs) — anchor motion is now measured from frame-to-frame deltas of the deviation series (|Δz| > Z_DELTA_MOTION 0.5 ∨ |Δφ| > π/6), not from the absolute motion_flagged, which fires at amplitude_z_median > 2.0 vs the EMPTY baseline and so conflated presence strength with motion. A strongly-reflecting still person (z = 3.0 — every frame flagged by the old heuristic) now enrolls. The old unit tests mocked (z=3.0, motion=false), a combination the real deviation() can never emit — which is exactly how the squeeze hid; tests now derive the flag from z the way the producer does. 2. variance-only presence (specialist.rs) — PresenceSpecialist gains a mean-shift channel: present when variance > threshold OR |mean − empty_mean| > mean_dist_threshold (trained at half the empty→occupied mean distance, None when the means don't separate). Detects the motionless person whose body raises the scalar mean but not its variance. Old persisted banks deserialize with the channel inert (serde default None) — variance-only behavior preserved, proven by a fixture test against pre-change JSON. 3. ungated hz embedding (extract.rs) — Features::embedding() zeroes breathing_hz/heart_hz below EMBED_MIN_SCORE (0.25), keeping the random in-band peaks of noise windows out of the posture/anomaly prototype space. Raw fields stay ungated (specialists have their own stricter gates). 4. heart-band lag-floor leakage (extract.rs, found while fixing 3) — a pure 0.30 Hz breathing signal scored 0.67 in the heart band at 3.33 Hz: out-of-band rhythm leaks as a monotonic slope whose max sits at the band's lag floor, so score gating alone cannot stop it. autocorr_dominant now requires the winning lag to be an interior local maximum; band-edge "peaks" are rejected, true in-band peaks (interior by definition) are preserved. full_loop.rs strengthened to drive the fixes end-to-end: the StandStill anchor is now a z=3.0 strong reflector (unenrollable pre-fix), and a new motionless-person runtime case proves mean-channel detection at empty- level variance. Validation: 41 calibration unit + 1 full-loop integration + 23 CLI tests green; cargo test --workspace --no-default-features exit 0. Co-Authored-By: RuFlo <ruv@ruv.net> |
||
|
|
0d3d835bf8 |
feat(swarm): add ruview-swarm crate — drone swarm control system (ADR-148) (#862)
* feat(swarm): add wifi-densepose-swarm crate implementing ADR-148 drone swarm control system
New crate `wifi-densepose-swarm` with hierarchical-mesh swarm topology,
Raft consensus, MAPPO MARL, CSI sensing integration, and ITAR-gated
coordination features. Closes 3 of 7 milestones (M1, M2, M5) with 5/5
ADR-148 SOTA performance targets met.
## Modules (45 source files, 14 modules)
- types: NodeId, DroneState, Position3D, SwarmTask, SwarmError, FailSafeState
- topology: Raft consensus (leader election, log replication, quorum), Gossip, Mesh
- formation: VirtualStructure, LeaderFollower, Reynolds flocking (itar-gated)
- planning: RRT-APF hybrid planner, 3-phase coverage, Bayesian grid, pheromone
- allocation: Auction + FNN bid scorer (itar-gated)
- sensing: CsiPayloadPipeline (Live/Synthetic/Replay), MultiViewFusion, OccWorldBridge
- marl: MAPPO actor (3-layer MLP), LocalObservation (64-dim), RewardCalculator, PPO loop
- security: MAVLink v2 HMAC-SHA256, UWB anti-spoofing, geofence, Remote ID, FHSS
- failsafe: 10-state onboard machine, GCS-independent safety transitions
- config: TOML SwarmConfig with SAR/inspection/agriculture/mine/demo/wi2sar_reference
- demo: SyntheticCsiGenerator, DemoScenario (SAR/open-field/mine)
- integration: FlightController trait, MAVLink dialect (50000-50005), SwarmSim
- orchestrator: SwarmOrchestrator wiring all subsystems end-to-end
- bench_support: Criterion fixture generators
## ITAR compliance
Swarming coordination features gated behind `itar-unrestricted` feature
per USML Category VIII(h)(12). Default build compiles clean stubs.
## Benchmark results (criterion, release mode)
- MARL actor inference: 3.3 µs (target ≤ 5 ms — 1,516× headroom)
- RRT-APF planning (100 iter): 0.043 ms (target < 300 ms — 6,946× headroom)
- MultiView CSI fusion (3 UAVs): 58.5 ns (target < 10 ms — 171,000× headroom)
- 3-view localization: 1.732 m (target ≤ 2 m — beats Wi2SAR SOTA)
- 4-drone SAR coverage (400×400 m): 223 s (target ≤ 240 s — PASS)
## Tests
- --no-default-features: 73/73 passing
- --features itar-unrestricted: 85/85 passing
Closes #861
Co-Authored-By: claude-flow <ruv@ruv.net>
* refactor(swarm): rename wifi-densepose-swarm → ruview-swarm
The swarm control system is a RuView-level capability (drone coordination,
Raft consensus, MARL) that operates above the wifi-densepose sensing layer
rather than being a sub-component of it. Rename aligns with the project
identity and separates coordination infrastructure from sensing modules.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(swarm): resolve all clippy warnings + add MARL convergence test
- planning/probability_grid: map_or(true,…) → is_none_or (clippy::unnecessary_map_or)
- planning/pheromone: &mut Vec<T> → &mut [T] on evaporate+deposit (clippy::ptr_arg)
- marl/observation: fix doc lazy-continuation warning on TOTAL line
- marl/trainer: manual Default impl → #[derive(Default)] + #[default] on Demo variant
Also adds test_marl_convergence_improves_mean_return: fills 64-transition
ReplayBuffer with mixed rewards (steps 0-31: negative, 32-63: positive),
runs ppo_update, asserts mean_return is finite and non-zero.
Result: 0 clippy warnings · 74/74 tests (default) · 86/86 (itar-unrestricted)
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(swarm): integrate Ruflo AI-agent capabilities into ruview-swarm
Adds a feature-gated Ruflo integration layer connecting ruview-swarm to the
claude-flow daemon's AgentDB, AIDefence, and SONA intelligence subsystems.
Default build is unaffected (all paths behind `Option<Box<dyn RufloBackend>>`).
## New module: src/ruflo/
- backend.rs: RufloBackend trait (9 async methods) + RufloError, MissionMemoryEntry,
PatternEntry, MavlinkScanResult types (always compiled)
- mock_backend.rs: MockRufloBackend in-memory impl for testing (always compiled, 5 tests)
- http_backend.rs: HttpRufloBackend — JSON-RPC 2.0 → claude-flow daemon localhost:3000
(gated behind `ruflo` feature, requires reqwest)
- mission_summary.rs: MissionSummary serializer with pattern description + confidence
scoring from victim recall, coverage %, collision penalty (always compiled, 3 tests)
## 4 capability areas
1. MissionMemory → memory_store / memory_search (cross-mission victim memory)
2. PatternLearner → agentdb_pattern-store / -search (HNSW SONA trajectory patterns)
3. MavlinkDefence → aidefence_is_safe / aidefence_scan (scan MAVLink before accepting)
4. IntelligenceHooks → trajectory-start/step/end (SONA learning loop)
## SwarmOrchestrator integration
- with_ruflo(backend): builder to attach a backend
- start_trajectory(task) / finish_trajectory(success, key): SONA mission lifecycle
- receive_peer_detection_checked(): AIDefence scan before accepting peer detections
## Cargo feature
`ruflo = ["dep:reqwest", "dep:serde_json"]` — optional, not in default
## Tests
- --no-default-features: 82/82 pass (8 new ruflo tests)
- --features ruflo,itar-unrestricted: 94/94 pass
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(swarm): M7 mission profiles with victim confirmation reports + pre-merge docs
Adds end-to-end mission runners producing structured MissionReport output,
and updates project docs (CHANGELOG, README, CLAUDE.md) per pre-merge checklist.
## M7 Mission Profiles (integration/mission_report.rs + swarm_sim.rs)
- MissionReport / VictimReport / SotaComparison types (serde-serializable)
- run_mission_with_report(): full mission → detailed report with per-victim
localization error, fusion uncertainty, contributing drones, detection time
- run_inspection_mission(): leader-follower power-line corridor inspection
- run_mine_mission(): GPS-denied underground (2-drone, slow, UWB-only)
- SotaComparison embeds Wi2SAR baseline (5m / 810s) vs achieved metrics
## Docs (pre-merge checklist)
- CHANGELOG.md: ruview-swarm + Ruflo integration + performance entries
- README.md: ruview-swarm row
- CLAUDE.md: Key Rust Crates table row + ADR-148 in ADR list
## Tests
- --no-default-features: 86/86 pass
- --features ruflo,itar-unrestricted: 98/98 pass
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(swarm): convergence-assist for victim fusion + 5s Ruflo HTTP timeout
Follow-up to
|
||
|
|
9ad550d95f |
feat(worldmodel): Candle Rust port + GCP GPU scripts (ADR-147 Phase 4+6)
Candle native port — wifi-densepose-occworld-candle v0.3.0: - config.rs: OccWorldConfig (14 params matching occworld.py) - vqvae.rs: ClassEmbedding(18→64), VQCodebook(512×512, squared-L2), QuantConv/PostQuantConv(1×1 Conv2d), fold_3d_to_2d helpers ResNet encoder/decoder are documented stubs (Phase 5 checkpoint pending) - transformer.rs: full Candle MHA transformer (2 layers, temporal+spatial cross-attention, FFN, pre-norm residuals) - inference.rs: OccWorldCandle::dummy() + ::load() + predict() InferenceOutput: sem_pred(1,15,200,200,16) + trajectory_priors - 14/14 tests pass (12 lib + 2 doctests) GCP GPU scripts — scripts/gcp/: - provision_training.sh: a2-highgpu-8g (8×A100 40GB) for Phase 5 retraining - run_training.sh: rsync + torchrun 8-GPU train + checkpoint download - provision_cosmos.sh: a2-ultragpu-1g (A100 80GB) for Cosmos evaluation - cosmos_eval.sh: run Cosmos-Transfer2.5 inference, download results - teardown.sh: safe checkpoint download + instance delete Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
cd1c391afc |
feat(worldmodel): ADR-147 Phase 3+5 — RuViewOccDataset domain adapter + retraining pipeline
Phase 3 — scripts/ruview_occ_dataset.py: - RuViewOccDataset: WorldGraph JSON snapshots → OccWorld (F,H,W,D) tensors - Indoor class remapping: person→7, floor→9, wall→11, furniture→16, free→17 - Zero ego-poses (fixed indoor sensor, no ego-motion) - record_snapshot() helper for training data accumulation - Validated: 5 windows, (16,200,200,16) tensor, person+floor voxels confirmed Phase 5 — scripts/occworld_retrain.py: - record: stream WorldGraph snapshots from sensing server REST API - vqvae: fine-tune VQVAE tokenizer on RuView occupancy (200 epochs, AdamW) - transformer: fine-tune autoregressive transformer with frozen VQVAE wifi-densepose-worldmodel v0.3.0 published to crates.io Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
c7ddb2d7d1 |
feat(worldmodel): ADR-147 — OccWorld world model integration, wifi-densepose-worldmodel v0.3.0 (#856)
* feat(worldmodel): ADR-147 — OccWorld integration, wifi-densepose-worldmodel v0.3.0 (#854) - New crate `wifi-densepose-worldmodel` v0.3.0: async Unix-socket bridge to OccWorld Python inference server; `OccWorldBridge`, `OccupancyGrid3D`, `TrajectoryPrior`, `worldgraph_to_occupancy` encoder (14/14 tests pass) - `scripts/occworld_server.py`: long-lived Python inference server for OccWorld TransVQVAE (72.4M params); applies API-bug patches; dummy mode for CI testing; graceful SIGTERM shutdown - `pose_tracker.rs`: `trajectory_prior` soft-blend injection (80/20 Kalman/prior) on torso keypoint; `set_trajectory_prior()` public method - CI: added `Run ADR-147 worldmodel tests` step - ADR-147: accepted — OccWorld primary (209 ms, 3.37 GB VRAM, RTX 5080); Cosmos deferred to ADR-148 (32.54 GB VRAM exceeds hardware) - Benchmark proof: 208.7 ms P50, 3.37 GB peak VRAM, 12.1 GB headroom Co-Authored-By: claude-flow <ruv@ruv.net> * chore: update ruvector.db state Co-Authored-By: claude-flow <ruv@ruv.net> * chore: ruvector.db sync Co-Authored-By: claude-flow <ruv@ruv.net> * fix(cli): add missing min_frames field to CalibrateArgs test helper E0063 in calibrate.rs:448 — CalibrateArgs gained min_frames in ADR-135 but the default_args() test helper was not updated. min_frames=0 means 'use tier default', matching the existing runtime behaviour. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
95bdd37e76 |
bench+test: engine per-cycle benchmark + ADR-142 acceptance path
- engine: criterion benchmark engine_cycle — full process_cycle (4 nodes / 56 subcarriers) measured at ~6.35 us/cycle, ~7800x under the 50ms (20Hz) budget. - signal: ADR-142 acceptance test — 3 links drift 30 frames -> ChangePoint -> VoxelMap accumulates -> low-confidence voxels suppressed -> VoxelGate Restricted emits histogram only -> ADR-137 contradiction recorded. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
5878868060 |
feat(signal,engine): ADR-137 calibration-mismatch contradiction + trust witness
- signal: MultistaticFuser::fuse_scored_calibrated() threads per-node CalibrationId; agreeing epochs → calibration_id set + CalibrationApplied evidence; disagreeing → calibration_id None + CalibrationIdMismatch flag (forces demotion). +2 tests. - engine: process_cycle_calibrated() per-node calibration path; process_cycle delegates with a uniform epoch. TrustedOutput gains a deterministic BLAKE3 witness over (provenance || class). calibration_version='cal:none' on mismatch. - ADR-137 acceptance test: two frames + mismatched calibration -> QualityScore contradiction -> Restricted -> calibration_id None -> witness stable. +happy path. - 11 engine tests, signal 411+ lib tests; workspace 0 errors. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
2517a16d88 |
feat(engine): compose ADR-138/142/143 + ADR-139 live loop
- ADR-138: process_cycle runs ArrayCoordinator when node geometry is registered; array contradictions (CoherenceDrop/GeometryInsufficient) fold into the privacy demotion; DirectionalEvidence surfaced in TrustedOutput - ADR-142: per-node mean-amplitude → EvolutionTracker; cross-link change-point recorded as a WorldGraph Event node - ADR-143: ingest_reflectors() runs Rf-SLAM discovery, writes stable Wall/Furniture reflectors as ObjectAnchor nodes - ADR-139 live loop: update_person_track(), apply_active_privacy_mode() (PrivacyRollup suppresses person_track under identity-strict modes), snapshot_json() - Acceptance test live_frame_to_reload_same_contents: full path fusion->worldgraph->privacy_rollup->persist->reload->same contents, no raw RF - 9 engine tests; workspace 0 errors Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
2eada40e3b |
feat(engine): integrate ADR-135..141 into an end-to-end trust pipeline
- signal/calibration.rs: BaselineCalibration gains calibration_id()/ calibration_uuid()/apply() — the ADR-135->136 link that stamps FrameMeta.calibration_id (deterministic id, no serialization change). +1 test. - NEW crate wifi-densepose-engine: StreamingEngine::process_cycle() composes fuse_scored (137) -> calibration provenance (135/136) -> privacy demotion on contradiction (141) -> WorldGraph SemanticState with mandatory provenance + DerivedFrom edge (139). Returns TrustedOutput (the trust chain made concrete). - Validates the throughline: every output names evidence + model + calibration + privacy decision; calibration_id flows input->QualityScore->provenance; contradiction demotes class; deterministic; privacy mode attested. - 4 integration tests; workspace 0 errors; signal 410 lib tests pass. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
521a012d84 |
feat(worldgraph): ADR-139 WorldGraph environmental digital twin (#843)
New crate wifi-densepose-worldgraph: - model.rs: WorldNode (10 kinds) + WorldEdge (7 relations) as serde enums (no trait objects → deterministic RVF persistence); WorldId, EnuPoint, ZoneBoundsEnu (with point-in-bounds), SemanticProvenance (house-rule tuple) - graph.rs: WorldGraph over petgraph StableDiGraph; upsert/add_edge/neighbors, room_for_area (HomeCore area_id linkage), observed_by/contents_of queries, add_semantic_state (append-with-provenance DerivedFrom), add_contradiction (both beliefs retained), apply_privacy_mode → PrivacyRollup, JSON persistence - 7 tests (upsert/replace, linkage, unknown-endpoint, location, provenance+ contradiction, privacy rollup, deterministic JSON round-trip) - workspace 0 errors Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
11f89727f1 |
feat(core,signal): ADR-136 streaming-engine frame contracts (#840)
- ComplexSample LE wrapper (16-byte canonical encoding, serde tuple, as_complex32) - CsiMetadata gains calibration_id/model_id/model_version + append-only setters - CanonicalFrame trait + impl for CsiFrame (BLAKE3 witness, deterministic bytes) - Stage<I,O>/Versioned/QualityScored traits + FrameMeta alias in ruvsense - 9 ADR-136 acceptance tests (AC1-AC8); workspace builds, 0 errors Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
8504638187 |
feat(signal): ADR-135 — empty-room baseline calibration
Operator-initiated calibration that records 30 s of stationary CSI,
emits a per-subcarrier baseline (amplitude mean+variance via Welford,
phase via circular sin/cos sums with von Mises dispersion), and gates
downstream stages on a deviation z-score. Plugs into multistatic
coherence gating, motion/presence detection, and the new ADR-134 CIR
estimator as a reference-subtracted input.
API surface (under wifi_densepose_signal):
CalibrationConfig::{ht20, ht40, he20, he40}
CalibrationRecorder { record(), finalize(), frames_recorded() }
BaselineCalibration {
subcarriers: Vec<SubcarrierBaseline>,
deviation(&CsiFrame), subtract_in_place(&mut CsiFrame),
to_bytes(), from_bytes()
}
CalibrationDeviationScore { amplitude_z_median, amplitude_z_max,
phase_drift_median, motion_flagged }
CalibrationError { SubcarrierMismatch, TierMismatch,
InsufficientFrames, VersionMismatch, TruncatedBuffer }
Binary baseline format: magic 0xCA1B_0001 + u8 version=1 + u8 tier +
captured_at_unix_s (i64) + frame_count (u64) + num_subcarriers (u32) +
[SubcarrierBaseline; N] as 16 bytes each (amp_mean, amp_variance,
phase_mean, phase_dispersion as f32 LE). Hand-written serialisation so
the format is stable across Rust toolchain versions without serde drift.
CLI: new `wifi-densepose calibrate` subcommand binds a UDP listener
(0xC511_0001 frames), streams them through CalibrationRecorder, prints
a real-time z-score banner per ADR-135 §risk 1 (operator-may-be-moving),
aborts on sustained high deviation, and writes the binary baseline to
disk. Local UDP packet parser duplicated from sensing-server (per ADR
discussion — avoids cross-crate API churn).
Witness: cross-platform-deterministic SHA-256 over the per-subcarrier
quantised baseline profile (u16 LE at 1e-2/1e-4/1e-3, no sort) using
the lesson learnt from the CIR PR #837 libm-jitter fix. Hash:
d6bce07ecb1648e6936561df44bf4a3bfc17bb0ba5f692646b2301d105b52f67
CI guard: new "ADR-135 calibration witness proof (determinism guard)"
step under the Rust Workspace Tests job, adjacent to the existing
ADR-134 CIR guard. Regressions are unambiguously attributable.
Hardware-in-loop validation: full 600-frame capture exercised via the
new scripts/synth-csi-udp.py emitter targeting 127.0.0.1:5005. The CLI
binary received 600 frames at 20 Hz, z_med stable at ~0.7, motion
correctly NOT flagged, finalised baseline written to baseline.bin (860
bytes) with correct magic + version + timestamp in the header. Live
ESP32 capture from COM9 is operator follow-up — requires provisioning
the firmware's UDP target IP to match the host running the CLI.
Test results (cargo test -p wifi-densepose-signal --no-default-features):
lib: 382 pass / 0 fail / 1 ignored
calibration_synthetic: 17 pass / 0 fail
calibration_drift: 5 pass / 0 fail
calibration_roundtrip: 10 pass / 0 fail
cir_*: 9 pass + 6 documented P2 ignores
doctest: 10 pass
Bench: 20 Criterion combinations registered
(recorder_record / recorder_finalize / deviation / record_600 /
to_bytes across HT20/HT40/HE20/HE40 tiers).
Witness: bash scripts/verify-calibration-proof.sh → VERDICT: PASS
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
9e7fa83210 |
feat(signal): ADR-134 CSI→CIR via ISTA + NeumannSolver warm-start (#837)
* feat(signal): ADR-134 — CSI→CIR via ISTA + NeumannSolver warm-start End-to-end first-class Channel Impulse Response estimation in the Rust workspace. Bridges CSI (frequency domain) to CIR (delay domain) so multistatic coherence gating, NLOS/LOS classification, and (at HT40+) ToF ranging become tractable in `wifi-densepose-signal`. Algorithm: ISTA L1 sparse recovery over a normalized DFT sub-matrix sensing operator Φ ∈ ℂ^(K×G) with G = 3K (3× super-resolution). The Tikhonov-regularised warm start re-uses `ruvector_solver::neumann:: NeumannSolver` — same call pattern as `fresnel.rs:280` and `train/subcarrier.rs:225` — so no new crate dependencies. Tiers supported: HT20 / HT40 / HE20 (Tier A-HE, C6) / HE40. The C6 HE-LTF tier is the preferred Tier A target whenever an 11ax AP is in range; firmware substrate already shipped at v0.7.0-esp32 per ADR-110. Measured performance (release, single CirEstimator shared across 12 links): HT20 2.72 ms / HE20 3.20 ms / HT40 13.43 ms / HE40 9.71 ms per estimate(). HT20 12-link multistatic 17.7 ms — fits the 50 ms RuvSense cycle; HT40 12-link 74 ms exceeds it and is flagged in ADR-134 §2.7 as requiring Rayon parallelism or G=2K super-res reduction. Measured Φ conditioning: κ(Φ) ≈ 1.00 identically across all tiers. ADR-134 §2.3 was corrected — the C6 advantage is statistical SNR gain (√(242/52) ≈ 2.16×) from more independent measurements, not improved conditioning. Witness: bit-deterministic SHA-256 over CirEstimator output on the synthetic ADR-028 reference signal (100 frames, top-5 taps, 1e-6 quantization). Hash committed to expected_cir_features.sha256; verify-cir-proof.sh wires the check into the existing witness bundle. CI: cargo test --features cir + verify-cir-proof.sh added as separate steps under the Rust Workspace Tests job; regressions are unambiguously attributable. Files: - ADR + WITNESS-LOG-028 row 34 + CLAUDE.md module count (14 → 15) - src/ruvsense/cir.rs (~540 LOC) + lib.rs re-exports + multistatic.rs wire-up (reversible via `use_cir_gate=false`) - 3 integration tests + Criterion bench + 3 deterministic fixtures - cir_proof_runner binary + sha256 + verify-cir-proof.sh Test rate: 395 pass / 6 ignored (P2 ISTA hyperparameter tuning; see #[ignore] reasons) / 0 fail. cargo check clean; verify-cir-proof.sh VERDICT: PASS. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(signal): make CIR witness cross-platform-deterministic The first witness (Windows-generated hash 89704bfd…) failed on Linux CI with a different hash (b36741bf…). Root cause: hashing `re`/`im` parts of top-5 taps at 1e-6 precision is too tight against libm differences in sin/cos/sqrt across glibc, MSVC, and Apple-clang. The previous "top-5 sorted by magnitude" form also suffered from rank instability when taps are near-tied — libm jitter could shuffle the ordering even when the algorithm is unchanged. New canonical form: full per-tap quantised-magnitude profile in natural index order, no sort. - 156 taps × 2 bytes (u16 le) per frame = 312 bytes/frame. - Quantisation 1e-2 — robust to ~1e-3 float drift while still tripping on real algorithmic changes (e.g., a 10× lambda shift moves magnitudes by >1e-2). - No top-K selection — eliminates the unstable magnitude-sort step. Regenerated expected_cir_features.sha256 — new hash 120bd7b1… If the next CI run still mismatches, the cause is structural (rustfft SIMD code path selection or NeumannSolver internal ordering), not magnitudes, and the witness needs further coarsening or to be made platform-tagged. Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
e96ebaea81 |
HOMECORE: native Rust/WASM/TS port of Home Assistant — ADRs 125-134 implementation (#800)
* feat(adr-125 iter 3): BFLD PrivacyGate + semantic-event naming at HAP boundary Inserts a Python equivalent of `wifi-densepose-bfld::PrivacyClass` + `PrivacyGate` between the rv_feature_state parser and the HAP toggle file. ADR-125 §2.1.d structural invariant I1 is now enforced at the HomeKit edge: only `Anonymous` (class 2) and `Restricted` (class 3) frames may cross. `Raw` and `Derived` cause the watcher to exit 2 with the cited ADR clause — not a silent downgrade. Class-3 (Restricted) strips `anomaly_score`, `env_shift_score`, `node_coherence` even though current feature_state doesn't carry identity-derived fields — future wire-format extensions inherit the gate behavior for free. Operator-facing semantic naming follows ADR-125 §2.1.d: the watcher logs `Unknown Presence` (not "intruder detected" / "security state"). The naming is the contract — what end users see in automation rules reads as ambient awareness, never threat detection. Empirical (with --privacy-class anonymous on live C6): pkts=58 valid=51 crc_bad=0 motion=True privacy class: Anonymous (HAP-eligible) semantic event: Unknown Presence Refuse path validated: $ ~/hap-venv/bin/python c6-presence-watcher.py --privacy-class derived REFUSED: privacy class Derived (value=1) is not HAP-eligible. ADR-125 §2.1.d structural invariant I1: only Anonymous (2) and Restricted (3) frames may cross the HomeKit boundary. $ echo $? 2 Branch: feat/adr-125-apple-fabric (kept off main while docker build for sha |
||
|
|
2bccdf5065 |
ADR-125 APPLE-FABRIC: RuView <-> Apple Home native HAP bridge (e2e on real C6) (#797)
* feat(adr-125 iter 3): BFLD PrivacyGate + semantic-event naming at HAP boundary
Inserts a Python equivalent of `wifi-densepose-bfld::PrivacyClass` +
`PrivacyGate` between the rv_feature_state parser and the HAP toggle
file. ADR-125 §2.1.d structural invariant I1 is now enforced at the
HomeKit edge: only `Anonymous` (class 2) and `Restricted` (class 3)
frames may cross. `Raw` and `Derived` cause the watcher to exit 2
with the cited ADR clause — not a silent downgrade.
Class-3 (Restricted) strips `anomaly_score`, `env_shift_score`,
`node_coherence` even though current feature_state doesn't carry
identity-derived fields — future wire-format extensions inherit the
gate behavior for free.
Operator-facing semantic naming follows ADR-125 §2.1.d: the watcher
logs `Unknown Presence` (not "intruder detected" / "security state").
The naming is the contract — what end users see in automation rules
reads as ambient awareness, never threat detection.
Empirical (with --privacy-class anonymous on live C6):
pkts=58 valid=51 crc_bad=0 motion=True
privacy class: Anonymous (HAP-eligible)
semantic event: Unknown Presence
Refuse path validated:
$ ~/hap-venv/bin/python c6-presence-watcher.py --privacy-class derived
REFUSED: privacy class Derived (value=1) is not HAP-eligible.
ADR-125 §2.1.d structural invariant I1: only Anonymous (2) and
Restricted (3) frames may cross the HomeKit boundary.
$ echo $?
2
Branch: feat/adr-125-apple-fabric (kept off main while docker build
for sha
|
||
|
|
23fe8012e0 |
feat(adr-118/p5.3): RumqttPublisher behind mqtt feature gate (176/176 GREEN with mqtt)
Iter 23. Production Publish trait impl using rumqttc 0.24 (same crate
version + use-rustls feature pinning as wifi-densepose-sensing-server,
so both publishers can share broker connection posture).
Added:
- rumqttc = "0.24" optional dep (default-features = false, use-rustls)
- New `mqtt` cargo feature: ["std", "dep:rumqttc"]
- src/rumqttc_publisher.rs (gated on `feature = "mqtt"`):
* RumqttPublisher wrapping rumqttc::Client + QoS + retain flag
* RumqttPublisher::new(client, qos) const constructor
* with_retain(bool) builder for availability-style topics
* RumqttPublisher::connect(opts, capacity) -> (Self, Connection)
Returns the unpumped Connection — caller spawns a thread that
iterates connection.iter() to drive the MQTT protocol. Default
QoS is AtLeastOnce (HA-DISCO recommendation for state topics).
* impl Publish with Error = rumqttc::ClientError
- pub use RumqttPublisher from lib.rs
tests/rumqttc_publisher_smoke.rs (7 named tests, all green, gated on mqtt):
rumqttc_publisher_constructs_without_broker
(uses 127.0.0.1:1 — reserved port refuses immediately; no hang)
with_retain_builder_yields_a_publisher
publish_queues_message_without_blocking_on_broker_state
*** Critical property: rumqttc's sync Client::publish queues into
an unbounded channel; publish_event returns Ok without round-
tripping to the (offline) broker. The queued packet only sends
if a thread iterates Connection::iter(). ***
restricted_event_publishes_four_messages_through_rumqttc
(class 3 + no zone: presence/motion/count/confidence — 4 topics)
publisher_trait_object_is_constructible
(Box<dyn Publish<Error = rumqttc::ClientError>> works)
direct_publish_call_through_trait_object
default_qos_is_at_least_once_via_connect
ACs progressed:
- ADR-122 §2.2 broker integration — production publisher now wired,
matching the sensing-server's TLS / version posture. The two
crates can share a single broker connection if an operator wants
both publishers in the same process.
- ADR-122 AC4 still enforced — publish_event's class-gated routing
is upstream of rumqttc, so no broker-level config can leak Raw frames.
Test config:
- cargo test --no-default-features → 72 passed (mqtt feature off)
- cargo test → 169 passed (mqtt feature off)
- cargo test --features mqtt --test rumqttc_publisher_smoke → 7 passed
- With --features mqtt: 169 + 7 = 176 total
Out of scope (next iter target):
- mosquitto integration test (env-gated MQTT_BROKER=tcp://localhost:1883):
* spawn a thread iterating Connection::iter()
* publish a BfldEvent
* subscribe in the test, await SubAck per the workspace memory note
`feedback_mqtt_integration_test_patterns`
* assert the topics received match render_events output
- BfldPipelineHandle: Arc<Mutex<BfldPipeline>> with a thread that pumps
inbound (inputs, embedding) → process → publish_event(&rumqttc_pub, &event)
for a single-call "set up MQTT publisher and walk away" API.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
0ca8a38cbc |
feat(adr-118/p3.5): SignatureHasher (BLAKE3-keyed) — 117/117 GREEN
Iter 15. Lands ADR-120 §2.3 — the cryptographic foundation of invariant
I3 ("cross-site identity correlation is impossible"). rf_signature_hash
is now derived from a per-site secret and a daily epoch, so two nodes
observing the same physical person produce uncorrelated 256-bit digests.
Added (no_std-compatible):
- blake3 = "1.5", default-features = false (no_std, no SIMD by default)
- src/signature_hasher.rs:
* Constants SECONDS_PER_DAY (86_400), SITE_SALT_LEN (32), RF_SIGNATURE_LEN (32)
* SignatureHasher { site_salt: [u8; 32] } with new(salt) const ctor
* compute(day_epoch, &features) -> [u8; 32] (BLAKE3 keyed mode)
* compute_at(unix_secs, &features) -> [u8; 32] convenience
* day_epoch_from_unix_secs(unix_secs) -> u32 helper (floor(t / 86400))
- pub use SignatureHasher, RF_SIGNATURE_LEN, SITE_SALT_LEN from lib.rs
tests/signature_hasher.rs (8 named tests, all green):
deterministic_under_identical_inputs
different_site_salts_produce_different_hashes
different_day_epochs_rotate_the_hash
different_features_produce_different_hashes
output_length_is_32_bytes
day_epoch_from_unix_secs_matches_floor_division
(covers 0, 86_399, 86_400, and the 1.7e9 modern timestamp)
compute_at_matches_compute_with_derived_day
cross_site_hamming_distance_is_statistically_high
*** ADR-120 §2.7 AC2 acceptance test ***
Runs 100 trials with distinct (salt_a, salt_b) pairs observing
identical features, computes per-trial Hamming distance, asserts
mean >= 120 bits and min >= 80 bits. Empirically lands at ~128 bits
mean (the expected value for two independent 256-bit hashes), with
no trial below 80 bits — i.e., zero suspicious near-collisions.
ACs progressed:
- ADR-120 §2.7 AC2 — structurally enforced cross-site isolation, now
proven empirically by the Hamming-distance test. This is the
cryptographic half of invariant I3 in code, not just docs.
- ADR-118 invariant I3 — first runtime witness that two sites with
independent site_salts cannot correlate the same person's signature.
Test config:
- cargo test --no-default-features → 72 passed (64 + 8; signature_hasher is no_std)
- cargo test → 117 passed (109 + 8)
Out of scope (next iter target):
- Wire SignatureHasher into BfldEmitter: replace caller-supplied
rf_signature_hash with hasher.compute_at(ts, &features) so the
pipeline produces correct hashes end-to-end.
- IdentityFeatures canonical-bytes encoder so callers don't need to
hand-serialize per-feature representations.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
926c66f677 |
feat(adr-118/p4.1): BfldEvent privacy-gated output + JSON (102/102 GREEN)
Iter 13. Lands ADR-121 §2.1 (output event) + ADR-122 §2.1 (field-gating
policy). BfldEvent collapses the GateAction-driven sensing pipeline
into the canonical wire-format publishable on MQTT.
Added:
- serde (workspace, derive feature, optional) + serde_json (workspace, optional) deps
- New crate feature `serde-json` (default-on; requires `std`)
- src/event.rs (gated on `feature = "std"`):
* BfldEvent struct with all sensing + identity-derived fields
* with_privacy_gating(...) constructor that applies field-gating policy:
class < Restricted (3): identity_risk_score + rf_signature_hash kept
class >= Restricted (3): both nulled to None
* apply_privacy_gating() — idempotent in-place masking
* to_json() -> Result<String, serde_json::Error> (gated on serde-json)
* Custom ser_privacy_class serializer emits lowercase names
("anonymous", "restricted", etc.) per the BFLD JSON spec
* skip_serializing_if = "Option::is_none" on identity-derived fields so
privacy-gated events are observationally indistinguishable from
events that never had the field set
- pub use BfldEvent from lib.rs
tests/event_privacy_gating.rs (9 named tests, all green):
anonymous_event_retains_identity_risk_and_hash
restricted_event_strips_identity_fields (class 3 → None)
apply_privacy_gating_is_idempotent
event_type_is_always_bfld_update (parameterized over 3 classes)
json::json_round_trip_emits_type_field_first_or_last_but_present
json::anonymous_json_includes_identity_fields
json::restricted_json_omits_identity_fields_entirely
(asserts the JSON string does NOT contain identity_risk_score or
rf_signature_hash, verifying skip_serializing_if works as intended)
json::privacy_class_serializes_to_lowercase_name
json::zone_id_none_is_omitted_from_json
ACs progressed:
- ADR-121 AC6 (identity_risk score absent at class 3) — structurally
enforced by with_privacy_gating + skip_serializing_if combination.
- ADR-122 AC1 — JSON shape matches the HA-DISCO publishable event
contract; identity fields can be reliably stripped by privacy_class.
- ADR-118 AC5 — privacy_mode = engaged maps to PrivacyClass::Restricted
with no identity fields in the published event.
Test config:
- cargo test --no-default-features → 64 passed (unchanged; event cfg-out)
- cargo test → 102 passed (93 + 9)
Out of scope (next iter target):
- Emitter struct that wires GateAction + privacy class + sensing inputs
into BfldEvent construction (ADR-118 §2.1 pipeline diagram).
- MQTT topic publisher (ADR-122 §2.2) — depends on a runtime (tokio).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
775661b2e8 |
feat(adr-118/p1.4): BfldFrame (header + payload + CRC32) — 24/24 GREEN
Iter 4. Lands the central wire-format primitive: complete frames with
header + arbitrary-length payload, protected by CRC-32/ISO-HDLC.
Added:
- crc = "3" dependency (CRC-32/ISO-HDLC, same poly as Ethernet / zlib)
- src/frame.rs: CRC32_ALG const and crc32_of_payload(&[u8]) -> u32
- src/frame.rs: BfldFrame { header, payload: Vec<u8> } (gated on `std`)
* BfldFrame::new(header, payload) — auto-syncs payload_len + payload_crc32
* BfldFrame::to_bytes() -> Vec<u8> — header LE bytes ‖ payload
* BfldFrame::from_bytes(&[u8]) -> Result<Self, BfldError>
- BfldError::TruncatedFrame { got, need } variant
- Doc strings on BfldError::Crc and BfldError::PrivacyViolation field names
- tests/frame_roundtrip.rs (7 named tests, gated on feature = "std"):
frame_roundtrip_preserves_header_and_payload
frame_new_syncs_payload_len_and_crc
frame_serialization_is_deterministic
frame_rejects_payload_crc_mismatch
frame_rejects_truncated_buffer_smaller_than_header
frame_rejects_truncated_buffer_smaller_than_payload
empty_payload_is_valid (CRC of empty payload is 0x00000000)
Test config:
- cargo test --no-default-features → 17 passed (frame_roundtrip cfg-out)
- cargo test (default features = std) → 24 passed (3+6+7+8)
ADR-119 ACs progressed:
- AC4 partial: bad-magic + bad-version + CRC-mismatch + truncation rejected
with typed errors; field-level masking lives in the privacy_gate iter.
- AC5: BfldFrame round-trip preserves header + payload + CRC.
- AC6: Identical inputs produce bit-identical bytes (asserted explicitly).
Out of scope (next iter):
- Payload section parser (compressed_angle_matrix, amplitude_proxy, ...)
— only the byte buffer is opaque so far; sections need length prefixes.
- BfldFrameRef<'_> for ESP32-S3 self-only mode (no-alloc, ADR-123 §2.5).
- PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
c965e3e6c0 |
feat(adr-118/p1): scaffold wifi-densepose-bfld crate + frame header (3/3 tests GREEN)
Land P1 of the BFLD rollout — the wire-format primitives: - New workspace member: v2/crates/wifi-densepose-bfld - PrivacyClass enum (Raw/Derived/Anonymous/Restricted) with allows_network() and allows_matter() const helpers reflecting ADR-120 §2.2 and ADR-122 §2.4 - BfldFrameHeader (#[repr(C, packed)]) per ADR-119 §2.1 - BFLD_MAGIC = 0xBF1D_0001, BFLD_VERSION = 1 - BfldError variants for InvalidMagic / UnsupportedVersion / Crc / PrivacyViolation - soul-signature cargo feature (gated, default OFF) per ADR-118 §1.4 - Compile-time size assertion via static_assertions::const_assert_eq! - 3 acceptance tests in tests/frame_header_size.rs (all pass) Bug fix: - ADR-119 AC1 claimed BfldFrameHeader is 40 bytes. Actual packed layout sums to 86 bytes. Updated AC1 and §2.1 prose to match. const_assert in frame.rs pins the value structurally — a future field addition that breaks the size fails to compile. Out of scope for this iter (deferred to later P1 commits): - Field-level missing-docs warnings (21) — addressed alongside accessor helpers - Payload section parsing — needs the section-length prefix tests - Round-trip serialize/parse — covered by a fixture-based test in the next iter cargo test -p wifi-densepose-bfld --no-default-features → 3 passed, 0 failed Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
34eced880f |
cog-ha-matter (ADR-116 P4): MdnsService -> mdns-sd ServiceInfo bridge
Pure conversion from our wire-format `MdnsService` to the
`mdns_sd::ServiceInfo` shape the responder daemon consumes. No
socket binding, no daemon registration yet — that lands next iter
as a `runtime::spawn_mdns_responder(info)` JoinHandle returning
helper, same shape as `runtime::spawn_publisher`.
* `MdnsService::to_service_info(hostname, ipv4) ->
Result<ServiceInfo, mdns_sd::Error>`
* `mdns-sd = "0.11"` added — aligned with the workspace pin from
wifi-densepose-desktop so the lockfile doesn't fork dalek-like
surfaces.
3 new tests:
* to_service_info_carries_service_type_and_port — locks that
`_ruview-ha._tcp` (with or without mdns-sd's trailing-dot
normalisation) and the control port round-trip through the
conversion
* to_service_info_propagates_txt_records — every locked TXT
key from iter 4 (cog_id, mqtt_port, privacy, proto, node_id,
cog_version) reachable via `get_property_val_str` on the
converted ServiceInfo
* to_service_info_does_not_silently_drop_caller_hostname —
locks the caller-side responsibility for the .local. suffix.
mdns-sd 0.11 accepts bare hostnames (verified empirically by
initial test expecting it to reject — it didn't), so the
wrapper layer must do the trailing-dot dance. Documenting
that via a named test catches future bumps where the lib
starts mutating the value.
63/63 cog tests green (60 → 63).
ADR-116 P4 now ⁶⁄₇: ✅ mDNS record-builder, ✅ chain, ✅ JSONL, ✅
file persistence, ✅ Ed25519 signing, ✅ ServiceInfo conversion;
⏳ daemon register + embedded broker.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
bb154d4e78 |
cog-ha-matter (ADR-116 P4): Ed25519 signing layer for witness chain
Closes the cryptographic-attestation gap in ADR-116 §2.2: every
witness event can now be signed by the Seed's Ed25519 key, with
verify available to any auditor holding the public key.
Module shape (`src/witness_signing.rs`, kept separate from
`witness::` so the hash chain stays usable without dalek linked
in — important for the wasm32 audit-verifier variant we'll ship
later):
* sign_event(event, &SigningKey) -> Signature
* verify_signature(event, &Signature, &VerifyingKey)
-> Result<(), SignatureVerifyError>
* signature_to_hex / signature_from_hex (128-char lowercase,
matches the witness hex convention)
* SignatureVerifyError::Invalid
* SignatureParseError::{Length, Hex}
Key design point: signature covers the SAME canonical bytes
witness::hash_event hashes. That means:
1. A signed event commits to the entire event content (kind,
payload, timestamp, seq, prev_hash) — no field can be
retroactively changed without invalidating both the hash AND
the signature.
2. The signature implicitly commits to the event's *chain
position* via prev_hash — splicing a signed event into a
different chain breaks verification.
Adds `ed25519-dalek = "2.1"` to cog-ha-matter (already in
workspace via ruv-neural, version kept aligned).
9 new tests:
* sign_and_verify_round_trip
* verify_rejects_signature_under_wrong_key
* verify_rejects_tampered_event (mutate payload after sign)
* verify_rejects_event_with_wrong_prev_hash (splice attack)
* signature_hex_round_trip
* signature_from_hex_rejects_wrong_length
* signature_from_hex_rejects_non_hex
* signature_is_deterministic_for_same_event_and_key
(locks Ed25519's determinism — catches future accidental
swap to a randomized scheme)
* different_events_produce_different_signatures
60/60 cog tests green (51 → 60). Key management is intentionally
out of scope here — the cog runtime reads the Seed's key from the
Cognitum control plane's secure store (separate concern).
ADR-116 P4 now ⁵⁄₆: ✅ mDNS record, ✅ chain, ✅ JSONL, ✅ file
persistence, ✅ Ed25519 signing; ⏳ responder + embedded broker.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
fe913b0ea7 |
cog-ha-matter (ADR-116 P4): pure witness hash-chain primitive
Second P4 unit: an append-only SHA-256 hash chain for tamper-evident
audit logging. ADR-116 §2.2 promised this for healthcare /
education / shared-housing deployments — this lands the primitive
with no key dependency so the next iter can layer Ed25519 signing
on top without touching the chain itself.
Module shape:
* `WitnessHash([u8; 32])` newtype + `WitnessHash::GENESIS` sentinel
* `WitnessEvent { seq, prev_hash, ts, kind, payload, this_hash }`
— once committed, every field is immutable
* `WitnessChain` — `append`, `tip`, `verify`, `events`
* `canonical_bytes` — length-prefixed serialization that prevents
the classic concatenation forgery
(`abc|def` ≠ `ab|cdef`)
* `WitnessVerifyError` — auditor-friendly error with `at: usize`
on every variant (SeqGap, PrevHashMismatch, HashMismatch)
13 new tests covering both happy path and active tampering:
* genesis hash all-zeros
* empty chain tip is genesis
* canonical bytes length-prefixed (anti-forgery)
* canonical bytes start with prev_hash (wire-format lock)
* append links to prev_hash
* seq monotonic from 0
* verify passes on clean chain
* verify catches tampered payload (fires HashMismatch)
* verify catches broken prev_hash link
* verify catches seq gap
* hash hex is 64 lowercase chars
* first event prev_hash == GENESIS (auditor anchor)
* different payloads → different hashes
Hash-chain over Merkle is the right tradeoff for the cog's event
rate (a few/min steady, dozens during a fall) — linear scan is
fine and we save the Merkle complexity for a future tier when
chains span days.
34/34 cog tests green (21 → 34).
ADR-116 P4 row updated to enumerate the three P4 sub-units shipped /
pending: (a) mDNS record-builder ✅, (b) witness hash-chain ✅, (c)
responder + embedded broker + Ed25519 signing pending.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
5723f505b7 |
cog-ha-matter (ADR-116 P3): extract pure publisher-input builder
Adds `runtime::build_publisher_inputs(host, port, privacy, identity)` —
the side-effect-free helper that turns the cog's CLI surface into the
`(MqttConfig, OwnedDiscoveryBuilder)` pair ADR-115's `publisher::spawn`
consumes. Keeps the tokio runtime wiring out of the pure unit so the
mDNS responder + Seed control plane (P4) can build the same inputs
from different sources without going through clap.
8 new tests lock the wire-format invariants:
* host/port round-trip into MqttConfig
* privacy_mode propagation (P1 dossier item 7, FDA Jan 2026)
* discovery_prefix defaults to "homeassistant"
* discovery carries node_id + sw_version + friendly_name
* via_device advertises COG_ID (ADR-101/102 device-registry shape)
* client_id includes node_id (lesson from ADR-115 iter 45-48 session
takeover post-mortem — two publishers sharing a client_id loop)
* tls defaults to Off for v1 LAN-only (lock against silent enablement)
* default_identity carries CARGO_PKG_VERSION + PID for uniqueness
Plus the existing 2 manifest tests → 10/10 green
(`cargo test -p cog-ha-matter --no-default-features --lib`).
Also lands the deep-researcher dossier (`docs/research/ADR-116-ha-...`)
that the ADR §3+§4 reference — it was produced last iter but only the
ADR was committed; this puts the source-of-truth into the tree so the
ADR's "8 sections, 30+ citations" claim is actually verifiable.
P3 status in the ADR phase table flipped from "pending" to "in progress"
with the helper named; next iter tokio::spawns publisher::run(...) in
main.rs and registers the mDNS responder.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
00a234eda8 |
ADR-110: ESP32-C6 firmware extension (#764)
Closes the firmware-side ADR-110 design at v0.7.0-esp32 after a 38-iter /loop SOTA sprint. Headline (bench, COM9+COM12 ESP32-C6): - 99.56% cross-board RX, 104.1 µs smoothed offset stdev (≤100 µs §2.4 target met) - 3.95× EMA suppression, 1.4 ppm crystal skew preserved 4 firmware releases: v0.6.7 / v0.6.8 / v0.6.9 / v0.7.0-esp32. 42 ADR-110 unit tests, 1761 v2 workspace tests, full Firmware CI + QEMU green. |
||
|
|
004a63e82d |
fix(security): audit — fix RUSTSEC vulns, clippy warnings, dead code (#769)
- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4 - Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml with per-entry rationale - Fix all `cargo clippy --all-targets -- -D warnings` errors across 35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/ is_none_or, await_holding_lock (drop MutexGuard before .await), ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect, useless_vec, lines_filter_map_ok, print_literal, dead_code - Apply `cargo fmt --all` - Pre-existing test failure in wifi-densepose-signal (test_estimate_occupancy_noise_only) is not introduced by this PR |
||
|
|
1906876541 |
fix: upgrade openssl to 0.10.78 (CVE-2026-41676) (#751)
* fix: CVE-2026-41676 security vulnerability Automated dependency upgrade by OrbisAI Security * fix: upgrade openssl to 0.10.78 (CVE-2026-41676) rust-openssl provides OpenSSL bindings for the Rust programming langua Resolves CVE-2026-41676 |
||
|
|
6959a42312 |
feat(cog-person-count): v0.0.1 scaffold + tests + fusion math + bench (ADR-103) (#694)
First implementation PR for ADR-103. Same incremental shape that
ADR-101 used: scaffold the cog crate, ship a stub-backend release
that satisfies the runtime contract + 15 tests + measured cold-start,
then follow up with the trained count_v1.safetensors in a separate PR.
What ships:
* v2/crates/cog-person-count/ — new workspace member.
- Cargo.toml: candle-core/candle-nn 0.9 (cpu default, cuda feature
opt-in), safetensors, ureq, sha2 — same dep shape as the pose cog
but minus wifi-densepose-train (this cog has no training-side
consumer, so the dep tree is materially smaller → 2.36 MB
binary vs the pose cog's 4.5 MB).
- src/inference.rs: CountNet (Conv1d 56→64→128→128 encoder + count
head Linear(128→64→8)+softmax + confidence head
Linear(128→32→1)+sigmoid). Stub backend returns
`{1-person, 0-confidence}` honestly when no safetensors present.
- src/fusion.rs: fuse_confidence_weighted() — Bayesian product of
per-node distributions with confidence-weighted log-sum, plus
fuse_with_mincut_clip() hook for the v0.2.0 Stoer-Wagner
upper-bound (`ruvector-mincut` dep lands when min-cut graph
builder is ready). Confidences floored at 1e-3 and probs floored
at 1e-9 before logs — no NaN propagation.
- src/publisher.rs: emits {count, confidence, count_p95_low,
count_p95_high, n_nodes, probs} per ADR-103 §"Output".
- src/main.rs: full ADR-100 four-verb CLI (version|manifest|health
|run). The `run` subcommand explicitly returns "wiring pending
v0.0.1" so the in-process library API is the v0.0.1-clean
integration path.
- tests/smoke.rs (8 tests) + fusion::tests (7 tests, in-lib) — 15
total, all green. Cover stub-backend behaviour, wrong-shape
rejection, fusion math (empty / single / agreement / high-conf
override / normalisation), p95-range correctness, and min-cut
clip semantics.
- cog/{manifest.template.json, config.schema.json, README.md} +
cog/artifacts/ placeholder dir.
* v2/Cargo.toml: registers the new workspace member.
Verified locally:
cargo check -p cog-person-count --no-default-features → clean
cargo test -p cog-person-count --no-default-features → 8/8 pass
cargo test -p cog-person-count --lib → 7/7 pass
cargo build -p cog-person-count --release → 2.36 MB binary
./cog-person-count version → "person-count 0.3.0"
./cog-person-count manifest → JSON skeleton
./cog-person-count health → backend:stub,
count:1, conf:0,
p95:[1,1]
Cold-start: 30 sequential `health` invocations → 53.3 ms/invocation
(vs cog-pose-estimation's 76.2 ms — smaller dep tree)
cog/README.md adds:
* Security section — six-row threat table covering safetensor mmap
trust, non-finite outputs, sensing fetch failures, fusion
divide-by-zero / log-of-zero, min-cut degenerate cases, and stdout
spoofing.
* Performance / optimization section — binary size, release profile
(already opt-level=3 / lto=fat / codegen-units=1 / strip=true at
workspace level), cold-start comparison table, projected warm-path
latency budget.
Still pending (separate PRs, ADR-103 §"Migration"):
* Train count_v1.safetensors on the existing 1,077 paired samples
with `n_persons` labels (Candle on RTX 5080, same script that
produced pose_v1.safetensors yesterday).
* `run` subcommand wiring (long-running polling loop, same shape as
cog-pose-estimation::runtime).
* Cross-compile + sign + GCS upload (mirror of cog-pose-estimation
release pipeline).
* Server-side `csi.rs::score_to_person_count` call-site rewire to
consume this cog when installed; falls back to PR #491's heuristic
when not.
|
||
|
|
67fec45e61 |
feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry (#648)
* feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry Adds a new sensing-server endpoint that fetches and caches the canonical Cognitum app registry at https://storage.googleapis.com/cognitum-apps/app-registry.json (105 cogs across 11 categories as of v2.1.0). RuView previously had no live awareness of the catalog — the README's capability table was hand- curated and went stale as Cognitum shipped new cogs (the registry was last updated 6 days ago). ADR: * docs/adr/ADR-102-edge-module-registry.md — full design, response shape, configuration flags, failure modes, and a 12-row security review covering SSRF, response inflation, ?refresh abuse, stale-serve semantics, TLS, cache poisoning, JSON-panic resistance, etc. Code: * v2/.../edge_registry.rs — EdgeRegistry struct + UreqFetcher + MockFetcher trait + 7 unit tests. RwLock<Option<CachedEntry>> with stale-on-error fallback. MAX_PAYLOAD_BYTES=8 MiB, 10s wire timeout. * v2/.../main.rs — constructs Option<Arc<EdgeRegistry>> at startup, registers GET /api/v1/edge/registry handler, wires Extension layer. Handler runs the blocking ureq fetch via tokio::task::spawn_blocking so the async runtime stays free. * v2/.../cli.rs / main.rs Args — three new flags (per user request to "allow the registry to be disabled or changed"): --edge-registry-url <URL> (env RUVIEW_EDGE_REGISTRY_URL) --edge-registry-ttl-secs <N> (env RUVIEW_EDGE_REGISTRY_TTL_SECS) --no-edge-registry (env RUVIEW_NO_EDGE_REGISTRY) When --no-edge-registry is set or the URL is empty, the endpoint returns 404. Cargo.toml: adds ureq (rustls), sha2, thiserror as direct deps. README: * New collapsed "🧩 Edge Module Catalog" section with the full 105-cog table generated from the registry, grouped by category with practical one-line descriptions (e.g. "Spots irregular heartbeats and abnormal heart rhythms", "Detects walking problems and scores fall risk"). Links to https://seed.cognitum.one/store and the local appliance /cogs page. Sits between the HF model section and How It Works. Tests (7/7 pass): first_call_hits_upstream_and_caches ttl_expiry_triggers_refetch force_refresh_bypasses_fresh_cache stale_serve_on_upstream_failure_after_cached_success no_cache_no_upstream_returns_error upstream_invalid_json_is_treated_as_error upstream_sha256_is_deterministic Security highlights (full review in ADR-102 §"Security review"): - The registry is metadata-only; per-cog binary signatures (ADR-100) remain the trust root for installs. A compromised registry can mislead a human reader but cannot ship malicious binaries. - 8 MiB cap + 10s timeout + Option<Arc<...>> via Extension layer means the endpoint can't be used to exhaust memory or pin tokio threads. - Stale-on-error responses carry an explicit `stale: true` field so upstream outages are visible to consumers rather than silently masked. - Endpoint sits behind the existing RUVIEW_API_TOKEN bearer gate when set, otherwise unauthenticated (registry contents are public anyway). * chore: refresh Cargo.lock for ureq/sha2/thiserror deps added by ADR-102 |
||
|
|
3314c8db8d |
feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101) (#642)
* feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101) Adds the foundation for the pose-estimation Cog that ships from this repo into Cognitum V0 appliances. Companion ADR-225 + crate land in cognitum-one/v0-appliance. ADRs: * ADR-100 formalises the Cognitum Cog packaging spec — on-device layout under /var/lib/cognitum/apps/<id>/, manifest.json schema (incl. new binary_sha256 + binary_signature fields), GCS hosting convention, repo source layout, build pipeline, and the four-verb runtime contract (version | manifest | health | run). Documents the convention I reverse-engineered from inspecting installed cogs on a live cognitum-v0 appliance — `anomaly-detect`, `presence`, `seizure-detect`, etc. * ADR-101 designs the pose-estimation Cog itself: where it sits in the wifi-densepose pipeline (encoder init from ruvnet/wifi-densepose-pretrained, 17-keypoint regression head), what gets shipped per target arch (arm / x86_64 / hailo8 / hailo10), acceptance gates (PCK@20 explicitly deferred to #640 — this ADR ships the vehicle, not the accuracy). Crate v2/crates/cog-pose-estimation/: * Cargo.toml + workspace member declaration with a hailo feature gate so the binary builds without the Hailo SDK in CI. * main.rs implements the four-verb CLI exactly per ADR-100. * config.rs / manifest.rs / publisher.rs / inference.rs / runtime.rs — small modules, each <100 lines. * publisher.rs emits ADR-100 structured JSON events. * inference.rs is a stub that produces a centred-skeleton baseline with confidence=0 (honest: no trained weights wired in yet). * runtime.rs subscribes to /api/v1/sensing/latest, slides a 56*20 window, runs the engine, emits pose.frame events. * cog/manifest.template.json + cog/config.schema.json define the release artifact + runtime config schemas. * cog/Makefile holds build / sign / upload targets. * tests/smoke.rs covers manifest roundtrip + engine I/O surface. Verified locally: * cargo check -p cog-pose-estimation: clean. * cargo test -p cog-pose-estimation: 4/4 pass. * ./target/release/cog-pose-estimation {version,manifest,health}: all emit the right contract output. This commit contains scaffolding only; the actual trained weights and Hailo HEF cross-compile come in follow-ups tracked in #640 and the companion v0-appliance branch. * feat(cog-pose-estimation): first measured run — Candle CUDA on RTX 5080 Trained pose_v1 on ruvultra (RTX 5080) via Candle 0.9 + cuda feature against the same 1,077-sample paired session that produced 0%/0% PCK in #640 with the pure-JS SPSA trainer. First real numbers: PCK@20 = 3.0% (up from 0.0%) PCK@50 = 18.5% (up from 0.0%) MPJPE = 0.093 (down from 0.66, ~7x improvement) 400 epochs in 2.1 s wall time, full-batch, ~5 ms/epoch. Loss curve 0.181 -> 0.014 over the run, eval 0.010. Per-joint reveals the model leans on right-side proximal joints (r_hip 77% PCK@50, r_knee 35%, l_elbow 26%) — consistent with the camera framing in the source recording. Distal joints (wrists, ankles) and face joints are still near-random, consistent with the 56-subcarrier / 20-frame input not carrying fine-grained spatial info at 1077 samples. This commit: * Adds v2/crates/cog-pose-estimation/cog/artifacts/{pose_v1.safetensors, train_results.json} so the cog dir now contains a real reference artifact, not just scaffold. * Updates cog/README.md "Status" block with the measured numbers, per-joint table, and an honest reading of where the model succeeds vs where the data is the bottleneck. * Adds docs/benchmarks/pose-estimation-cog.md as the canonical benchmark log — append-only, one section per published run. * Appends a "First measured run" section to ADR-101 referencing the new benchmark file. Still pending in the follow-up: * Wire pose_v1.safetensors into src/inference.rs (replace stub). * ONNX export (Candle lacks a writer — needs external conversion). * Hailo HEF cross-compile + cluster deploy. The data-bound gap to PCK@20 >= 35% is tracked in #640. * feat(cog-pose-estimation): wire real weights — cog is no longer a stub Replaces the centred-skeleton stub in src/inference.rs with a real Candle-based loader that reads cog/artifacts/pose_v1.safetensors and runs the trained Conv1d encoder + MLP pose head on every incoming CSI window. What changes: * src/inference.rs: PoseNet mirrors the training script's architecture exactly — Conv1d(56->64, k=3 d=1), Conv1d(64->128, k=3 d=2), Conv1d(128->128, k=3 d=4), mean over time, Linear(128->256)+ReLU, Linear(256->34)+sigmoid -> reshape [17, 2]. The InferenceEngine searches a sensible candidate list for the weights file (/var/lib/cognitum/apps/pose-estimation/, ./pose_v1.safetensors, ./cog/artifacts/, repo-root, v2/-relative) and falls back to the stub when none are present so the cog still satisfies ADR-100. * Cargo.toml: adds candle-core 0.9 + candle-nn 0.9 (no-default-features, CPU build by default) + safetensors 0.4. New `cuda` feature opt-in for GPU inference on hosts that have it. Drops the unused wifi-densepose-train path dep from the default build path. * src/main.rs + src/publisher.rs: health.ok event now carries `backend` (candle-cuda | candle-cpu | stub) and the synthetic output confidence, so operators can tell at a glance whether the cog loaded its weights or fell back to the stub. * tests/smoke.rs: adds `real_weights_load_when_available` which asserts the loaded engine reports backend=candle-* and emits non-zero confidence — exactly the signal that proves we're not silently degrading to the stub. Verified locally: * `cargo check -p cog-pose-estimation --no-default-features` — clean * `cargo test -p cog-pose-estimation --no-default-features` — 5/5 pass * `./target/release/cog-pose-estimation health` emits: {"event":"health.ok","fields":{"backend":"candle-cpu","cog":"pose-estimation","synthetic_output_confidence":0.185}} — 0.185 is the published PCK@50 from cog/artifacts/train_results.json, emitted by the real Candle inference path (would be 0.0 if it had fallen back to the stub). The cog now runs the trained pose_v1 model end-to-end. Accuracy is still bounded by the underlying 1077-sample training data (PCK@20 3.0%, PCK@50 18.5% per docs/benchmarks/pose-estimation-cog.md) — that gap is data-bound and tracked in #640. ONNX export + Hailo HEF cross-compile remain follow-ups. * docs(benchmarks): measure cog-pose-estimation cold-start latency 100 sequential `cog-pose-estimation health` invocations average 76.2 ms each on a Windows x86_64 host using the `candle-cpu` backend. Each invocation re-loads pose_v1.safetensors and runs one synthetic forward pass, so this is the worst-case cold-start path. Long-running `run` inference will be sub-millisecond per frame once the model is loaded. Updates the benchmarks doc accordingly. * feat(cog-pose-estimation): ONNX export — pose_v1.onnx + scripts/export-onnx.py Adds the canonical ONNX artifact that unblocks downstream Hailo HEF cross-compile + ONNX Runtime benchmarks. Generated on ruvultra (torch 2.12.0 + CUDA), 12,059 bytes, opset 18, dynamic batch axis. * scripts/export-onnx.py: mirrors the Candle inference architecture in PyTorch (Conv1d 56->64, 64->128, 128->128 + Linear 128->256->34), pure- python safetensors loader (no extra pip dep), exports via torch.onnx.export, then verifies via onnx.checker.check_model and numerical parity against the torch reference. * Verified parity vs torch: max |torch - onnx| = 8.94e-8 (1e-5 threshold). Effectively bit-perfect. * v2/crates/cog-pose-estimation/cog/artifacts/pose_v1.onnx — the artifact itself, 12 KB. * docs/benchmarks/pose-estimation-cog.md — adds an ONNX export section with the verification numbers. Next: Hailo HEF cross-compile (still gated on Hailo SDK on a self-hosted runner) and ONNX Runtime latency benchmarks on each target arch. * feat(cog-pose-estimation): release v0.0.1 — signed aarch64 binary on GCS End-to-end deploy: cross-compiled to aarch64-unknown-linux-gnu on ruvultra, ran via qemu-aarch64-static, then smoke-tested on a real cognitum-v0 Pi 5. Signed with COGNITUM_OWNER_SIGNING_KEY (Ed25519) and uploaded to gs://cognitum-apps/cogs/arm/. Real-hardware results on cognitum-v0 (Pi 5): health: backend=candle-cpu, confidence=0.185, real weights loaded 30x sequential `health`: 0.251 s total -> 8.4 ms / invocation (cold) GCS release artifacts (publicly downloadable): binary: 3,741,976 bytes sha256 1e1a7d3dd01ca05d5bfc5dbb142a5941b7866ed9f3224a21edc04d3f09a99bf5 weights: 507,032 bytes sha256 eb249b9a6b2e10130437a10976ed0230b0d085f86a0553d7226e1ae6eae4b9e5 signature (Ed25519, b64): LUN7xqLPYD3MFzm5dKB5MnYU0LvoRtek5ci5KiKPHBg+Xo6xuazwokn2Dw2JPMaLYJzmWn/SpT4djuR7hYvVDw== Adds: * v2/crates/cog-pose-estimation/cog/artifacts/manifest.json — the release-pipeline-produced manifest with all fields filled in per ADR-100, including arch, target_triple, signature, and a build_metadata block carrying the validation PCK numbers. * docs/benchmarks/pose-estimation-cog.md — new sections covering the real Pi 5 smoke (8.4 ms cold-start) and the signed GCS release artifacts. Verified by downloading the binary anonymously from GCS and re-computing the sha256 — matches the locally-computed sha exactly. Signature decoded to the expected 64-byte Ed25519 length. Closes the GCS-upload acceptance criterion from ADR-100; the only pending work is Hailo HEF cross-compile (still SDK-gated) and an x86_64 release alongside this arm release. * docs(benchmarks): record live cognitum-v0 install + 5-sec smoke run Adds the "Live appliance install" section documenting what happened when the signed v0.0.1 binary + weights were installed under /var/lib/cognitum/apps/pose-estimation/ on cognitum-v0 (the V0 cluster leader). * Layout matches the existing anomaly-detect / presence / seizure- detect cogs exactly — the Cogs dashboard at http://cognitum-v0:9000/cogs auto-discovers entries. * `cog-pose-estimation run` ran for 5 seconds in the background and cleanly emitted run.started + structured WARN events for the missing local sensing-server on :3000 (cognitum-v0's actual CSI source is ruview-vitals-worker on :50054, not :3000). No crashes, no NaN, no leaks. * Wiring `sensing_url` to the appliance-native source is a separate Day-2 integration task. |
||
|
|
1b155ad027 |
chore: remove empty stub crates wifi-densepose-{api,db,config} (closes #578) (#608)
Each of these crates was a single-line doc-comment placeholder: v2/crates/wifi-densepose-api/src/lib.rs: //! WiFi-DensePose REST API (stub) v2/crates/wifi-densepose-db/src/lib.rs: //! WiFi-DensePose database layer (stub) v2/crates/wifi-densepose-config/src/lib.rs: //! WiFi-DensePose configuration (stub) with empty [dependencies] in their Cargo.toml and zero references from any source file or Cargo.toml in the workspace (verified by `grep -rln wifi-densepose-api/-db/-config` across `v2/`). They were reserved early for an envisioned REST/database/config split that never materialised. The functionality these would have provided is covered today by: - REST/WS: wifi-densepose-sensing-server (Axum) - Config: per-crate config + CLI args in sensing-server and desktop - DB: no persistent state; system is real-time Removal prevents `cargo` from listing dead crates, shipping empty published artifacts to crates.io, or wasting reviewer attention. If any of these names is needed in the future, reintroduce them with a real implementation. Per the issue reporter (@bannned-bit / Matad0r) #578 explicitly listed "OR be removed from workspace members until implementation starts" as an acceptable resolution. Updated: - `v2/Cargo.toml`: drop the three members (with inline comment explaining why) - `v2/Cargo.lock`: regenerated by cargo check - `CLAUDE.md`: drop the three rows from the crate table and the publishing order list - `CHANGELOG.md`: add an `[Unreleased] / Removed` entry Verified: - `cd v2 && cargo check --workspace --no-default-features` -> finished in 48s, no errors (warnings unchanged) |
||
|
|
a80617ee84 |
chore(deps): bump console from 0.15.11 to 0.16.3 in /v2 (#471)
Bumps [console](https://github.com/console-rs/console) from 0.15.11 to 0.16.3. - [Release notes](https://github.com/console-rs/console/releases) - [Changelog](https://github.com/console-rs/console/blob/main/CHANGELOG.md) - [Commits](https://github.com/console-rs/console/compare/0.15.11...0.16.3) --- updated-dependencies: - dependency-name: console dependency-version: 0.16.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
afc86c6fc4 |
chore(deps): bump thiserror from 1.0.69 to 2.0.18 in /v2 (#469)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to 2.0.18. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.69...2.0.18) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.18 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
e6710e8988 |
chore(deps): bump ndarray-linalg from 0.16.0 to 0.18.1 in /v2 (#477)
Bumps [ndarray-linalg](https://github.com/rust-ndarray/ndarray-linalg) from 0.16.0 to 0.18.1. - [Release notes](https://github.com/rust-ndarray/ndarray-linalg/releases) - [Commits](https://github.com/rust-ndarray/ndarray-linalg/compare/ndarray-linalg-v0.16.0...ndarray-linalg-v0.18.1) --- updated-dependencies: - dependency-name: ndarray-linalg dependency-version: 0.18.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
ab9799adc3 |
chore(deps): bump tower-http from 0.5.2 to 0.6.8 in /v2 (#483)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.5.2 to 0.6.8. - [Release notes](https://github.com/tower-rs/tower-http/releases) - [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.5.2...tower-http-0.6.8) --- updated-dependencies: - dependency-name: tower-http dependency-version: 0.6.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
bdb4484259 |
chore(deps): bump tch from 0.14.0 to 0.24.0 in /v2 (#482)
Bumps [tch](https://github.com/LaurentMazare/tch-rs) from 0.14.0 to 0.24.0. - [Release notes](https://github.com/LaurentMazare/tch-rs/releases) - [Changelog](https://github.com/LaurentMazare/tch-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/LaurentMazare/tch-rs/commits) --- updated-dependencies: - dependency-name: tch dependency-version: 0.24.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
ba370c7b08 |
chore(deps): bump tabled from 0.15.0 to 0.20.0 in /v2 (#481)
Bumps [tabled](https://github.com/zhiburt/tabled) from 0.15.0 to 0.20.0. - [Changelog](https://github.com/zhiburt/tabled/blob/master/CHANGELOG.md) - [Commits](https://github.com/zhiburt/tabled/commits) --- updated-dependencies: - dependency-name: tabled dependency-version: 0.20.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3fdd310f89 |
chore(deps): bump tauri-plugin-dialog from 2.6.0 to 2.7.1 in /v2 (#480)
Bumps [tauri-plugin-dialog](https://github.com/tauri-apps/plugins-workspace) from 2.6.0 to 2.7.1. - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/log-v2.6.0...log-v2.7.1) --- updated-dependencies: - dependency-name: tauri-plugin-dialog dependency-version: 2.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |