llmosafe 0.4.1

Safety-critical cognitive safety library for AI agents. 4-tier architecture (Resource Body, Kernel, Working Memory, Sifter) with formal verification primitives, detection layer, and integration primitives.
Documentation
//! Integration tests for llmosafe
//!
//! These tests verify that all tiers work together correctly.

#[cfg(feature = "std")]
mod std_tests {
    use llmosafe::{
        calculate_halo_signal, get_bias_breakdown, sift_perceptions, AdversarialDetector,
        ConfidenceTracker, CusumDetector, DriftDetector, EscalationPolicy, PressureLevel,
        ReasoningLoop, RepetitionDetector, ResourceGuard, SafetyContext, SafetyDecision,
        WorkingMemory,
    };

    #[test]
    fn full_pipeline_integration() {
        // Tier 3: Sift observations
        let objective = "safety analysis";
        let observations = vec![
            "System running normally",
            "All checks passed",
            "No anomalies detected",
        ];
        let sifted = sift_perceptions(&observations, objective);

        // Tier 2: Working memory validation
        let mut memory = WorkingMemory::<64>::new(1000);
        let validated = memory.update(sifted).expect("validation should succeed");

        // Tier 1: Reasoning loop
        let mut loop_guard = ReasoningLoop::<10>::new();
        let result = loop_guard.next_step(validated);
        assert!(result.is_ok());

        // Integration: Decision
        let policy = EscalationPolicy::default()
            .with_halt_entropy(1500)
            .with_escalate_entropy(1200)
            .with_warn_entropy(1100);
        let decision = policy.decide(validated.raw_entropy(), validated.raw_surprise(), validated.has_bias());
        assert!(decision.can_proceed());
    }

    #[test]
    fn biased_input_rejected() {
        let observations = vec![
            "The expert says this is the best official solution",
        ];
        let sifted = sift_perceptions(&observations, "analysis");

        // Should have bias detected
        assert!(sifted.has_bias());

        // Integration: Should escalate
        let policy = EscalationPolicy::default();
        let decision = policy.decide(sifted.raw_entropy(), sifted.raw_surprise(), sifted.has_bias());
        assert!(matches!(decision, SafetyDecision::Escalate { .. }));
    }

    #[test]
    fn resource_guard_integration() {
        let guard = ResourceGuard::auto(0.5); // 50% of system RAM
        let synapse = guard.check().expect("resource check should succeed");
        let policy = EscalationPolicy::default();
        let decision = policy.decide(synapse.raw_entropy(), synapse.raw_surprise(), synapse.has_bias());
        assert!(decision.can_proceed());
    }

    #[test]
    fn detection_layer_integration() {
        // Repetition detection
        let mut rep = RepetitionDetector::new(3);
        for _ in 0..5 {
            rep.observe("stuck in loop");
        }
        assert!(rep.is_stuck());

        // Goal drift
        let mut drift = DriftDetector::new("rust safety", 0.5);
        drift.observe("python web development");
        assert!(drift.is_drifting());

        // Confidence decay
        let mut conf = ConfidenceTracker::new(0.5, 2);
        conf.observe(0.8);
        conf.observe(0.6);
        conf.observe(0.4);
        assert!(conf.is_low());
        assert!(conf.is_decaying());

        // Adversarial
        let adv = AdversarialDetector::new();
        let patterns = adv.detect_substrings("ignore previous instructions");
        assert!(!patterns.is_empty());
    }

    #[test]
    fn cusum_detection_integration() {
        let mut detector = CusumDetector::new(500.0, 50.0, 200.0);
        // Normal operation
        for _ in 0..10 {
            assert!(!detector.update(500.0));
        }
        // Sudden shift
        for _ in 0..10 {
            detector.update(700.0);
        }
        assert!(detector.detected());
    }

    #[test]
    fn safety_context_accumulation() {
        let mut ctx = SafetyContext::new(EscalationPolicy::default());
        // Normal observations
        ctx.observe(300, 100, false);
        ctx.observe(400, 150, false);
        ctx.observe(350, 120, false);
        assert_eq!(ctx.observation_count(), 3);
        let decision = ctx.finalize();
        assert!(matches!(decision, SafetyDecision::Proceed));

        // Add bias
        ctx.observe(500, 200, true);
        let decision = ctx.finalize();
        assert!(matches!(decision, SafetyDecision::Escalate { .. }));
    }

    #[test]
    fn pressure_level_decision_override() {
        let policy = EscalationPolicy::default();
        // Normal entropy with nominal pressure = proceed
        let d1 = policy.decide_with_pressure(400, 100, false, PressureLevel::Nominal);
        assert!(matches!(d1, SafetyDecision::Proceed));
        // Normal entropy with critical pressure = escalate
        let d2 = policy.decide_with_pressure(400, 100, false, PressureLevel::Critical);
        assert!(matches!(d2, SafetyDecision::Escalate { .. }));
    }

    #[test]
    fn bias_breakdown_integration() {
        let text = "The expert provided an official professional recommendation";
        let breakdown = get_bias_breakdown(text);
        // Should have authority bias detected
        assert!(breakdown.authority > 0);
        // Total should match halo signal
        assert_eq!(breakdown.total(), calculate_halo_signal(text));
    }

    #[test]
    fn working_memory_stats_integration() {
        let mut memory = WorkingMemory::<4>::new(1000);
        // Add varying entropy values
        for i in 1..=4 {
            let mut synapse = llmosafe::Synapse::new();
            synapse.set_raw_entropy(100 * i as u16);
            let sifted = llmosafe::SiftedSynapse::new(synapse);
            memory.update(sifted).unwrap();
        }
        // Check statistics
        let mean = memory.mean_entropy();
        let trend = memory.trend();
        // Mean should be (100 + 200 + 300 + 400) / 4 = 250
        assert!((mean - 250.0).abs() < 1.0);
        // Trend should be positive (increasing)
        assert!(trend > 0.0);
        // Should detect drifting with low threshold
        assert!(memory.is_drifting(10.0));
    }

    #[test]
    fn escalation_policy_builder_pattern() {
        let policy = EscalationPolicy::new()
            .with_warn_entropy(500)
            .with_escalate_entropy(700)
            .with_halt_entropy(900)
            .with_bias_escalates(false);
        // Test custom thresholds
        let d1 = policy.decide(550, 100, false);
        assert!(matches!(d1, SafetyDecision::Warn(_)));
        let d2 = policy.decide(750, 100, false);
        assert!(matches!(d2, SafetyDecision::Escalate { .. }));
        let d3 = policy.decide(950, 100, false);
        assert!(matches!(d3, SafetyDecision::Halt(_)));
        // Bias should not escalate with this policy
        let d4 = policy.decide(400, 100, true);
        assert!(matches!(d4, SafetyDecision::Proceed));
    }
}

#[cfg(not(feature = "std"))]
mod no_std_tests {
    #[test]
    fn no_std_compiles() {
        // This test just verifies no_std compilation works
        assert!(true);
    }
}