use crate::llmosafe_kernel::{CognitiveStability, KernelError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SafetyDecision {
Proceed,
Warn(&'static str),
Escalate {
entropy: u16,
reason: EscalationReason,
},
Halt(KernelError),
}
impl SafetyDecision {
pub fn can_proceed(&self) -> bool {
matches!(self, Self::Proceed | Self::Warn(_))
}
pub fn must_halt(&self) -> bool {
matches!(self, Self::Halt(_))
}
pub fn severity(&self) -> u8 {
match self {
Self::Proceed => 0,
Self::Warn(_) => 1,
Self::Escalate { .. } => 2,
Self::Halt(_) => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EscalationReason {
EntropyApproachingLimit,
SurpriseElevated,
BiasDetected,
ResourcePressure,
AnomalyDetected,
Custom(&'static str),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PressureLevel {
Nominal,
Elevated,
Critical,
Emergency,
}
impl PressureLevel {
pub fn from_percentage(pct: u8) -> Self {
match pct {
0..=25 => Self::Nominal,
26..=50 => Self::Elevated,
51..=75 => Self::Critical,
76..=100 => Self::Emergency,
_ => Self::Emergency, }
}
pub fn requires_action(&self) -> bool {
matches!(self, Self::Critical | Self::Emergency)
}
}
impl From<u8> for PressureLevel {
fn from(pct: u8) -> Self {
Self::from_percentage(pct)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EscalationPolicy {
pub warn_entropy: u16,
pub escalate_entropy: u16,
pub halt_entropy: u16,
pub warn_surprise: u16,
pub escalate_surprise: u16,
pub bias_escalates: bool,
pub escalate_pressure: PressureLevel,
}
impl Default for EscalationPolicy {
fn default() -> Self {
Self {
warn_entropy: 600,
escalate_entropy: 800,
halt_entropy: 1000,
warn_surprise: 300,
escalate_surprise: 500,
bias_escalates: true,
escalate_pressure: PressureLevel::Critical,
}
}
}
impl EscalationPolicy {
pub fn new() -> Self {
Self::default()
}
pub const fn with_warn_entropy(mut self, threshold: u16) -> Self {
self.warn_entropy = threshold;
self
}
pub const fn with_escalate_entropy(mut self, threshold: u16) -> Self {
self.escalate_entropy = threshold;
self
}
pub const fn with_halt_entropy(mut self, threshold: u16) -> Self {
self.halt_entropy = threshold;
self
}
pub const fn with_bias_escalates(mut self, value: bool) -> Self {
self.bias_escalates = value;
self
}
pub fn decide(&self, entropy: u16, surprise: u16, has_bias: bool) -> SafetyDecision {
if has_bias && self.bias_escalates {
return SafetyDecision::Escalate {
entropy,
reason: EscalationReason::BiasDetected,
};
}
if entropy >= self.halt_entropy {
return SafetyDecision::Halt(KernelError::CognitiveInstability);
}
if entropy >= self.escalate_entropy {
return SafetyDecision::Escalate {
entropy,
reason: EscalationReason::EntropyApproachingLimit,
};
}
if entropy >= self.warn_entropy {
return SafetyDecision::Warn("entropy elevated");
}
if surprise >= self.escalate_surprise {
return SafetyDecision::Escalate {
entropy,
reason: EscalationReason::SurpriseElevated,
};
}
if surprise >= self.warn_surprise {
return SafetyDecision::Warn("surprise elevated");
}
SafetyDecision::Proceed
}
pub fn decide_with_pressure(
&self,
entropy: u16,
surprise: u16,
has_bias: bool,
pressure: PressureLevel,
) -> SafetyDecision {
if pressure >= self.escalate_pressure {
return SafetyDecision::Escalate {
entropy,
reason: EscalationReason::ResourcePressure,
};
}
self.decide(entropy, surprise, has_bias)
}
pub fn decide_from_stability(&self, stability: CognitiveStability) -> SafetyDecision {
match stability {
CognitiveStability::Stable => SafetyDecision::Proceed,
CognitiveStability::Pressure => SafetyDecision::Warn("cognitive pressure detected"),
CognitiveStability::Unstable => SafetyDecision::Halt(KernelError::CognitiveInstability),
}
}
}
#[cfg(feature = "std")]
pub struct SafetyContext {
max_entropy: u16,
max_surprise: u16,
any_bias: bool,
decision_count: usize,
policy: EscalationPolicy,
}
#[cfg(feature = "std")]
impl SafetyContext {
pub fn new(policy: EscalationPolicy) -> Self {
Self {
max_entropy: 0,
max_surprise: 0,
any_bias: false,
decision_count: 0,
policy,
}
}
pub fn default_context() -> Self {
Self::new(EscalationPolicy::default())
}
pub fn observe(&mut self, entropy: u16, surprise: u16, has_bias: bool) {
self.max_entropy = self.max_entropy.max(entropy);
self.max_surprise = self.max_surprise.max(surprise);
self.any_bias = self.any_bias || has_bias;
self.decision_count += 1;
}
pub fn finalize(&self) -> SafetyDecision {
self.policy.decide(self.max_entropy, self.max_surprise, self.any_bias)
}
pub fn reset(&mut self) {
self.max_entropy = 0;
self.max_surprise = 0;
self.any_bias = false;
self.decision_count = 0;
}
pub fn observation_count(&self) -> usize {
self.decision_count
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_safety_decision_severity() {
assert_eq!(SafetyDecision::Proceed.severity(), 0);
assert_eq!(SafetyDecision::Warn("test").severity(), 1);
assert_eq!(
SafetyDecision::Escalate {
entropy: 500,
reason: EscalationReason::BiasDetected
}
.severity(),
2
);
assert_eq!(SafetyDecision::Halt(KernelError::CognitiveInstability).severity(), 3);
}
#[test]
fn test_pressure_level_from_percentage() {
assert_eq!(PressureLevel::from_percentage(10), PressureLevel::Nominal);
assert_eq!(PressureLevel::from_percentage(30), PressureLevel::Elevated);
assert_eq!(PressureLevel::from_percentage(60), PressureLevel::Critical);
assert_eq!(PressureLevel::from_percentage(90), PressureLevel::Emergency);
assert_eq!(PressureLevel::from_percentage(255), PressureLevel::Emergency);
}
#[test]
fn test_escalation_policy_default_decide() {
let policy = EscalationPolicy::default();
let decision = policy.decide(400, 100, false);
assert!(matches!(decision, SafetyDecision::Proceed));
let decision = policy.decide(650, 100, false);
assert!(matches!(decision, SafetyDecision::Warn(_)));
let decision = policy.decide(850, 100, false);
assert!(matches!(decision, SafetyDecision::Escalate { .. }));
let decision = policy.decide(1100, 100, false);
assert!(matches!(decision, SafetyDecision::Halt(_)));
let decision = policy.decide(400, 100, true);
assert!(matches!(decision, SafetyDecision::Escalate { .. }));
}
#[test]
fn test_escalation_policy_builder() {
let policy = EscalationPolicy::new()
.with_warn_entropy(500)
.with_escalate_entropy(700)
.with_halt_entropy(900)
.with_bias_escalates(false);
let decision = policy.decide(400, 100, true);
assert!(matches!(decision, SafetyDecision::Proceed));
let decision = policy.decide(550, 100, false);
assert!(matches!(decision, SafetyDecision::Warn(_)));
}
#[test]
fn test_safety_context_accumulation() {
let mut ctx = SafetyContext::default_context();
ctx.observe(300, 100, false);
ctx.observe(500, 200, false);
ctx.observe(400, 250, false);
assert_eq!(ctx.observation_count(), 3);
let decision = ctx.finalize();
assert!(matches!(decision, SafetyDecision::Proceed));
}
#[test]
fn test_safety_context_with_bias() {
let mut ctx = SafetyContext::default_context();
ctx.observe(300, 100, false);
ctx.observe(400, 100, true); ctx.observe(350, 100, false);
let decision = ctx.finalize();
assert!(matches!(decision, SafetyDecision::Escalate { .. }));
}
#[test]
fn test_pressure_level_ordering() {
assert!(PressureLevel::Nominal < PressureLevel::Elevated);
assert!(PressureLevel::Elevated < PressureLevel::Critical);
assert!(PressureLevel::Critical < PressureLevel::Emergency);
}
#[test]
fn test_decide_from_stability() {
let policy = EscalationPolicy::default();
let decision = policy.decide_from_stability(CognitiveStability::Stable);
assert!(matches!(decision, SafetyDecision::Proceed));
let decision = policy.decide_from_stability(CognitiveStability::Pressure);
assert!(matches!(decision, SafetyDecision::Warn(_)));
let decision = policy.decide_from_stability(CognitiveStability::Unstable);
assert!(matches!(decision, SafetyDecision::Halt(_)));
}
#[test]
fn test_safety_context_reset() {
let mut ctx = SafetyContext::default_context();
ctx.observe(800, 500, true);
assert_eq!(ctx.observation_count(), 1);
ctx.reset();
assert_eq!(ctx.observation_count(), 0);
assert_eq!(ctx.max_entropy, 0);
assert!(!ctx.any_bias);
}
#[test]
fn test_escalation_policy_with_pressure() {
let policy = EscalationPolicy::default();
let decision = policy.decide_with_pressure(400, 100, false, PressureLevel::Nominal);
assert!(matches!(decision, SafetyDecision::Proceed));
let decision = policy.decide_with_pressure(400, 100, false, PressureLevel::Critical);
assert!(matches!(decision, SafetyDecision::Escalate { .. }));
}
}