Files
ruvnet--RuView/docs/adr
Dragan Spiridonov 89cceaf835 fix(auth): close P1/P2/P3 — JWKS stall, 12h session, cookie shadowing
All three deferred findings from the qe-court round. Each fix is guarded by a
test confirmed to FAIL against the old behaviour.

P1 — JWKS: self-inflicted stall, and a blocking fetch on a tokio worker.

`fetched_at` advances only on SUCCESS, and the only rate limiter sat behind
`if fresh`. So once the TTL elapsed after the last successful fetch, `fresh`
was permanently false, the limiter was never consulted, and EVERY request
performed its own blocking 3s-timeout fetch. A Pi that loses WAN stalled
itself 300s later with no attacker present; an attacker could force the same
state by flooding tokens with an unknown `kid`.

Now: `last_attempt_at` is recorded BEFORE every fetch regardless of outcome,
and gates the stale path too; a stale-but-present key is served rather than
erroring, which is the offline tolerance this module always claimed.
Measured by the new test: 26 outbound fetches before, 1 after.

Kept as TWO independent limiters. Merging them looks tidy and is wrong — a
routine refetch would then suppress the unknown-`kid` path for 30s and delay
pickup of a key rotation inside the TTL. I made that mistake first; two
existing tests caught it.

The blocking call also now runs in `spawn_blocking` at the verify boundary,
matching what `main.rs` already does for the token exchange, where the comment
reads "the same mistake this codebase had to fix in jwks.rs". The hot
verification path had never been given the same treatment. A panicked task
fails closed.

P2 — session lifetime, per decision: 1 hour, plus step-up.

SESSION_TTL_SECS 12h -> 1h, and privileged (`sensing:admin`) actions now
require the user to have authenticated within ADMIN_REVERIFY_SECS (5 min),
tracked by a new `auth_time` claim. Reads ride the full session; only the
routes where a stale session does damage are re-verified, so a dashboard whose
main use is watching a live stream does not re-auth hourly.

`auth_time` is `#[serde(default)]`, so a cookie issued before the field existed
reads as 0 — infinitely stale. Such a session keeps working for reads and
cannot perform privileged actions. Fail-closed and self-healing on next sign-in.

The refusal carries an RFC 6750 `WWW-Authenticate` error code, because the
client's correct response differs from a plain 401: the user IS signed in and
needs to prove it again. `api.service.js` acts on that and redirects through
`/oauth/start` — otherwise a stale-session delete surfaces as a generic
"Request failed" with no hint that signing in again fixes it.

P3 — cookie shadowing.

`read_cookie` returned the FIRST match, and RFC 6265 §5.4 sends longer-`Path`
cookies first. Cookies are not isolated by port or scheme, so any other service
on the host — or a plain-HTTP MITM injecting Set-Cookie — could plant
`ruview_session=<their own validly signed session>; Path=/ui`. The victim sent
both, the attacker's first, and it verified because it genuinely was signed:
silent session takeover, with `/oauth/status` reporting the attacker's account.

The signature was doing its job throughout, which is why "it's signed" never
answered this. `__Host-` would, but requires `Secure`, and RuView is routinely
reached over plain HTTP on a LAN.

So both credential paths now accept only when EXACTLY ONE candidate verifies.
An attacker can still cause a refusal by planting a second valid cookie — a
nuisance — but no longer a takeover. Planting junk changes nothing, so this
does not become a trivial DoS.

Tests: +1 jwks (26-vs-1 fetch amplification), +4 step-up, +4 shadowing, +1
duplicate-name reader. Mutation-verified: reverting the stale-path guard gives
26 fetches; reverting to first-match cookie reads fails
`a_shadowing_cookie_cannot_silently_take_over_the_session`.

Verified: workspace 176 suites clean under CI flags, ruview-auth 62+25+2 with
--all-features, UI 22.

ADR-271: P1/P2/P3 marked RESOLVED with the analysis retained, since it explains
why each fix has the shape it does.

Co-Authored-By: Ruflo & AQE
2026-07-23 13:13:42 +02:00
..

Architecture Decision Records

Latest proposed decisions:

This folder contains 182 Architecture Decision Records (ADRs) that document every significant technical choice in the RuView / WiFi-DensePose project. (The index tables below list a curated subset per domain; see the directory listing for the full set.)

