Commit Graph

1109 Commits

Author SHA1 Message Date
ruv 65da488add feat(adr-185): add weight-loading capability to AETHER EmbeddingExtractor (§13.a)
Closes the ADR-185 §13.a follow-up (the tractable, data-independent one of
the three §6.7 gaps): the bound EmbeddingExtractor was random-Xavier-init
only, with NO way to load real weights — so it was structurally untrained.
This adds the *capability* to load weights whenever a trained checkpoint
exists. It does NOT itself produce trained/SOTA embeddings and does NOT
close §6.7 (still no trained checkpoint, no labeled data, no eval harness).

Serialization: not greenfield — EmbeddingExtractor already had
flatten_weights()/unflatten_weights() (flat Vec<f32>). wifi-densepose-aether
is a deliberately dependency-free std-only leaf crate (ADR-185 §13), so
rather than add safetensors/serde/bincode (which would undo the zero-dep
property), the on-disk format is raw little-endian f32 with a 12-byte header
(magic "AETHERW1" + u32 param count) — zero new deps.

Native (v2/crates/wifi-densepose-aether/src/embedding.rs):
- EmbeddingExtractor::save_weights(path) / load_weights(path). load_weights
  never panics: errors on unreadable file, short/oversized payload, bad
  magic, or a param-count mismatch (delegated to unflatten_weights).
- Default (no weights) construction is UNCHANGED — still random init,
  clearly labeled untrained. Purely additive.

Python binding (python/src/bindings/aether.rs, .pyi):
- EmbeddingExtractor.load_weights(path) / save_weights(path) / param_count,
  GIL-released, ValueError on bad input.

Tests (both Rust and Python, per the task):
- Rust unit (embedding.rs): load_weights_actually_replaces_weights_and_
  round_trips proves the loaded weights MOVE the embedding away from the
  random-init baseline (not a silent no-op), match the source extractor
  (round-trip), and are bit-identical after the file round-trip; plus a
  bad-magic/wrong-count rejection test.
- Cross-language golden (aether_weights_parity.rs + test_aether.py): a
  shared deterministic weight formula (w[i]=k/65536-0.5, exact in f32+f64)
  written to the AETHER format; both native Rust and the Python binding load
  it and must produce the byte-identical embedding SHA-256
  (tests/golden/aether_loaded_embedding.sha256) — proving the binding's
  load path is bit-identical to native, and (vs the random baseline) that
  the loaded weights are actually used.

Verified:
  cargo test -p wifi-densepose-aether                       98 passed, 0 failed
  cargo test --features aether --test aether_parity
    --test aether_weights_parity                            3 passed, 0 failed
  maturin develop --features aether + pytest test_aether.py 13/13 pass
  default cargo build (no aether feature)                   clean
2026-07-21 18:20:23 -07:00
ruv e0cb7ed3b3 docs(adr-185): record §6.7 as genuine gap — untrained bindings, no eval harness 2026-07-21 18:12:38 -07:00
ruv 8409f434c9 fix(adr-186): make exported .rvf filenames process-unique (fixes CI flake)
Root cause: the trained-model filename was `trained-{type}-{%Y%m%d_%H%M%S}` — a
**second-resolution** timestamp. Two training runs of the same type that finish
in the same wall-clock second compute the SAME path and silently overwrite each
other's artifact. This is a real robustness gap (a second rapid run clobbers the
first model), and it surfaced as a flaky test on the Linux CI runner:
`http_train_start_produces_model_and_streams` asserts (via a before/after
directory diff) that a new `.rvf` appeared, but the concurrent
`training_job_streams_real_progress_and_writes_model` test — same `supervised`
type, same second — wrote to the identical path and then deleted it in its own
cleanup, so the diff saw no new file. Windows scheduling happened to avoid the
same-second overlap; Linux CI hit it (the sibling `TRAIN_ENV_LOCK` holders then
failed via std::sync::Mutex poison-cascade after the first panic).

Reproduced locally by isolating the three model-writing tests (which forces the
overlap): ~10/20 runs flaked before; 0/30 after.

Fix: `next_model_id()` appends microseconds (`%6f`) plus a monotonic per-process
`AtomicU64` counter, so every run gets a distinct path even for same-microsecond
concurrent completions. Pinned by `model_ids_are_unique_per_call` (1000 ids, all
distinct) so the scheme can't regress to non-unique. No test-level band-aid
(no added sleeps / serialization) — the collision is removed at the source.

Verified: cargo test -p wifi-densepose-sensing-server -p wifi-densepose-train
--no-default-features — bin 218 passed / 0 failed, all suites 0 failed; the
isolated-tests stress loop is 0/15 flakes post-fix.
2026-07-21 18:10:17 -07:00
ruv d99ea153ff docs(adr-185): correct AETHER wheel-size claim to measured reality 2026-07-21 17:47:03 -07:00
ruv a47bb71b22 fix(adr-185): hoist AETHER compute surface into a std-only leaf crate
ADR-185 §13 flagged the `[aether]` wheel as linking the whole
`wifi-densepose-sensing-server` Axum/tokio/worldgraph/ruvector tree via the
non-optional deps of that crate. Hoist the pure-compute AETHER stack out so the
binding depends only on pure Rust.

