From b6420ac9bad9b0646891db460d8193a5f9dbdd21 Mon Sep 17 00:00:00 2001 From: rUv Date: Mon, 8 Jun 2026 18:07:39 +0200 Subject: [PATCH] fix(server): make synthetic CSI opt-in only (sibling fix to #937) (#979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background Issue #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 (#943, #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/docker-compose.yml | 7 +++-- docker/docker-entrypoint.sh | 11 ++++++- .../src/commands/server.rs | 12 ++++++-- .../wifi-densepose-sensing-server/src/main.rs | 29 +++++++++++++++++-- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b511141d..f7dcf273 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,10 +24,13 @@ services: environment: - RUST_LOG=info # CSI_SOURCE controls the data source for the sensing server. - # Options: auto (default) — probe for ESP32 UDP then fall back to simulation + # Options: auto (default) — probe for ESP32 UDP then host WiFi; **fail + # hard with exit 78 if neither is detected**. + # Synthetic data is no longer a silent fallback + # (issue #937 fix) — operators must opt in. # esp32 — receive real CSI frames from an ESP32 on UDP port 5005 # wifi — use host Wi-Fi RSSI/scan data (Windows netsh) - # simulated — generate synthetic CSI data (no hardware required) + # simulated — explicitly generate synthetic CSI for demo mode - CSI_SOURCE=${CSI_SOURCE:-auto} # MODELS_DIR controls where the server scans for .rvf model files. # Mount a host directory and set this to make models visible: diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 681ae69d..192746c5 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -11,7 +11,16 @@ # docker run ruvnet/wifi-densepose:latest --model /app/models/my.rvf # # Environment variables: -# CSI_SOURCE — data source: auto (default), esp32, wifi, simulated +# CSI_SOURCE — data source. Valid values: +# auto — try ESP32 then Windows WiFi, **fail-loud if no +# real hardware is detected** (issue #937 fix: +# the server no longer silently falls back to +# synthetic data — that's now opt-in only). +# esp32 — listen for UDP CSI on the configured port. +# wifi — Windows-native WiFi capture. +# simulated — explicit demo mode with synthetic CSI. +# Default is `auto`. Set CSI_SOURCE=simulated when you want +# fake data tagged as such; never set it implicitly. # MODELS_DIR — directory to scan for .rvf model files (default: data/models) set -e diff --git a/v2/crates/wifi-densepose-desktop/src/commands/server.rs b/v2/crates/wifi-densepose-desktop/src/commands/server.rs index d240f888..4b53d731 100644 --- a/v2/crates/wifi-densepose-desktop/src/commands/server.rs +++ b/v2/crates/wifi-densepose-desktop/src/commands/server.rs @@ -108,8 +108,14 @@ pub async fn start_server( cmd.args(["--log-level", log_level]); } - // Set data source (default to "simulate" if not specified for demo mode) - let source = config.source.as_deref().unwrap_or("simulate"); + // Default to explicit "simulated" demo mode when the desktop user hasn't + // chosen a source — this is the *Tauri demo* app, not a production + // sensing endpoint, so the demo default is correct here. Critically, the + // value passed downstream is the **explicit** "simulated", not "auto", + // which means the sensing-server will tag the data as synthetic in its + // API responses rather than silently fall back (issue #937 fix in + // sensing-server's `auto` handler). + let source = config.source.as_deref().unwrap_or("simulated"); cmd.args(["--source", source]); // Redirect stdout/stderr to pipes for monitoring @@ -317,7 +323,7 @@ pub async fn restart_server( log_level: None, bind_address: None, server_path: None, - source: None, // Use default (simulate) + source: None, // Falls through to explicit "simulated" — Tauri demo default. } }; diff --git a/v2/crates/wifi-densepose-sensing-server/src/main.rs b/v2/crates/wifi-densepose-sensing-server/src/main.rs index ae59d797..2d954e2a 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/main.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/main.rs @@ -6421,7 +6421,17 @@ async fn main() { info!(" UI path: {}", args.ui_path.display()); info!(" Source: {}", args.source); - // Auto-detect data source + // Auto-detect data source. + // + // Issue #937 / sibling fix: previously `auto` silently fell back to the + // synthetic data source when no ESP32 or Windows WiFi was reachable, with + // only an `info!` log line as the signal. Downstream API consumers + // (`/api/v1/sensing/latest`, `/ws/sensing`) had no in-band way to know they + // were being served fake CSI tagged as production telemetry. That is the + // exact "where's the real data?" pattern external reviewers (#943, #934) + // cited as the most damaging evidence of the project misrepresenting its + // posture. Synthetic-data is now opt-in only — operators who want demo + // mode must explicitly set `--source simulated` or `CSI_SOURCE=simulated`. let source = match args.source.as_str() { "auto" => { info!("Auto-detecting data source..."); @@ -6432,10 +6442,23 @@ async fn main() { info!(" Windows WiFi detected"); "wifi" } else { - info!(" No hardware detected, using simulation"); - "simulate" + error!( + "No real CSI source detected. Auto-detection refuses to silently \ + fall back to synthetic data because that would expose downstream \ + consumers (/api/v1/sensing/latest, /ws/sensing) to fake telemetry \ + tagged as production. To run with synthetic data, set the source \ + explicitly: --source simulated (or CSI_SOURCE=simulated in Docker). \ + To use real hardware: provision an ESP32 to emit CSI on UDP :{} or \ + install the Windows WiFi capture driver. See \ + https://github.com/ruvnet/RuView/issues/937 for context.", + args.udp_port + ); + std::process::exit(78); // EX_CONFIG } } + // "simulate" is a synonym for "simulated" (back-compat alias kept so + // existing operators who already opted in don't get broken by this fix). + "simulate" => "simulated", other => other, };