From 83f549d3080ba1c53aed0a47b911839d815fdec2 Mon Sep 17 00:00:00 2001 From: ruv Date: Tue, 14 Jul 2026 11:08:37 -0400 Subject: [PATCH] fix: post-review fixes for PRs #1311/#1312/#1313 + CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CHANGELOG.md | 3 +++ .../esp32-csi-node/sdkconfig.defaults.devkitc | 2 +- ui/services/api.service.js | 15 ++++++++++++++- ui/utils/quick-settings.js | 17 +++-------------- v2/crates/wifi-densepose-engine/src/lib.rs | 10 ++++++---- vendor/rvcsi | 2 +- vendor/sublinear-time-solver | 2 +- 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02bd8e5..9fd3bffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`@ruvnet/rvagent` startup optimization — stdio time-to-first-response ~242 ms → ~189 ms (−22%; MEASURED, median of repeated `initialize` round-trips against `dist/index.js`, this container, reproduce with a piped-stdin timer).** Two changes: (1) `./http-transport.js` is now imported **lazily** inside the `RVAGENT_HTTP_PORT` branch — it chain-loads the MCP SDK's `streamableHttp` module (~48 ms MEASURED via per-module `import()` timing), which the default stdio path never uses; (2) the advertised JSON Schemas generated from the Zod sources are memoized per tool instead of re-walking the Zod tree on every `tools/list` (matters under the session-per-server HTTP model where each session lists tools). No behavior change: 99/99 jest tests, HTTP session flow re-smoke-tested through the lazy path. The `@ruvnet/ruview` harness CLI was profiled too and left alone — 50 ms vs the ~29 ms bare `node -e ''` floor on the same box (MEASURED), i.e. already near the interpreter floor with zero dependencies. ### Fixed +- **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` 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). +- **`/api/v1/stream/pose` WebSocket reachable with `RUVIEW_API_TOKEN` set + dashboard bearer-token field** (#1310, PR #1313, @erichkusuki). Browsers cannot attach an `Authorization` header to a WS upgrade, so the Live Demo pose stream always failed when auth was on; the path is now on a narrow exact-match exemption list (mirrors `/ws/sensing`), with a regression test pinning that the exemption doesn't leak to other `/api/v1/*` paths. The QuickSettings panel gains an "API Access" field storing the bearer token in `localStorage`; the token is applied at `api.service.js` module load so the very first request carries it. +- **Display-less DevKitC-1 boards: `sdkconfig.defaults.devkitc` build overlay** (#1308, PR #1311, @erichkusuki). The ADR-045 runtime display probe false-positives on stock ESP32-S3-DevKitC-1 (floating QSPI pins), which silently skipped the RuView#893 MGMT+DATA CSI upgrade and collapsed CSI yield to 0 pps. The overlay compiles display support out (`has_display` constant-false). Also fixes stale `espressif/idf:v5.2` README references to v5.4 (source requires `esp_driver_uart`, IDF ≥5.3). Hardware-verified on 2× DevKitC-1-N16R8 (0 → 40–45 pps). - **ADR-263/264/265 implemented — the RuView npm surface fixed end-to-end (`@ruvnet/ruview@0.2.0`, `@ruvnet/rvagent@0.2.0`, `@ruv/ruview-cli`).** Harness (ADR-263 O1–O9): `claim-check` now **fails closed** on empty input (CLI exit 2 + `empty_text` tool error); the MCP stdio server dispatches `tools/call` asynchronously over promise-based `spawn` — `ping` answers while a long `verify`/`calibrate` runs (pinned by a new e2e test that runs a 3 s fake proof and asserts sub-second ping); the two `optionalDependencies` are gone so a cold `npx` installs exactly 1 package (MEASURED: was 4 packages / 620 kB / 71 files, `npm i` in a clean prefix); child output is captured as bounded rolling tails (no more 1 MiB `maxBuffer` kills); `node_monitor` passes the port via `sys.argv` instead of splicing it into `python -c` source; the MCP `serverInfo.version` reads package.json; `.claude/skills/*/SKILL.md` are generated from `skills/*.md` by a `prepack` sync script (byte-equality pinned by test); `which()` is a memoized dep-free PATH scan; tools are underscore-canonical (`ruview_claim_check`, …) with the dotted names accepted as call-time aliases, plus `resources/list`/`prompts/list` stubs; the guardrail's `METRIC_TERMS` matching is precision-fixed (word-boundary `map`/`f1`/`auc`/`iou`, code-span + label scrubbing, quantitative-claims-only) — ADR-263/264/265 and both package READMEs now PASS `claim-check` while real untagged claims still flag. 30/30 tests (MEASURED, `node --test`). rvagent (ADR-264 O1–O9): `exports` fixed (types-first, the never-built `dist/index.cjs` `require` target removed — verified broken in the published 0.1.0 tarball); tarball is map-free (127,704 B unpacked / 46 files / 0 maps — MEASURED, `npm pack --dry-run`, down from 188 kB with 44 maps); the Streamable HTTP transport is **actually wired** behind `RVAGENT_HTTP_PORT` with one transport + one MCP server per session (`mcp-session-id` routing), a 1 MiB body cap (413), and a port-aware localhost origin gate — the "dual-transport" description is now true; tools renamed to underscore-canonical with dotted router aliases; ONE Zod validation gate per call with the advertised JSON Schema generated from the same Zod source (`zod-to-json-schema`); `train_count` closes its log fds (was leaking 2/job) and persists job records to `/.json` so `job_status` survives restarts, with bounded log-tail reads; `detectCogBinary` actually probes its candidate paths; version reads package.json; `@types/express` dropped, `@types/jest` aligned to jest 29; README rewritten to match reality (no phantom `stdio`/`http`/`policy grant` subcommands; unimplemented ADR-124 catalog tools labeled roadmap). 99/99 jest tests (MEASURED); stdio handshake + HTTP session flow + 403/400/404/413 gates smoke-tested live. CLI: bin renamed `ruview-cli` (the `ruview` bin belongs to `@ruvnet/ruview`, ADR-265 D4), version single-sourced. Distribution (ADR-265 D1–D4): new `npm-packages.yml` (3-package × Node 20/22 matrix: tests, version-literal grep gate, pack-content/size gate, tarball-install smoke test incl. the fail-closed claim-check and an ESM-import probe that would have caught the broken `require` export, README claim-check) and `ruview-npm-release.yml` (publish from CI only, `npm publish --provenance`); `ci.yml` NODE_VERSION 18→20. - **Multistatic fusion never ran on a mixed-mode ESP32 mesh — live bridge fed raw, un-canonicalized per-node CSI to the fuser (#1170).** `node_frame_from_state` (`multistatic_bridge.rs`) wrapped each node's **raw** amplitude vector (HT20 ≈ 64 bins, HT40 ≈ 128/192) into a struct *named* `CanonicalCsiFrame` without ever resampling, so `MultistaticFuser::fuse` tripped `DimensionMismatch` on every cycle, silently fell back to per-node sum/dedup, and spun `total_engine_errors` unbounded. Added `HardwareNormalizer::resample_to_canonical` (resample-only, **no z-score** — preserves the amplitude scale the person-score's `variance/mean²` relies on) and run every node frame through it onto the canonical 56-tone grid before fusion. Heterogeneous meshes now fuse instead of erroring. Pinned by `heterogeneous_node_counts_canonicalize_and_fuse` (mixed 64/192 → fuses), `resample_to_canonical_is_length_only_no_zscore`, and an updated `test_node_frame_conversion`; the pre-existing `engine_bridge::observe_cycle_counts_engine_errors` was retargeted to force a `TimestampMismatch` (its old 56-vs-30 setup now canonicalizes cleanly). `wifi-densepose-signal` 501 / `wifi-densepose-sensing-server` 677 tests, 0 failed. - **`csi_fps_ema` reported the CSI frame rate 40–840× too high under bursty UDP delivery (#1180).** `update_csi_fps_ema` only rejected deltas `≤ 0` or `≥ 1 s`, so a 36 µs intra-burst arrival delta yielded `1/dt ≈ 27 kHz` straight into the EMA — the metric measured server arrival jitter, not the node's ~40 fps production rate. Added a `MIN_PLAUSIBLE_CSI_DT_SEC = 0.005` floor (derived from the firmware's 50 fps `CSI_MIN_SEND_INTERVAL_US` ceiling, ×4 slack) and made `observe_csi_frame_arrival` keep its anchor across sub-floor bursts so the next genuine inter-frame gap measures true cadence. Pinned by `subms_burst_delta_rejected`, `burst_interleaved_with_nominal_stays_in_band`, and `observe_csi_frame_arrival_ignores_subms_bursts`. diff --git a/firmware/esp32-csi-node/sdkconfig.defaults.devkitc b/firmware/esp32-csi-node/sdkconfig.defaults.devkitc index 8cb1962a..aa1d8dee 100644 --- a/firmware/esp32-csi-node/sdkconfig.defaults.devkitc +++ b/firmware/esp32-csi-node/sdkconfig.defaults.devkitc @@ -10,7 +10,7 @@ # Build (from repo root, per README "Docker — the only reliable method"): # MSYS_NO_PATHCONV=1 docker run --rm \ # -v "$(pwd)/firmware/esp32-csi-node:/project" -w /project \ -# espressif/idf:v5.2 bash -c \ +# espressif/idf:v5.4 bash -c \ # "rm -rf build sdkconfig && idf.py -DSDKCONFIG_DEFAULTS='sdkconfig.defaults;sdkconfig.defaults.devkitc' set-target esp32s3 && idf.py -DSDKCONFIG_DEFAULTS='sdkconfig.defaults;sdkconfig.defaults.devkitc' build" # CONFIG_DISPLAY_ENABLE is not set diff --git a/ui/services/api.service.js b/ui/services/api.service.js index 79b94432..4e5daee7 100644 --- a/ui/services/api.service.js +++ b/ui/services/api.service.js @@ -147,4 +147,17 @@ export class ApiService { } // Create singleton instance -export const apiService = new ApiService(); \ No newline at end of file +export const apiService = new ApiService(); + +// Storage key shared with the QuickSettings "API Access" panel. +export const API_TOKEN_STORAGE_KEY = 'ruview-api-token'; + +// Apply a previously-saved bearer token at module load — before app init +// dispatches its first request — so a configured RUVIEW_API_TOKEN works from +// the very first /api/v1/* call. The server only ever checks the +// `Authorization: Bearer` header (see bearer_auth.rs) — this intentionally +// never puts the token in a URL query string. +try { + const storedToken = localStorage.getItem(API_TOKEN_STORAGE_KEY); + if (storedToken) apiService.setAuthToken(storedToken); +} catch { /* storage unavailable (private browsing etc.) */ } \ No newline at end of file diff --git a/ui/utils/quick-settings.js b/ui/utils/quick-settings.js index 5112ee9a..afae95eb 100644 --- a/ui/utils/quick-settings.js +++ b/ui/utils/quick-settings.js @@ -1,9 +1,7 @@ // Quick Settings Panel - Centralized configuration for all UI features // Accessible via gear icon in header -import { apiService } from '../services/api.service.js'; - -const API_TOKEN_STORAGE_KEY = 'ruview-api-token'; +import { apiService, API_TOKEN_STORAGE_KEY } from '../services/api.service.js'; export class QuickSettings { constructor(app) { @@ -13,22 +11,13 @@ export class QuickSettings { this.isOpen = false; } + // A stored token is applied at api.service.js module load (before any + // request fires) — this panel only saves/clears it. init() { - this.applyStoredApiToken(); this.createButton(); this.createPanel(); } - // Apply a previously-saved bearer token to apiService as early as - // possible, before any tab's REST calls fire. The server only ever - // checks the `Authorization: Bearer` header (see bearer_auth.rs) — this - // intentionally never puts the token in a URL query string. - applyStoredApiToken() { - let token = null; - try { token = localStorage.getItem(API_TOKEN_STORAGE_KEY); } catch { /* noop */ } - if (token) apiService.setAuthToken(token); - } - createButton() { this.button = document.createElement('button'); this.button.className = 'settings-gear'; diff --git a/v2/crates/wifi-densepose-engine/src/lib.rs b/v2/crates/wifi-densepose-engine/src/lib.rs index f9b02c6b..ec21ca1d 100644 --- a/v2/crates/wifi-densepose-engine/src/lib.rs +++ b/v2/crates/wifi-densepose-engine/src/lib.rs @@ -226,10 +226,6 @@ impl StreamingEngine { } } - /// Activate a per-room calibration adapter (ADR-150 §3.4). From the next - /// cycle on, the adapter id is part of provenance `model_version` — and - /// therefore of the witness — so the exact weights shaping inference are - /// pinned in the trust chain. Pass the result of hashing the adapter file. /// Override the multistatic fuser's timestamp guard interval (#1049/#1057). /// Without this, `StreamingEngine::new` always builds /// `MultistaticFuser::with_config(MultistaticConfig::default())` — a @@ -237,10 +233,16 @@ impl StreamingEngine { /// caller derived from `WDP_TDM_SLOTS`/`WDP_GUARD_INTERVAL_US`, so /// WiFi/ESP-NOW-synced multi-node deployments spuriously fail governed /// trust cycles even after widening the guard elsewhere. + /// + /// Rebuilds the fuser, so call before any frames are processed. pub fn set_multistatic_config(&mut self, cfg: MultistaticConfig) { self.fuser = MultistaticFuser::with_config(cfg); } + /// Activate a per-room calibration adapter (ADR-150 §3.4). From the next + /// cycle on, the adapter id is part of provenance `model_version` — and + /// therefore of the witness — so the exact weights shaping inference are + /// pinned in the trust chain. Pass the result of hashing the adapter file. pub fn set_room_adapter(&mut self, info: AdapterInfo) { self.adapter = Some(info); } diff --git a/vendor/rvcsi b/vendor/rvcsi index 72891d74..77c8b6e0 160000 --- a/vendor/rvcsi +++ b/vendor/rvcsi @@ -1 +1 @@ -Subproject commit 72891d740f92903c78a8208a9069f6c82a4d1cc2 +Subproject commit 77c8b6e051fe8a2a3370715d4c3139311b93c364 diff --git a/vendor/sublinear-time-solver b/vendor/sublinear-time-solver index c25dddf1..47804fc5 160000 --- a/vendor/sublinear-time-solver +++ b/vendor/sublinear-time-solver @@ -1 +1 @@ -Subproject commit c25dddf163d8c413628ecdc6e979583d39270f22 +Subproject commit 47804fc5ca495d4fd608866fdb1e0960b34bffbf