//! Owned, primitive input types for the ADR-262 bridge. //! //! These deliberately **mirror** the shapes RuView's sensing cycle produces //! (the `/ws/sensing` `SensingUpdate` build site at //! `wifi-densepose-sensing-server/src/main.rs:~5938` and the `TrustedOutput` //! trust state surfaced via `EngineBridge` at `main.rs:~5886`) **without //! importing** RuView's internal crates. Keeping the bridge an anti-corruption //! layer (ADR-262 §5.4) means it takes owned primitives, not `SensingUpdate` //! or `TrustedOutput` directly — so this crate never depends on //! `wifi-densepose-sensing-server`. use serde::{Deserialize, Serialize}; /// The CSI feature scalars RuView publishes on every `/ws/sensing` cycle. /// /// Mirrors `FeatureInfo` (`main.rs:368-377`). All values are in RuView's own /// units; the bridge normalizes them into `Observation.features` for fusion. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SensingFeatures { /// Mean RSSI across the CSI window (dBm). pub mean_rssi: f64, /// CSI amplitude variance. pub variance: f64, /// Motion-band spectral power (drives `motion_energy`). pub motion_band_power: f64, /// Breathing-band spectral power (drives `breathing_band`). pub breathing_band_power: f64, /// Dominant frequency of the CSI window (Hz). pub dominant_freq_hz: f64, /// Number of change points detected in the window (drives `transient`). pub change_points: usize, /// Total spectral power of the window. pub spectral_power: f64, } /// The RuView classification block. Mirrors `ClassificationInfo` /// (`main.rs:379-384`). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SensingClass { /// Coarse motion level label (e.g. `"none"`, `"low"`, `"high"`). pub motion_level: String, /// Whether a person is present. pub presence: bool, /// Classification confidence `0.0..=1.0`. pub confidence: f64, } /// A RuView signal field — a floor-plane grid of field values. Mirrors /// `SignalField` (`main.rs:386-390`). The bridge derives a real position from /// the strongest field peak (like `field_localize`) and **never fabricates** /// coordinates when this is absent. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SignalField { /// Grid dimensions `[x, y, z]`. pub grid_size: [usize; 3], /// Row-major flattened field values; `len() == grid_size.product()`. pub values: Vec, } impl SignalField { /// Index `[x, y, z]` of the strongest field cell, or `None` if the grid is /// empty / all-NaN. This is the honest "strongest field peak" readout that /// `field_localize` (`field_localize.rs:16-27`) exposes — **not** calibrated /// triangulation. #[must_use] pub fn peak_cell(&self) -> Option<[i32; 3]> { let [nx, ny, nz] = self.grid_size; if nx == 0 || ny == 0 || nz == 0 || self.values.is_empty() { return None; } let mut best_idx: Option = None; let mut best_val = f64::NEG_INFINITY; for (i, &v) in self.values.iter().enumerate() { if v.is_finite() && v > best_val { best_val = v; best_idx = Some(i); } } let idx = best_idx?; // Row-major: idx = ((x * ny) + y) * nz + z. let z = idx % nz; let y = (idx / nz) % ny; let x = idx / (nz * ny); Some([x as i32, y as i32, z as i32]) } } /// RuView's effective privacy class (the `effective_class` / privacy byte on /// `TrustedOutput`). /// /// This **mirrors** `wifi_densepose_bfld::PrivacyClass` (`bfld/lib.rs:103-116`, /// `#[repr(u8)]`) — the four byte-level classes. The byte values are /// **deliberately non-monotonic in information content**: `Derived = 1` carries /// an identity embedding yet sorts *below* `Anonymous = 2`. The bridge's /// `map_privacy` must therefore map by information content, NEVER by byte value /// (ADR-262 §3.3 — the central correctness item). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuViewPrivacyClass { /// Byte `0` — raw CSI amplitude, local-only. Raw, /// Byte `1` — derived **identity** features (identity_embedding + /// identity_risk_score), LAN-only. The dangerous one (§3.3). Derived, /// Byte `2` — aggregate occupancy / motion, no identity. Anonymous, /// Byte `3` — care/regulated: occupancy minus risk score and hash; /// raw suppressed. Restricted, } impl RuViewPrivacyClass { /// The raw byte value used by RuView's `#[repr(u8)]` enum /// (`bfld/lib.rs:103`). Exposed only so callers can demonstrate the /// non-monotonicity trap in tests; the bridge never maps off this byte. #[must_use] pub fn raw_byte(self) -> u8 { match self { RuViewPrivacyClass::Raw => 0, RuViewPrivacyClass::Derived => 1, RuViewPrivacyClass::Anonymous => 2, RuViewPrivacyClass::Restricted => 3, } } } /// One sensing cycle, as a bridge input. Mirrors the join of `SensingUpdate` /// (features + classification + signal_field) and the `TrustedOutput` trust /// state (`trust_class`) that ADR-262 §1.2 / P1 say must be done at the bridge. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SensingSnapshot { /// Capture time, nanoseconds since Unix epoch (the real `SensingUpdate` /// timestamp, ns). pub timestamp_ns: u64, /// CSI feature scalars (`/ws/sensing` feature set). pub features: SensingFeatures, /// Classification (motion level / presence / confidence). pub classification: SensingClass, /// Optional signal field for a real position readout. pub signal_field: Option, /// RuView's effective privacy class (the source-of-truth, §3.3). pub trust_class: RuViewPrivacyClass, /// Whether the governed engine demoted this cycle (`TrustedOutput.demoted`). /// When `true` the emitted event must be `>= P2` and raw suppressed /// (§3.3 / §4 P2 gate (b)). pub demoted: bool, /// Whether this cycle's identity surface is bound to an enrolled identity /// (RuView's `identity_bound`). Promotes `Derived` to P5 when set. pub identity_bound: bool, /// Stable node id (e.g. `"esp32_room_01"`). pub node_id: String, }