finish treetest documentation sweep

This commit is contained in:
Michael Mikovsky
2026-04-24 18:30:44 -06:00
parent 9e74f6d6f3
commit b38d9d2149
6 changed files with 113 additions and 0 deletions
+6
View File
@@ -1,4 +1,8 @@
//! Simulator stepping helpers.
//!
//! These helpers are intentionally tiny. They advance the mailboxes one frame at
//! a time or until idle, which keeps the step-by-step demo behavior deterministic
//! and easy to explain in the UI.
use crossbeam_channel::TryRecvError;
use unshell::protocol::decode_frame;
@@ -28,6 +32,8 @@ impl Simulation {
return Ok(true);
}
Err(TryRecvError::Disconnected) => {
// A disconnected mailbox means the simulated topology is no
// longer internally consistent, so surface it as a hard error.
return Err(SimError::Protocol("mailbox disconnected".to_owned()));
}
Err(TryRecvError::Empty) => {}
+6
View File
@@ -1,4 +1,7 @@
//! Read-only simulator queries used by tests and UI widgets.
//!
//! Keeping these accessors separate makes it clear which simulator APIs mutate
//! protocol state and which ones merely summarize it for assertions or display.
use crate::model::Selection;
@@ -9,6 +12,8 @@ use super::super::types::{RecordedEvent, Simulation};
impl Simulation {
/// Returns the latest fault observed at the root, if any.
pub fn latest_root_fault(&self) -> Option<&FaultMessage> {
// Walk newest-to-oldest because the footer and tests only care about the
// most recent root-visible result.
self.recorded_events
.iter()
.rev()
@@ -22,6 +27,7 @@ impl Simulation {
/// Returns the latest root data message as utf-8 for tests and status text.
pub fn latest_root_data_text(&self) -> Option<String> {
// Lossy decoding keeps the query usable even for non-text payloads.
self.recorded_events
.iter()
.rev()
+20
View File
@@ -51,12 +51,15 @@ pub struct RootKnowledge {
}
impl RootKnowledge {
/// Builds the initial root knowledge from static scenario truth.
pub(super) fn new(tree: &crate::model::DemoTree) -> Self {
let mut knowledge = Self {
nodes: BTreeMap::new(),
};
for node in &tree.nodes {
if node.path.is_empty() || node.path.len() == 1 {
// Realistic mode intentionally starts with root plus direct children,
// not the full transitive tree.
let direct_child = node.path.len() == 1;
let mut learned = LearnedNode {
path: node.path.clone(),
@@ -69,6 +72,8 @@ impl RootKnowledge {
};
if node.path.is_empty() {
// The root always knows its own procedures and leaves because
// those are locally configured, not discovered remotely.
learned.endpoint_procedures = node
.endpoint_procedures
.iter()
@@ -101,6 +106,7 @@ impl RootKnowledge {
knowledge
}
/// Returns an existing learned node or creates a new placeholder record.
pub(super) fn ensure_node(&mut self, demo_node: &crate::model::DemoNode) -> &mut LearnedNode {
let direct_child = demo_node.path.len() == 1;
self.nodes
@@ -121,6 +127,8 @@ impl RootKnowledge {
demo_node: &crate::model::DemoNode,
procedure: &EndpointProcedureSpec,
) {
// Procedures are keyed by full `procedure_id`, so repeated observation
// simply enriches one existing record instead of duplicating it.
let learned_node = self.ensure_node(demo_node);
push_procedure(
&mut learned_node.endpoint_procedures,
@@ -134,6 +142,8 @@ impl RootKnowledge {
demo_node: &crate::model::DemoNode,
leaf_spec: &crate::model::LeafSpec,
) {
// Direct user targeting is enough for the root to remember a leaf exists,
// even before remote introspection returns richer confirmation.
let learned_node = self.ensure_node(demo_node);
let leaf = ensure_leaf(
&mut learned_node.leaves,
@@ -154,6 +164,8 @@ impl RootKnowledge {
demo_node: &crate::model::DemoNode,
introspection: &EndpointIntrospection,
) {
// Endpoint introspection is the moment a node becomes explicitly known to
// have been queried rather than merely inferred by path.
let learned_node = self.ensure_node(demo_node);
learned_node.endpoint_introspected = true;
for summary in &introspection.leaves {
@@ -195,18 +207,23 @@ impl RootKnowledge {
}
pub(super) fn clear_deeper_than_one_hop(&mut self) {
// This powers the realistic-mode reset, which forgets transitive state
// and keeps only root-local plus direct-child knowledge.
self.nodes.retain(|path, _| path.len() <= 1);
}
/// Returns one learned node by absolute path.
pub fn node(&self, path: &[String]) -> Option<&LearnedNode> {
self.nodes.get(path)
}
/// Returns every path currently known to the root host.
pub fn known_paths(&self) -> Vec<Vec<String>> {
self.nodes.keys().cloned().collect()
}
}
/// Returns one learned leaf entry, creating it if necessary.
fn ensure_leaf<'a>(
leaves: &'a mut Vec<LearnedLeaf>,
leaf_name: String,
@@ -227,6 +244,7 @@ fn ensure_leaf<'a>(
leaves.last_mut().expect("just pushed")
}
/// Inserts or enriches one learned procedure entry.
fn push_procedure(
procedures: &mut Vec<LearnedProcedure>,
procedure_id: String,
@@ -236,6 +254,8 @@ fn push_procedure(
.iter_mut()
.find(|procedure| procedure.procedure_id == procedure_id)
{
// Preserve the first available description, then upgrade missing details
// later if richer information is learned from introspection or config.
if existing.description.is_none() {
existing.description = description;
}
+33
View File
@@ -18,43 +18,58 @@ use super::knowledge::{InspectorMode, RootKnowledge};
/// User-facing outcome of a root-originated action.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionResult {
/// Human-readable summary shown in the footer after one action completes.
pub label: String,
/// Hook id allocated for the action, if the action opened or used one.
pub hook_id: Option<u64>,
}
/// Snapshot of a hook interaction observed by the demo.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HookSnapshot {
/// Hook identifier scoped to the root host.
pub hook_id: u64,
/// Host path for the hook, usually the root in this demo.
pub host_path: Vec<String>,
/// Peer endpoint currently associated with the hook.
pub peer_path: Vec<String>,
/// Procedure contract that established the hook.
pub procedure_id: String,
/// Optional target leaf when the originating call addressed one leaf.
pub target_leaf: Option<String>,
/// Whether the hook has finished normally or faulted.
pub closed: bool,
/// Most recent human-readable payload summary for the UI.
pub last_message: String,
}
/// Trace entry shown in the UI and asserted in tests.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceEvent {
/// Monotonic event number assigned by the simulator.
pub tick: u64,
/// Display path of the node that emitted the trace line.
pub node_path: String,
/// Human-readable event summary.
pub summary: String,
}
/// Summary of one local protocol event.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordedEvent {
/// Local hook data event.
Data {
node_path: String,
header: PacketHeader,
message: DataMessage,
},
/// Local protocol fault event.
Fault {
node_path: String,
header: PacketHeader,
message: FaultMessage,
},
/// Local call-delivery event.
Call {
node_path: String,
header: PacketHeader,
@@ -86,15 +101,22 @@ pub enum SimError {
/// Fully built simulation for one scenario.
#[derive(Debug)]
pub struct Simulation {
/// Active scenario definition the simulation was built from.
pub scenario: ScenarioDefinition,
/// Flattened tree model used by both simulator and UI.
pub tree: DemoTree,
pub(super) nodes: Vec<SimNode>,
pub(super) root_id: NodeId,
pub(super) next_tick: u64,
/// Rolling trace buffer shown in the UI.
pub trace: VecDeque<TraceEvent>,
/// Exact local events emitted by the protocol runtime.
pub recorded_events: Vec<RecordedEvent>,
/// Live and historical hook snapshots for display.
pub hooks: BTreeMap<u64, HookSnapshot>,
/// Which knowledge view the inspector currently renders.
pub inspector_mode: InspectorMode,
/// Root-host knowledge accumulated from direct config and observed traffic.
pub root_knowledge: RootKnowledge,
pub(super) chat_sessions: BTreeMap<u64, ChatSession>,
}
@@ -102,25 +124,36 @@ pub struct Simulation {
/// Per-node runtime wiring used by the simulator.
#[derive(Debug)]
pub(super) struct SimNode {
/// Optional parent node in the explicit tree.
pub(super) parent: Option<NodeId>,
/// Child node ids in display order.
pub(super) children: Vec<NodeId>,
/// Backing protocol runtime for this endpoint.
pub(super) endpoint: ProtocolEndpoint,
/// Mailbox sender used by other nodes when forwarding frames here.
pub(super) tx: Sender<Envelope>,
/// Mailbox receiver consumed by `Simulation::step`.
pub(super) rx: Receiver<Envelope>,
}
/// Internal packet delivery envelope.
#[derive(Debug, Clone)]
pub(super) struct Envelope {
/// Ingress side seen by the receiving protocol runtime.
pub(super) ingress: Ingress,
/// Fully framed packet bytes.
pub(super) frame: FrameBytes,
}
/// Application-level chat state layered on top of hook traffic.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ChatSession {
/// Node hosting the application-level chat behavior.
pub(super) node_id: NodeId,
/// Hook id that the chat session is bound to.
pub(super) hook_id: u64,
/// Path of the hook host to which replies must be routed.
pub(super) host_path: Vec<String>,
/// Procedure contract associated with the chat stream.
pub(super) procedure_id: String,
}