New crate `wifi-densepose-aether` (std-only, ZERO external deps) holds the four
self-contained modules the AETHER surface needs — `embedding`, `graph_transformer`,
`sona`, `sparse_inference` (verified: no serde/tokio/axum/crate:: refs outside the
set; the four form a closed dependency graph). `wifi-densepose-sensing-server`
now `pub use`s them from the leaf crate, so its own code (`crate::embedding`, …)
and public API (`wifi_densepose_sensing_server::embedding`, …) are unchanged —
`main.rs`/`trainer.rs` untouched. The Python binding + parity test + `[aether]`
feature repoint at the leaf crate; the sensing-server dep is dropped from python.

MEASURED wheel size (maturin build --release --features aether --strip; each
wheel install-verified to actually contain `AetherConfig`):
  BEFORE (aether -> sensing-server): 369,782 B (~361 KB)
  AFTER  (aether -> leaf crate):     319,719 B (~312 KB)

HONEST CORRECTION to the §9/§13 premise: the pre-hoist wheel was NOT over the
ADR-117 §5.4 5 MB budget — it was ~361 KB, ~14x under. Linker dead-code
elimination (--gc-sections on the pyo3 cdylib) already strips the unreached
server tree because the binding only reaches pure-compute symbols. So this is
"blows the budget" CLAIMED, not MEASURED. The hoist is still worthwhile and
lands the real, measured wins:
  - `[aether]` build time 71s -> 12s (no server tree to compile),
  - python/Cargo.lock -1238 lines (server tree no longer resolved),
  - ~50 KB smaller wheel,
  - honest, minimal declared dependency surface + removes the latent risk that a
    future change makes some server code reachable and *then* bloats the wheel.

Verified this session (real counts):
  - cargo test --features aether --test aether_parity: 2 passed / 0 failed
  - pytest tests/test_aether.py (maturin develop --features aether): 9 passed
  - cargo test -p wifi-densepose-sensing-server --no-default-features: 217 bin +
    388 lib, 0 failed (no regression)
  - cargo test -p wifi-densepose-aether: 96 passed (the relocated unit tests)

The §9/§13 wheel-size narrative in ADR-185 (owned by the ADR author) should be
corrected to reflect the measured reality — flagged to the team lead.
2026-07-21 17:43:32 -07:00
ruv f74e73ccf7 chore(adr-184): promote wifi-densepose to 2.0.0 stable + ruview 2.0.0 (P2)
ADR-184 P2 (version promotion + changelog) — version-metadata prep only;
triggers NO publish (P3's real PyPI upload is still gated on the manual
Trusted Publisher registration on pypi.org).

- python/pyproject.toml: version 2.0.0a1 -> 2.0.0; classifier
  "Development Status :: 3 - Alpha" -> "5 - Production/Stable".
- python/ruview-meta/pyproject.toml: version 2.0.0a1 -> 2.0.0; same
  classifier bump; repointed the wifi-densepose dependency pins
  (base + [client]) from ==2.0.0a1 to ==2.0.0 so the stable ruview
  meta-package depends on the stable wifi-densepose, not the alpha.
- python/wifi_densepose/__init__.py: runtime __version__ 2.0.0a1 -> 2.0.0
  (caught during the sanity check — it would otherwise contradict the
  stable wheel version reported by `wifi_densepose.__version__`).
- CHANGELOG.md: [Unreleased] entry recording the promotion.

Sanity-checked that the core package genuinely deserves "Production/Stable"
(not just a version-string change):
  maturin build --release --strip  -> default wheel 279 KB
  pytest python/tests/ (excluding [aether]/[meridian]/[mat] extra modules)
    -> 185 passed, 0 failed (smoke/keypoint/pose/vitals/bfld/security/
       WS+MQTT client)
  post-bump: wheel installs as 2.0.0, __version__ = 2.0.0, smoke 6/6 pass.
No publish/tag/release commands were run.
2026-07-21 17:36:37 -07:00
ruv 99fea9df9b fix(adr-185): drop dead wifi-densepose-nn dep from train to slim [meridian] wheel
Root cause (investigated, same rigor as the MAT fix): `wifi-densepose-train`
declared `wifi-densepose-nn` as a dependency but NEVER imported it anywhere
in src/ or bin/ — a dead dependency. (The tch-backend model path uses the
`tch` crate directly, not wifi-densepose-nn; `grep -r wifi_densepose_nn
src/` returns nothing.) That dead dep pulled `ort` (ONNX Runtime) +
reqwest/hyper into every downstream consumer, including the ADR-185
`[meridian]` wheel, which binds only train's pure-compute geometry /
rapid_adapt / eval modules + signal::hardware_norm.

`cargo tree -i wifi-densepose-nn` confirmed train was the SOLE path pulling
nn into the wheel; after removal, the package is absent from the graph.

Fix: remove the dead dep line from train's Cargo.toml (no code change, no
feature-gating needed — nothing referenced it). Not a hoist: the coupling
turned out to be nonexistent, not deep.

Measured (maturin build --release --features meridian --strip):
  BEFORE: 1.8 MB   AFTER: 1.7 MB   (both already within the ADR-117 §5.4
  <=5 MB budget). Honest note: unlike MAT — which instantiates OnnxSession
  and thus bundles the ort native runtime (8.4 MB) — train never references
  ort, so the native lib was dead-stripped and the wheel was already under
  budget. The real win is removing ort/reqwest/hyper from train's dependency
  graph and compile for ALL consumers, not a dramatic wheel shrink.

