Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
431
crates/ruvector-mincut-gated-transformer/tests/gate.rs
Normal file
431
crates/ruvector-mincut-gated-transformer/tests/gate.rs
Normal file
@@ -0,0 +1,431 @@
|
||||
//! Gate decision tests.
|
||||
//!
|
||||
//! Verifies that synthetic lambda traces produce expected tier changes.
|
||||
|
||||
use ruvector_mincut_gated_transformer::{
|
||||
gate::GateController, GateDecision, GatePacket, GatePolicy, GateReason, SpikePacket,
|
||||
};
|
||||
|
||||
fn create_controller() -> GateController {
|
||||
GateController::new(GatePolicy::default())
|
||||
}
|
||||
|
||||
// ============ Lambda-based decisions ============
|
||||
|
||||
#[test]
|
||||
fn test_lambda_above_min_allows() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100, // Well above min (30)
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::Allow);
|
||||
assert_eq!(decision.tier, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_below_min_quarantines() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 20, // Below min (30)
|
||||
lambda_prev: 100,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::QuarantineUpdates);
|
||||
assert_eq!(decision.reason, GateReason::LambdaBelowMin);
|
||||
assert_eq!(decision.tier, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_drop_flushes_kv() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 40,
|
||||
lambda_prev: 100, // 60% drop - exceeds default max ~37.5%
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::FlushKv);
|
||||
assert_eq!(decision.reason, GateReason::LambdaDroppedFast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_gradual_drop_allows() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 90,
|
||||
lambda_prev: 100, // 10% drop - within tolerance
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::Allow);
|
||||
}
|
||||
|
||||
// ============ Boundary-based decisions ============
|
||||
|
||||
#[test]
|
||||
fn test_boundary_spike_reduces_scope() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 30, // Above max (20)
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::ReduceScope);
|
||||
assert_eq!(decision.reason, GateReason::BoundarySpike);
|
||||
assert_eq!(decision.tier, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_concentration_reduces_scope() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 25000, // Above max (~62.5%)
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::ReduceScope);
|
||||
assert_eq!(decision.reason, GateReason::BoundaryConcentrationSpike);
|
||||
}
|
||||
|
||||
// ============ Partition-based decisions ============
|
||||
|
||||
#[test]
|
||||
fn test_partition_drift_reduces_scope() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 15, // Above max (10)
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::ReduceScope);
|
||||
assert_eq!(decision.reason, GateReason::PartitionDrift);
|
||||
}
|
||||
|
||||
// ============ Flag-based decisions ============
|
||||
|
||||
#[test]
|
||||
fn test_force_safe_flag() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: GatePacket::FLAG_FORCE_SAFE,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.decision, GateDecision::FreezeWrites);
|
||||
assert_eq!(decision.reason, GateReason::ForcedByFlag);
|
||||
assert_eq!(decision.tier, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_flag() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: GatePacket::FLAG_SKIP,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert!(decision.skip);
|
||||
assert_eq!(decision.tier, 3);
|
||||
}
|
||||
|
||||
// ============ Spike-based decisions ============
|
||||
|
||||
#[test]
|
||||
fn test_spike_inactive_skips() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let spike = SpikePacket {
|
||||
fired: 0, // Not fired
|
||||
rate_q15: 10000,
|
||||
novelty_q15: 15000,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, Some(&spike));
|
||||
assert!(decision.skip);
|
||||
assert_eq!(decision.tier, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spike_active_allows() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let spike = SpikePacket {
|
||||
fired: 1, // Fired
|
||||
rate_q15: 10000, // Normal rate
|
||||
novelty_q15: 15000,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, Some(&spike));
|
||||
assert!(!decision.skip);
|
||||
assert_eq!(decision.decision, GateDecision::Allow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spike_storm_freezes() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
boundary_concentration_q15: 8192,
|
||||
partition_count: 3,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let spike = SpikePacket {
|
||||
fired: 1,
|
||||
rate_q15: 30000, // Very high - exceeds max
|
||||
novelty_q15: 5000,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, Some(&spike));
|
||||
assert_eq!(decision.decision, GateDecision::FreezeWrites);
|
||||
assert_eq!(decision.reason, GateReason::SpikeStorm);
|
||||
assert_eq!(decision.tier, 2);
|
||||
}
|
||||
|
||||
// ============ Policy variants ============
|
||||
|
||||
#[test]
|
||||
fn test_conservative_policy() {
|
||||
let controller = GateController::new(GatePolicy::conservative());
|
||||
|
||||
// Same conditions that would pass with default policy
|
||||
let gate = GatePacket {
|
||||
lambda: 40, // Below conservative min (50) but above default (30)
|
||||
lambda_prev: 45,
|
||||
boundary_edges: 8, // Below conservative max (10)
|
||||
boundary_concentration_q15: 12000,
|
||||
partition_count: 4,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
// Conservative should intervene
|
||||
assert_eq!(decision.decision, GateDecision::QuarantineUpdates);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_permissive_policy() {
|
||||
let controller = GateController::new(GatePolicy::permissive());
|
||||
|
||||
// Conditions that would trigger intervention with default policy
|
||||
let gate = GatePacket {
|
||||
lambda: 25, // Above permissive min (20) but below default (30)
|
||||
lambda_prev: 35,
|
||||
boundary_edges: 40, // Above default max (20) but below permissive (50)
|
||||
boundary_concentration_q15: 20000,
|
||||
partition_count: 15, // Above default max (10) but below permissive (20)
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
// Permissive should allow
|
||||
assert_eq!(decision.decision, GateDecision::Allow);
|
||||
}
|
||||
|
||||
// ============ Tier decision properties ============
|
||||
|
||||
#[test]
|
||||
fn test_tier0_full_layers() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 5,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.tier, 0);
|
||||
assert!(!decision.skip);
|
||||
// In default controller, layers_to_run should be layers_normal (4)
|
||||
assert_eq!(decision.layers_to_run, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tier1_reduced_layers() {
|
||||
let controller = create_controller();
|
||||
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
boundary_edges: 30, // Triggers ReduceScope
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let decision = controller.evaluate(&gate, None);
|
||||
assert_eq!(decision.tier, 1);
|
||||
// Should have reduced layers
|
||||
assert!(decision.layers_to_run < 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kv_writes_permission() {
|
||||
let controller = create_controller();
|
||||
|
||||
// Allow case - KV writes enabled
|
||||
let gate_allow = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
..Default::default()
|
||||
};
|
||||
let decision = controller.evaluate(&gate_allow, None);
|
||||
assert!(decision.decision.allows_kv_writes());
|
||||
|
||||
// FreezeWrites case - KV writes disabled
|
||||
let gate_freeze = GatePacket {
|
||||
lambda: 100,
|
||||
flags: GatePacket::FLAG_FORCE_SAFE,
|
||||
..Default::default()
|
||||
};
|
||||
let decision = controller.evaluate(&gate_freeze, None);
|
||||
assert!(!decision.decision.allows_kv_writes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_external_writes_permission() {
|
||||
let controller = create_controller();
|
||||
|
||||
// Allow case - external writes enabled
|
||||
let gate_allow = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 95,
|
||||
..Default::default()
|
||||
};
|
||||
let decision = controller.evaluate(&gate_allow, None);
|
||||
assert!(decision.decision.allows_external_writes());
|
||||
|
||||
// ReduceScope case - external writes disabled
|
||||
let gate_reduce = GatePacket {
|
||||
lambda: 100,
|
||||
boundary_edges: 30,
|
||||
..Default::default()
|
||||
};
|
||||
let decision = controller.evaluate(&gate_reduce, None);
|
||||
assert!(!decision.decision.allows_external_writes());
|
||||
}
|
||||
|
||||
// ============ Lambda delta calculation ============
|
||||
|
||||
#[test]
|
||||
fn test_lambda_delta_positive() {
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 80,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(gate.lambda_delta(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_delta_negative() {
|
||||
let gate = GatePacket {
|
||||
lambda: 80,
|
||||
lambda_prev: 100,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(gate.lambda_delta(), -20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_ratio_calculation() {
|
||||
let gate = GatePacket {
|
||||
lambda: 50,
|
||||
lambda_prev: 100, // 50% drop
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ratio = gate.drop_ratio_q15();
|
||||
// Should be around 16384 (50% of 32768)
|
||||
assert!(ratio > 16000 && ratio < 17000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_ratio_no_drop() {
|
||||
let gate = GatePacket {
|
||||
lambda: 100,
|
||||
lambda_prev: 80, // Increase, not drop
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ratio = gate.drop_ratio_q15();
|
||||
assert_eq!(ratio, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user