Why ADRs?

Building a system that turns WiFi signals into human pose estimation involves hundreds of non-obvious decisions: which signal processing algorithms to use, how to bridge ESP32 firmware to a Rust pipeline, whether to run inference on-device or on a server, how to handle multi-person separation with limited subcarriers.

ADRs capture the context, options considered, decision made, and consequences for each of these choices. They serve three purposes:

  1. Institutional memory — Six months from now, anyone (human or AI) can read why we chose IIR bandpass filters over FIR for vital sign extraction, not just see the code.

  2. AI-assisted development — When an AI agent works on this codebase, ADRs give it the constraints and rationale it needs to make changes that align with the existing architecture. Without them, AI-generated code tends to drift — reinventing patterns that already exist, contradicting earlier decisions, or optimizing for the wrong tradeoffs.

  3. Review checkpoints — Each ADR is a reviewable artifact. When a proposed change touches the architecture, the ADR forces the author to articulate tradeoffs before writing code, not after.

ADRs and Domain-Driven Design

The project uses Domain-Driven Design (DDD) to organize code into bounded contexts — each with its own language, types, and responsibilities. ADRs and DDD work together:

  • ADRs define boundaries: ADR-029 (RuvSense) established multistatic sensing as a separate bounded context from single-node CSI. ADR-042 (CHCI) defined a new aggregate root for coherent channel imaging.
  • DDD models define the language: The RuvSense domain model defines terms like "coherence gate", "dwell time", and "TDM slot" that ADRs reference precisely.
  • Together they prevent drift: An AI agent reading ADR-039 knows that edge processing tiers are configured via NVS keys, not compile-time flags — because the ADR says so. The DDD model tells it which aggregate owns that configuration.

How ADRs are structured

Each ADR follows a consistent format:

  • Context — What problem or gap prompted this decision
  • Decision — What we chose to do and how
  • Consequences — What improved, what got harder, and what risks remain
  • References — Related ADRs, papers, and code paths

Statuses: Proposed (under discussion), Accepted (approved and/or implemented), Superseded (replaced by a later ADR).


ADR Index

Hardware and firmware

ADR Title Status
ADR-012 ESP32 CSI Sensor Mesh for Distributed Sensing Accepted (partial)
ADR-018 ESP32 Development Implementation Path Proposed
ADR-028 ESP32 Capability Audit and Witness Record Accepted
ADR-029 RuvSense Multistatic Sensing Mode (TDM, channel hopping) Proposed
ADR-032 Multistatic Mesh Security Hardening Accepted
ADR-039 ESP32-S3 Edge Intelligence Pipeline (on-device vitals) Accepted (hardware-validated)
ADR-040 WASM Programmable Sensing (Tier 3) Accepted
ADR-041 WASM Module Collection (65 edge modules) Accepted (hardware-validated)
ADR-044 Provisioning Tool Enhancements Proposed
ADR-110 ESP32-C6 firmware extension — Wi-Fi 6 / 802.15.4 / TWT / LP-core Accepted, P1-P10 complete, firmware-side substrate closed at v0.7.0-esp32. Companion docs: WITNESS-LOG-110 (13 §A0.x entries · 99.56 % cross-board RX · 104.1 µs smoothed sync stdev · ≤100 µs target met), ADR-110-REVIEW-GUIDE (one-page reviewer tour), ADR-110-BRANCH-STATE (coordination map vs feat/adr-115-ha-mqtt-matter). Host decoders + tests: Python SyncPacketParser (10) + Rust wifi_densepose_hardware::SyncPacket (15), cross-language hex pin gates drift.

Signal processing and sensing

ADR Title Status
ADR-013 Feature-Level Sensing on Commodity Gear Accepted
ADR-014 SOTA Signal Processing Algorithms Accepted
ADR-021 Vital Sign Detection (breathing, heart rate) Partial
ADR-030 Persistent Field Model and Drift Detection Proposed
ADR-033 CRV Signal Line Sensing Integration Proposed
ADR-037 Multi-Person Pose Detection from Single ESP32 Proposed
ADR-042 Coherent Human Channel Imaging (beyond CSI) Proposed
ADR-134 First-Class Channel Impulse Response (CIR) Support Proposed
ADR-135 Empty-Room Baseline Calibration (per-subcarrier Welford statistics) Proposed

