Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`archive/v1` (the original pure-Python implementation) formally deprecated (ADR-187)** — commits `1fb5397dd`, `b1417fb6e`; refs #509, #1125. Added `archive/v1/DEPRECATED.md` (a loud tombstone) and a `> ⚠️ DEPRECATED` notice atop `archive/v1/README.md`, both pointing at the maintained `v2/` workspace and the `wifi-densepose 2.x` / `ruview` pip wheel (ADR-117). Records the honest fact behind #509: `archive/v1`'s `DensePoseHead` is **architecture-only** — random `kaiming_normal_` init with **zero committed checkpoints** under `archive/v1/` (MEASURED by Glob over `**/*.{pth,onnx,safetensors,pt,ckpt,bin}`). The ADR-028 deterministic proof `archive/v1/data/proof/verify.py` stays live and is explicitly out of scope. The same effort added a **"Model weights: what's real, what's not" three-tier table** to `README.md` + `docs/user-guide.md`, separating real-and-validated checkpoints (presence 82.3% held-out temporal-triplet, MM-Fi pose 82.69% torso-PCK@20, `count_v1`) from the real-but-weak on-device `pose_v1` (PCK@20 = 3.0%, runtime `confidence=0` stub, below the ADR-079 ≥35% target) from the architecture-only `archive/v1` head — and caveated every live single-ESP32 17-keypoint advertisement accordingly. Docs/labeling only; no code or model behavior changed.

### Fixed
- **`DELETE /api/v1/recording/:id` and `DELETE /api/v1/models/:id` 404'd for every id — axum 0.8 path syntax on an axum 0.7 dependency.** Both routes were declared with `{id}`, the matchit-0.8 / axum-0.8 capture syntax, but the workspace pins `axum = "0.7"` (0.7.9 resolved), where `{id}` matches as a **literal** path segment. No real id ever matched, so requests fell through to the 404 fallback and never reached their handlers — surfacing in the dashboard's Training tab as recording deletion failing with 404. Neither handler can even emit a 404 (both return `Json`, i.e. HTTP 200), which confirms the request was never routed. Changed to `:id` across `main.rs` (the 2 live routes), plus `model_manager.rs` and `recording.rs` (3 routes in routers not currently merged, corrected for consistency). Verified against a running server: `DELETE /api/v1/recording/does-not-exist-test` now returns `200 {"error":"recording not found","success":false}` instead of a bare 404, and `GET /api/v1/models/<id>` returns 405 (path matches, method not registered) instead of 404.
- **In-server training reconnected — "Start Training" no longer silently no-ops; `/ws/train/progress` streams real progress (ADR-186, issue #1233).** The dashboard's Start Training button POSTed a config, got `success:true`, and nothing happened: `/api/v1/train/start` was a stub that flipped a status string and logged one line, and `/ws/train/progress` 404'd. The full pure-Rust trainer in `training_api.rs` (loads recorded CSI, gradient-descent, exports a `.rvf`) already existed but was **orphaned** — never declared as a module (no `mod training_api;`), so it wasn't compiled at all. Fix (`wifi-densepose-sensing-server`): declared the module, reconciled `AppStateInner` (replaced the `training_status`/`training_config` stub fields with a shared `TrainingState` status handle + cooperative cancel flag + a `training_progress_tx` broadcast), deleted the stub handlers, and merged the real `training_api::routes()` (so `/api/v1/train/{start,stop,status,pretrain,lora}` and `/ws/train/progress` resolve under the existing `/api/v1/*` bearer gate). The training core was decoupled from the ~60-field server state so it is unit-testable. **P5 honesty guarantee:** with `RUVIEW_DISABLE_SERVER_TRAINING` set, start returns a structured `{enabled:false, cli:"wifi-densepose train-room"}` HTTP 409 — never a silent success — and the dashboard disables the Start buttons with a CLI tooltip (enablement is surfaced on `/api/v1/train/status`). Pinned by 8 new tests incl. a **live-socket** test that completes a genuine 101 WebSocket handshake and receives a real progress frame after a POST start, a full POST→poll-status→`.rvf`-exists round-trip, a path-traversal rejection, cancellation, and the disabled-409 path. `cargo test -p wifi-densepose-sensing-server -p wifi-densepose-train --no-default-features` — 0 failed.
- **FastAPI health/metrics endpoints event-loop starvation.** Calling `psutil.cpu_percent(interval=1)` blocked the single-threaded async event loop for 1.0 second on every health check or metrics collection tick, stalling all incoming requests and WebSocket operations. Fixed by changing `cpu_percent` to use non-blocking `interval=None` and offloading all blocking OS metrics gathering to background thread pools via `asyncio.to_thread`. Verified event loop responsiveness via concurrency regression tests.
- **EngineBridge now honors `WDP_GUARD_INTERVAL_US`/`WDP_SOFT_GUARD_US`/`WDP_TDM_SLOTS`+`WDP_TDM_SLOT_US`** (#1309, PR #1312, @erichkusuki). The governed trust path previously built its multistatic fuser from a hardcoded `MultistaticConfig::default()` (60 ms guard), so multi-node deployments with WiFi/ESP-NOW time sync (10–150 ms drift) failed every governed cycle regardless of configuration — while the startup log claimed the override took effect. New `StreamingEngine::set_multistatic_config()`; `EngineBridge::new()` takes an `Option<MultistaticConfig>` threaded from the same env-derived config as `AppState.multistatic_fuser`. Hardware-verified on a live 2-node ESP32-S3 setup (90 s window, 0 fusion errors; previously every cycle failed).
Expand Down
4 changes: 2 additions & 2 deletions v2/crates/wifi-densepose-sensing-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8156,14 +8156,14 @@ async fn main() {
.route("/api/v1/models/active", get(get_active_model))
.route("/api/v1/models/load", post(load_model))
.route("/api/v1/models/unload", post(unload_model))
.route("/api/v1/models/{id}", delete(delete_model))
.route("/api/v1/models/:id", delete(delete_model))
.route("/api/v1/models/lora/profiles", get(list_lora_profiles))
.route("/api/v1/models/lora/activate", post(activate_lora_profile))
// Recording endpoints
.route("/api/v1/recording/list", get(list_recordings))
.route("/api/v1/recording/start", post(start_recording))
.route("/api/v1/recording/stop", post(stop_recording))
.route("/api/v1/recording/{id}", delete(delete_recording))
.route("/api/v1/recording/:id", delete(delete_recording))
// Training endpoints (ADR-186 TRAIN-RECONNECT): the real in-server
// trainer + `/ws/train/progress` stream. Merged while the router is
// still `Router<SharedState>` (before `.with_state`) so these routes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ pub fn routes() -> Router<AppState> {
.route("/api/v1/models/unload", post(unload_model))
.route("/api/v1/models/lora/activate", post(activate_lora))
.route("/api/v1/models/lora/profiles", get(list_lora_profiles))
.route("/api/v1/models/{id}", get(get_model))
.route("/api/v1/models/:id", get(get_model))
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions v2/crates/wifi-densepose-sensing-server/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,10 @@ pub fn routes() -> Router<AppState> {
.route("/api/v1/recording/stop", post(stop_recording))
.route("/api/v1/recording/list", get(list_recordings))
.route(
"/api/v1/recording/download/{id}",
"/api/v1/recording/download/:id",
get(download_recording),
)
.route("/api/v1/recording/{id}", delete(delete_recording))
.route("/api/v1/recording/:id", delete(delete_recording))
}

#[cfg(test)]
Expand Down