Releases: CloudEngineHub/RuView
Release v30
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
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
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
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...
Release v14
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:
-
Stale fixed sample rate. estimate_bpm_zero_crossing() used a hardcoded
sample_rate = 10.0f(and the biquads a separatefs = 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"). -
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
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
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
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:
-
It scanned a path that no longer exists.
bandit -r src/and
semgrep … src/pointed at the repo-rootsrc/, but the Python code
moved toarchive/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
whybandit-results.sarifwas "Path does not exist" on recent runs).
Fixed both toarchive/v1/src/. -
Deprecated + redundant + flaky semgrep step. The
returntocorp/semgrep-action@v1step pulledreturntocorp/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 pipsemgrep --sarifstep is what
feeds GitHub Security; the action only pushed to the Semgrep cloud app via
SEMGREP_APP_TOKEN. Removed it and folded itsp/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
-
Reassign
WASM_OUTPUT_MAGICto0xC511_0007(next free slot per
the registry inrv_feature_state.h). Smaller blast radius than
moving fused-vitals — the registry already treats0xC511_0004as
fused-vitals canonical and several years of deployed feature
tracking depends on that assignment. -
Add
parse_edge_fused_vitals+EdgeFusedVitalsPacketin
wifi-densepose-sensing-server::main. Byte layout taken directly
fromedge_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. -
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. -
Update the registry comment in
rv_feature_state.hto add the new
0x...0007 row. -
Add five tests in a new
issue_928_magic_collision_testsmod:parse_edge_fused_vitals_extracts_fields_correctlyparse_edge_fused_vitals_rejects_short_bufferparse_edge_fused_vitals_rejects_wrong_magicparse_wasm_output_rejects_legacy_0004_magicparse_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
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
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