Skip to content

Releases: CloudEngineHub/RuView

Release v30

14 Jun 10:38
8c24b8b

Choose a tag to compare

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/RuView:8c24b8bdfeb95d0cbe2aeec5074811546618afc5

Release v25

13 Jun 17:36
626b4b2

Choose a tag to compare

Automated release from CI pipeline

Changes:
Merge pull request ruvnet#1042 from ruvnet/docs/adr-164-gap-analysis

docs(adr): ADR-164 — ADR corpus gap analysis & remediation backlog (162 ADRs)

Docker Image:
ghcr.io/CloudEngineHub/RuView:626b4b2e97851816cd85be63eb5e03ffcca6a4da

Release v20

12 Jun 21:29
427c568

Choose a tag to compare

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/RuView:427c56881bac4b995b959b678b0a0789ed7bf95f

Release v15

11 Jun 22:21
2a30713

Choose a tag to compare

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), 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 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...
Read more

Release v14

10 Jun 20:38
992c2b2

Choose a tag to compare

Automated release from CI pipeline

Changes:
fix(firmware): correct ESP32 edge heart rate — sample-rate + harmonic lock (ruvnet#987) (ruvnet#988)

  • fix(firmware): correct heart-rate estimation — sample-rate + harmonic lock

The edge vitals HR was stuck at ~45 BPM regardless of true heart rate
(Apple Watch ground truth 87 BPM read as ~45) and "dropped a lot" between
frames. Two root causes:

  1. Stale fixed sample rate. estimate_bpm_zero_crossing() used a hardcoded
    sample_rate = 10.0f (and the biquads a separate fs = 20.0f). That
    constant was correct when CSI came from ~10 Hz beacons, but ruvnet#985's
    self-ping raised the callback rate to a VARIABLE ~13-19 Hz. BPM scales as
    (assumed_rate / actual_rate) x true, so a true 87 read ~45, and because
    the real rate fluctuates with CSI yield while the code assumed a fixed
    value, the reported HR swung frame-to-frame (the "drops").

  2. Breathing-harmonic lock. Zero-crossing HR estimation locked onto a
    breathing harmonic — a 0.25 Hz breathing fundamental puts its 3rd
    harmonic at ~0.74 Hz ~= 44 BPM, right in the HR band — so it parked at
    ~45 BPM independent of the real heartbeat.

Fix:

  • Measure the real sample rate from inter-frame timestamps (EMA-smoothed,
    clamped 8-30 Hz); use it for both BPM conversion and biquad design, and
    re-tune the filters when the rate drifts >15% so the passbands stay in
    real Hz.
  • Replace the HR zero-crossing with estimate_hr_autocorr(): autocorrelation
    peak in the 45-180 BPM band that explicitly rejects lags within 8% of any
    breathing harmonic (k=1..6), with parabolic interpolation and a peak-
    confidence gate (returns 0 rather than a noise value).
  • Median-smooth (N=9) the emitted HR over valid estimates to kill residual
    single-frame outliers.

Validated on hardware (ESP32-S3, COM8/192.168.1.80) vs an unmodified board
(192.168.1.67) and an Apple Watch (87 BPM):

  • old firmware: HR pegged 40-52 BPM (median ~45)
  • fixed firmware: HR reaches the true 88-91 BPM range (peak 88.5, vs 87 GT)

Known limitation: under subject motion (motion=Y) HR is still noisy because
the breathing estimate degrades and misguides harmonic rejection; motion
gating + breathing robustness are follow-ups.

Co-Authored-By: claude-flow ruv@ruv.net

  • fix(firmware): robust HR harmonic rejection via autocorr breathing period (ruvnet#987)

Follow-up to 332c2a98d. The HR harmonic rejection was fed the noisy
zero-crossing breathing estimate, which under motion notched the wrong
frequencies and let the autocorr lock onto the ~0.75 Hz breathing harmonic
(~45 BPM). Generalize estimate_hr_autocorr -> estimate_periodicity_autocorr
and drive HR harmonic rejection from a robust autocorrelation breathing
period instead; widen the HR median smoother to N=13.

Hardware A/B (fixed .80 vs unmodified control .67, both edge_tier=2, subject
in motion 100% of frames):

  • control (old fw): HR pegged 40-43 BPM (median 40.6)
  • fixed: HR 60-91 BPM (median 71.9) — sub-60 harmonic locks
    eliminated, spread 42->31 BPM vs previous build

Reported breathing is unchanged (still zero-crossing); the autocorr breathing
period is used only internally for HR harmonic rejection.

Co-Authored-By: claude-flow ruv@ruv.net

  • docs(changelog): record ESP32 heart-rate fix (ruvnet#987)

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/CloudEngineHub/RuView:992c2b25cb6c6fcf3ba4d80d2be906920831f54c

Release v13

09 Jun 20:04
b6420ac

Choose a tag to compare

Automated release from CI pipeline

Changes:
fix(server): make synthetic CSI opt-in only (sibling fix to ruvnet#937) (ruvnet#979)

Background

Issue ruvnet#937 in the cognitum-v0 appliance repo flagged that the
cognitum-csi-capture systemd unit shipped --simulate by default,
silently serving synthetic CSI tagged as production telemetry on
/api/v1/sensor/stream. That's a textbook trust-eroding pattern — the
single most-cited "where's the real data?" evidence external reviewers
(ruvnet#943, ruvnet#934) point at when they call the project AI-slop.

A grep across THIS tree surfaced the exact same anti-pattern in three
places:

docker/docker-compose.yml:27 # auto (default) — probe ESP32, fall back to simulation
docker/docker-entrypoint.sh:14 # CSI_SOURCE — data source: auto (default), ...
main.rs:6435 info!("No hardware detected, using simulation"); "simulate"

The sensing-server's auto source resolver at main.rs:6425-6440
silently fell back to synthetic with only an info! log line as the
signal. Downstream consumers calling /api/v1/sensing/latest or
/ws/sensing had no in-band way to know they were being served fake
data.

Fix

auto now refuses to fall back. When neither ESP32 UDP nor host WiFi
is detected, the server logs a clear error! explaining the situation
and exits 78 (EX_CONFIG). The error message names the two ways to
proceed: provision real hardware, or set --source simulated /
CSI_SOURCE=simulated explicitly. Existing operators who already use
--source simulated (or its legacy simulate alias) are unaffected —
the alias is preserved for back-compat.

Docker entrypoint comment, docker-compose comment, and the Tauri
desktop app's source-default path also updated to reflect the new
posture. The desktop app keeps its simulated default because it's
an explicit demo product — the value passed downstream is the
explicit simulated, not auto, so the server tags it correctly
and never lies about its data source.

Validation

cargo build -p wifi-densepose-sensing-server --no-default-features
cargo test -p wifi-densepose-sensing-server --no-default-features
→ 122 / 122 pass, build clean (existing pre-fix warnings unchanged).

Deployment

⚠ Breaking change for unattended deployments that relied on the
auto → simulated silent fallback. That is exactly the failure mode
this PR fixes: pretending to serve real sensing data when the source
is fake. Operators who genuinely want demo mode set
CSI_SOURCE=simulated explicitly; the error message and the
docker-compose comment both point them there.

Docker Image:
ghcr.io/CloudEngineHub/RuView:b6420ac9bad9b0646891db460d8193a5f9dbdd21

Release v12

05 Jun 21:02
872d759

Choose a tag to compare

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))²

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/RuView:872d7593bbeeed63524386aa60e6805bb4e1b26c

Release v11

04 Jun 20:42
2c136ac

Choose a tag to compare

Automated release from CI pipeline

Changes:
fix(protocol): resolve 0xC511_0004 magic collision (closes ruvnet#928) (ruvnet#931)

  • fix(ci): SAST actually scans the code + drop deprecated flaky semgrep action

Two real problems in the Static Application Security Testing job:

  1. It scanned a path that no longer exists. bandit -r src/ and
    semgrep … src/ pointed at the repo-root src/, but the Python code
    moved to archive/v1/src/ (64 .py files) when the runtime was rewritten
    in Rust. So the SAST scan matched nothing — a silent no-op (this is also
    why bandit-results.sarif was "Path does not exist" on recent runs).
    Fixed both to archive/v1/src/.

  2. Deprecated + redundant + flaky semgrep step. The
    returntocorp/semgrep-action@v1 step pulled returntocorp/semgrep-agent:v1
    from Docker Hub every run (intermittently timing out → red check, e.g. on
    ruvnet#929) and is EOL. It was redundant: the pip semgrep --sarif step is what
    feeds GitHub Security; the action only pushed to the Semgrep cloud app via
    SEMGREP_APP_TOKEN. Removed it and folded its p/docker + p/kubernetes
    rulesets into the pip semgrep command, so coverage is preserved with no
    Docker pull.

The job stays continue-on-error: true (non-gating). YAML validated.

Co-Authored-By: claude-flow ruv@ruv.net

  • fix(protocol): resolve 0xC511_0004 magic collision (closes ruvnet#928)

Background

0xC511_0004 was assigned to two different packet formats in firmware
EDGE_FUSED_MAGIC (ADR-063, 48-byte edge_fused_vitals_pkt_t) and
WASM_OUTPUT_MAGIC (ADR-040, variable-length wasm_output_pkt_t).
Both were transmitted. The sensing-server only had a WASM parser for
that magic and no fused-vitals parser, so on the ESP32-C6 + MR60BHA2
mmWave configuration the fused-vitals packet was silently misparsed
as a malformed WASM output — breathing_rate was read as
event_count, mmWave-fused vitals were lost, and spurious WASM events
were emitted to subscribers.

Fix

  1. Reassign WASM_OUTPUT_MAGIC to 0xC511_0007 (next free slot per
    the registry in rv_feature_state.h). Smaller blast radius than
    moving fused-vitals — the registry already treats 0xC511_0004 as
    fused-vitals canonical and several years of deployed feature
    tracking depends on that assignment.

  2. Add parse_edge_fused_vitals + EdgeFusedVitalsPacket in
    wifi-densepose-sensing-server::main. Byte layout taken directly
    from edge_processing.h:129, mirroring the firmware's
    _Static_assert(sizeof(edge_fused_vitals_pkt_t) == 48) so future
    firmware changes that grow the packet will break this parser
    loudly instead of silently.

  3. Add a dispatch arm in the UDP receive loop. Fused-vitals is tried
    BEFORE WASM so a stale firmware (still emitting 0xC511_0004 with
    the WASM payload) fails to parse as fused-vitals (size mismatch),
    then fails to parse as WASM (magic mismatch on the new 0x...0007),
    and gets dropped — a deliberate "fail loud" outcome rather than the
    pre-fix silent garbage.

  4. Update the registry comment in rv_feature_state.h to add the new
    0x...0007 row.

  5. Add five tests in a new issue_928_magic_collision_tests mod:

    • parse_edge_fused_vitals_extracts_fields_correctly
    • parse_edge_fused_vitals_rejects_short_buffer
    • parse_edge_fused_vitals_rejects_wrong_magic
    • parse_wasm_output_rejects_legacy_0004_magic
    • parse_wasm_output_accepts_new_0007_magic

WebSocket payload

Fused-vitals now broadcasts as {"type": "edge_fused_vitals", ...}
with the mmWave-specific block nested under mmwave. Schema is
additive — existing subscribers that only inspect type are
unaffected; subscribers that switch on type gain a new branch.

Deployment note

This is a wire-protocol change. Firmware older than this commit that
emits WASM output on 0xC511_0004 will lose its WASM event stream
against an updated host (host expects 0xC511_0007). Per the issue
discussion, "fail loud" is preferred to silent misparsing. Operators
running C6+mmWave should reflash firmware concurrent with the host
upgrade.

Test results
cargo test -p wifi-densepose-sensing-server --no-default-features
--bin sensing-server
→ 122 passed / 0 failed (5 new + 117 existing, unchanged)

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/CloudEngineHub/RuView:2c136aca7456ee5555a21fdcf7176fae38b8cf38

Release v10

03 Jun 19:00
f5d0e1e

Choose a tag to compare

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/RuView:f5d0e1e69ef07cefc3008d6c0594562e35c0d430

Release v9

01 Jun 08:21
f850d46

Choose a tag to compare

Automated release from CI pipeline

Changes:
Merge pull request ruvnet#874 from ruvnet/feat/adr-149-aether-arena

feat(aether-arena): ADR-149 Spatial-Intelligence Benchmark — scorer + CI harness gate

Docker Image:
ghcr.io/CloudEngineHub/RuView:f850d46e9a92d3af50f1c2415c7c61d87f51c882