Verified:
  cargo build -p wifi-densepose-train                       (default=[]) OK
  cargo tree -i wifi-densepose-nn  -> no longer in the graph
  cargo test --features meridian --test meridian_parity     2/2 pass
  maturin develop --features meridian + pytest test_meridian 13/13 pass
2026-07-21 17:30:33 -07:00
ruv daa82092a0 docs(adr-186): confirm full-workspace regression green post race-fix (0 failed) 2026-07-21 17:25:16 -07:00
ruv 7ed57f0415 fix(adr-185): feature-gate MAT's ONNX ml module to cut [mat] wheel 8.4MB->2.0MB
Root cause (investigated, not assumed): the heavy deps in a [mat] wheel
were NOT on the survivor-detection/triage path the Python binding uses.
`wifi-densepose-mat` pulled `wifi-densepose-nn` as a NON-optional dep, and
nn's default `onnx` feature drags in `ort` (ONNX Runtime) + its
download/reqwest/hyper stack. But nn is used ONLY by mat's `src/ml/`
module (debris + vital-signs ONNX classifiers), which is an OPTIONAL
runtime enhancement: `DetectionPipeline.ml_pipeline` is `Option<_>`,
created only when `DetectionConfig::enable_ml` is set, and
`from_disaster_config` (the DisasterResponse path) never enables it. So
the ONNX stack was compiled-and-linked but never executed by the binding.

Fix = feature-gating (not a hoist — a hoist was unnecessary):
- `wifi-densepose-nn` made optional; new `ml` Cargo feature gates it.
- `default = [..., "ml"]` so existing consumers are unchanged; `api`
  implies `ml` (the REST surface exposes `ml_ready`).
- `#[cfg(feature = "ml")]` on the `ml` module, its crate-root + prelude
  re-exports, the `MatError::Ml` variant, and every ml touchpoint in
  detection/pipeline.rs (ml_config/ml_pipeline fields, constructor,
  process_zone enhancement block, enhance_with_ml/get_ml_results/
  initialize_ml/ml_ready/ml_pipeline accessors, update_config).
- Fixed a latent feature-unification bug surfaced by dropping nn:
  integration/hardware_adapter.rs uses `tokio::select!`, which needs
  `tokio/macros`. That was only satisfied transitively via nn; now
  declared explicitly on mat's own tokio dep so --no-default-features
  builds compile.

The ADR-185 [mat] wheel already depended on mat with
`default-features = false, features = ["std"]`, so no python/ change was
needed — dropping `ml` from the default now actually removes ort.

Measured (maturin build --release --features mat --strip):
  BEFORE (ml/ort in wheel): 8.4 MB  (over the ADR-117 §5.4 <=5 MB budget)
  AFTER  (ml gated off):    2.0 MB  (under budget) — 76% smaller

Verified:
  cargo build -p wifi-densepose-mat                         (default+ml)  OK
  cargo build -p wifi-densepose-mat --no-default-features --features std  OK
  cargo test --features mat --test mat_parity              2/2 pass
  maturin develop --features mat + pytest tests/test_mat.py 7/7 pass
  (same detection result: 1 survivor, triage Delayed — behavior-transparent)
2026-07-21 17:22:20 -07:00
ruv 65648fe4c2 feat(adr-186): P5/P6 + acceptance verification — dashboard honesty, HTTP tests, Accepted
Completes ADR-186 phases P5 (dashboard honesty / fallback guarantee) and P6
(tests + witness) on top of the P1–P4 reconnection, and flips the ADR to Accepted.

P5 — dashboard honesty:
- Backend (training_api): a runtime enablement gate (`RUVIEW_DISABLE_SERVER_TRAINING`)
  makes the three start endpoints return a structured `{enabled:false, reason,
  cli:"wifi-densepose train-room"}` HTTP 409 (never a silent success) when server
  training is disabled; `/api/v1/train/status` now carries an `enabled` flag. A runtime
  flag (not a Cargo feature) so `--no-default-features` builds keep training ON (resolves
  §9.4). Handlers return `Response` to carry the 409.
- Frontend (TrainingPanel.js): reads `enabled` off the status payload and disables the
  Start/Pretrain/LoRA buttons with a CLI tooltip when server training is off; a rejected
  start tears down the optimistically-opened WS and refreshes. The enabled path was
  already fully wired (opens the WS before POST, renders live epoch/loss/ETA + terminal
  state) — verified.

P6 — tests & witness:
- New HTTP-level tests in a `#[cfg(test)] mod adr186_http_tests` built on a minimal
  `AppStateInner::minimal()` test-state helper: a live-socket test that completes a
  genuine 101 WebSocket handshake (tokio-tungstenite) and receives a real progress frame
  after a POST start; a 426-not-404 route-wired check; a full POST→poll-status→`.rvf`-exists
  round-trip; and the disabled-409 structured-response test. Added `tokio-tungstenite`
  dev-dep (version already in the workspace lock).
- CHANGELOG updated; ADR §6 ledger + §7 acceptance criteria checked off with evidence;
  Status → Accepted. README/CLAUDE have no training route table (no edit needed).

