From b38d9d2149484d18ba49bb579becd5c73e9f590c Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:30:44 -0600 Subject: [PATCH] finish treetest documentation sweep --- treetest/src/app/ui/inspector.rs | 9 +++++++ treetest/src/model.rs | 39 +++++++++++++++++++++++++++++ treetest/src/sim/actions/driver.rs | 6 +++++ treetest/src/sim/actions/queries.rs | 6 +++++ treetest/src/sim/knowledge.rs | 20 +++++++++++++++ treetest/src/sim/types.rs | 33 ++++++++++++++++++++++++ 6 files changed, 113 insertions(+) diff --git a/treetest/src/app/ui/inspector.rs b/treetest/src/app/ui/inspector.rs index 1530442..93a25fb 100644 --- a/treetest/src/app/ui/inspector.rs +++ b/treetest/src/app/ui/inspector.rs @@ -18,6 +18,10 @@ use crate::{ use super::super::App; impl App { + /// Renders the inspector pane for the current selection. + /// + /// Rationale: the inspector is the only pane whose data source changes with + /// inspector mode, so it owns the `ground truth` vs `realistic` branch. pub(super) fn render_inspector(&self, frame: &mut Frame<'_>, area: ratatui::layout::Rect) { let selection = self.selected(); let body = match self.simulation.inspector_mode { @@ -33,6 +37,7 @@ impl App { ); } + /// Renders the inspector using full scenario truth. fn render_ground_truth_inspector(&self, selection: &Selection) -> Text<'static> { match selection { Selection::Node(node_id) => { @@ -84,6 +89,7 @@ impl App { } } + /// Renders the inspector using only what the root host has learned. fn render_realistic_inspector(&self, selection: &Selection) -> Text<'static> { match selection { Selection::Node(node_id) => { @@ -133,6 +139,9 @@ impl App { } Text::from(lines) } else { + // Showing an explicit unknown state is better than silently + // falling back to ground truth, because the whole point of + // realistic mode is to expose what the root does not know. Text::from(vec![ Line::from(node.display_path()).bold(), Line::from( diff --git a/treetest/src/model.rs b/treetest/src/model.rs index efc7734..aa05921 100644 --- a/treetest/src/model.rs +++ b/treetest/src/model.rs @@ -73,6 +73,23 @@ pub struct DemoNode { impl DemoNode { /// Returns a display path that keeps the root easy to recognize in the UI. + /// + /// # Example + /// ```rust + /// use treetest::model::{DemoNode, NodeId}; + /// + /// let node = DemoNode { + /// id: NodeId(0), + /// parent: None, + /// children: Vec::new(), + /// path: vec!["alpha".to_owned()], + /// title: "Alpha".to_owned(), + /// description: String::new(), + /// leaves: Vec::new(), + /// endpoint_procedures: Vec::new(), + /// }; + /// assert_eq!(node.display_path(), "/alpha"); + /// ``` pub fn display_path(&self) -> String { format_path(&self.path) } @@ -89,6 +106,7 @@ pub struct DemoTree { impl DemoTree { /// Builds a flattened tree from a recursive specification. pub fn from_root(spec: &NodeSpec) -> Self { + // Flatten once so later UI and simulation code can use stable ids. let mut nodes = Vec::new(); let mut path_index = BTreeMap::new(); let root = Self::push_node(spec, None, &[], &mut nodes, &mut path_index); @@ -106,6 +124,8 @@ impl DemoTree { nodes: &mut Vec, path_index: &mut BTreeMap, NodeId>, ) -> NodeId { + // Node ids are assigned in insertion order so parent/child relationships + // can be expressed without any separate allocation table. let id = NodeId(nodes.len()); let path = if spec.segment.is_empty() { base_path.to_vec() @@ -130,6 +150,7 @@ impl DemoTree { let child_ids = spec .children .iter() + // Recurse after inserting the parent so children can record `parent: Some(id)`. .map(|child| Self::push_node(child, Some(id), &path, nodes, path_index)) .collect::>(); nodes[id.0].children = child_ids; @@ -137,6 +158,9 @@ impl DemoTree { } /// Returns the node with the given id. + /// + /// Rationale: indexing by `NodeId` keeps later code short and avoids passing + /// mutable references deep through the UI and simulator layers. pub fn node(&self, id: NodeId) -> &DemoNode { &self.nodes[id.0] } @@ -175,6 +199,11 @@ pub struct ScenarioDefinition { } /// Formats a path the same way throughout the UI and tests. +/// +/// # Example +/// ```rust +/// assert_eq!(treetest::model::format_path(&[]), "/"); +/// ``` pub fn format_path(path: &[String]) -> String { if path.is_empty() { "/".to_owned() @@ -184,11 +213,21 @@ pub fn format_path(path: &[String]) -> String { } /// Formats a leaf reference using the protocol document's descriptive syntax. +/// +/// # Example +/// ```rust +/// assert_eq!(treetest::model::format_leaf_ref(&["a".into()], "echo"), "/a { leaf: echo }"); +/// ``` pub fn format_leaf_ref(path: &[String], leaf_name: &str) -> String { format!("{} {{ leaf: {} }}", format_path(path), leaf_name) } /// Formats a hook reference using the protocol document's descriptive syntax. +/// +/// # Example +/// ```rust +/// assert_eq!(treetest::model::format_hook_ref(&[], 7), "/ { hook: 7 }"); +/// ``` pub fn format_hook_ref(path: &[String], hook_id: u64) -> String { format!("{} {{ hook: {} }}", format_path(path), hook_id) } diff --git a/treetest/src/sim/actions/driver.rs b/treetest/src/sim/actions/driver.rs index 7797c3a..212b634 100644 --- a/treetest/src/sim/actions/driver.rs +++ b/treetest/src/sim/actions/driver.rs @@ -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) => {} diff --git a/treetest/src/sim/actions/queries.rs b/treetest/src/sim/actions/queries.rs index 5c77820..3c7a4a9 100644 --- a/treetest/src/sim/actions/queries.rs +++ b/treetest/src/sim/actions/queries.rs @@ -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 { + // Lossy decoding keeps the query usable even for non-text payloads. self.recorded_events .iter() .rev() diff --git a/treetest/src/sim/knowledge.rs b/treetest/src/sim/knowledge.rs index aef3ae8..ee4c137 100644 --- a/treetest/src/sim/knowledge.rs +++ b/treetest/src/sim/knowledge.rs @@ -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> { self.nodes.keys().cloned().collect() } } +/// Returns one learned leaf entry, creating it if necessary. fn ensure_leaf<'a>( leaves: &'a mut Vec, 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, 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; } diff --git a/treetest/src/sim/types.rs b/treetest/src/sim/types.rs index 58420d1..2acb2b9 100644 --- a/treetest/src/sim/types.rs +++ b/treetest/src/sim/types.rs @@ -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, } /// 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, + /// Peer endpoint currently associated with the hook. pub peer_path: Vec, + /// Procedure contract that established the hook. pub procedure_id: String, + /// Optional target leaf when the originating call addressed one leaf. pub target_leaf: Option, + /// 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, pub(super) root_id: NodeId, pub(super) next_tick: u64, + /// Rolling trace buffer shown in the UI. pub trace: VecDeque, + /// Exact local events emitted by the protocol runtime. pub recorded_events: Vec, + /// Live and historical hook snapshots for display. pub hooks: BTreeMap, + /// 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, } @@ -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, + /// Child node ids in display order. pub(super) children: Vec, + /// Backing protocol runtime for this endpoint. pub(super) endpoint: ProtocolEndpoint, + /// Mailbox sender used by other nodes when forwarding frames here. pub(super) tx: Sender, + /// Mailbox receiver consumed by `Simulation::step`. pub(super) rx: Receiver, } /// 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, + /// Procedure contract associated with the chat stream. pub(super) procedure_id: String, }