git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
716 lines
20 KiB
Rust
716 lines
20 KiB
Rust
//! Integration tests for Coherence Gate and Compute Ladder
|
|
//!
|
|
//! Tests the Execution bounded context, verifying:
|
|
//! - Gate decisions based on energy thresholds
|
|
//! - Compute ladder escalation (O(1) -> O(n) -> O(n^o(1)))
|
|
//! - Persistence detection for blocking decisions
|
|
//! - Throttling behavior under high energy
|
|
//! - Multi-lane processing
|
|
|
|
use std::collections::VecDeque;
|
|
use std::time::{Duration, Instant};
|
|
|
|
// ============================================================================
|
|
// TEST TYPES
|
|
// ============================================================================
|
|
|
|
/// Gate decision outcomes
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum GateDecision {
|
|
/// Green light - proceed without restriction
|
|
Allow,
|
|
/// Amber light - throttle the action
|
|
Throttle { factor: u32 },
|
|
/// Red light - block the action
|
|
Block,
|
|
}
|
|
|
|
/// Compute lane for escalation
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
enum ComputeLane {
|
|
/// O(1) - Local tile check, immediate response
|
|
Local,
|
|
/// O(k) - k-hop neighborhood check
|
|
Neighborhood { k: usize },
|
|
/// O(n) - Full graph traversal
|
|
Global,
|
|
/// O(n^o(1)) - Subpolynomial spectral analysis
|
|
Spectral,
|
|
}
|
|
|
|
/// Threshold configuration
|
|
#[derive(Clone, Debug)]
|
|
struct ThresholdConfig {
|
|
/// Energy below this -> Allow
|
|
green_threshold: f32,
|
|
/// Energy below this (but above green) -> Throttle
|
|
amber_threshold: f32,
|
|
/// Energy above this -> Block
|
|
red_threshold: f32,
|
|
/// Enable compute ladder escalation
|
|
escalation_enabled: bool,
|
|
/// Maximum escalation lane
|
|
max_escalation_lane: ComputeLane,
|
|
}
|
|
|
|
impl Default for ThresholdConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
green_threshold: 0.1,
|
|
amber_threshold: 0.5,
|
|
red_threshold: 1.0,
|
|
escalation_enabled: true,
|
|
max_escalation_lane: ComputeLane::Spectral,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Coherence gate engine
|
|
struct CoherenceGate {
|
|
config: ThresholdConfig,
|
|
current_lane: ComputeLane,
|
|
decision_history: VecDeque<GateDecision>,
|
|
persistence_window: usize,
|
|
}
|
|
|
|
impl CoherenceGate {
|
|
fn new(config: ThresholdConfig) -> Self {
|
|
Self {
|
|
config,
|
|
current_lane: ComputeLane::Local,
|
|
decision_history: VecDeque::new(),
|
|
persistence_window: 5,
|
|
}
|
|
}
|
|
|
|
/// Make a gate decision based on current energy
|
|
fn decide(&mut self, energy: f32) -> GateDecision {
|
|
let decision = if energy < self.config.green_threshold {
|
|
GateDecision::Allow
|
|
} else if energy < self.config.amber_threshold {
|
|
// Calculate throttle factor based on energy
|
|
let ratio = (energy - self.config.green_threshold)
|
|
/ (self.config.amber_threshold - self.config.green_threshold);
|
|
let factor = (1.0 + ratio * 9.0) as u32; // 1x to 10x throttle
|
|
GateDecision::Throttle { factor }
|
|
} else {
|
|
GateDecision::Block
|
|
};
|
|
|
|
// Track history for persistence detection
|
|
self.decision_history.push_back(decision);
|
|
if self.decision_history.len() > self.persistence_window {
|
|
self.decision_history.pop_front();
|
|
}
|
|
|
|
decision
|
|
}
|
|
|
|
/// Check if blocking is persistent
|
|
fn is_persistent_block(&self) -> bool {
|
|
if self.decision_history.len() < self.persistence_window {
|
|
return false;
|
|
}
|
|
|
|
self.decision_history
|
|
.iter()
|
|
.all(|d| matches!(d, GateDecision::Block))
|
|
}
|
|
|
|
/// Escalate to higher compute lane
|
|
fn escalate(&mut self) -> Option<ComputeLane> {
|
|
if !self.config.escalation_enabled {
|
|
return None;
|
|
}
|
|
|
|
let next_lane = match self.current_lane {
|
|
ComputeLane::Local => Some(ComputeLane::Neighborhood { k: 2 }),
|
|
ComputeLane::Neighborhood { k } if k < 5 => {
|
|
Some(ComputeLane::Neighborhood { k: k + 1 })
|
|
}
|
|
ComputeLane::Neighborhood { .. } => Some(ComputeLane::Global),
|
|
ComputeLane::Global => Some(ComputeLane::Spectral),
|
|
ComputeLane::Spectral => None, // Already at max
|
|
};
|
|
|
|
if let Some(lane) = next_lane {
|
|
if lane <= self.config.max_escalation_lane {
|
|
self.current_lane = lane;
|
|
return Some(lane);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// De-escalate to lower compute lane
|
|
fn deescalate(&mut self) -> Option<ComputeLane> {
|
|
let prev_lane = match self.current_lane {
|
|
ComputeLane::Local => None,
|
|
ComputeLane::Neighborhood { k } if k > 2 => {
|
|
Some(ComputeLane::Neighborhood { k: k - 1 })
|
|
}
|
|
ComputeLane::Neighborhood { .. } => Some(ComputeLane::Local),
|
|
ComputeLane::Global => Some(ComputeLane::Neighborhood { k: 5 }),
|
|
ComputeLane::Spectral => Some(ComputeLane::Global),
|
|
};
|
|
|
|
if let Some(lane) = prev_lane {
|
|
self.current_lane = lane;
|
|
return Some(lane);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Get current compute lane
|
|
fn current_lane(&self) -> ComputeLane {
|
|
self.current_lane
|
|
}
|
|
|
|
/// Clear decision history
|
|
fn reset_history(&mut self) {
|
|
self.decision_history.clear();
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// BASIC GATE DECISION TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_gate_allows_low_energy() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let decision = gate.decide(0.05);
|
|
|
|
assert_eq!(decision, GateDecision::Allow);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gate_throttles_medium_energy() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let decision = gate.decide(0.3);
|
|
|
|
match decision {
|
|
GateDecision::Throttle { factor } => {
|
|
assert!(factor >= 1);
|
|
assert!(factor <= 10);
|
|
}
|
|
_ => panic!("Expected Throttle decision, got {:?}", decision),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gate_blocks_high_energy() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let decision = gate.decide(0.8);
|
|
|
|
assert_eq!(decision, GateDecision::Block);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gate_blocks_above_red_threshold() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
red_threshold: 0.5,
|
|
..Default::default()
|
|
});
|
|
|
|
let decision = gate.decide(0.6);
|
|
|
|
assert_eq!(decision, GateDecision::Block);
|
|
}
|
|
|
|
#[test]
|
|
fn test_throttle_factor_increases_with_energy() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let decision_low = gate.decide(0.15);
|
|
let decision_high = gate.decide(0.45);
|
|
|
|
match (decision_low, decision_high) {
|
|
(GateDecision::Throttle { factor: f1 }, GateDecision::Throttle { factor: f2 }) => {
|
|
assert!(
|
|
f2 > f1,
|
|
"Higher energy should produce higher throttle factor"
|
|
);
|
|
}
|
|
_ => panic!("Expected both to be Throttle decisions"),
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// THRESHOLD BOUNDARY TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_boundary_just_below_green() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
green_threshold: 0.1,
|
|
..Default::default()
|
|
});
|
|
|
|
let decision = gate.decide(0.099);
|
|
assert_eq!(decision, GateDecision::Allow);
|
|
}
|
|
|
|
#[test]
|
|
fn test_boundary_at_green() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
green_threshold: 0.1,
|
|
..Default::default()
|
|
});
|
|
|
|
// At the threshold, should still be Allow (< comparison)
|
|
let decision = gate.decide(0.1);
|
|
assert!(matches!(decision, GateDecision::Throttle { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_boundary_just_below_amber() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
green_threshold: 0.1,
|
|
amber_threshold: 0.5,
|
|
..Default::default()
|
|
});
|
|
|
|
let decision = gate.decide(0.499);
|
|
assert!(matches!(decision, GateDecision::Throttle { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_boundary_at_amber() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
green_threshold: 0.1,
|
|
amber_threshold: 0.5,
|
|
..Default::default()
|
|
});
|
|
|
|
let decision = gate.decide(0.5);
|
|
assert_eq!(decision, GateDecision::Block);
|
|
}
|
|
|
|
// ============================================================================
|
|
// COMPUTE LADDER ESCALATION TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_initial_lane_is_local() {
|
|
let gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
assert_eq!(gate.current_lane(), ComputeLane::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn test_escalation_from_local_to_neighborhood() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let new_lane = gate.escalate();
|
|
|
|
assert_eq!(new_lane, Some(ComputeLane::Neighborhood { k: 2 }));
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 2 });
|
|
}
|
|
|
|
#[test]
|
|
fn test_escalation_through_neighborhood_k() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Local -> Neighborhood k=2
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 2 });
|
|
|
|
// Neighborhood k=2 -> k=3
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 3 });
|
|
|
|
// k=3 -> k=4
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 4 });
|
|
|
|
// k=4 -> k=5
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 5 });
|
|
|
|
// k=5 -> Global
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Global);
|
|
}
|
|
|
|
#[test]
|
|
fn test_escalation_to_spectral() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Escalate all the way
|
|
while let Some(_) = gate.escalate() {
|
|
// Keep escalating
|
|
}
|
|
|
|
assert_eq!(gate.current_lane(), ComputeLane::Spectral);
|
|
}
|
|
|
|
#[test]
|
|
fn test_escalation_respects_max_lane() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
max_escalation_lane: ComputeLane::Global,
|
|
..Default::default()
|
|
});
|
|
|
|
// Escalate to max
|
|
while let Some(_) = gate.escalate() {}
|
|
|
|
// Should stop at Global, not Spectral
|
|
assert_eq!(gate.current_lane(), ComputeLane::Global);
|
|
}
|
|
|
|
#[test]
|
|
fn test_escalation_disabled() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig {
|
|
escalation_enabled: false,
|
|
..Default::default()
|
|
});
|
|
|
|
let result = gate.escalate();
|
|
|
|
assert_eq!(result, None);
|
|
assert_eq!(gate.current_lane(), ComputeLane::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deescalation_from_spectral() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Escalate to spectral
|
|
while let Some(_) = gate.escalate() {}
|
|
assert_eq!(gate.current_lane(), ComputeLane::Spectral);
|
|
|
|
// Deescalate one step
|
|
let lane = gate.deescalate();
|
|
assert_eq!(lane, Some(ComputeLane::Global));
|
|
assert_eq!(gate.current_lane(), ComputeLane::Global);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deescalation_to_local() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Escalate a few times
|
|
gate.escalate();
|
|
gate.escalate();
|
|
assert_eq!(gate.current_lane(), ComputeLane::Neighborhood { k: 3 });
|
|
|
|
// Deescalate all the way
|
|
while let Some(_) = gate.deescalate() {}
|
|
|
|
assert_eq!(gate.current_lane(), ComputeLane::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deescalation_from_local_returns_none() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
let result = gate.deescalate();
|
|
|
|
assert_eq!(result, None);
|
|
assert_eq!(gate.current_lane(), ComputeLane::Local);
|
|
}
|
|
|
|
// ============================================================================
|
|
// PERSISTENCE DETECTION TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_no_persistence_initially() {
|
|
let gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
assert!(!gate.is_persistent_block());
|
|
}
|
|
|
|
#[test]
|
|
fn test_persistence_detected_after_window() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Block persistently
|
|
for _ in 0..5 {
|
|
gate.decide(0.9); // Block
|
|
}
|
|
|
|
assert!(gate.is_persistent_block());
|
|
}
|
|
|
|
#[test]
|
|
fn test_no_persistence_with_mixed_decisions() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Mix of decisions
|
|
gate.decide(0.9); // Block
|
|
gate.decide(0.05); // Allow
|
|
gate.decide(0.9); // Block
|
|
gate.decide(0.9); // Block
|
|
gate.decide(0.9); // Block
|
|
|
|
// Not all blocks, so not persistent
|
|
assert!(!gate.is_persistent_block());
|
|
}
|
|
|
|
#[test]
|
|
fn test_persistence_window_sliding() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Start with allows
|
|
for _ in 0..3 {
|
|
gate.decide(0.05); // Allow
|
|
}
|
|
|
|
assert!(!gate.is_persistent_block());
|
|
|
|
// Then all blocks
|
|
for _ in 0..5 {
|
|
gate.decide(0.9); // Block
|
|
}
|
|
|
|
// Now persistent
|
|
assert!(gate.is_persistent_block());
|
|
}
|
|
|
|
#[test]
|
|
fn test_reset_clears_persistence() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Build up persistence
|
|
for _ in 0..5 {
|
|
gate.decide(0.9);
|
|
}
|
|
assert!(gate.is_persistent_block());
|
|
|
|
// Reset
|
|
gate.reset_history();
|
|
|
|
assert!(!gate.is_persistent_block());
|
|
}
|
|
|
|
// ============================================================================
|
|
// MULTI-LANE PROCESSING TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_lane_complexity_ordering() {
|
|
// Verify lanes are properly ordered by complexity
|
|
assert!(ComputeLane::Local < ComputeLane::Neighborhood { k: 2 });
|
|
assert!(ComputeLane::Neighborhood { k: 2 } < ComputeLane::Neighborhood { k: 3 });
|
|
assert!(ComputeLane::Neighborhood { k: 5 } < ComputeLane::Global);
|
|
assert!(ComputeLane::Global < ComputeLane::Spectral);
|
|
}
|
|
|
|
#[test]
|
|
fn test_automatic_escalation_on_block() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Simulate escalation policy: escalate on block
|
|
let energy = 0.8;
|
|
let decision = gate.decide(energy);
|
|
|
|
if matches!(decision, GateDecision::Block) {
|
|
let escalated = gate.escalate().is_some();
|
|
assert!(escalated, "Should escalate on block");
|
|
}
|
|
|
|
// After one escalation
|
|
assert!(gate.current_lane() > ComputeLane::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn test_automatic_deescalation_on_allow() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// First, escalate
|
|
gate.escalate();
|
|
gate.escalate();
|
|
assert!(gate.current_lane() > ComputeLane::Local);
|
|
|
|
// Then allow (low energy)
|
|
let decision = gate.decide(0.01);
|
|
assert_eq!(decision, GateDecision::Allow);
|
|
|
|
// Deescalate after allow
|
|
gate.deescalate();
|
|
assert!(gate.current_lane() < ComputeLane::Neighborhood { k: 3 });
|
|
}
|
|
|
|
// ============================================================================
|
|
// CUSTOM THRESHOLD TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_custom_thresholds() {
|
|
let config = ThresholdConfig {
|
|
green_threshold: 0.05,
|
|
amber_threshold: 0.15,
|
|
red_threshold: 0.25,
|
|
escalation_enabled: true,
|
|
max_escalation_lane: ComputeLane::Global,
|
|
};
|
|
|
|
let mut gate = CoherenceGate::new(config);
|
|
|
|
// Very low energy -> Allow
|
|
assert_eq!(gate.decide(0.03), GateDecision::Allow);
|
|
|
|
// Low-medium energy -> Throttle
|
|
assert!(matches!(gate.decide(0.10), GateDecision::Throttle { .. }));
|
|
|
|
// Medium energy -> Block (with these tight thresholds)
|
|
assert_eq!(gate.decide(0.20), GateDecision::Block);
|
|
}
|
|
|
|
#[test]
|
|
fn test_zero_thresholds() {
|
|
let config = ThresholdConfig {
|
|
green_threshold: 0.0,
|
|
amber_threshold: 0.0,
|
|
red_threshold: 0.0,
|
|
..Default::default()
|
|
};
|
|
|
|
let mut gate = CoherenceGate::new(config);
|
|
|
|
// Any positive energy should block
|
|
assert_eq!(gate.decide(0.001), GateDecision::Block);
|
|
assert_eq!(gate.decide(1.0), GateDecision::Block);
|
|
|
|
// Zero energy should... well, it's at the boundary
|
|
// < 0 is Allow, >= 0 is the next category
|
|
// With all thresholds at 0, any energy >= 0 goes to Block
|
|
}
|
|
|
|
// ============================================================================
|
|
// CONCURRENT GATE ACCESS TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_gate_thread_safety_simulation() {
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
|
|
// Wrap gate in mutex for thread-safe access
|
|
let gate = Arc::new(Mutex::new(CoherenceGate::new(ThresholdConfig::default())));
|
|
|
|
let handles: Vec<_> = (0..4)
|
|
.map(|i| {
|
|
let gate = Arc::clone(&gate);
|
|
thread::spawn(move || {
|
|
let energy = 0.1 * (i as f32);
|
|
let mut gate = gate.lock().unwrap();
|
|
let decision = gate.decide(energy);
|
|
(i, decision)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
|
|
|
|
// Verify each thread got a decision
|
|
assert_eq!(results.len(), 4);
|
|
}
|
|
|
|
// ============================================================================
|
|
// ENERGY SPIKE HANDLING TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_energy_spike_causes_immediate_block() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Normal operation
|
|
for _ in 0..3 {
|
|
let decision = gate.decide(0.05);
|
|
assert_eq!(decision, GateDecision::Allow);
|
|
}
|
|
|
|
// Energy spike
|
|
let decision = gate.decide(0.9);
|
|
assert_eq!(decision, GateDecision::Block);
|
|
}
|
|
|
|
#[test]
|
|
fn test_recovery_after_spike() {
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
|
|
// Spike
|
|
gate.decide(0.9);
|
|
assert!(!gate.is_persistent_block()); // Not persistent yet
|
|
|
|
// Recovery
|
|
for _ in 0..5 {
|
|
gate.decide(0.05);
|
|
}
|
|
|
|
assert!(!gate.is_persistent_block());
|
|
|
|
// All recent decisions should be Allow
|
|
assert_eq!(gate.decide(0.05), GateDecision::Allow);
|
|
}
|
|
|
|
// ============================================================================
|
|
// LANE LATENCY SIMULATION TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_lane_latency_simulation() {
|
|
/// Simulated latency for each lane
|
|
fn lane_latency(lane: ComputeLane) -> Duration {
|
|
match lane {
|
|
ComputeLane::Local => Duration::from_micros(10),
|
|
ComputeLane::Neighborhood { k } => Duration::from_micros(100 * k as u64),
|
|
ComputeLane::Global => Duration::from_millis(10),
|
|
ComputeLane::Spectral => Duration::from_millis(100),
|
|
}
|
|
}
|
|
|
|
let lanes = vec![
|
|
ComputeLane::Local,
|
|
ComputeLane::Neighborhood { k: 2 },
|
|
ComputeLane::Neighborhood { k: 5 },
|
|
ComputeLane::Global,
|
|
ComputeLane::Spectral,
|
|
];
|
|
|
|
let latencies: Vec<_> = lanes.iter().map(|l| lane_latency(*l)).collect();
|
|
|
|
// Verify latencies are increasing
|
|
for i in 1..latencies.len() {
|
|
assert!(
|
|
latencies[i] > latencies[i - 1],
|
|
"Higher lanes should have higher latency"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// REAL-TIME BUDGET TESTS
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_local_lane_meets_budget() {
|
|
// Local lane should complete in <1ms budget
|
|
let budget = Duration::from_millis(1);
|
|
|
|
let start = Instant::now();
|
|
|
|
// Simulate local computation (just a decision)
|
|
let mut gate = CoherenceGate::new(ThresholdConfig::default());
|
|
for _ in 0..1000 {
|
|
gate.decide(0.05);
|
|
}
|
|
|
|
let elapsed = start.elapsed();
|
|
|
|
// 1000 decisions should still be fast
|
|
assert!(
|
|
elapsed < budget * 100, // Very generous for test environment
|
|
"Local computation took too long: {:?}",
|
|
elapsed
|
|
);
|
|
}
|