Fixed a test-only parallelism race found under `cargo test --workspace`: two
model-writing tests deleted `.rvf`s by directory-diff and could remove a file a third
test asserted existed. Each test now cleans only its own artifact (data/models is
gitignored).

Verified this session: `cargo test -p wifi-densepose-sensing-server -p wifi-densepose-train
--no-default-features` — sensing-server bin 217 passed / 0 failed, all train suites 0
failed. Full `--workspace` re-run in progress to reconfirm untouched crates.
2026-07-21 17:19:20 -07:00
ruv a4a3f456e1 docs(adr-185): record P1-P4 implementation status, remaining gaps 2026-07-21 17:07:01 -07:00
ruv 0f405213d3 feat(adr-185): P4 benchmarks, examples, and README extras for the SOTA wheels
ADR-185 §4.2 pytest-benchmark micro-benchmarks + runnable examples +
README extras table for the aether/meridian/mat bindings.

- python/bench/test_bench_{aether,meridian,mat}.py — follow the existing
  test_bench_vitals.py pattern (skipped by default; --benchmark-only).
- python/examples/{reid_from_csi,cross_room_calibrate,mat_triage}.py —
  typed, runnable, mypy --strict clean.
- python/README.md — SOTA extras table + example links.

Measured on a RELEASE wheel (maturin develop --release --features sota),
reference machine per ADR-117 §10:
  AETHER embed()          mean ~150 us/window   (target <2 ms)   PASS
    batch scaling 1/8/64: 140 / 1091 / 8509 us  (linear, no O(n^2)) PASS
  MERIDIAN normalize()    mean ~2.2 us/frame     (target <200 us) PASS
  MERIDIAN encode()       mean ~6.9 us           (target <200 us) PASS
  MAT ingest+scan_once()  mean ~40 ms/256-frame  (< 500 ms interval) PASS

Acceptance self-verification (ADR-185 §6), all run just now:
  §6.1 default wheel 279 KB (<=5 MB); build_features has no p6-* feature  PASS
  §6.2 pytest tests/test_aether.py    9/9   PASS
  §6.3 pytest tests/test_meridian.py  13/13 PASS
  §6.4 pytest tests/test_mat.py       7/7   PASS
  §6.5 benchmarks meet all targets (above)                                PASS
  §6.6 parity harness: 3/3 SHA golden gates green (cargo test --features
       sota, 6/6); CI *wiring* as a release gate is out of python/ scope  PARTIAL
  §6.7 SOTA accuracy bars on labeled fixtures: NOT met (no labeled
       fixtures / trained models available; parity proves path-equality,
       not accuracy)                                                       OPEN
  §6.8 .pyi stubs present for all three; mypy --strict on the 3 examples   PASS
  §6.9 base wheel `import wifi_densepose.{aether,meridian,mat}` raises a
       clear ImportError naming the extra                                  PASS
  No regression: 76 pre-existing tests pass on the default wheel.

Status NOT flipped to Accepted: §6.7 (accuracy bars) is unmet, §6.6 CI
wiring is pending, and the per-extra wheel-size hoists (sensing-server /
train / mat leaf crates) remain follow-ups. docs/adr/ is owned by another
agent this session, so the ADR ledger edit is deferred to that owner.
2026-07-21 17:03:49 -07:00
ruv 1c9727f9cf feat(adr-185): P3 MAT bindings (wifi_densepose.mat) + parity harness
Bind the ADR-024 MAT (Mass Casualty Assessment Tool) disaster-survivor
detection + START triage surface into the wheel behind a gated [mat]
extra / Cargo `mat` feature, mirroring the upstream disaster/ML gating.
Also adds the [sota] superset extra (aether+meridian+mat).

Surface (bound against the REAL code at HEAD, not the ADR wishlist):
- DisasterType (9 variants) / TriageStatus (5, START) enums
- DisasterConfig (builder-backed, continuous_monitoring forced off)
- DisasterResponse: initialize_event / add_zone / push_csi_data /
  scan_once / survivors / survivors_by_triage
- Survivor (id, triage_status, confidence, location, latest_vitals)
- VitalSignsReading (breathing/heartbeat rate, movement, confidence)
- ScanZone.rectangle / ScanZone.circle
push_csi_data + scan_once are GIL-released.

Honest deviations from ADR section 3.4 (documented in module header):
- ADR proposed adding a Rust-side sync scan_once() (section 11.3). That was
  UNNECESSARY: the public async start_scanning() runs exactly one
  scan_cycle and returns when continuous_monitoring == false. The binding
  forces that flag off and drives one cycle on a private current-thread
  tokio runtime -- NO change to wifi-densepose-mat.
- scan_cycle requires an active event + Active zone, which the ADR surface
  omitted; initialize_event + add_zone are bound as required additions.
- Survivor.vital_signs is a *history* in the real code; bound as
  Survivor.latest_vitals -> Optional[VitalSignsReading].
- DisasterType has 9 variants at HEAD (adds Landslide/MineCollapse/
  Industrial/TunnelCollapse); all bound.

