Merge PR #1312: wire WDP_GUARD_INTERVAL_US/WDP_TDM_* into EngineBridge

This commit is contained in:
ruv 2026-07-13 13:50:35 -04:00
commit da81eab714
3 changed files with 50 additions and 12 deletions

View File

@ -230,6 +230,17 @@ impl StreamingEngine {
/// cycle on, the adapter id is part of provenance `model_version` — and /// cycle on, the adapter id is part of provenance `model_version` — and
/// therefore of the witness — so the exact weights shaping inference are /// therefore of the witness — so the exact weights shaping inference are
/// pinned in the trust chain. Pass the result of hashing the adapter file. /// pinned in the trust chain. Pass the result of hashing the adapter file.
/// Override the multistatic fuser's timestamp guard interval (#1049/#1057).
/// Without this, `StreamingEngine::new` always builds
/// `MultistaticFuser::with_config(MultistaticConfig::default())` — a
/// hardcoded 60 ms hard guard that ignores whatever schedule/override the
/// caller derived from `WDP_TDM_SLOTS`/`WDP_GUARD_INTERVAL_US`, so
/// WiFi/ESP-NOW-synced multi-node deployments spuriously fail governed
/// trust cycles even after widening the guard elsewhere.
pub fn set_multistatic_config(&mut self, cfg: MultistaticConfig) {
self.fuser = MultistaticFuser::with_config(cfg);
}
pub fn set_room_adapter(&mut self, info: AdapterInfo) { pub fn set_room_adapter(&mut self, info: AdapterInfo) {
self.adapter = Some(info); self.adapter = Some(info);
} }

View File

@ -38,6 +38,7 @@ use wifi_densepose_bfld::{PrivacyClass, PrivacyMode};
use wifi_densepose_engine::{AdapterInfo, EngineError, StreamingEngine, TrustedOutput}; use wifi_densepose_engine::{AdapterInfo, EngineError, StreamingEngine, TrustedOutput};
use wifi_densepose_geo::types::GeoRegistration; use wifi_densepose_geo::types::GeoRegistration;
use wifi_densepose_signal::ruvsense::fusion_quality::CalibrationId; use wifi_densepose_signal::ruvsense::fusion_quality::CalibrationId;
use wifi_densepose_signal::ruvsense::multistatic::MultistaticConfig;
use wifi_densepose_worldgraph::WorldId; use wifi_densepose_worldgraph::WorldId;
use super::multistatic_bridge::node_frames_from_states; use super::multistatic_bridge::node_frames_from_states;
@ -79,8 +80,24 @@ pub struct EngineBridge {
impl EngineBridge { impl EngineBridge {
/// Build a bridge for one installation. `room_area_id`/`room_name` name the /// Build a bridge for one installation. `room_area_id`/`room_name` name the
/// observation scope; `mode` is the starting privacy mode. /// observation scope; `mode` is the starting privacy mode.
pub fn new(mode: PrivacyMode, model_version: u16, room_area_id: &str, room_name: &str) -> Self { ///
/// `multistatic_cfg` is `None` on the pure default (60 ms/20 ms guard);
/// callers that derive a config from `WDP_TDM_SLOTS`/`WDP_GUARD_INTERVAL_US`
/// (see `main.rs::multistatic_guard_config_from_env`) should pass it here
/// too — otherwise the governed trust cycle silently keeps using the
/// hardcoded default even though the sibling `multistatic_fuser` field on
/// `AppState` picked up the override (#1049/#1057).
pub fn new(
mode: PrivacyMode,
model_version: u16,
room_area_id: &str,
room_name: &str,
multistatic_cfg: Option<MultistaticConfig>,
) -> Self {
let mut engine = StreamingEngine::new(mode, model_version, GeoRegistration::default()); let mut engine = StreamingEngine::new(mode, model_version, GeoRegistration::default());
if let Some(cfg) = multistatic_cfg {
engine.set_multistatic_config(cfg);
}
let room = engine.add_room(room_area_id, room_name); let room = engine.add_room(room_area_id, room_name);
Self { Self {
engine, engine,
@ -267,7 +284,7 @@ mod tests {
#[test] #[test]
fn empty_states_produce_no_belief() { fn empty_states_produce_no_belief() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "living_room", "Living Room"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "living_room", "Living Room", None);
let out = bridge.process_cycle_from_states(&HashMap::new(), 1_000); let out = bridge.process_cycle_from_states(&HashMap::new(), 1_000);
assert!(out.is_none()); assert!(out.is_none());
// No belief published, no sensor wired. // No belief published, no sensor wired.
@ -276,7 +293,7 @@ mod tests {
#[test] #[test]
fn live_cycle_produces_witnessed_belief_with_provenance() { fn live_cycle_produces_witnessed_belief_with_provenance() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "living_room", "Living Room"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "living_room", "Living Room", None);
let states = two_node_states(); let states = two_node_states();
let out = bridge let out = bridge
.process_cycle_from_states(&states, 10_000) .process_cycle_from_states(&states, 10_000)
@ -299,7 +316,7 @@ mod tests {
fn live_path_is_deterministic() { fn live_path_is_deterministic() {
let states = two_node_states_fixed(); let states = two_node_states_fixed();
let run = || { let run = || {
let mut b = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut b = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
b.process_cycle_from_states(&states, 5_000).unwrap().unwrap() b.process_cycle_from_states(&states, 5_000).unwrap().unwrap()
}; };
let a = run(); let a = run();
@ -325,7 +342,7 @@ mod tests {
#[test] #[test]
fn nodes_registered_once_across_cycles() { fn nodes_registered_once_across_cycles() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
let states = two_node_states(); let states = two_node_states();
bridge.process_cycle_from_states(&states, 1_000); bridge.process_cycle_from_states(&states, 1_000);
bridge.process_cycle_from_states(&states, 2_000); bridge.process_cycle_from_states(&states, 2_000);
@ -336,7 +353,7 @@ mod tests {
#[test] #[test]
fn retention_bounds_world_graph_growth() { fn retention_bounds_world_graph_growth() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
bridge.set_semantic_retention(5); bridge.set_semantic_retention(5);
let states = two_node_states(); let states = two_node_states();
for i in 0..20i64 { for i in 0..20i64 {
@ -349,7 +366,7 @@ mod tests {
#[test] #[test]
fn adapter_identity_flows_into_live_witness() { fn adapter_identity_flows_into_live_witness() {
let states = two_node_states_fixed(); let states = two_node_states_fixed();
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
let base = bridge let base = bridge
.process_cycle_from_states(&states, 1_000) .process_cycle_from_states(&states, 1_000)
.unwrap() .unwrap()
@ -381,7 +398,7 @@ mod tests {
/// the status endpoint, with a zero error count on the happy path. /// the status endpoint, with a zero error count on the happy path.
#[test] #[test]
fn observe_cycle_records_trust_state() { fn observe_cycle_records_trust_state() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
assert!(bridge.last_trust_witness().is_none()); assert!(bridge.last_trust_witness().is_none());
assert_eq!(bridge.effective_class(), None); assert_eq!(bridge.effective_class(), None);
@ -430,7 +447,7 @@ mod tests {
m m
} }
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
let mismatched = mismatched_states(); let mismatched = mismatched_states();
assert!(bridge.observe_cycle(&mismatched, 1_000).is_none()); assert!(bridge.observe_cycle(&mismatched, 1_000).is_none());
@ -461,7 +478,7 @@ mod tests {
/// mapping bfld's privacy gate applies at `Restricted`. /// mapping bfld's privacy gate applies at `Restricted`.
#[test] #[test]
fn restricted_class_suppresses_raw_outputs() { fn restricted_class_suppresses_raw_outputs() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
bridge.set_privacy_mode(PrivacyMode::StrictNoIdentity); // base = Restricted bridge.set_privacy_mode(PrivacyMode::StrictNoIdentity); // base = Restricted
bridge bridge
.observe_cycle(&two_node_states(), 1_000) .observe_cycle(&two_node_states(), 1_000)
@ -472,7 +489,7 @@ mod tests {
#[test] #[test]
fn identity_strict_mode_is_carried_into_provenance() { fn identity_strict_mode_is_carried_into_provenance() {
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R"); let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None);
bridge.set_privacy_mode(PrivacyMode::StrictNoIdentity); bridge.set_privacy_mode(PrivacyMode::StrictNoIdentity);
let out = bridge let out = bridge
.process_cycle_from_states(&two_node_states(), 7_000) .process_cycle_from_states(&two_node_states(), 7_000)

View File

@ -7514,6 +7514,11 @@ async fn main() {
let field_surface: rufield_surface::FieldState = let field_surface: rufield_surface::FieldState =
Arc::new(RwLock::new(rufield_surface::FieldSurface::from_env())); Arc::new(RwLock::new(rufield_surface::FieldSurface::from_env()));
// Populated inside the `multistatic_fuser` field initializer below, then
// threaded into `engine_bridge` so both fusion paths honor the same
// WDP_TDM_SLOTS/WDP_GUARD_INTERVAL_US-derived guard (#1049/#1057).
let mut engine_bridge_multistatic_cfg: Option<MultistaticConfig> = None;
let state: SharedState = Arc::new(RwLock::new(AppStateInner { let state: SharedState = Arc::new(RwLock::new(AppStateInner {
latest_update: None, latest_update: None,
rssi_history: VecDeque::new(), rssi_history: VecDeque::new(),
@ -7588,7 +7593,7 @@ async fn main() {
); );
let mut fuser = MultistaticFuser::with_config(MultistaticConfig { let mut fuser = MultistaticFuser::with_config(MultistaticConfig {
min_nodes: 1, // single-node passthrough min_nodes: 1, // single-node passthrough
..cfg ..cfg.clone()
}); });
if let Some(ref pos_str) = args.node_positions { if let Some(ref pos_str) = args.node_positions {
let positions = field_bridge::parse_node_positions(pos_str); let positions = field_bridge::parse_node_positions(pos_str);
@ -7600,6 +7605,10 @@ async fn main() {
fuser.set_node_positions(positions); fuser.set_node_positions(positions);
} }
} }
engine_bridge_multistatic_cfg = Some(MultistaticConfig {
min_nodes: 1,
..cfg
});
fuser fuser
}, },
engine_bridge: engine_bridge::EngineBridge::new( engine_bridge: engine_bridge::EngineBridge::new(
@ -7607,6 +7616,7 @@ async fn main() {
1, 1,
"default", "default",
"Default Room", "Default Room",
engine_bridge_multistatic_cfg,
), ),
field_model: if args.calibrate { field_model: if args.calibrate {
info!("Field model calibration enabled — room should be empty during startup"); info!("Field model calibration enabled — room should be empty during startup");