Machine learning and training

ADR Title Status
ADR-005 SONA Self-Learning for Pose Estimation Partial
ADR-006 GNN-Enhanced CSI Pattern Recognition Partial
ADR-015 Public Dataset Strategy (MM-Fi, Wi-Pose) Accepted
ADR-016 RuVector Training Pipeline Integration Accepted
ADR-017 RuVector Signal + MAT Integration Proposed
ADR-020 Migrate AI Inference to Rust (ONNX Runtime) Accepted
ADR-023 Trained DensePose Model with RuVector Pipeline Proposed
ADR-024 Project AETHER: Contrastive CSI Embeddings Required
ADR-027 Project MERIDIAN: Cross-Environment Generalization Proposed
ADR-149 AetherArena: public spatial-intelligence benchmark on Hugging Face Proposed
ADR-150 RF Foundation Encoder: pose-preserving, subject/room/device-invariant CSI embedding Proposed
ADR-151 Per-Room Calibration & Specialized Model Training (room-first → bank of small ruVector specialists) Proposed
ADR-152 WiFi-Pose SOTA 2026 Intake: geometry-conditioned calibration, external benchmarks, foundation-encoder recipe Proposed

Platform and UI

ADR Title Status
ADR-019 Sensing-Only UI with Gaussian Splats Accepted
ADR-022 Windows WiFi Enhanced Fidelity (multi-BSSID) Partial
ADR-025 macOS CoreWLAN WiFi Sensing Proposed
ADR-031 RuView Sensing-First RF Mode Proposed
ADR-034 Expo React Native Mobile App Accepted
ADR-035 Live Sensing UI Accuracy and Data Transparency Accepted
ADR-036 Training Pipeline UI Integration Proposed
ADR-043 Sensing Server UI API Completion (14 endpoints) Accepted
ADR-115 Home Assistant integration via MQTT auto-discovery + Matter bridge (HA-DISCO + HA-FABRIC + HA-MIND) Accepted (MQTT track) / Proposed (Matter SDK P8b)
ADR-169 adam-mode — light theme toggle for the three.js realtime demo Proposed
ADR-170 yoga-mode — yoga pose detection, classification, and scoring for the three.js realtime demo Proposed

Architecture and infrastructure

ADR Title Status
ADR-001 WiFi-Mat Disaster Detection Architecture Accepted
ADR-002 RuVector RVF Integration Strategy Superseded
ADR-003 RVF Cognitive Containers for CSI Proposed
ADR-004 HNSW Vector Search for Fingerprinting Partial
ADR-007 Post-Quantum Cryptography for Sensing Proposed
ADR-008 Distributed Consensus for Multi-AP Proposed
ADR-009 RVF WASM Runtime for Edge Deployment Proposed
ADR-010 Witness Chains for Audit Trail Integrity Proposed
ADR-011 Proof-of-Reality and Mock Elimination Proposed
ADR-026 Survivor Track Lifecycle (MAT crate) Accepted
ADR-038 Sublinear GOAP for Roadmap Optimization Proposed
ADR-095 rvCSI — Edge RF Sensing Runtime Platform Proposed
ADR-096 rvCSI — Crate Topology, the napi-c Shim, and the napi-rs Node Surface Proposed
ADR-097 Adopt rvCSI as RuView's primary CSI runtime (phased adoption) Proposed
ADR-098 Evaluate ruvnet/midstream for RuView's CSI / WebSocket / mesh pipeline Rejected
ADR-099 Adopt midstream as RuView's real-time introspection + low-latency tap Proposed
ADR-263 @ruvnet/ruview npm harness — deep review + optimization strategy Proposed
ADR-264 @ruvnet/rvagent MCP server + @ruv/ruview-cli — deep review + optimization strategy Proposed
ADR-265 RuView npm distribution strategy — CI gate, provenance, version single-sourcing, namespace Proposed

  • DDD Domain Models — Bounded context definitions, aggregate roots, and ubiquitous language
  • User Guide — Setup, API reference, and hardware instructions
  • Build Guide — Building from source