feat(engine): integrate ADR-135..141 into an end-to-end trust pipeline

- signal/calibration.rs: BaselineCalibration gains calibration_id()/
  calibration_uuid()/apply() — the ADR-135->136 link that stamps
  FrameMeta.calibration_id (deterministic id, no serialization change). +1 test.
- NEW crate wifi-densepose-engine: StreamingEngine::process_cycle() composes
  fuse_scored (137) -> calibration provenance (135/136) -> privacy demotion on
  contradiction (141) -> WorldGraph SemanticState with mandatory provenance +
  DerivedFrom edge (139). Returns TrustedOutput (the trust chain made concrete).
- Validates the throughline: every output names evidence + model + calibration
  + privacy decision; calibration_id flows input->QualityScore->provenance;
  contradiction demotes class; deterministic; privacy mode attested.
- 4 integration tests; workspace 0 errors; signal 410 lib tests pass.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-05-29 08:21:48 -04:00
parent f2e9e2f2bd
commit 2eada40e3b
6 changed files with 413 additions and 0 deletions

12
v2/Cargo.lock generated
View File

@ -10652,6 +10652,17 @@ dependencies = [
"uuid",
]
[[package]]
name = "wifi-densepose-engine"
version = "0.3.0"
dependencies = [
"wifi-densepose-bfld",
"wifi-densepose-core",
"wifi-densepose-geo",
"wifi-densepose-signal",
"wifi-densepose-worldgraph",
]
[[package]]
name = "wifi-densepose-geo"
version = "0.1.0"
@ -10826,6 +10837,7 @@ dependencies = [
"serde_json",
"sha2",
"thiserror 2.0.18",
"uuid",
"wifi-densepose-core",
"wifi-densepose-ruvector",
]

View File

