// ============================================================
// Runtime health checks for integration boundaries
// ============================================================
///|
/// A compact health record for a tree runner.
pub struct RunnerHealth {
frame : Int
terminal : Bool
status : String
}
///|
/// Inspect a runner without changing its execution state.
pub fn inspect_runner(runner : TreeRunner) -> RunnerHealth {
let status = match runner.last_status() {
None => "NotStarted"
Some(value) => value.to_string()
}
{ frame: runner.frame(), terminal: runner.is_terminal(), status }
}
///|
/// Return a stable line suitable for a readiness endpoint.
pub fn RunnerHealth::to_line(self : RunnerHealth) -> String {
"frame=\{self.frame};terminal=\{self.terminal};status=\{self.status}"
}
///|
/// Return the inspected frame number.
pub fn RunnerHealth::frame(self : RunnerHealth) -> Int {
self.frame
}
///|
/// Return whether the inspected runner is terminal.
pub fn RunnerHealth::is_terminal(self : RunnerHealth) -> Bool {
self.terminal
}
///|
/// Return the status label without formatting.
pub fn RunnerHealth::status(self : RunnerHealth) -> String {
self.status
}
///|
/// Return the type name stored under a key, or None when absent.
pub fn blackboard_value_type(bb : Blackboard, key : String) -> String? {
match bb.get_value(key) {
Some(value) => Some(value.type_name())
None => None
}
}
///|
/// Check that a blackboard contains all required keys.
pub fn has_required_keys(bb : Blackboard, required : Array[String]) -> Bool {
for key in required {
if !bb.has(key) {
return false
}
}
true
}
///|
/// Count how many required keys are present.
pub fn count_present_keys(bb : Blackboard, required : Array[String]) -> Int {
let mut count = 0
for key in required {
if bb.has(key) {
count = count + 1
}
}
count
}
///|
/// Execute a tree only when its required context is complete.
pub fn context_guard_node(required : Array[String], child : Node) -> Node {
let tick = fn(bb : Blackboard) {
if has_required_keys(bb, required) {
child.tick(bb)
} else {
child.reset()
Status::BTFailure
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}