Parity (section 4.1, release-blocking): committed fixture mat_input.json
(synthetic breathing-modulated CSI stream) -> native Rust reference
(tests/mat_parity.rs, drives DisasterResponse directly) locks
tests/golden/mat_result.sha256 over a canonical
`count=<K>;triage_priorities=<sorted>` string (survivor UUIDs/timestamps
excluded as non-deterministic); pytest (tests/test_mat.py) runs the same
stream through the binding and asserts the identical hash. Both detect
exactly 1 survivor, triage Delayed. Honest: synthetic fixture proves
binding==native path equality, NOT live detection accuracy.

Verified:
  cargo test --features mat --test mat_parity -> 2/2 pass
  maturin develop --features mat + pytest tests/test_mat.py -> 7/7 pass
  default cargo build clean, 0 mat/tokio refs in the default dep graph.

WHEEL-SIZE FINDING (ADR-185 section 9): default-features=false drops MAT's
`api` (axum) and `ruvector` features, but MAT still carries NON-optional
tokio (rt/sync/time), wifi-densepose-nn (ort/ONNX + reqwest/hyper),
rustfft, geo, ndarray. So a [mat] wheel exceeds the ADR-117 section 5.4
<=5 MB budget -- same leaf-crate-hoist follow-up as AETHER/MERIDIAN. The
default wheel is untouched (feature-gated).
2026-07-21 16:49:49 -07:00
ruv 189ac9dfb0 feat(adr-185): P2 MERIDIAN bindings (wifi_densepose.meridian) + parity harness
Bind the ADR-027 MERIDIAN cross-environment domain-generalization surface
into the wheel behind a gated [meridian] extra / Cargo `meridian` feature.
Inference/adaptation path only (tch-free), per ADR-185 section 3.3.

Surface (bound against the REAL code at HEAD, not the ADR wishlist):
- HardwareType / HardwareNormalizer / CanonicalCsiFrame (from
  wifi-densepose-signal::hardware_norm)
- MeridianGeometryConfig / GeometryEncoder (64-dim, permutation-invariant)
- RapidAdaptation / AdaptationResult (push_frame + adapt, LoRA deltas)
- CrossDomainEvaluator + mpjpe (from wifi-densepose-train, NO tch-backend)
All compute paths GIL-released (py.allow_threads).

Honest deviations from ADR section 3.3 (documented in the module header):
- ADR's RapidAdaptation.calibrate(windows) and AdaptationResult.converged
  DO NOT EXIST. Real API is push_frame + adapt(); result carries
  {lora_weights, final_loss, frames_used, adaptation_epochs}. Bound as-is.
- ADR's HardwareType.detect exposed as a staticmethod delegating to the
  real HardwareNormalizer::detect_hardware.
- ADR's normalize(frame: CsiFrame, hw) is really normalize(amplitude,
  phase, hw) over f64 vectors returning Result; bound faithfully.
- CanonicalCsiFrame fields are singular amplitude/phase (ADR said plural).
Training-time types (DomainFactorizer, GradientReversalLayer,
VirtualDomainAugmentor) are out of P6 scope (need the libtorch tier).

Parity (section 4.1, release-blocking): committed fixture
meridian_input.json -> native Rust reference (tests/meridian_parity.rs,
calls hardware_norm + geometry + rapid_adapt directly) locks
tests/golden/meridian_output.sha256 over the concatenated f32 outputs
(esp32+intel canonical frames, 64-dim geometry vector, rapid-adapt LoRA
weights); pytest (tests/test_meridian.py) runs the same fixture through
the binding and asserts the identical SHA-256. Both pass.

Verified:
  cargo test --features meridian --test meridian_parity -> 2/2 pass
  maturin develop --features meridian + pytest tests/test_meridian.py
    -> 13/13 pass
  default cargo build clean, 0 train/signal/sensing-server refs in the
    default dep graph (gate keeps the base wheel lean).

