mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-19 05:10:13 -05:00
Merge PR #2133 (aepod) into release integration
This commit is contained in:
commit
e9d0cef610
4 files changed
+89
-1
No files matched your search
@@ -116,6 +116,7 @@ handler (`health_ready`, `main.rs:8128,8133`) and return a `trust` block
|
||||
"waiting_for_frames": false,
|
||||
"last_frame_age_ms": 42,
|
||||
"trust": {
|
||||
"node_positions": { "1": [0.5, 0.0, 1.0], "2": [4.0, 3.0, 1.0] },
|
||||
"last_witness": "…64 hex chars or null…",
|
||||
"effective_class": "Anonymous | Restricted | …",
|
||||
"demoted": false,
|
||||
@@ -126,6 +127,11 @@ handler (`health_ready`, `main.rs:8128,8133`) and return a `trust` block
|
||||
}
|
||||
```
|
||||
|
||||
`node_positions` is the `--node-positions` map the governed fuser holds,
|
||||
keyed by node id. It is `{}` when no positions are configured, and then every
|
||||
node fuses at the origin. A node the map does not name also fuses at the
|
||||
origin.
|
||||
|
||||
**This is a real, currently-shipped diagnostic surface — but it is honestly
|
||||
limited.** It tells you *that* errors are occurring and *that* the current
|
||||
class is demoted, and the total count, but not *why* for your specific run:
|
||||
|
||||
@@ -264,6 +264,14 @@ impl StreamingEngine {
|
||||
self.fuser.set_node_positions_by_id(positions);
|
||||
}
|
||||
|
||||
/// Node positions the governed fuser currently holds, keyed by node id.
|
||||
/// Empty until [`Self::set_node_positions`] is called, in which case every
|
||||
/// node fuses at the origin.
|
||||
#[must_use]
|
||||
pub fn node_positions(&self) -> &std::collections::HashMap<u8, [f32; 3]> {
|
||||
self.fuser.node_positions_by_id()
|
||||
}
|
||||
|
||||
/// Activate a per-room calibration adapter (ADR-150 §3.4). From the next
|
||||
/// cycle on, the adapter id is part of provenance `model_version` — and
|
||||
/// therefore of the witness — so the exact weights shaping inference are
|
||||
|
||||
@@ -135,6 +135,21 @@ impl EngineBridge {
|
||||
self.engine.set_semantic_retention(max_states);
|
||||
}
|
||||
|
||||
/// Give the governed fuser the `--node-positions` map, keyed by node id.
|
||||
/// The fuser resolves each frame's position by its `node_id`, so nodes
|
||||
/// discovered later pick up their entry without another call; nodes with
|
||||
/// no entry fuse at the origin.
|
||||
#[must_use]
|
||||
pub fn with_node_positions(mut self, positions: HashMap<u8, [f32; 3]>) -> Self {
|
||||
self.engine.set_node_positions(positions);
|
||||
self
|
||||
}
|
||||
|
||||
/// Node positions the governed fuser is using, keyed by node id.
|
||||
pub fn node_positions(&self) -> &HashMap<u8, [f32; 3]> {
|
||||
self.engine.node_positions()
|
||||
}
|
||||
|
||||
/// Switch the active privacy mode (operator/control-plane action).
|
||||
pub fn set_privacy_mode(&mut self, mode: PrivacyMode) {
|
||||
self.engine.set_privacy_mode(mode);
|
||||
@@ -309,6 +324,33 @@ mod tests {
|
||||
m
|
||||
}
|
||||
|
||||
fn configured_positions() -> HashMap<u8, [f32; 3]> {
|
||||
[(0u8, [0.5, 0.0, 1.0]), (1u8, [4.0, 3.0, 1.0])]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_node_positions_reaches_the_governed_fuser() {
|
||||
let bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", None)
|
||||
.with_node_positions(configured_positions());
|
||||
assert_eq!(bridge.node_positions(), &configured_positions());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_positions_survive_a_guard_config() {
|
||||
let cfg = MultistaticConfig {
|
||||
guard_interval_us: 200_000,
|
||||
min_nodes: 1,
|
||||
..MultistaticConfig::default()
|
||||
};
|
||||
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "r", "R", Some(cfg))
|
||||
.with_node_positions(configured_positions());
|
||||
assert_eq!(bridge.node_positions(), &configured_positions());
|
||||
assert!(bridge.observe_cycle(&two_node_states(), 1_000).is_some());
|
||||
assert_eq!(bridge.node_positions(), &configured_positions());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_states_produce_no_belief() {
|
||||
let mut bridge = EngineBridge::new(PrivacyMode::PrivateHome, 1, "living_room", "Living Room", None);
|
||||
|
||||
@@ -7142,6 +7142,31 @@ async fn health_live(State(state): State<SharedState>) -> Json<serde_json::Value
|
||||
}))
|
||||
}
|
||||
|
||||
/// Governed-fuser positions for `/api/v1/status`, keyed by node id and sorted.
|
||||
fn engine_node_positions_json(positions: &HashMap<u8, [f32; 3]>) -> serde_json::Value {
|
||||
let sorted: BTreeMap<String, [f32; 3]> = positions
|
||||
.iter()
|
||||
.map(|(id, p)| (id.to_string(), *p))
|
||||
.collect();
|
||||
serde_json::json!(sorted)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod engine_node_positions_json_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn engine_node_positions_json_is_keyed_by_id() {
|
||||
let configured: HashMap<u8, [f32; 3]> = [(12, [4.0, 3.0, 1.0]), (11, [0.5, 0.0, 1.0])]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
engine_node_positions_json(&configured),
|
||||
serde_json::json!({"11": [0.5, 0.0, 1.0], "12": [4.0, 3.0, 1.0]})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lowercase hex of a 32-byte witness for JSON exposure.
|
||||
fn witness_hex(w: [u8; 32]) -> String {
|
||||
use std::fmt::Write;
|
||||
@@ -7168,6 +7193,8 @@ async fn health_ready(State(state): State<SharedState>) -> Json<serde_json::Valu
|
||||
// witness + privacy class + recalibration flag, and the engine error
|
||||
// audit — previously write-only on AppState, now readable here.
|
||||
"trust": {
|
||||
// Positions the governed fuser uses, from --node-positions.
|
||||
"node_positions": engine_node_positions_json(s.engine_bridge.node_positions()),
|
||||
"last_witness": s.engine_bridge.last_trust_witness().map(witness_hex),
|
||||
"effective_class": s.engine_bridge.effective_class().map(|c| format!("{c:?}")),
|
||||
"demoted": s.engine_bridge.demoted(),
|
||||
@@ -13027,6 +13054,9 @@ async fn main() {
|
||||
// WDP_TDM_SLOTS/WDP_GUARD_INTERVAL_US-derived guard (#1049/#1057).
|
||||
let mut engine_bridge_multistatic_cfg: Option<MultistaticConfig> = None;
|
||||
let mut node_positions_config: HashMap<u8, [f32; 3]> = HashMap::new();
|
||||
// The same map for the governed fuser behind `engine_bridge`, which
|
||||
// otherwise places every node at the origin.
|
||||
let mut engine_bridge_node_positions: HashMap<u8, [f32; 3]> = HashMap::new();
|
||||
let state: SharedState = Arc::new(RwLock::new(AppStateInner {
|
||||
latest_update: None,
|
||||
rssi_history: VecDeque::new(),
|
||||
@@ -13146,6 +13176,7 @@ async fn main() {
|
||||
// no `node_id:` prefix takes its list index as its id), so
|
||||
// an existing `--node-positions` string keeps its meaning.
|
||||
fuser.set_node_positions_by_id(node_positions_config.clone());
|
||||
engine_bridge_node_positions = node_positions_config.clone();
|
||||
}
|
||||
}
|
||||
engine_bridge_multistatic_cfg = Some(MultistaticConfig {
|
||||
@@ -13162,7 +13193,8 @@ async fn main() {
|
||||
"default",
|
||||
"Default Room",
|
||||
engine_bridge_multistatic_cfg,
|
||||
),
|
||||
)
|
||||
.with_node_positions(engine_bridge_node_positions),
|
||||
field_model: bootstrap_field_model,
|
||||
installation_id: args.installation_id.clone(),
|
||||
bootstrap_baseline: bootstrap_metadata.clone(),
|
||||
|
||||
Reference in new issue
Block a user