-
Notifications
You must be signed in to change notification settings - Fork 0
Releases: CloudEngineHub/wifi-densepose
Release v37
8c24b8b Automated release from CI pipeline
Changes:
refactor(beyond-sota): ADR-154 M3 — clear §7.4 P3 backlog (22 de-magic + 6 boundary tests, backlog 36→0) (ruvnet#1057)
- refactor(signal): de-magic motion.rs tuning constants (ADR-154 §7.4 #18)
Lift the bare fusion weights, normalization scales, confidence-indicator
weights, and adaptive-threshold clamp bounds in motion.rs out of the
scoring functions into named, documented EMPIRICAL-DEFAULT consts. Values
are bit-identical to the prior literals — this is cleanup, no behaviour
change.
Adds boundary/characterization tests pinning current behaviour:
- motion_tuning_consts_unchanged_from_literals (consts == old literals)
- doppler_component_saturates_at_full_scale (/100 then clamp(0,1))
- correlation_score_zero_below_n2_boundary (n<2 guard)
- temporal_variance_zero_below_two_history (len<2 guard)
- adaptive_threshold_engages_at_history_boundary (history 9 vs 10)
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): gesture.rs euclidean length guard + de-magic (ADR-154 §7.4 #12)
- Add a debug_assert! to euclidean_distance documenting the same-dimension
caller contract: zip() silently truncates on a length mismatch, so a
mismatch is now loud in debug builds while the release operating path and
output are unchanged. - De-magic the bare 1e-10 confidence epsilon into a documented const
CONFIDENCE_SECOND_BEST_EPSILON (value unchanged).
Tests pinning current behaviour:
- confidence_epsilon_unchanged_from_literal
- dtw_empty_sequence_is_infinite (n=0/m=0 boundary)
- euclidean_distance_equal_length_is_l2 (same-dim contract)
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic longitudinal.rs drift thresholds (ADR-154 §7.4)
Lift the bare drift-detection literals (7-day baseline, 2-sigma z-score,
3-day sustained, 7-day escalation, EMA alpha, cosine epsilon) into named,
documented EMPIRICAL-DEFAULT consts encoding the module's Key Invariants.
The duplicated >= 7 in is_ready/is_ready_at now share one const. EMA alpha
kept as the exact 0.05 literal (1.0 - 0.95_f32 is not bit-identical in f32).
Values unchanged.
Tests:
- drift_consts_unchanged_from_literals
- is_ready_at_day_boundary (day 6 vs 7)
- cosine_similarity_zero_vector_is_zero (zero-norm guard)
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic division/zero-norm epsilons + boundary tests (ADR-154 §7.4)
De-magic the bare division-guard epsilons in four modules into named,
documented consts (values unchanged) and pin the previously-untested
zero-norm / zero-variance / degenerate boundaries:
- cross_room.rs: COSINE_SIMILARITY_EPSILON (1e-9) + test_cosine_similarity_zero_vector
- multiband.rs: PEARSON_DENOMINATOR_EPSILON (1e-12) + pearson_correlation_zero_variance
- intention.rs: LEAD_TIME_MIN_ACCEL (1e-10) + lead_time_zero_for_static_stream
- hampel.rs: ZERO_MAD_EPSILON (1e-15) + test_zero_half_window_error
- test_zero_mad_constant_window; documented hampel_filter # Errors
Each module also gets a *_unchanged_from_literal const-pin test.
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic rf_slam + attractor_drift constants (ADR-154 §7.4)
rf_slam.rs:
- NS_PER_DAY (86_400_000_000_000.0), MIGRATION_MIN_SPAN_DAYS (1e-9), and the
fixed-map defaults (FIXED_MAP_ASSOC_RADIUS_M/MIN_SIGHTINGS/MIN_COHERENCE)
lifted out of inline literals (values unchanged). - migration_zero_span_is_zero_rate pins the single-sighting zero-span guard.
attractor_drift.rs:
- METRIC_BUFFER_CAPACITY (365), STABLE_CENTER_WINDOW (10) de-magicked.
- Documented the implicit recent.len()>=1 divide-safety in the PointAttractor
branch (guaranteed by the count < min_observations guard). - analyze_min_observations_boundary pins the off-by-one boundary.
Each module gets a *_consts_unchanged_from_literals pin test.
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic coherence.rs variance floor + default decay (ADR-154 §7.4)
Completes the M1 #9 de-magic for coherence.rs: the four bare 1e-6 variance-floor
literals (update_reference floor + coherence_score/per_subcarrier_zscores epsilon)
collapse to one VARIANCE_FLOOR const, and the inline 0.95 default decay becomes
DEFAULT_EMA_DECAY. Values unchanged.
Tests:
- drift_consts_unchanged_from_literals extended (VARIANCE_FLOOR, DEFAULT_EMA_DECAY)
- coherence_score_finite_with_zero_variance pins the floor's effect
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic calibration.rs thresholds + min-frames default (ADR-154 §7.4 #2)
Lift the bare calibration literals into named EMPIRICAL-DEFAULT consts (values
unchanged, bit-identical; calibration is off the Python proof path):
- DEFAULT_MIN_FRAMES (600) — was repeated across all four tier constructors
- AMP_STD_FLOOR (1e-12) z-score divisor floor
- MOTION_AMP_Z_THRESHOLD (2.0) / MOTION_PHASE_DRIFT_THRESHOLD (π/6) — the two
motion_flagged sites now share one definition - SUBTRACT_MIN_NORM (1e-30) baseline-subtraction guard
Test calibration_consts_unchanged_from_literals pins all five and asserts every
tier constructor shares DEFAULT_MIN_FRAMES.
Co-Authored-By: claude-flow ruv@ruv.net
- refactor(signal): de-magic fusion_quality + temporal_gesture constants (ADR-154 §7.4)
fusion_quality.rs:
- CONTRADICTION_PENALTY (0.8) and CONTRADICTION_BOUND_HALFWIDTH (0.1) named.
- no_contradiction_is_identity pins the n=0 boundary (penalty 0.8^0 = 1.0,
zero-width bounds).
temporal_gesture.rs:
- CONFIDENCE_SECOND_BEST_EPSILON (1e-10, mirrors gesture.rs) and
NORM_QUANTIZATION_SCALE (1000.0) named.
Each module gets a *_consts_unchanged_from_literals pin test. Values unchanged.
Co-Authored-By: claude-flow ruv@ruv.net
- docs(adr-154): record Milestone-3 — §7.4 row #21-45 P3 backlog cleared
Replace the lumped #21-45 backlog row with the enumerated M3 resolution: 22
magic constants de-magicked into named EMPIRICAL-DEFAULT consts (each pinned ==
prior literal), 6 boundary/characterization tests, ~4 doc-only, across 11
modules; not-real findings reported + skipped (unreachable attractor_drift
div0, non-existent gesture thresholds, proof-path features.rs). Update residual
P3 rows #2/#12/#17/#18 to RESOLVED, the deferred count (36 -> 0), the scope
field, and the Horizon-ledger one-liner. §7.4 backlog fully cleared across
M0-M3. CHANGELOG [Unreleased] entry added.
Validation: signal lib --no-default-features 476/0/1; --features cir 476/0;
workspace 3,275/0; Python proof PASS, hash f8e76f21...46f7a UNCHANGED.
Co-Authored-By: claude-flow ruv@ruv.net
Co-authored-by: ruv ruvnet@gmail.com
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:8c24b8bdfeb95d0cbe2aeec5074811546618afc5
Assets 2
Release v28
427c568 Automated release from CI pipeline
Changes:
Merge pull request ruvnet#1023 from ruvnet/feat/v2-beyond-sota-sweep
Beyond-SOTA v2/crates sweep (ADR-154–158) + implement every stub for real (no AI-slop)
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:427c56881bac4b995b959b678b0a0789ed7bf95f
Assets 2
Release v24
2a30713 Automated release from CI pipeline
Changes:
feat: per-room calibration system (ADR-151) + cognitum-v0 appliance integration spec (ruvnet#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 ruvnet#989:
- Cross-compile (the appliance blocker): make wifi-densepose-mat optional
and feature-gate it (mat), socargo build -p wifi-densepose-cli --no-default-featuresexcludes the mat→nn→ort(ONNX)→openssl-sys chain.
Verified:cargo tree --no-default-featuresshows 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_idto [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 asynctokio::fs::write. - Docs: ADR-151 STALE wording matches the impl (baseline-id change;
drift-threshold = P6 refinement); integration doc gets the
--no-default-featuresbuild + 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 ruvnet#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-serv...
Assets 2
Release v23
872d759 Automated release from CI pipeline
Changes:
fix: IDF v6.0 ESP-NOW callback compat (ruvnet#944) + occupancy noise-floor anchor (ruvnet#942) (ruvnet#945)
- fix(firmware): on_send ESP-NOW callback compat for IDF v6.0 (closes ruvnet#944)
ESP-IDF v6.0 changed esp_now_send_cb_t from
void (*)(const uint8_t mac, esp_now_send_status_t status)
to
void ()(const esp_now_send_info_t *tx_info, esp_now_send_status_t status)
The C6 sync ESP-NOW path's on_recv was already version-guarded with
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) (lines 102-112)
but the on_send sibling missed the equivalent guard. CI runs against
IDF v5.4 so the regression slipped through; the reporter on IDF v6.0.1
with xtensa-esp-elf esp-15.2.0_20251204 hit:
c6_sync_espnow.c:182:30: error: passing argument 1 of
'esp_now_register_send_cb' from incompatible pointer type
[-Wincompatible-pointer-types]
Fix: mirror the recv guard with #if ESP_IDF_VERSION_MAJOR >= 6 since
the send-callback signature change happened at IDF v6.0 (not v5.x like
the recv-callback). Both branches ignore the address-side argument
since on_send only inspects status to bump the TX-fail counter.
Adds #include "esp_idf_version.h" so the macro is in scope.
Closes ruvnet#944
Co-Authored-By: claude-flow ruv@ruv.net
- fix(signal): anchor estimate_occupancy noise floor to calibration (closes ruvnet#942)
test_estimate_occupancy_noise_only asserts that 20 noise-only frames
fed through a 50-frame calibrated FieldModel yield 0 occupancy.
Failure reported on the upstream Linux + BLAS build.
Root cause
Calibration and estimation each compute their own Marcenko-Pastur
threshold:
threshold = noise_var · (1 + sqrt(p / N))2
with noise_var = median of the bottom half of positive eigenvalues
from their own covariance. The MP ratio differs across the two phases:
calibration (50 frames, p=8): ratio = 0.16, factor ≈ 1.96
estimation (20 frames, p=8): ratio = 0.40, factor ≈ 2.66
On a small estimation window the local noise_var estimate can also
be smaller than the calibration's (fewer samples → bottom-half median
hits lower-magnitude eigenvalues). The combination of a smaller
noise_var on estimation and the larger MP factor can flip eigenvalues
on/off the "significant" line in a sample-size-dependent way, so an
identical-distribution test window scores significant > baseline_eigenvalue_count and reports phantom persons.
Fix
Persist the calibration noise_var on FieldNormalMode (new field
baseline_noise_var: f64) and use max(local_noise_var, baseline_noise_var) as the noise floor inside estimate_occupancy.
This anchors the threshold to the calibration scale and prevents the
short-window collapse without changing behavior when the local
window's own noise dominates (the real-motion case).
baseline_noise_var defaults to 0.0 in the diagonal-fallback paths;
the estimation code treats 0.0 as "no anchored floor available" and
preserves the pre-ruvnet#942 single-window behavior — so older FieldNormalMode
instances deserialised from disk continue to work unchanged.
Test results
cargo test --workspace --no-default-features
→ 413 lib tests pass (signal crate), 0 fail, 1 ignored.
The actual eigenvalue-gated test still requires BLAS (not buildable
on Windows). Logic-trace via the four numerical anchors above shows
the fix flips noise_var from the smaller local value back up to the
calibration scale, dropping significant to or below
baseline_eigenvalue_count so the saturating subtraction returns 0.
Closes ruvnet#942
Co-Authored-By: claude-flow ruv@ruv.net
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:872d7593bbeeed63524386aa60e6805bb4e1b26c
Assets 2
Release v22
f5d0e1e Automated release from CI pipeline
Changes:
fix(ruvnet#894): actionable diagnostic when --model gets a non-RVF file (ruvnet#919)
Users who downloaded ruvnet/wifi-densepose-pretrained and passed
model.safetensors / model-q4.bin / model.rvf.jsonl to --model hit a bare
"Progressive loader init failed: invalid magic at offset 0: expected
0x52564653, got 0x77455735" and were stuck — the server then silently fell
back to signal heuristics (which over-count, feeding "is it fake" reports).
The HF files are a different format and encoder architecture than the RVF
binary container the progressive loader expects, so they can't load directly.
Now the load-failure path detects the common cases (safetensors header,
JSONL manifest, quantized .bin blob) and emits a plain explanation naming the
format, what --model actually expects (RVF RVFS container from
wifi-densepose-train), and that it's continuing with heuristics — with a
pointer to ruvnet#894.
Pure, testable diagnose_model_load_error() + 4 unit tests (run under the
default --no-default-features CI). Full crate unit suite: 429 + 114 passed,
0 failed.
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:f5d0e1e69ef07cefc3008d6c0594562e35c0d430
Assets 2
Release v21
Automated release from CI pipeline
Changes:
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, squared-L2),
QuantConv/PostQuantConv(×ばつ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 (×ばつ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
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:9ad550d95f492a8aa75710bfb9701ede94e13721
Assets 2
Release v20
9e7fa83 Automated release from CI pipeline
Changes:
feat(signal): ADR-134 CSI→CIR via ISTA + NeumannSolver warm-start (ruvnet#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 Φ ∈ C^(×ばつG) with G = 3K (×ばつ 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) ≈ ×ばつ) 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 viause_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 ×ばつ 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
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:9e7fa83210cdf05bc245610b732ad2a9c93cc985
Assets 2
Release v19
Automated release from CI pipeline
Changes:
feat(homecore-ui iter 6): Settings probe-before-persist token validation
CRUD increment 6/6 — closes the sprint. Bearer-token editor now
probes /api/config with the new value BEFORE writing it to
localStorage, so a typo'd or revoked token can't lock the UI out
of the backend.
Three actions:
- Test token probe /api/config, no localStorage write
- Probe & Save probe; write only on 2xx
- Clear remove from localStorage
Inline probe result with sigils:
✓ token accepted (40 ms) — server v0.1.0-alpha.0
✗ HTTP 401: unauthorized
⋯ probing /api/config...
currently stored: line shows masked + length: dev-...ken (9 chars)
so the operator can see what's persisted without exposing the secret.
Empty input → red border + disabled Test/Save buttons. Bad probes
do NOT persist (this is the whole point — never write a token that
the backend rejects).
frontend/src/pages/Settings.ts — full rewrite (~190 LOC, +110 vs
previous version). No new dependencies.
Browser-verified end-to-end:
- Backend section: Home / 0.1.0-alpha.0 / RUNNING / components OK
- Test token: probe ✓, 40 ms, version reported
- Empty input: buttons disabled + red border
- Probe & Save: persists to localStorage, toast shown,
currently stored:updates to masked new token - Clear: localStorage null,
currently stored: (empty) - 0 unexpected console errors
Note: a clean reload lands on Dashboard (the SPA router has no
URL-encoded view yet). The token persistence itself survives reload
correctly; route persistence is a small follow-up if you want
direct URLs like /?view=settings.
CRUD sprint summary (6/6 runtime-validated):
iter 1 Add Entity e7215a1
iter 2 Edit Entity 89190b6
iter 3 Delete + DELETE route c0bb6f4
iter 4 Live validation polish 3f5a741
iter 5 Call Service 99c78f5
iter 6 Settings probe-before-persist (this)
Co-Authored-By: claude-flow ruv@ruv.net
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:224689a5bcce61b87ce9f670649569bc2e023f00
Assets 2
Release v17
a91004e Automated release from CI pipeline
Changes:
feat(adr-124): SENSE-BRIDGE — @ruvnet/rvagent MCP server + 6 sensing tools (v0.1.0) (ruvnet#791)
- 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 } (gated on
std)- BfldFrame::new(header, payload) — auto-syncs payload_len + payload_crc32
- BfldFrame::to_bytes() -> Vec — 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
- feat(adr-118/p1.5): payload section parser (BfldPayload) — 32/32 GREEN
Iter 5. Implements ADR-119 §2.2 payload layout: 4-byte LE length prefix
followed by section bytes, in this fixed order:
compressed_angle_matrix ‖ amplitude_proxy ‖ phase_proxy ‖ snr_vector
‖ csi_delta (iff flags.bit0)
‖ vendor_extension (length 0 allowed)
Added:
- src/payload.rs (gated on
feature = "std"):- BfldPayload struct with 6 fields (csi_delta: Option<Vec>)
- SECTION_PREFIX_LEN const (= 4)
- to_bytes(include_csi_delta: bool) -> Vec
- wire_len(include_csi_delta: bool) -> usize (predictive, no allocation)
- from_bytes(&[u8], expect_csi_delta: bool) -> Result<Self, BfldError>
- push_section / read_section helpers (private)
- BfldError::MalformedSection { offset, reason } variant
- pub use BfldPayload from lib.rs (cfg-gated mirror of BfldFrame)
tests/payload_sections.rs (8 named tests, all green):
payload_roundtrip_with_csi_delta
payload_roundtrip_without_csi_delta
wire_len_matches_to_bytes_length
empty_payload_has_five_zero_length_sections
parser_rejects_buffer_shorter_than_first_length_prefix
parser_rejects_section_body_running_past_buffer_end
parser_rejects_trailing_bytes_after_vendor_extension
csi_delta_flag_mismatch_with_payload_is_detectable_via_trailing_bytes
ACs progressed:
- AC5 ↑ — full section-level round-trip preservation (round-trip with and
without csi_delta both pass). - AC6 ↑ — deterministic section encoding (length prefixes use to_le_bytes,
body is byte-stable). - AC1 partial — section layout now parses with bounded errors; CBFR-specific
parsing (Phi/Psi Givens decoders) is a separate iter inside extractor.rs.
Test config:
- cargo test --no-default-features → 17 passed (payload module cfg-out)
- cargo test → 32 passed (3 + 6 + 7 + 8 + 8)
Out of scope (next iter target):
- Wire integration: feed BfldPayload bytes through BfldFrame::new so the
header.payload_crc32 covers the section-prefixed bytes per ADR-119 §2.2
("CRC32 covers all section bytes including length prefixes"). - A no_std-friendly BfldPayloadRef<'_> borrowing variant (ESP32-S3 path).
- Givens-rotation angle decoder (Phi/Psi extraction from compressed_angle_matrix).
Co-Authored-By: claude-flow ruv@ruv.net
- feat(adr-118/p1.6): BfldFrame <-> BfldPayload wire integration (39/39 GREEN)
Iter 6. Connects the typed payload parser (iter 5) to the framed
wire format (iter 4): the CRC32 now covers the section-prefixed
payload bytes per ADR-119 §2.2 ("CRC32 covers all section bytes
including length prefixes").
Added:
- BfldFrame::from_payload(header, &BfldPayload) -> Self
Auto-syncs header.flags HAS_CSI_DELTA bit from payload.csi_delta.is_some(),
serializes payload via to_bytes(), feeds BfldFrame::new() which computes
payload_len + payload_crc32 over the section-prefixed bytes. - BfldFrame::parse_payload(&self) -> Result<BfldPayload, BfldError>
Reads HAS_CSI_DELTA bit from header.flags and dispatches to
BfldPayload::from_bytes(&self.payload, expect_csi_delta).
tests/frame_payload_integration.rs (7 named tests, all green):
from_payload_then_parse_payload_is_identity
from_payload_autosets_has_csi_delta_flag
from_payload_clears_has_csi_delta_flag_when_csi_absent
(verifies the flag is cleared when csi_delta is None even if caller
pre-set the bit; other flag bits like PRIVACY_MODE are preserved)
frame_crc_covers_section_prefixed_bytes
(mutating a byte inside section body trips CRC, not magic/length)
frame_crc_covers_section_length_prefixes
(mutating a section length-prefix byte trips CRC before parser ever runs)
empty_typed_payload_roundtrips
end_to_end_wire_roundtrip_via_bytes
(BfldPayload -> from_payload -> to_bytes -> from_bytes -> parse_payload
is the identity function modulo flag auto-set)
ACs progressed:
- AC5 ↑ — full payload round-trip through the framed bytes (closes
the round-trip leg from BfldPayload through wire and back). - AC6 ↑ — same input produces same bytes through both layers.
- AC4 ↑ — CRC mismatch on tampered section bodies and tampered section
length prefixes both surface as BfldError::Crc, not as silent acceptance
or as a deeper parser error.
Test config:
- cargo test --no-default-features → 17 passed (integration tests cfg-out)
- cargo test → 39 passed (3 + 6 + 7 + 8 + 8 + 7)
Out of scope (next iter target):
- PrivacyGate::demote(frame, target_class) — ADR-120 §2.4 class transition
transformer with subtle::Zeroize on dropped fields. - IdentityEmbedding newtype with no Serialize impl (ADR-120 §2.5 / I2).
Co-Authored-By: claude-flow ruv@ruv.net
- feat(adr-118/p2.1): IdentityEmbedding newtype + zeroizing Drop — 44/44 GREEN
Iter 7. First structural enforcement of ADR-118 invariant I2 — the
identity embedding is in-RAM-only and cannot be serialized, cloned,
or copied. Lands the type itself; ring-buffer lifecycle is next.
Added:
- src/embedding.rs (no_std-compatible; lives in the lib regardless of features):
- IdentityEmbedding wrapping [f32; EMBEDDING_DIM=128]
- from_raw(values), as_slice() -> &[f32], l2_norm(), len(), is_empty()
- NO Serialize, NO Clone, NO Copy impl
- Custom Debug emits only dim + L2 norm + "" — never raw values
- Drop overwrites storage with 0.0 then core::hint::black_box(...) to defeat
dead-store elimination (DSE would otherwise let the compiler skip the write)
- Compile-time structural guards via static_assertions:
assert_impl_all!(IdentityEmbedding: Drop)
assert_not_impl_any!(IdentityEmbedding: Copy, Clone) - pub use IdentityEmbedding, EMBEDDING_DIM from lib.rs
tests/identity_embedding.rs (5 named tests, all green):
from_raw_preserves_values_through_as_slice
l2_norm_is_correct
debug_output_redacts_raw_values
(asserts the formatted output does NOT contain decimal text of values)
embedding_is_not_clonable
(runtime witness; compile-time assertion lives in src/embedding.rs)
drop_overwrites_storage_with_zeros
(Drop runs without panic; bit-level zeroization is asserted by the
black_box-guarded loop. Unsafe peek-after-free is intentionally avoided.)
ACs progressed:
- AC5 ↑ — even in
privacy_mode, the IdentityEmbedding type can't be reached
from any serialization path because the type system rejects the impl. - I2 ↑ — Drop, no Clone, no Copy, redacted Debug are all in place as
compile-time guarantees.
Test config:
- cargo test --no-default-features → 22 passed
- cargo test → 44 passed (3 + 6 + 7 + 8 + 8 + 7 + 5)
Out of scope (next iter target):
- EmbeddingRing — 64-entry FIFO ring buffer holding IdentityEmbeddings,
drained on coherence-gate Recalibrate (ADR-121 §2.4). - PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).
Co-Authored-By: claude-flow ruv@ruv.net
- feat(adr-118/p2.2): EmbeddingRing 64-entry FIFO buffer — 53/53 GREEN
Iter 8. Lands the lifecycle half of ADR-120 §2.5: a bounded, in-place,
no_std-compatible ring of IdentityEmbeddings. Insertion is O(1); when
full, push evicts the oldest entry, whose Drop runs and zeroizes the
f32 storage. drain() clears the ring on the coherence-gate Recalibrate
action (ADR-121 §2.4).
Added:
- src/embedding_ring.rs (no_std-compatible; no heap):
- EmbeddingRing struct with [Option; RING_CAPACITY=64]
backing array, head cursor, count - EmbeddingRing::new() / Default impl
- push(emb) -> Option (evicted oldest when full)
- len / is_empty / capacity / is_full / iter
- iter() returns occupied slots in insertion order (oldest first)
- drain() -> usize (empties the ring, returns count drained)
- EmbeddingRing struct with [Option; RING_CAPACITY=64]
- pub use EmbeddingRing, RING_CAPACITY from lib.rs
Uses [const { None }; RING_CAPACITY] (stable since 1.79) to initialize
the slot array for a non-Copy element type.
te...
Assets 2
Release v16
68abb38 Automated release from CI pipeline
Changes:
docs(readme): swap hero image to ruview-seed.png (ruvnet#753)
Replaces assets/ruview-small-gemini.jpg with assets/ruview-seed.png as
the hero image. Same Cognitum Seed link target.
Docker Image:
ghcr.io/CloudEngineHub/wifi-densepose:68abb385ae9c49f9ee5433619e50cf7c10f02723