@ -27,6 +27,7 @@ members = [
"crates/wifi-densepose-pointcloud",
"crates/wifi-densepose-geo",
"crates/wifi-densepose-worldgraph", # ADR-139 — WorldGraph environmental digital twin
"crates/wifi-densepose-engine", # ADR-135..146 integration/composition layer
"crates/nvsim",
"crates/nvsim-server",
"crates/homecore", # ADR-127 — HOMECORE state machine

View File

@ -0,0 +1,22 @@
[package]
name = "wifi-densepose-engine"
description = "RuView streaming-engine integration layer — composes the ADR-135..146 building blocks into one trust-traceable pipeline cycle"
version = "0.3.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
# Composed building blocks (ADR-135..146).
wifi-densepose-core = { version = "0.3.0", path = "../wifi-densepose-core" }
wifi-densepose-signal = { version = "0.3.1", path = "../wifi-densepose-signal", default-features = false }
# bfld is no_std by default; the privacy CONTROL PLANE (PrivacyModeRegistry) is
# std-gated, so request std explicitly even under a workspace --no-default-features build.
wifi-densepose-bfld = { version = "0.3.0", path = "../wifi-densepose-bfld", features = ["std"] }
wifi-densepose-worldgraph = { version = "0.3.0", path = "../wifi-densepose-worldgraph" }
wifi-densepose-geo = { path = "../wifi-densepose-geo" }
[lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"

View File

@ -0,0 +1,316 @@
//! # RuView Streaming Engine — integration layer
//!
//! This crate is the **composition root** that wires the ADR-135..146 building
//! blocks into one end-to-end *trust-traceable* pipeline cycle. Each block was
//! built and unit-tested independently; this crate proves they compose and that
//! the **trust throughline** holds end-to-end:
//!
//! > *Why believe the system when it says a person is present?* — every
//! > [`TrustedOutput`] names its **signal evidence** (ADR-137 `EvidenceRef`),
//! > its **model version** (ADR-136), its **calibration version** (ADR-135
//! > baseline id, ADR-136 `calibration_id`), and the **privacy decision**
//! > (ADR-141 mode → class) it was emitted under — and is anchored as a
//! > provenance-bearing node in the ADR-139 WorldGraph.
//!
//! One [`StreamingEngine::process_cycle`] performs, in order:
//! 1. **Fuse + score** the node frames (ADR-137 `fuse_scored`) → `QualityScore`
//! with per-node weights, evidence, and tolerated contradiction flags.
//! 2. **Stamp calibration provenance** (ADR-135/136): the `CalibrationId` the
//! calibration stage applied is recorded on the `QualityScore`.
//! 3. **Privacy control plane** (ADR-141): if the fusion recorded a tolerated
//! contradiction, the active privacy class is **demoted one step** before
//! emission (monotonic — information only ever removed).
//! 4. **Semantic state** (ADR-139/140): a `SemanticState` node is appended to
//! the WorldGraph with mandatory provenance and a `DerivedFrom` edge to the
//! room it was observed in.
//!
//! What is intentionally *not* here: the live 20 Hz I/O loop (sensing-server),
//! UWB hardware (ADR-144), and model training (ADR-146). This is the
//! composition + validation layer those will plug into.
#![forbid(unsafe_code)]
use wifi_densepose_bfld::{PrivacyClass, PrivacyMode, PrivacyModeRegistry};
use wifi_densepose_geo::types::GeoRegistration;
use wifi_densepose_signal::ruvsense::fusion_quality::CalibrationId;
use wifi_densepose_signal::ruvsense::multistatic::{MultistaticConfig, MultistaticFuser};
use wifi_densepose_signal::ruvsense::{MultiBandCsiFrame, QualityScore};
use wifi_densepose_worldgraph::{
EnuPoint, SemanticProvenance, WorldEdge, WorldGraph, WorldId, WorldNode, ZoneBoundsEnu,
};
/// Errors from an engine cycle.
#[derive(Debug)]
pub enum EngineError {
/// Multistatic fusion failed (no frames, timestamp spread, dimension mismatch).
Fusion(wifi_densepose_signal::ruvsense::multistatic::MultistaticError),
}
impl core::fmt::Display for EngineError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EngineError::Fusion(e) => write!(f, "fusion error: {e}"),
}
}
}
impl std::error::Error for EngineError {}
impl From<wifi_densepose_signal::ruvsense::multistatic::MultistaticError> for EngineError {
fn from(e: wifi_densepose_signal::ruvsense::multistatic::MultistaticError) -> Self {
EngineError::Fusion(e)
}
}
/// The auditable result of one engine cycle — the trust chain made concrete.
#[derive(Debug, Clone)]
pub struct TrustedOutput {
/// The `SemanticState` node id created in the WorldGraph.
pub semantic_id: WorldId,
/// The fusion quality record (evidence + contradictions + calibration).
pub quality: QualityScore,
/// The privacy class the output was emitted under (after any demotion).
pub effective_class: PrivacyClass,
/// Whether a tolerated contradiction forced a privacy demotion this cycle.
pub demoted: bool,
/// The mandatory provenance attached to the semantic node.
pub provenance: SemanticProvenance,
}
/// Composition root for the RuView streaming engine.
pub struct StreamingEngine {
fuser: MultistaticFuser,
coherence_accept: f32,
privacy: PrivacyModeRegistry,
world: WorldGraph,
model_version: u16,
cycle: u64,
}
impl StreamingEngine {
/// Build an engine with a starting privacy mode and model version. The
/// WorldGraph is registered to the installation origin.
#[must_use]
pub fn new(mode: PrivacyMode, model_version: u16, registration: GeoRegistration) -> Self {
Self {
fuser: MultistaticFuser::with_config(MultistaticConfig::default()),
coherence_accept: 0.85,
privacy: PrivacyModeRegistry::new(mode),
world: WorldGraph::new(registration),
model_version,
cycle: 0,
}
}
/// Register a room and return its WorldGraph id (the observation scope).
pub fn add_room(&mut self, area_id: &str, name: &str) -> WorldId {
self.world.upsert_node(WorldNode::Room {
id: WorldId::UNASSIGNED,
area_id: Some(area_id.to_string()),
name: name.to_string(),
bounds_enu: ZoneBoundsEnu::Rectangle { min_e: 0.0, min_n: 0.0, max_e: 5.0, max_n: 4.0 },
floor: 0,
})
}
/// Register a sensor node and an `observes` edge to a room.
pub fn add_sensor(&mut self, device_id: &str, room: WorldId) -> WorldId {
let id = self.world.upsert_node(WorldNode::Sensor {
id: WorldId::UNASSIGNED,
device_id: device_id.to_string(),
position: EnuPoint { east_m: 0.0, north_m: 0.0, up_m: 0.0 },
modality: wifi_densepose_worldgraph::SensorModality::WifiCsi,
});
let _ = self.world.add_edge(
id,
room,
WorldEdge::Observes { quality: 1.0, last_seen_unix_ms: 0 },
);
id
}
/// Switch the active privacy mode (records a hash-chained attestation).
pub fn set_privacy_mode(&mut self, mode: PrivacyMode) {
self.privacy.set_mode(mode);
}
/// Borrow the WorldGraph (for queries / persistence).
#[must_use]
pub fn world(&self) -> &WorldGraph {
&self.world
}
/// Borrow the privacy registry (for attestation audit).
#[must_use]
pub fn privacy(&self) -> &PrivacyModeRegistry {
&self.privacy
}
/// Cycles processed so far.
#[must_use]
pub fn cycle_count(&self) -> u64 {
self.cycle
}
/// Run one full trust-traceable cycle (see crate docs for the steps).
///
/// `calibration` is the [`CalibrationId`] the calibration stage applied to
/// these frames (ADR-135 `BaselineCalibration::calibration_id()`); `room` is
/// the observation scope (an existing WorldGraph Room id).
///
/// # Errors
/// [`EngineError::Fusion`] if multistatic fusion rejects the input.
pub fn process_cycle(
&mut self,
node_frames: &[MultiBandCsiFrame],
calibration: CalibrationId,
room: WorldId,
now_ms: i64,
) -> Result<TrustedOutput, EngineError> {
// 1. Fuse + score (ADR-137).
let (fused, mut quality) = self.fuser.fuse_scored(node_frames, self.coherence_accept)?;
// 2. Stamp calibration provenance (ADR-135 → ADR-136 → ADR-137).
quality.calibration_id = Some(calibration);
// 3. Privacy control plane (ADR-141): demote on contradiction.
let base_class = self.privacy.active_class();
let demoted = quality.forces_privacy_demotion();
let effective_class = if demoted { demote_one(base_class) } else { base_class };
// 4. Semantic state with mandatory provenance (ADR-139/140).
let provenance = SemanticProvenance {
evidence: quality.evidence_refs.iter().map(|e| format!("{e:?}")).collect(),
model_version: format!("rfenc-v{}", self.model_version),
calibration_version: format!("cal:{:016x}", calibration.0),
privacy_decision: format!("{:?}/{:?}", self.privacy.active_mode(), effective_class),
};
let statement = format!(
"occupancy coherence={:.2} nodes={} demoted={}",
quality.base_coherence, fused.active_nodes, demoted
);
let semantic_id = self.world.add_semantic_state(
statement,
quality.penalized_coherence(),
now_ms,
provenance.clone(),
&[room],
);
self.cycle += 1;
Ok(TrustedOutput { semantic_id, quality, effective_class, demoted, provenance })
}
}
/// Demote a privacy class by one step (more restrictive), clamped at `Restricted`.
/// Monotonic: information is only ever removed (ADR-120/141).
fn demote_one(c: PrivacyClass) -> PrivacyClass {
let next = (c.as_u8() + 1).min(PrivacyClass::Restricted.as_u8());
PrivacyClass::try_from(next).unwrap_or(PrivacyClass::Restricted)
}
#[cfg(test)]
mod tests {
use super::*;
use wifi_densepose_signal::hardware_norm::{CanonicalCsiFrame, HardwareType};
fn node_frame(node_id: u8, ts_us: u64, n_sub: usize) -> MultiBandCsiFrame {
MultiBandCsiFrame {
node_id,
timestamp_us: ts_us,
channel_frames: vec![CanonicalCsiFrame {
amplitude: (0..n_sub).map(|i| 1.0 + 0.1 * i as f32).collect(),
phase: (0..n_sub).map(|i| i as f32 * 0.05).collect(),
hardware_type: HardwareType::Esp32S3,
}],
frequencies_mhz: vec![2412],
coherence: 0.9,
}
}
fn engine() -> (StreamingEngine, WorldId) {
let mut e = StreamingEngine::new(PrivacyMode::PrivateHome, 1, GeoRegistration::default());
let room = e.add_room("living_room", "Living Room");
e.add_sensor("esp32-com9", room);
(e, room)
}
/// End-to-end trust invariant: a clean cycle produces a SemanticState whose
/// provenance names evidence + model + calibration + privacy decision, and
/// the calibration id flows from input → QualityScore → provenance.
#[test]
fn cycle_carries_full_provenance() {
let (mut e, room) = engine();
let cal = CalibrationId(0xABCD_1234);
let frames = [node_frame(0, 1000, 56), node_frame(1, 1001, 56)];
let out = e.process_cycle(&frames, cal, room, 10_000).unwrap();
// Calibration flows all the way through.
assert_eq!(out.quality.calibration_id, Some(cal));
assert_eq!(out.provenance.calibration_version, "cal:00000000abcd1234");
// Model + privacy provenance present.
assert_eq!(out.provenance.model_version, "rfenc-v1");
assert!(out.provenance.privacy_decision.starts_with("PrivateHome/"));
// Evidence refs recorded.
assert!(!out.provenance.evidence.is_empty());
// Clean cycle (tight timestamps) → no demotion, stays Anonymous (PrivateHome).
assert!(!out.demoted);
assert_eq!(out.effective_class, PrivacyClass::Anonymous);
// The SemanticState is in the graph with a DerivedFrom edge to the room.
assert!(e.world().node(out.semantic_id).is_some());
assert!(e
.world()
.neighbors(out.semantic_id)
.iter()
.any(|(to, edge)| *to == room && matches!(edge, WorldEdge::DerivedFrom { .. })));
}
/// A tolerated contradiction (loose timestamp spread, within the hard guard)
/// demotes the privacy class one step — proving ADR-137 → ADR-141 wiring.
#[test]
fn contradiction_demotes_privacy() {
let (mut e, room) = engine();
let cal = CalibrationId(7);
// 2 ms spread: within the 5 ms hard guard but above the 1 ms soft guard.
let frames = [node_frame(0, 1000, 56), node_frame(1, 3000, 56)];
let out = e.process_cycle(&frames, cal, room, 20_000).unwrap();
assert!(out.demoted, "loose alignment must demote");
// PrivateHome base = Anonymous(2) → demoted to Restricted(3).
assert_eq!(out.effective_class, PrivacyClass::Restricted);
assert!(out.provenance.privacy_decision.contains("Restricted"));
// Penalized coherence is below the base coherence.
assert!(out.quality.penalized_coherence() <= out.quality.base_coherence);
}
/// Determinism: identical input twice → identical provenance + class
/// (the ADR-136 witness-replay spirit, end-to-end through the engine).
#[test]
fn cycle_is_deterministic() {
let cal = CalibrationId(42);
let frames = [node_frame(0, 1000, 56), node_frame(1, 1001, 56)];
let (mut e1, r1) = engine();
let o1 = e1.process_cycle(&frames, cal, r1, 5_000).unwrap();
let (mut e2, r2) = engine();
let o2 = e2.process_cycle(&frames, cal, r2, 5_000).unwrap();
assert_eq!(o1.provenance.calibration_version, o2.provenance.calibration_version);
assert_eq!(o1.provenance.evidence, o2.provenance.evidence);
assert_eq!(o1.effective_class, o2.effective_class);
assert_eq!(o1.quality.per_node_weights, o2.quality.per_node_weights);
}
/// The privacy mode switch is recorded in a verifiable attestation chain
/// (ADR-141), and a stricter mode raises the emitted class.
#[test]
fn privacy_mode_switch_is_attested_and_effective() {
let (mut e, room) = engine();
e.set_privacy_mode(PrivacyMode::StrictNoIdentity);
assert!(e.privacy().verify_chain());
let out = e
.process_cycle(&[node_frame(0, 1000, 56), node_frame(1, 1001, 56)], CalibrationId(1), room, 1)
.unwrap();
// StrictNoIdentity base = Restricted, even with no contradiction.
assert_eq!(out.effective_class, PrivacyClass::Restricted);
}
}

