wifi-densepose/vendor/ruvector/crates/prime-radiant/src/execution/ladder.rs

578 lines
18 KiB
Rust

//! # Compute Ladder: Escalation Logic for Coherence-Gated Execution
//!
//! Implements the compute ladder from ADR-014, providing threshold-based escalation
//! from low-latency reflex operations to human-in-the-loop review.
//!
//! ## Design Principle
//!
//! > Most updates stay in low-latency reflex lane (<1ms); sustained/growing
//! > incoherence triggers escalation.
//!
//! The compute ladder is not about being smart - it's about knowing when to stop
//! and when to ask for help.
//!
//! ## Lanes
//!
//! | Lane | Name | Latency | Description |
//! |------|------|---------|-------------|
//! | 0 | Reflex | <1ms | Local residual updates, simple aggregates |
//! | 1 | Retrieval | ~10ms | Evidence fetching, lightweight reasoning |
//! | 2 | Heavy | ~100ms | Multi-step planning, spectral analysis |
//! | 3 | Human | async | Human escalation for sustained incoherence |
use serde::{Deserialize, Serialize};
use std::fmt;
/// Compute lanes for escalating complexity.
///
/// CRITICAL: Most updates stay in Lane 0 (Reflex).
/// Escalation only occurs on sustained/growing incoherence.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum ComputeLane {
/// Lane 0: Local residual updates, simple aggregates (<1ms)
/// THE DEFAULT - most updates stay here
Reflex = 0,
/// Lane 1: Evidence fetching, lightweight reasoning (~10ms)
/// Triggered by: transient energy spike
Retrieval = 1,
/// Lane 2: Multi-step planning, spectral analysis (~100ms)
/// Triggered by: sustained incoherence above threshold
Heavy = 2,
/// Lane 3: Human escalation for sustained incoherence
/// Triggered by: persistent incoherence that automated systems cannot resolve
Human = 3,
}
impl ComputeLane {
/// Get the expected latency budget for this lane in microseconds.
#[inline]
pub const fn latency_budget_us(&self) -> u64 {
match self {
ComputeLane::Reflex => 1_000, // 1ms
ComputeLane::Retrieval => 10_000, // 10ms
ComputeLane::Heavy => 100_000, // 100ms
ComputeLane::Human => u64::MAX, // No limit (async)
}
}
/// Get the expected latency budget for this lane in milliseconds.
#[inline]
pub const fn latency_budget_ms(&self) -> u64 {
match self {
ComputeLane::Reflex => 1,
ComputeLane::Retrieval => 10,
ComputeLane::Heavy => 100,
ComputeLane::Human => u64::MAX,
}
}
/// Whether this lane allows automatic action execution.
///
/// Returns `false` only for Human lane, which requires explicit approval.
#[inline]
pub const fn allows_automatic_execution(&self) -> bool {
!matches!(self, ComputeLane::Human)
}
/// Whether this lane is the default low-latency lane.
#[inline]
pub const fn is_reflex(&self) -> bool {
matches!(self, ComputeLane::Reflex)
}
/// Whether this lane requires escalation (not reflex).
#[inline]
pub const fn is_escalated(&self) -> bool {
!matches!(self, ComputeLane::Reflex)
}
/// Get the next escalation level, if any.
pub const fn escalate(&self) -> Option<ComputeLane> {
match self {
ComputeLane::Reflex => Some(ComputeLane::Retrieval),
ComputeLane::Retrieval => Some(ComputeLane::Heavy),
ComputeLane::Heavy => Some(ComputeLane::Human),
ComputeLane::Human => None,
}
}
/// Get the previous de-escalation level, if any.
pub const fn deescalate(&self) -> Option<ComputeLane> {
match self {
ComputeLane::Reflex => None,
ComputeLane::Retrieval => Some(ComputeLane::Reflex),
ComputeLane::Heavy => Some(ComputeLane::Retrieval),
ComputeLane::Human => Some(ComputeLane::Heavy),
}
}
/// Parse from u8 value.
pub const fn from_u8(value: u8) -> Option<ComputeLane> {
match value {
0 => Some(ComputeLane::Reflex),
1 => Some(ComputeLane::Retrieval),
2 => Some(ComputeLane::Heavy),
3 => Some(ComputeLane::Human),
_ => None,
}
}
/// Convert to u8 value.
#[inline]
pub const fn as_u8(&self) -> u8 {
*self as u8
}
/// Get a human-readable name for this lane.
pub const fn name(&self) -> &'static str {
match self {
ComputeLane::Reflex => "Reflex",
ComputeLane::Retrieval => "Retrieval",
ComputeLane::Heavy => "Heavy",
ComputeLane::Human => "Human",
}
}
/// Get a description of what triggers this lane.
pub const fn trigger_description(&self) -> &'static str {
match self {
ComputeLane::Reflex => "Default lane - low energy, no trigger needed",
ComputeLane::Retrieval => "Transient energy spike above reflex threshold",
ComputeLane::Heavy => "Sustained incoherence above retrieval threshold",
ComputeLane::Human => "Persistent incoherence exceeding all automatic thresholds",
}
}
}
impl Default for ComputeLane {
fn default() -> Self {
ComputeLane::Reflex
}
}
impl fmt::Display for ComputeLane {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Lane {} ({})", self.as_u8(), self.name())
}
}
/// Threshold configuration for compute lane escalation.
///
/// These thresholds determine when energy levels trigger lane transitions.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct LaneThresholds {
/// Energy threshold for Lane 0 (Reflex) - stay in reflex if below this
pub reflex: f32,
/// Energy threshold for Lane 1 (Retrieval) - escalate to retrieval if above reflex
pub retrieval: f32,
/// Energy threshold for Lane 2 (Heavy) - escalate to heavy if above retrieval
pub heavy: f32,
}
impl LaneThresholds {
/// Create thresholds with explicit values.
pub const fn new(reflex: f32, retrieval: f32, heavy: f32) -> Self {
Self {
reflex,
retrieval,
heavy,
}
}
/// Create conservative thresholds (prefer escalation).
pub const fn conservative() -> Self {
Self {
reflex: 0.1,
retrieval: 0.3,
heavy: 0.6,
}
}
/// Create aggressive thresholds (prefer staying in reflex).
pub const fn aggressive() -> Self {
Self {
reflex: 0.5,
retrieval: 0.8,
heavy: 0.95,
}
}
/// Validate that thresholds are properly ordered.
pub fn validate(&self) -> Result<(), ThresholdError> {
if self.reflex < 0.0 || self.reflex > 1.0 {
return Err(ThresholdError::OutOfRange {
name: "reflex",
value: self.reflex,
});
}
if self.retrieval < 0.0 || self.retrieval > 1.0 {
return Err(ThresholdError::OutOfRange {
name: "retrieval",
value: self.retrieval,
});
}
if self.heavy < 0.0 || self.heavy > 1.0 {
return Err(ThresholdError::OutOfRange {
name: "heavy",
value: self.heavy,
});
}
if self.reflex >= self.retrieval {
return Err(ThresholdError::InvalidOrdering {
lower: "reflex",
upper: "retrieval",
});
}
if self.retrieval >= self.heavy {
return Err(ThresholdError::InvalidOrdering {
lower: "retrieval",
upper: "heavy",
});
}
Ok(())
}
/// Determine which lane an energy level requires.
///
/// Optimized with branchless comparison using conditional moves
/// for better branch prediction on modern CPUs.
#[inline]
pub fn lane_for_energy(&self, energy: f32) -> ComputeLane {
// Use branchless comparison for better performance
// The compiler can convert this to conditional moves (CMOVcc)
let is_above_reflex = (energy >= self.reflex) as u8;
let is_above_retrieval = (energy >= self.retrieval) as u8;
let is_above_heavy = (energy >= self.heavy) as u8;
// Sum determines the lane: 0=Reflex, 1=Retrieval, 2=Heavy, 3=Human
let lane_index = is_above_reflex + is_above_retrieval + is_above_heavy;
// SAFETY: lane_index is guaranteed to be 0-3
match lane_index {
0 => ComputeLane::Reflex,
1 => ComputeLane::Retrieval,
2 => ComputeLane::Heavy,
_ => ComputeLane::Human,
}
}
/// Fast lane check using array lookup (alternative implementation)
#[inline]
pub fn lane_for_energy_lookup(&self, energy: f32) -> ComputeLane {
// Store thresholds in array for potential SIMD comparison
let thresholds = [self.reflex, self.retrieval, self.heavy];
// Count how many thresholds are exceeded
let mut lane = 0u8;
for &t in &thresholds {
lane += (energy >= t) as u8;
}
// SAFETY: lane is 0-3
ComputeLane::from_u8(lane).unwrap_or(ComputeLane::Human)
}
/// Get the threshold for a specific lane transition.
pub fn threshold_for_lane(&self, lane: ComputeLane) -> f32 {
match lane {
ComputeLane::Reflex => 0.0, // Always accessible
ComputeLane::Retrieval => self.reflex,
ComputeLane::Heavy => self.retrieval,
ComputeLane::Human => self.heavy,
}
}
}
impl Default for LaneThresholds {
fn default() -> Self {
Self {
reflex: 0.2,
retrieval: 0.5,
heavy: 0.8,
}
}
}
/// Error type for threshold validation.
#[derive(Debug, Clone, thiserror::Error)]
pub enum ThresholdError {
#[error("Threshold '{name}' value {value} is out of range [0.0, 1.0]")]
OutOfRange { name: &'static str, value: f32 },
#[error("Invalid threshold ordering: {lower} must be less than {upper}")]
InvalidOrdering {
lower: &'static str,
upper: &'static str,
},
}
/// Escalation reason describing why a lane transition occurred.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EscalationReason {
/// Energy exceeded threshold for current lane.
EnergyThreshold {
/// The measured energy level.
energy: u32, // Fixed point (energy * 1000)
/// The threshold that was exceeded.
threshold: u32,
},
/// Persistent incoherence detected (energy above threshold for duration).
PersistentIncoherence {
/// Duration in milliseconds that energy was elevated.
duration_ms: u64,
/// Configured persistence window in milliseconds.
window_ms: u64,
},
/// Growing incoherence trend detected.
GrowingIncoherence {
/// Energy growth rate per second.
growth_rate: i32, // Fixed point (rate * 1000)
},
/// External trigger requested escalation.
ExternalTrigger {
/// Source of the trigger.
source: String,
},
/// System override (e.g., maintenance mode).
SystemOverride {
/// Reason for override.
reason: String,
},
}
impl EscalationReason {
/// Create an energy threshold escalation.
pub fn energy(energy: f32, threshold: f32) -> Self {
Self::EnergyThreshold {
energy: (energy * 1000.0) as u32,
threshold: (threshold * 1000.0) as u32,
}
}
/// Create a persistent incoherence escalation.
pub fn persistent(duration_ms: u64, window_ms: u64) -> Self {
Self::PersistentIncoherence {
duration_ms,
window_ms,
}
}
/// Create a growing incoherence escalation.
pub fn growing(growth_rate: f32) -> Self {
Self::GrowingIncoherence {
growth_rate: (growth_rate * 1000.0) as i32,
}
}
/// Is this a persistence-based escalation?
pub fn is_persistence_based(&self) -> bool {
matches!(self, Self::PersistentIncoherence { .. })
}
/// Is this an external trigger?
pub fn is_external(&self) -> bool {
matches!(
self,
Self::ExternalTrigger { .. } | Self::SystemOverride { .. }
)
}
}
impl fmt::Display for EscalationReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EnergyThreshold { energy, threshold } => {
write!(
f,
"Energy {:.3} exceeded threshold {:.3}",
*energy as f32 / 1000.0,
*threshold as f32 / 1000.0
)
}
Self::PersistentIncoherence {
duration_ms,
window_ms,
} => {
write!(
f,
"Persistent incoherence for {}ms (window: {}ms)",
duration_ms, window_ms
)
}
Self::GrowingIncoherence { growth_rate } => {
write!(
f,
"Growing incoherence at {:.3}/s",
*growth_rate as f32 / 1000.0
)
}
Self::ExternalTrigger { source } => {
write!(f, "External trigger from: {}", source)
}
Self::SystemOverride { reason } => {
write!(f, "System override: {}", reason)
}
}
}
}
/// Lane transition record for audit trail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaneTransition {
/// Previous lane.
pub from_lane: ComputeLane,
/// New lane.
pub to_lane: ComputeLane,
/// Reason for transition.
pub reason: EscalationReason,
/// Timestamp of transition (Unix millis).
pub timestamp_ms: u64,
/// Energy at time of transition.
pub energy: f32,
}
impl LaneTransition {
/// Create a new lane transition record.
pub fn new(
from_lane: ComputeLane,
to_lane: ComputeLane,
reason: EscalationReason,
energy: f32,
) -> Self {
Self {
from_lane,
to_lane,
reason,
timestamp_ms: Self::current_timestamp_ms(),
energy,
}
}
/// Get current timestamp in milliseconds.
fn current_timestamp_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
/// Whether this is an escalation (moving to higher lane).
pub fn is_escalation(&self) -> bool {
self.to_lane > self.from_lane
}
/// Whether this is a de-escalation (moving to lower lane).
pub fn is_deescalation(&self) -> bool {
self.to_lane < self.from_lane
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lane_ordering() {
assert!(ComputeLane::Reflex < ComputeLane::Retrieval);
assert!(ComputeLane::Retrieval < ComputeLane::Heavy);
assert!(ComputeLane::Heavy < ComputeLane::Human);
}
#[test]
fn test_lane_escalation() {
assert_eq!(ComputeLane::Reflex.escalate(), Some(ComputeLane::Retrieval));
assert_eq!(ComputeLane::Retrieval.escalate(), Some(ComputeLane::Heavy));
assert_eq!(ComputeLane::Heavy.escalate(), Some(ComputeLane::Human));
assert_eq!(ComputeLane::Human.escalate(), None);
}
#[test]
fn test_lane_deescalation() {
assert_eq!(ComputeLane::Reflex.deescalate(), None);
assert_eq!(
ComputeLane::Retrieval.deescalate(),
Some(ComputeLane::Reflex)
);
assert_eq!(
ComputeLane::Heavy.deescalate(),
Some(ComputeLane::Retrieval)
);
assert_eq!(ComputeLane::Human.deescalate(), Some(ComputeLane::Heavy));
}
#[test]
fn test_lane_automatic_execution() {
assert!(ComputeLane::Reflex.allows_automatic_execution());
assert!(ComputeLane::Retrieval.allows_automatic_execution());
assert!(ComputeLane::Heavy.allows_automatic_execution());
assert!(!ComputeLane::Human.allows_automatic_execution());
}
#[test]
fn test_default_thresholds() {
let thresholds = LaneThresholds::default();
assert!(thresholds.validate().is_ok());
}
#[test]
fn test_threshold_validation() {
// Valid thresholds
let valid = LaneThresholds::new(0.1, 0.5, 0.9);
assert!(valid.validate().is_ok());
// Invalid ordering
let invalid = LaneThresholds::new(0.5, 0.3, 0.9);
assert!(invalid.validate().is_err());
// Out of range
let out_of_range = LaneThresholds::new(-0.1, 0.5, 0.9);
assert!(out_of_range.validate().is_err());
}
#[test]
fn test_lane_for_energy() {
let thresholds = LaneThresholds::new(0.2, 0.5, 0.8);
assert_eq!(thresholds.lane_for_energy(0.1), ComputeLane::Reflex);
assert_eq!(thresholds.lane_for_energy(0.3), ComputeLane::Retrieval);
assert_eq!(thresholds.lane_for_energy(0.6), ComputeLane::Heavy);
assert_eq!(thresholds.lane_for_energy(0.9), ComputeLane::Human);
}
#[test]
fn test_escalation_reason_display() {
let reason = EscalationReason::energy(0.75, 0.5);
assert!(reason.to_string().contains("exceeded threshold"));
let persistent = EscalationReason::persistent(5000, 3000);
assert!(persistent.to_string().contains("5000ms"));
}
#[test]
fn test_lane_transition() {
let transition = LaneTransition::new(
ComputeLane::Reflex,
ComputeLane::Retrieval,
EscalationReason::energy(0.3, 0.2),
0.3,
);
assert!(transition.is_escalation());
assert!(!transition.is_deescalation());
}
}