Files
ruvnet--RuView/docs/adr/ADR-080-qe-remediation-plan.md
T
rUv 42dcf49f4d fix(adr): resolve duplicate ADR numbers + close ADR-080 security + ADR-154 M1 signal backlog (#1051)
* fix(signal): circular phase variance for ghost-tap guard (ADR-154 §7.4 #1)

`phase_variance` computed a LINEAR sample variance over phase angles that
wrap at ±π, so a tightly-clustered set straddling the branch cut reported
spuriously HIGH dispersion — false-tripping the `> TAU` ghost-tap guard on
real, tightly-clustered CIR taps.

Replace with Mardia's circular variance V = 1 − R̄, bounded [0,1] and
invariant to where the cluster sits on the circle. Re-derive the guard
against the bounded metric via a named const
`GHOST_TAP_CIRCULAR_VARIANCE_MAX` (the old TAU-scaled threshold is
meaningless on [0,1]).

Grade: metric fix MEASURED; threshold value DATA-GATED — a clean single-path
ramp also sweeps the circle, so V alone cannot separate clean from
unsanitized without labelled frames. Conservative default (0.99) errs toward
never false-rejecting, strictly more permissive at the wrap boundary than the
buggy linear guard.

Fails-on-old test: `phase_variance_circular_not_fooled_by_branch_cut` —
inlines the old linear variance to show it exceeds TAU on wrap-straddling
phases while circular V≈0 and the guard no longer trips. Plus
`phase_variance_circular_is_bounded_and_extremal` (V∈[0,1], V≈0 identical,
V≈1 uniform).

cargo test -p wifi-densepose-signal --no-default-features --features cir --lib
→ 432 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(signal): pin Welford n=0/n=1 finiteness guard (ADR-154 §7.4 #10)

The shared `WelfordStats` (field_model.rs, used by longitudinal.rs and others)
relies on `count < 2` guards in `variance`/`sample_variance`/`std_dev`/
`z_score` to stay finite at the boundaries. The guards existed but the n=0
boundary was UNTESTED — exactly the §4 divide-by-(n−1) family the ADR groups
this with.

Add `welford_finite_at_n0_and_n1` asserting every statistic is finite and
returns the documented sentinel (0.0) at n=0 and n=1, plus load-bearing doc
comments on the two guards.

Fails-on-old proof: with the `sample_variance` guard removed, the test FAILS
with "attempt to subtract with overflow" at the `(self.count - 1)` underflow
(0usize − 1); `variance` would similarly yield 0.0/0.0 = NaN. The guard is
restored; the test pins it so a future regression is caught.

Grade: MEASURED (boundary finiteness is asserted; the guard is the §4-family
fix made testable).

cargo test -p wifi-densepose-signal --no-default-features --lib field_model
→ 22 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* refactor(signal): de-magic adversarial thresholds + boundary tests (ADR-154 §7.4 #13)

Lift the bare numeric literals buried in `check`/`check_consistency` into
named, documented module consts (FIELD_MODEL_GINI_VIOLATION=0.8,
ENERGY_RATIO_HIGH_VIOLATION=2.0, ENERGY_RATIO_LOW_VIOLATION=0.1,
CONSISTENCY_ACTIVE_FRACTION_OF_MEAN=0.1, SCORE_W_* weights). VALUES UNCHANGED —
each const equals the original literal; only names + pinning tests are new.

Grade: DATA-GATED. The operating values stay empirical (defensible values need
labelled spoofed/clean CSI — Wi-Spoof, §6.2/§7.3). The de-magicking +
characterization tests are MEASURED: `tuning_consts_unchanged_from_literals`,
`energy_ratio_high_boundary`, `energy_ratio_low_boundary`,
`field_model_gini_boundary`, `consistency_active_fraction_boundary` pin the
decision boundaries at/just-below/just-above each threshold, so a future
data-driven retune is a visible, tested change.

Fails-on-change proof: bumping ENERGY_RATIO_HIGH_VIOLATION 2.0→3.0 makes
`energy_ratio_high_boundary` FAIL (restored). Operating values explicitly
NOT changed.

cargo test -p wifi-densepose-signal --no-default-features --lib ruvsense::adversarial
→ 20 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* refactor(signal): de-magic coherence drift/gate thresholds (ADR-154 §7.4 #9)

Lift the bare detection literals in `coherence.rs::classify_drift`
(DRIFT_STABLE_SCORE=0.85, DRIFT_STEP_CHANGE_MAX_STALE=10) and the
`coherence_gate.rs` Default impl (DEFAULT_ACCEPT_THRESHOLD=0.85,
DEFAULT_REJECT_THRESHOLD=0.5, DEFAULT_MAX_STALE_FRAMES=200,
DEFAULT_PREDICT_ONLY_NOISE=3.0) into named, documented consts. VALUES
UNCHANGED. The gate already exposed these via GatePolicyConfig (config seam);
this names + pins the defaults.

Grade: DATA-GATED. Operating values stay empirical (defensible Z-score
thresholds need labelled stable/drifting coherence traces). De-magicking +
boundary tests are MEASURED: `classify_drift_stable_score_boundary`,
`classify_drift_stale_count_boundary` pin the at/just-below/just-above
decisions; `drift_consts_unchanged_from_literals` /
`gate_default_consts_unchanged_from_literals` pin the values. Operating values
explicitly NOT changed.

cargo test -p wifi-densepose-signal --no-default-features --lib ruvsense::coherence
→ 40 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(adr-154): mark §7.4 P1 backlog cleared — Milestone-1 (#1,#10 RESOLVED; #9,#13 DATA-GATED)

Update ADR-154 §7.4 backlog rows #1, #9, #10, #13 with commit refs + grades,
the §7.4 intro count (four P1 items cleared, ~41 P2/P3 remain), the
Horizon-ledger one-liner (Milestone-1 DONE), and the §8 honest-limits #1 line
(metric now correct; threshold still DATA-GATED). Add CHANGELOG [Unreleased]
entry.

Grades: #1 RESOLVED (MEASURED metric / DATA-GATED threshold), #10 RESOLVED
(MEASURED), #9 & #13 RESOLVED-PARTIAL (DATA-GATED — de-magicked + boundary
tested, operating values unchanged).

Validation: cargo test --workspace --no-default-features → 2057 passed, 0
failed; wifi-densepose-signal lib → 442 passed (no-default + --features cir);
python archive/v1/data/proof/verify.py → VERDICT: PASS, hash f8e76f21…46f7a
UNCHANGED (CIR ghost-tap guard is not on the deterministic proof path).

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(sensing-server): stop leaking internal errors in HTTP responses (ADR-080 #2)

Six handlers in `main.rs` serialized the internal error `Display` straight
into the JSON response body, leaking server internals to any client (ADR-080
finding #2, CWE-209; reframed onto the Rust boundary by ADR-164 G11):

  - edge_registry_endpoint: a panicked spawn_blocking `JoinError`
    ("task … panicked") in a 500, and the raw upstream error in a 503
  - delete_model / delete_recording / start_recording: std::io::Error
    strings carrying OS detail / filesystem paths
  - calibration_start / calibration_stop: the FieldModel error chain

New `error_response` module: `internal_error` / `internal_error_json` /
`upstream_unavailable` log the full detail server-side only (tagged with a
correlation id) and return a generic body
(`{"error":"internal_error","correlation_id":…}`) — no `panicked`, no file
paths, no Debug chain. The correlation id lets an operator join a client
report to the exact server log line without ever shipping the detail.

Pinned by 5 error_response tests, incl. a leak-substring guard
(internal_error_body_does_not_leak_detail) verified to FAIL on the reverted
old body (returns the panic message / path / "os error"). The HOMECORE sweep
(ADR-161) covered homecore-server, not this crate.

Co-Authored-By: claude-flow <ruv@ruv.net>

* test(sensing-server): pin XFF-immunity + no-query-token (ADR-080 #1, #3)

Findings #1 (XFF-spoofing bypass) and #3 (JWT-in-URL, CWE-598) were logged
against the Python v1 API but are VERIFIED ABSENT on the current Rust
sensing-server, so they get regression tests rather than redundant fixes:

  - #1 XFF: there is no IP-based rate-limiter or IP-allowlist to bypass, and
    neither security middleware reads a forwarded header. Added
    bearer_auth::xff_header_never_affects_auth_decision (spoofed
    X-Forwarded-For never flips a 401<->200 decision) and
    host_validation::forwarded_headers_never_bypass_host_allowlist (spoofed
    X-Forwarded-Host: localhost never lets Host: evil.com past the allowlist).

  - #3 JWT-in-URL: require_bearer reads the token only from the Authorization
    header; WS handlers take no query token; the sole Query extractor
    (EdgeRegistryParams) is a non-secret refresh flag. Added
    bearer_auth::query_string_token_is_never_accepted — ?token= / ?access_token=
    in the URL never authenticates (stays 401) while the header path still 200s.
    Verified to FAIL when a query-token path is injected into require_bearer.

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(adr-080): mark P0 security findings #1-#3 RESOLVED; close ADR-164 G11

- ADR-080: Status note + per-finding closure (#1 XFF and #3 JWT-in-URL
  verified absent + regression-pinned; #2 leaked errors fixed via the
  error_response module). Records the v1-vs-Rust boundary distinction
  explicitly: v1 paths remain archived; this closure governs the shipped
  Rust sensing-server.
- ADR-164: Gap Register G11 and the Open/Gated Backlog entry marked
  RESOLVED with the fix + branch reference.
- CHANGELOG: [Unreleased] -> ### Security entry covering all three findings.

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(adr): renumber 6 displaced ADRs to resolve duplicate-number collisions (ADR-164 G1)

Resolves the 5 duplicate ADR numbers (6 displaced files) flagged by ADR-164
Gap Register item G1. Canonical keeper per number = first file committed at
that number (date tie-broken by inbound cross-reference count / parent-appendix
relationship). Displaced files renumbered to the next free numbers (166-171):

  050 keeps provisioning-tool-enhancements (5 refs vs 1)
    -> ADR-166-quality-engineering-security-hardening
  052 keeps tauri-desktop-frontend (parent ADR)
    -> ADR-167-ddd-bounded-contexts (its appendix)
  147 keeps nvidia-cosmos/OccWorld (the actual ADR, has Status header)
    -> ADR-168-benchmark-proof (proof companion, no Status)
    -> ADR-169-adam-mode-light-theme (was untracked)
  148 keeps drone-swarm-control-system (committed #862)
    -> ADR-170-yoga-mode-pose-system (was untracked)
  149 keeps public-community-leaderboard-huggingface (committed 16:47 vs 17:38)
    -> ADR-171-swarm-benchmarking-evaluation-methodology

Updates in-file `# ADR-NNN` headers and intra-file self-references (yoga-modes

* docs(adr): repoint inbound cross-references to renumbered ADRs (166-171)

Follow-up to the ADR renumbering (ADR-164 G1). Updates every inbound reference
that pointed at a displaced ADR, disambiguating shared numbers by title/slug so
only references to the DISPLACED topic move and keeper references stay put.

ADR-168 (was 147 benchmark-proof): README, CHANGELOG, user-guide,
  proof-of-capabilities, research docs 00/03 — all path/label refs updated.
ADR-169 (was 147 adam-mode) / ADR-170 (was 148 yoga-mode): docs/adr/README index.
ADR-171 (was 149 swarm-benchmarking): all ruview-swarm eval code+docs
  (Cargo.toml, evals/, eval_swarm.rs, metrics/mod/report/runner.rs), research
  doc 03 (every §-ref matched ADR-171 sections, not AetherArena), 00-system-review,
  series README, CHANGELOG, and ADR-148's forward/"open issues" pointers.
ADR-166 (was 050 quality-engineering / security-hardening): disambiguated from the
  ADR-050 provisioning KEEPER by topic. The HMAC/secure_tdm, directory-traversal,
  bind-address, and OTA-PSK-auth references in code comments
  (wifi-densepose-hardware Cargo.toml + secure_tdm.rs, sensing-server main.rs) and
  in ADR-052-tauri / ADR-167 all describe the security-hardening ADR -> ADR-166.
ADR-167 (was 052 ddd-appendix): inbound appendix references.

Index/registry updates: docs/adr/README.md, gap-analysis/census.md (rows +
header count), gap-analysis/lens-findings.md (collision table marked RESOLVED),
and ADR-164 Gap Register G1 marked RESOLVED with the full renumber map.

Keeper references deliberately untouched: all ADR-147 OccWorld code, all ADR-148
drone-swarm code/docs, all ADR-149 AetherArena refs (incl. ADR-150's SSL/resampling
refs, which ADR-150 explicitly binds to the AetherArena benchmark), ADR-050
provisioning refs, ADR-052 tauri refs. The frozen GitHub blob URLs in
docs/adr/.issue-177-body.md (pinned to an old branch) are left as historical.

Comment-only code edits; no behavior change. wifi-densepose-hardware compiles
clean; the sensing-server build's sole blocker is the pre-existing upstream
midstreamer-temporal-compare@0.2.1 registry crate, unrelated to these edits.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-13 14:31:38 -04:00

8.4 KiB
Raw Blame History

ADR-080: QE Analysis Remediation Plan

Context

An 8-agent QE swarm analyzed ~305K lines across Rust, Python, C firmware, and TypeScript on 2026-04-05. The overall score was 55/100 (C+) — Quality Gate FAILED. This ADR captures the findings and establishes a remediation plan.

Decision

Address the 15 prioritized issues from the QE analysis in three waves: P0 (immediate), P1 (this sprint), P2 (this quarter).

Security P0 closure note (2026-06-13) — Rust sensing-server boundary

The three P0 security findings below were logged against the Python v1 API (archive/v1/src/…). ADR-164 G11 re-scoped them to the shipped boundary: wifi-densepose-sensing-server (Rust). They were verified against the current Rust crate and closed on branch fix/adr-080-sensing-server-security. Each fix (or already-fixed finding) is pinned by a test that fails on the old behavior. The Python v1 paths remain as-is — v1 is archived and not the shipped surface; this closure governs the live Rust server only.

P0 — Fix Immediately

1. Rate Limiter Bypass / XFF spoofing (Security HIGH) — RESOLVED (verified absent on Rust boundary)

  • Original location (v1): archive/v1/src/middleware/rate_limit.py:200-206
  • Problem: Trusts X-Forwarded-For without validation. Any client bypasses rate limits via header spoofing.
  • Rust verification (2026-06-13): The Rust sensing-server has no XFF-trusting control to bypass — there is no IP-based rate-limiter and no IP-allowlist, and neither security middleware reads a forwarded header. bearer_auth.rs authenticates on the token alone (require_bearer inspects only the AUTHORIZATION header); host_validation.rs decides on the Host header only. A repo-wide grep for x-forwarded-for|forwarded|peer_addr|client_ip|real-ip over wifi-densepose-sensing-server returns nothing. The only "rate limiter" is the MQTT sample-rate gate (mqtt/state.rs), a per-entity publish throttle with no IP/header input.
  • Resolution: No code change needed (no vulnerable surface). Regression tests pin the immunity: bearer_auth::tests::xff_header_never_affects_auth_decision (spoofed XFF never flips a 401↔200 decision) and host_validation::tests::forwarded_headers_never_bypass_host_allowlist (spoofed X-Forwarded-Host: localhost never lets a foreign Host: evil.com past the allowlist). Residual: if an IP-based control is ever added, it must derive the peer from the socket (ConnectInfo<SocketAddr>) and only honor XFF from an explicit --trusted-proxy CIDR — captured as guidance in the test docstrings.

2. Exception Details Leaked in Responses (Security HIGH, CWE-209) — RESOLVED

  • Original location (v1): archive/v1/src/api/routers/pose.py:140, stream.py:297, +5 endpoints
  • Problem: Internal error/stack-trace detail serialized into client responses.
  • Rust finding (2026-06-13): Six handlers in wifi-densepose-sensing-server/src/main.rs serialized the internal error Display into the JSON body: edge_registry_endpoint returned a panicked spawn_blocking JoinError ("task … panicked") in a 500 and the raw upstream error in a 503; delete_model/delete_recording/start_recording returned std::io::Error strings (OS detail / path); calibration_start/calibration_stop returned the FieldModel error chain.
  • Fix: New src/error_response.rs module — internal_error / internal_error_json / upstream_unavailable log the full detail server-side only (tagged with a correlation id) and return a generic body ({"error":"internal_error","correlation_id":…}) with no panicked, no file paths, no Debug chain. All six call-sites rewired. Pinned by error_response::tests::internal_error_body_does_not_leak_detail (leak-substring guard, verified to fail on the reverted old body) + 4 sibling tests.

3. WebSocket JWT in URL (Security HIGH, CWE-598) — RESOLVED (verified absent on Rust boundary)

  • Original location (v1): archive/v1/src/api/routers/stream.py:74, archive/v1/src/middleware/auth.py:243
  • Problem: Tokens in query strings visible in logs/proxies/browser history.
  • Rust verification (2026-06-13): The Rust sensing-server never reads a token from the URL. require_bearer (bearer_auth.rs) inspects only the Authorization header; the WebSocket handlers (ws_sensing_handler/ws_introspection_handler/ws_pose_handler) take a bare WebSocketUpgrade with no Query extractor; the single Query in the crate (EdgeRegistryParams) is a non-secret refresh flag.
  • Resolution: No code change needed (no query-token path exists). Regression test bearer_auth::tests::query_string_token_is_never_accepted proves ?token=/?access_token= in the URL never authenticates (stays 401) while the same token in the header succeeds (200) — verified to fail if a query-token path is re-introduced.

4. Rust Tests Not in CI

  • Problem: 2,618 tests across 153K lines of Rust — zero run in any GitHub Actions workflow. Regressions ship undetected.
  • Fix: Add cargo test --workspace --no-default-features to CI. 1-2 hour task.

5. WebSocket Path Mismatch (Bug)

  • Location: ui/mobile/src/services/ws.service.ts:104 constructs /ws/sensing, but constants/websocket.ts:1 defines WS_PATH = '/api/v1/stream/pose'.
  • Problem: Mobile WebSocket silently fails.
  • Fix: Align paths. Verify which endpoint the server actually serves.

P1 — Fix This Sprint

# Issue Location Impact
6 God file: 4,846 lines, CC=121 sensing-server/src/main.rs Untestable monolith
7 O(L×V) voxel scan per frame ruvsense/tomography.rs:345-383 ~10ms wasted; use DDA ray march
8 Sequential neural inference wifi-densepose-nn inference.rs:334-336 2-4× GPU latency penalty
9 720 .unwrap() in Rust Workspace-wide Each = potential panic in RT paths
10 112KB alloc/frame in Python csi_processor.py:412-414 Deque→list→numpy every frame

P2 — Fix This Quarter

# Issue Impact
11 11/12 Python modules have zero unit tests (12,280 LOC) Services, middleware, DB untested
12 Firmware at 19% coverage (WASM runtime, OTA, swarm) Security-critical code untested
13 MAT screen auto-falls back to simulated data Disaster responders could monitor fake data
14 Token blacklist never consulted during auth Revoked tokens remain valid
15 50ms frame budget never benchmarked Real-time requirement unverified

Bright Spots

  • 79 ADRs (exceptional governance)
  • Witness bundle system (ADR-028) with SHA-256 proof
  • 2,618 Rust tests with mathematical rigor
  • Daily security scanning (Bandit, Semgrep, Safety)
  • Ed25519 WASM signature verification on firmware
  • Clean mobile state management with good test coverage

Full QE Reports (9 files, 4,914 lines)

Report What it covers
EXECUTIVE-SUMMARY.md Top-level synthesis with all scores and priority matrix
00-qe-queen-summary.md Master coordination, quality posture, test pyramid
01-code-quality-complexity.md Cyclomatic complexity, code smells, top 20 hotspots
02-security-review.md 15 security findings (3 HIGH, 7 MEDIUM), OWASP coverage
03-performance-analysis.md 23 perf findings (4 CRITICAL), frame budget analysis
04-test-analysis.md 3,353 tests inventoried, duplication, quality grading
05-quality-experience.md API/CLI/Mobile/DX UX assessment
06-product-assessment-sfdipot.md SFDIPOT analysis, 57 test ideas, 14 session charters
07-coverage-gaps.md Coverage matrix, top 20 risk gaps, 8-week roadmap

Consequences

  • P0 fixes eliminate 3 security vulnerabilities and 2 functional bugs
  • P1 fixes improve performance, reliability, and maintainability
  • P2 fixes close coverage gaps and harden the system for production
  • Target score improvement: 55 → 75+ after P0+P1 completion

Generated from QE swarm analysis (fleet-02558e91) on 2026-04-05