View File

@ -46,6 +46,9 @@ ruvector-solver = { workspace = true }
midstreamer-temporal-compare = { workspace = true }
midstreamer-attractor = { workspace = true }
# ADR-136: deterministic calibration-id → FrameMeta.calibration_id (ADR-135 link)
uuid = { version = "1.6", features = ["v4"] }
# Internal
wifi-densepose-core = { version = "0.3.0", path = "../wifi-densepose-core" }
# ADR-084 Pass 2: sketch-prefilter for the EmbeddingHistory search loop.

View File

@ -260,6 +260,44 @@ impl BaselineCalibration {
Ok(CalibrationDeviationScore { amplitude_z_median, amplitude_z_max, phase_drift_median, motion_flagged })
}
/// Deterministic calibration epoch id (ADR-137 `CalibrationId`), derived
/// from the immutable baseline fields — stable across reboots, changes only
/// on recalibration. Deterministic (no RNG) so the ADR-136 witness replay
/// stays reproducible.
#[must_use]
pub fn calibration_id(&self) -> super::fusion_quality::CalibrationId {
// splitmix64 over (captured_at, frame_count, subcarrier_count, tier).
let mut h = (self.captured_at_unix_s as u64)
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add(self.frame_count.wrapping_mul(0xBF58_476D_1CE4_E5B9))
.wrapping_add((self.subcarriers.len() as u64).wrapping_mul(0x94D0_49BB_1331_11EB))
.wrapping_add(self.tier as u64);
h ^= h >> 30;
h = h.wrapping_mul(0xBF58_476D_1CE4_E5B9);
h ^= h >> 27;
super::fusion_quality::CalibrationId(h)
}
/// The ADR-136 `FrameMeta.calibration_id` value (a UUID derived
/// deterministically from [`Self::calibration_id`]).
#[must_use]
pub fn calibration_uuid(&self) -> uuid::Uuid {
uuid::Uuid::from_u128(self.calibration_id().0 as u128)
}
/// ADR-136 §2.4 calibration **Stage**: subtract the baseline AND stamp the
/// frame's `calibration_id` provenance field. This is the only place that
/// sets `calibration_id` (the append-only boundary rule).
///
/// # Errors
/// [`CalibrationError::SubcarrierMismatch`] if the frame's subcarrier count
/// does not match this baseline.
pub fn apply(&self, frame: &mut CsiFrame) -> Result<(), CalibrationError> {
self.subtract_in_place(frame)?;
frame.metadata.set_calibration(self.calibration_uuid());
Ok(())
}
/// Subtract the amplitude baseline from `frame.data` in-place.
/// Only amplitude mean is subtracted; phase is left untouched.
pub fn subtract_in_place(&self, frame: &mut CsiFrame) -> Result<(), CalibrationError> {
@ -597,6 +635,27 @@ mod tests {
}
}
// ADR-136: calibration Stage stamps calibration_id deterministically.
#[test]
fn apply_stamps_calibration_id_deterministically() {
let mut cfg = CalibrationConfig::ht20();
cfg.min_frames = 2;
let mut rec = CalibrationRecorder::new(cfg);
rec.record(&constant_frame(52, 0.8, 0.5)).unwrap();
rec.record(&constant_frame(52, 0.9, 0.6)).unwrap();
let baseline = rec.finalize().unwrap();
// id is stable across calls (no RNG).
assert_eq!(baseline.calibration_id(), baseline.calibration_id());
assert_eq!(baseline.calibration_uuid(), baseline.calibration_uuid());
// apply() subtracts AND stamps the frame's provenance field.
let mut frame = constant_frame(52, 1.0, 0.5);
assert_eq!(frame.metadata.calibration_id, None);
baseline.apply(&mut frame).unwrap();
assert_eq!(frame.metadata.calibration_id, Some(baseline.calibration_uuid()));
}
// (d) Tier dispatch: each config constructor produces the correct counts.
#[test]
fn tier_dispatch_correct_counts() {