WHEEL-SIZE FINDING (ADR-185 section 9 / section 1.2): the libtorch risk
the ADR feared is AVOIDED -- wifi-densepose-train's `tch` dep is properly
optional (feature tch-backend, OFF), so no libtorch links. BUT train
still carries NON-optional deps: tokio (rt subset), the five ruvector-*
crates, and wifi-densepose-nn (which itself pulls `ort` / ONNX Runtime +
reqwest/hyper). So a [meridian] wheel exceeds the ADR-117 section 5.4
<=5 MB budget (though lighter than AETHER's axum/tokio server tree). The
clean fix is the same leaf-crate hoist: move the pure inference modules
(geometry, rapid_adapt, eval, hardware_norm) into a tch/tokio/ort-free
leaf crate. A required pre-release follow-up, not a functional blocker;
P2 binds real code and proves parity today.
2026-07-21 16:38:17 -07:00
ruv 569cd237fb feat(adr-186): reconnect orphaned in-server trainer + real /ws/train/progress
The training pipeline in `training_api.rs` was written and committed but never
declared as a module (no `mod training_api;`), so it was dead, uncompiled code.
The live `/api/v1/train/start` was a stub that flipped a status string and
logged one line without starting any job, and `/ws/train/progress` 404'd
(issue #1233). This wires the real trainer into the live server.

- main.rs: declare `mod training_api;` (+ `mod path_safety;` for its load
  guard), replace the `training_status`/`training_config` stub fields on
  AppStateInner with `training_state: training_api::TrainingState` +
  `training_progress_tx`, delete the three stub handlers, and
  `.merge(training_api::routes())` before `.with_state` so `/api/v1/train/*`
  (bearer-gated) and `/ws/train/progress` resolve against the shared state.
- training_api.rs: decouple the training core (`run_training_job`) from the
  ~60-field AppStateInner via a shared status handle (`Arc<Mutex<TrainingStatus>>`)
  + cooperative cancel flag (`Arc<AtomicBool>`) + an owned frame_history
  snapshot, making it unit-testable. Self-contained input schema (local
  `RecordedFrame`/`RECORDINGS_DIR`) instead of coupling to the still-orphaned
  `recording.rs`. Consolidate the single-job guard + spawn into
  `spawn_training_job`.
- Tests: real end-to-end test (synthetic CSI dataset -> real progress frames
  stream + an actual `.rvf` artifact is written), plus path-traversal-rejection
  and cancellation tests.

Verified: cargo test -p wifi-densepose-sensing-server -p wifi-densepose-train
--no-default-features -> 0 failed.

P5 (feature-flagged disabled-build honesty) deferred as follow-up; the
default/enabled build no longer silently no-ops.
2026-07-21 16:29:42 -07:00
ruv d060998e3b feat(adr-185): P1 AETHER bindings (wifi_densepose.aether) + parity harness
Bind the ADR-024 contrastive CSI embedding surface into the wheel behind
a gated [aether] extra / Cargo `aether` feature, per ADR-185 section 3.2.

Surface (bound against the REAL code at HEAD, not the ADR wishlist):
- AetherConfig       -> EmbeddingConfig {d_model,d_proj,temperature,normalize}
- CsiAugmenter       -> augment_pair(window, seed)
- EmbeddingExtractor -> embed(csi): 128-dim L2-normed, GIL-released
- info_nce_loss, cosine_similarity (module fns)

ADR-185 section 3.2 also names aether_loss/VICReg components,
alignment_metric, uniformity_metric, forward_dual, and vicreg_* config
fields. None exist in embedding.rs at HEAD, so they are intentionally NOT
bound (a Rust-side gap, not a binding gap) rather than fabricated.

Parity (section 4.1, release-blocking): committed fixture
aether_input.json -> native Rust reference (tests/aether_parity.rs, calls
sensing-server EmbeddingExtractor directly) locks
tests/golden/aether_embedding.sha256; pytest (tests/test_aether.py) runs
the same fixture through the binding and asserts the identical SHA-256 of
the LE f32 bytes. Both pass.

Verified: cargo test --features aether --test aether_parity -> 2/2 pass;
maturin develop --features aether + pytest tests/test_aether.py -> 9/9
pass; default cargo build clean with 0 sensing-server refs in the dep
graph (gate keeps the base wheel lean).

HONEST WHEEL-SIZE FINDING (ADR-185 section 9 / Open Q 11.1, unresolved):
wifi-densepose-sensing-server is an Axum/tokio crate whose tokio/axum/
worldgraph/ruvector deps are NON-optional (only mqtt/matter are
features), so default-features=false does NOT drop them. An [aether]
wheel therefore links the full server tree and BREAKS the ADR-117 section
5.4 <=5 MB budget. The fix is the ADR-sanctioned hoist of embedding.rs
(+ its graph_transformer/sona siblings) into a leaf crate so the wheel
links pure compute only -- a change INSIDE wifi-densepose-sensing-server,
owned by another agent this session, so deferred as a required
pre-release follow-up. P1 binds real code and proves parity today; the
hoist is a wheel-size optimization, not a functional blocker.

Note: building required initializing the worldgraph/rufield/ruvector
submodules (absent in the fresh worktree).
2026-07-21 16:28:14 -07:00
ruv 5426fe830c docs(changelog): record ADR-184/187 landed work
Under [Unreleased]:
- Changed: ADR-184 pip-release.yml migrated to PyPI OIDC Trusted Publishing (cc153e8b5),
  flagged not-yet-active pending manual pypi.org Trusted Publisher registration.
- Deprecated (new section): ADR-187 archive/v1 formal deprecation + three-tier
  model-weights honest labeling in README.md/docs/user-guide.md (1fb5397dd, b1417fb6e).
- Added: ADR-184/185/186/187 decision records; ADR-185/186 marked Proposed-only
  (not yet implemented on this branch).
2026-07-21 16:16:14 -07:00
ruv b1417fb6e5 docs(adr-187): mark P1-P3 done, verify acceptance criteria
Verified all 6 acceptance criteria against current file contents and checked them.
Set Status to Accepted; marked P1/P2/P3 DONE (1fb5397dd); left P4 as ACCEPTED-FUTURE (#645).

To make criterion 4 genuinely true (live ESP32 17-keypoint feature nowhere advertised
without the first-cut/below-target/runtime-stub caveat), added the caveat to the three
remaining live-pose advertisements in README.md: the recommended-hardware capability
table, the hero image caption, and the Live-ESP32-pipeline note.

Refs #509, #1125
2026-07-21 16:13:09 -07:00
ruv 1fb5397ddf docs(adr-187): deprecate archive/v1 + honest three-tier model-weights labeling
Add archive/v1/DEPRECATED.md tombstone and a loud deprecation notice atop
archive/v1/README.md: DensePoseHead is architecture-only (random kaiming_normal_
init, zero committed checkpoints under archive/v1/), superseded by the v2/ workspace
and the wifi-densepose 2.x / ruview pip wheel.

Add a 'Model weights: what's real, what's not' three-tier table to README.md and
docs/user-guide.md distinguishing real+validated (presence 82.3%, MM-Fi pose 82.69%
torso-PCK@20, count_v1), real-but-weak (committed pose_v1 at PCK@20=3.0%, runtime
confidence=0 stub, below ADR-079 target), and architecture-only (archive/v1). Caveat
the live single-ESP32 17-keypoint cog advertisement and answer #509 SISO / #1125.

Refs #509, #1125
2026-07-21 16:08:40 -07:00
ruv dfc4c1abd6 docs(adr-184): record P1 OIDC workflow migration status (cc153e8b5), await pypi.org registration 2026-07-21 16:07:40 -07:00
ruv cc153e8b56 ci(adr-184): migrate pip-release publish to PyPI OIDC Trusted Publishing
Drop all four PYPI_API_TOKEN password inputs from the publish-v2 and
publish-tombstone jobs, grant id-token: write, and bind both jobs to the
pypi GitHub environment so pypa/gh-action-pypi-publish uses secretless
Trusted Publishing. Rewrite the header + add step comments documenting
the blocking manual pypi.org trusted-publisher registration (ADR-184
§3.1) and the token fallback (§3.2). Build matrix untouched.

Refs ADR-184 P1.
2026-07-21 16:07:11 -07:00
ruv cca5bd8113 docs(adr): index ADR-184..187 in ADR README, correct count to 193 2026-07-21 16:01:42 -07:00
ruv c6b4d36ba3 docs(adr-186): propose training progress API fix (refs #1233) 2026-07-21 16:00:28 -07:00
ruv abf6d2aee0 docs(adr-185): propose P6 Python bindings for AETHER/MERIDIAN/MAT 2026-07-21 15:59:16 -07:00
ruv 601370d61a docs(adr-187): propose archive/v1 deprecation + model-weights honest labeling (refs #509, #1125) 2026-07-21 15:59:06 -07:00
ruv 5dbb7f0d02 docs(adr-184): note interim PYPI_API_TOKEN rotation, OIDC migration still pending 2026-07-21 15:58:42 -07:00
ruv f8ab1fef94 docs(adr-184): propose ADR-117 completion via PyPI Trusted Publishing (refs #785) 2026-07-21 15:57:33 -07:00
rUv aa7cb449bc fix(ci): bypass hardened entrypoint for asset inspection (#1364) v1911 2026-07-19 02:50:47 -04:00
rUv 47dbdb29c0 fix(ci): restore workflow and LAN smoke tests (#1362) v1909 2026-07-19 01:11:45 -04:00
rUv 6ee1b55896 feat: implement ADR-270 vendor provider beta (#1360) v0.9.3-vendor-providers-beta.1 2026-07-19 00:09:50 -04:00
rUv 76c80c33d7 feat(hardware): add Qualcomm CSI simulator and vendor roadmap (#1359) v0.9.2-qualcomm-beta.1 2026-07-18 23:03:45 -04:00
rUv 232b1c79f6 feat(hardware): add MediaTek Filogic CSI simulator (#1358) v0.9.1-mediatek-beta.1 2026-07-18 22:00:09 -04:00
rUv 8a5af5dad4 feat: add RTL8720F Realtek radar beta support (#1356)
* docs(adr): plan RTL8720F radar SDK integration

* feat(hardware): add Rust RTL8720F radar simulator

* feat(ruview): ingest RTL8720F radar frames
v0.9.0-realtek-beta.1
2026-07-18 19:48:40 -04:00
rUv c7b3f0bcc5 Merge pull request #1355 from ruvnet/agent/open-issues-release
fix: open issue sweep and ESP32 v0.8.4 release
v0.8.4-esp32
2026-07-18 18:17:38 -04:00
ruv 8c0bdaef92 fix open issue release blockers 2026-07-18 18:01:23 -04:00
rjperry36 1692b16f6e fix(sensing-server): canonicalize calibration frames to 56-tone grid (real HT40 fix)
Follow-up to the calibration deadlock fix. With the status gate unstuck,
maybe_feed_calibration reached feed_calibration, but a real ESP32 HT40 node
streams 128-wide amplitude frames while the single-link FieldModel is the
canonical 56-tone grid. LinkStats::update returned DimensionMismatch,
feed_calibration bubbled it, and maybe_feed_calibration swallowed it at debug
level — so frame_count stayed pinned at 0 on live hardware (presence/vitals,
which read the global history, were unaffected).

Resample each frame onto the model's canonical 56-tone grid via
HardwareNormalizer::resample_to_canonical before feeding — the same length-only
canonicalization the multistatic fusion path uses (#1170).

Pinned by maybe_feed_calibration_resamples_wide_frames_and_accumulates
(128-wide -> Collecting + count 1). sensing-server bin: 173 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-18 17:27:30 -04:00
rjperry36 4bab48e15f fix(sensing-server): unstick empty-room field-model calibration deadlock
POST /api/v1/calibration/start created the FieldModel in Uncalibrated, but
field_bridge::maybe_feed_calibration only fed frames while already Collecting
— and the only thing that sets Collecting is feed_calibration on its first
fed frame. The two gates deadlocked: no first frame was ever fed, so
calibration_frame_count stayed 0 and status never left Uncalibrated. Observed
live on a streaming ESP32 node as {"status":"Uncalibrated","frame_count":0}
that never advanced.

- field_bridge::maybe_feed_calibration: feed while Uncalibrated | Collecting
  so the first frame flips the model to Collecting and the count advances.
- calibration_stop: return structured {success:false, frame_count,
  frames_needed} instead of an opaque 500 when finalized with too few frames.
- FieldModel::min_calibration_frames() accessor for the guard above.
- Regression test: maybe_feed_calibration_advances_uncalibrated_to_collecting.

Presence/motion/vitals were unaffected (separate auto rolling baseline).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-18 17:27:08 -04:00
Matías J. Apablaza 6f0114c488 fix(firmware): accept WPA/WPA2-mixed routers; warn about compact-board thermal risk
Lower the STA auth threshold from WPA2_PSK to WPA_PSK so routers running
WPA/WPA2-mixed compatibility mode aren't rejected with
WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD (reason=211), and log the
disconnect reason/rssi instead of retrying blind. Also document the thermal
risk of running this firmware's continuous-radio, tier-2 DSP pipeline on
coin-sized clone boards (ESP32-S3-Zero, SuperMini) with minimal PCB copper
and budget regulators, after a field report of three such boards failing to
power on following a normal session.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-18 17:26:17 -04:00
Sushant c8e990c36f Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-18 17:26:17 -04:00
Sushant Guri 7925aa94ab fix(api): resolve event loop blocking in health and metrics endpoints 2026-07-18 17:26:17 -04:00
Matt Van Horn c2cf180afe docs: align firmware ESP-IDF version references to v5.4 (fixes #1177 b 2026-07-18 17:26:16 -04:00
Matt Van Horn 8c232d0894 fix: rename HeartRateExtractor.extract() weights param to phases 2026-07-18 17:26:16 -04:00
ruv 82c1b8fdf8 chore: bump wifi-densepose-signal 0.3.5 for crates.io (#1334)
Published 0.3.4 predates HardwareNormalizer::resample_to_canonical and
MultistaticConfig::for_tdm_schedule, which the sensing-server binary
uses — its publish verify fails against the registry 0.3.4. The in-repo
version had not been bumped since those APIs landed.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-14 13:31:42 -04:00
ruv 9dceb976c7 chore(publish): version rufield deps + bump worldgraph/rufield submodules (#1334)
- wifi-densepose-rufield: add version="0.1.0" to the four rufield path
  deps — rufield-core/-provenance/-privacy/-fusion are now published to
  crates.io, making this crate (and wifi-densepose-sensing-server 0.3.4)
  publishable
- v2/crates/worldgraph -> 4441bc0: wifi-densepose-worldgraph 0.3.2
  published (adds prune_semantic_states; unblocks wifi-densepose-engine
  0.3.1 publish)
- vendor/rufield -> f3c1492: breaks the fusion<->adapters circular
  dev-dependency (path-only dev-dep, stripped at publish)

Closes the crates.io publish blockers in #1334.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-14 13:18:43 -04:00
rUv 7b244bdc8c Merge pull request #1332 from ruvnet/integrate/pr-1311-1313
Integrate community fixes: DevKitC-1 overlay, EngineBridge guard config, pose-WS bearer auth (#1308 #1309 #1310)
2026-07-14 12:48:11 -04:00
github-actions[bot] 90667d0f1d chore: update vendor submodules to latest upstream (#1331)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-14 12:15:39 -04:00
ruv 8c1d3d772a chore: bump wifi-densepose-engine 0.3.1, wifi-densepose-sensing-server 0.3.4
engine 0.3.1: additive StreamingEngine::set_multistatic_config (#1312)
sensing-server 0.3.4: bearer-auth pose-WS exemption + EngineBridge guard
config threading (#1312, #1313); no public lib API change (engine_bridge
is binary-internal)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-14 12:12:48 -04:00
ruv 83f549d308 fix: post-review fixes for PRs #1311/#1312/#1313 + CHANGELOG
- sdkconfig.defaults.devkitc: header build command idf v5.2 -> v5.4
  (source needs esp_driver_uart, IDF >=5.3 — same reason the PR fixed
  the README refs)
- engine/lib.rs: separate doc comments — set_room_adapter's ADR-150
  adapter/witness doc had merged into set_multistatic_config's doc
- ui: apply stored bearer token at api.service.js module load instead
  of QuickSettings.init — services + dashboard tab dispatch their first
  /api/v1/* requests before initializeEnhancements() runs, so the first
  requests on every load went out without the Authorization header
- CHANGELOG: Unreleased entries for #1308/#1309/#1310

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-07-14 11:08:37 -04:00
ruv d59ca00baa Merge PR #1313: exempt /api/v1/stream/pose WS from bearer auth + UI token field 2026-07-13 13:50:36 -04:00
ruv da81eab714 Merge PR #1312: wire WDP_GUARD_INTERVAL_US/WDP_TDM_* into EngineBridge 2026-07-13 13:50:35 -04:00