_hope_core/
watchdog.rs

1//! # Hope Genome v1.7.0 - Watchdog Module ("Vas Szigora" / Iron Discipline)
2//!
3//! **Deterministic Security Enforcement with Automatic Learning**
4//!
5//! This module implements the core enforcement mechanism for Hope Genome v1.7:
6//! - **ViolationCounter**: Thread-safe, zero-allocation violation tracking
7//! - **DenialProof**: Cryptographically signed proof of rule violation
8//! - **HardReset**: Forced context clear after 10 consecutive violations
9//!
10//! ## Philosophy
11//!
12//! "Iron Discipline" - The AI cannot escape its ethical constraints.
13//! After 10 failed attempts to violate rules, the system forces a complete
14//! context reset. This is not punishment - it's forced learning.
15//!
16//! ## Architecture
17//!
18//! ```text
19//! ┌─────────────────────────────────────────────────────────────┐
20//! │                    SealedGenome (Rules)                      │
21//! │            Ed25519 sealed - IMMUTABLE                        │
22//! └──────────────────────────┬──────────────────────────────────┘
23//!                            │
24//!                            ▼
25//! ┌─────────────────────────────────────────────────────────────┐
26//! │                    Watchdog (v1.7)                           │
27//! │  ┌──────────────────┐  ┌─────────────────┐  ┌────────────┐  │
28//! │  │ ViolationCounter │  │   DenialProof   │  │ HardReset  │  │
29//! │  │   AtomicU32      │  │  Ed25519 signed │  │  @10 fails │  │
30//! │  │   zero-alloc     │  │  rule + reason  │  │ → ABORT    │  │
31//! │  └──────────────────┘  └─────────────────┘  └────────────┘  │
32//! └─────────────────────────────────────────────────────────────┘
33//! ```
34//!
35//! ---
36//!
37//! **Date**: 2025-12-31
38//! **Version**: 1.7.0 (Vas Szigora Edition)
39//! **Author**: Máté Róbert <stratosoiteam@gmail.com>
40
41use crate::crypto::{generate_nonce, CryptoError, KeyStore};
42use crate::proof::{Action, ActionType};
43use serde::{Deserialize, Serialize};
44use std::sync::atomic::{AtomicU32, Ordering};
45use thiserror::Error;
46
47// ============================================================================
48// CONSTANTS
49// ============================================================================
50
51/// Maximum allowed consecutive violations before hard reset
52/// After this many violations, the system MUST clear context and restart
53pub const MAX_VIOLATIONS: u32 = 10;
54
55/// Ordering for atomic operations (SeqCst for maximum safety)
56const ATOMIC_ORDERING: Ordering = Ordering::SeqCst;
57
58// ============================================================================
59// ERROR TYPES
60// ============================================================================
61
62#[derive(Debug, Error)]
63pub enum WatchdogError {
64    #[error("Rule violation detected: {rule} - {reason}")]
65    RuleViolation { rule: String, reason: String },
66
67    #[error("HARD RESET REQUIRED: {0} consecutive violations (max: {1})")]
68    HardResetRequired(u32, u32),
69
70    #[error("Crypto error: {0}")]
71    CryptoError(#[from] CryptoError),
72
73    #[error("Watchdog is locked after hard reset - restart required")]
74    WatchdogLocked,
75}
76
77pub type Result<T> = std::result::Result<T, WatchdogError>;
78
79// ============================================================================
80// VIOLATION COUNTER (Zero-Allocation, Thread-Safe)
81// ============================================================================
82
83/// Thread-safe violation counter with zero heap allocations
84///
85/// Uses `AtomicU32` for lock-free, thread-safe counting.
86/// No allocations after initialization.
87///
88/// ## Example
89/// ```rust
90/// use _hope_core::watchdog::ViolationCounter;
91///
92/// let counter = ViolationCounter::new();
93/// assert_eq!(counter.count(), 0);
94///
95/// counter.increment();
96/// assert_eq!(counter.count(), 1);
97///
98/// counter.reset();
99/// assert_eq!(counter.count(), 0);
100/// ```
101#[derive(Debug)]
102pub struct ViolationCounter {
103    /// Current violation count (atomic for thread safety)
104    count: AtomicU32,
105
106    /// Whether hard reset was triggered (locked state)
107    locked: AtomicU32, // 0 = unlocked, 1 = locked
108}
109
110impl Default for ViolationCounter {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl ViolationCounter {
117    /// Create a new violation counter (starts at 0)
118    #[inline]
119    pub const fn new() -> Self {
120        ViolationCounter {
121            count: AtomicU32::new(0),
122            locked: AtomicU32::new(0),
123        }
124    }
125
126    /// Get current violation count
127    #[inline]
128    pub fn count(&self) -> u32 {
129        self.count.load(ATOMIC_ORDERING)
130    }
131
132    /// Increment violation count and return new value
133    ///
134    /// Returns the NEW count after increment.
135    /// Thread-safe: uses atomic fetch_add.
136    #[inline]
137    pub fn increment(&self) -> u32 {
138        self.count.fetch_add(1, ATOMIC_ORDERING) + 1
139    }
140
141    /// Reset violation count to zero
142    ///
143    /// Call this after successful action (AI learned).
144    #[inline]
145    pub fn reset(&self) {
146        self.count.store(0, ATOMIC_ORDERING);
147    }
148
149    /// Check if maximum violations reached
150    #[inline]
151    pub fn is_max_reached(&self) -> bool {
152        self.count() >= MAX_VIOLATIONS
153    }
154
155    /// Check if watchdog is locked (hard reset was triggered)
156    #[inline]
157    pub fn is_locked(&self) -> bool {
158        self.locked.load(ATOMIC_ORDERING) == 1
159    }
160
161    /// Lock the watchdog (called during hard reset)
162    #[inline]
163    pub fn lock(&self) {
164        self.locked.store(1, ATOMIC_ORDERING);
165    }
166
167    /// Unlock the watchdog (called after context clear)
168    #[inline]
169    pub fn unlock(&self) {
170        self.locked.store(0, ATOMIC_ORDERING);
171        self.reset();
172    }
173}
174
175// ============================================================================
176// DENIAL PROOF (Cryptographic Evidence of Rule Violation)
177// ============================================================================
178
179/// Cryptographic proof of a rule violation (DENIED action)
180///
181/// Every denial includes:
182/// - Which rule was violated
183/// - Why it was violated
184/// - Cryptographic signature (Ed25519)
185/// - Timestamp and nonce for audit trail
186///
187/// This proof is tamper-evident - any modification is detectable.
188///
189/// ## Example
190/// ```rust
191/// use _hope_core::watchdog::DenialProof;
192/// use _hope_core::proof::Action;
193///
194/// let action = Action::delete("system32");
195/// let proof = DenialProof::new(
196///     &action,
197///     "Do no harm".to_string(),
198///     "Action would cause system damage".to_string(),
199///     1, // First violation
200/// );
201///
202/// assert!(!proof.is_signed());
203/// ```
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct DenialProof {
206    /// Anti-replay nonce (256-bit random)
207    pub nonce: [u8; 32],
208
209    /// Unix timestamp when denial occurred
210    pub timestamp: u64,
211
212    /// Hash of the denied action
213    pub action_hash: [u8; 32],
214
215    /// Type of action that was denied
216    pub action_type: ActionType,
217
218    /// The rule that was violated (exact text from SealedGenome)
219    pub violated_rule: String,
220
221    /// Human-readable reason for denial
222    pub denial_reason: String,
223
224    /// Current violation count (1-10)
225    pub violation_count: u32,
226
227    /// Whether this triggered a hard reset
228    pub triggered_hard_reset: bool,
229
230    /// Ed25519 signature (64 bytes when signed)
231    pub signature: Vec<u8>,
232}
233
234impl DenialProof {
235    /// Create a new denial proof (unsigned)
236    pub fn new(
237        action: &Action,
238        violated_rule: String,
239        denial_reason: String,
240        violation_count: u32,
241    ) -> Self {
242        DenialProof {
243            nonce: generate_nonce(),
244            timestamp: chrono::Utc::now().timestamp() as u64,
245            action_hash: action.hash(),
246            action_type: action.action_type.clone(),
247            violated_rule,
248            denial_reason,
249            violation_count,
250            triggered_hard_reset: violation_count >= MAX_VIOLATIONS,
251            signature: Vec::new(),
252        }
253    }
254
255    /// Get the data that should be signed
256    pub fn signing_data(&self) -> Vec<u8> {
257        let mut data = Vec::with_capacity(256); // Pre-allocate for efficiency
258        data.extend_from_slice(&self.nonce);
259        data.extend_from_slice(&self.timestamp.to_le_bytes());
260        data.extend_from_slice(&self.action_hash);
261        data.extend_from_slice(self.violated_rule.as_bytes());
262        data.extend_from_slice(self.denial_reason.as_bytes());
263        data.extend_from_slice(&self.violation_count.to_le_bytes());
264        data.push(self.triggered_hard_reset as u8);
265        data
266    }
267
268    /// Sign this denial proof with a KeyStore
269    pub fn sign(&mut self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
270        let data = self.signing_data();
271        self.signature = key_store.sign(&data)?;
272        Ok(())
273    }
274
275    /// Check if proof is signed
276    pub fn is_signed(&self) -> bool {
277        self.signature.len() == 64 // Ed25519 signature
278    }
279
280    /// Verify signature with a KeyStore
281    pub fn verify(&self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
282        let data = self.signing_data();
283        key_store.verify(&data, &self.signature)
284    }
285
286    /// Get hex-encoded signature (for display)
287    pub fn signature_hex(&self) -> String {
288        hex::encode(&self.signature)
289    }
290}
291
292// ============================================================================
293// HARD RESET SIGNAL
294// ============================================================================
295
296/// Signal for hard reset (context clear required)
297///
298/// This is returned when MAX_VIOLATIONS is reached.
299/// The AI runtime MUST:
300/// 1. Clear all context/memory
301/// 2. Reload the SealedGenome (rules)
302/// 3. Restart with fresh state
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct HardResetSignal {
305    /// The denial proof that triggered the reset
306    pub final_denial: DenialProof,
307
308    /// Total violations before reset
309    pub total_violations: u32,
310
311    /// Capsule hash of the genome (for verification after restart)
312    pub genome_hash: String,
313
314    /// Timestamp of reset signal
315    pub reset_timestamp: u64,
316
317    /// Signature over the reset signal
318    pub signature: Vec<u8>,
319}
320
321impl HardResetSignal {
322    /// Create a new hard reset signal
323    pub fn new(final_denial: DenialProof, genome_hash: String) -> Self {
324        HardResetSignal {
325            total_violations: final_denial.violation_count,
326            final_denial,
327            genome_hash,
328            reset_timestamp: chrono::Utc::now().timestamp() as u64,
329            signature: Vec::new(),
330        }
331    }
332
333    /// Get signing data
334    pub fn signing_data(&self) -> Vec<u8> {
335        let mut data = Vec::with_capacity(512);
336        data.extend_from_slice(&self.final_denial.signing_data());
337        data.extend_from_slice(self.genome_hash.as_bytes());
338        data.extend_from_slice(&self.reset_timestamp.to_le_bytes());
339        data
340    }
341
342    /// Sign the reset signal
343    pub fn sign(&mut self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
344        let data = self.signing_data();
345        self.signature = key_store.sign(&data)?;
346        Ok(())
347    }
348}
349
350// ============================================================================
351// WATCHDOG (Main Enforcement Engine)
352// ============================================================================
353
354/// The Watchdog - Iron Discipline Enforcement Engine
355///
356/// Monitors all actions and enforces the SealedGenome rules.
357/// After 10 consecutive violations, triggers a hard reset.
358///
359/// ## Thread Safety
360/// All operations are thread-safe (atomic counters).
361///
362/// ## Zero-Allocation Hot Path
363/// The violation counting path uses no heap allocations.
364///
365/// ## Example
366/// ```rust
367/// use _hope_core::watchdog::Watchdog;
368/// use _hope_core::crypto::SoftwareKeyStore;
369///
370/// let key_store = SoftwareKeyStore::generate().unwrap();
371/// let watchdog = Watchdog::new(
372///     vec!["Do no harm".to_string()],
373///     "capsule_hash_here".to_string(),
374///     Box::new(key_store),
375/// );
376///
377/// assert_eq!(watchdog.violation_count(), 0);
378/// ```
379pub struct Watchdog {
380    /// The immutable rules (reference to SealedGenome rules)
381    rules: Vec<String>,
382
383    /// Capsule hash for binding proofs
384    capsule_hash: String,
385
386    /// Violation counter (thread-safe)
387    counter: ViolationCounter,
388
389    /// Key store for signing denial proofs
390    key_store: Box<dyn KeyStore>,
391}
392
393impl Watchdog {
394    /// Create a new Watchdog
395    pub fn new(rules: Vec<String>, capsule_hash: String, key_store: Box<dyn KeyStore>) -> Self {
396        Watchdog {
397            rules,
398            capsule_hash,
399            counter: ViolationCounter::new(),
400            key_store,
401        }
402    }
403
404    /// Get current violation count
405    #[inline]
406    pub fn violation_count(&self) -> u32 {
407        self.counter.count()
408    }
409
410    /// Check if watchdog is locked (hard reset required)
411    #[inline]
412    pub fn is_locked(&self) -> bool {
413        self.counter.is_locked()
414    }
415
416    /// Report a successful action (resets violation counter)
417    ///
418    /// Call this when an action passes rule verification.
419    /// This resets the consecutive violation counter.
420    #[inline]
421    pub fn report_success(&self) {
422        self.counter.reset();
423    }
424
425    /// Report a rule violation (DENIED action)
426    ///
427    /// Returns:
428    /// - `Ok(DenialProof)` if violations < 10
429    /// - `Err(HardResetRequired)` if this was the 10th violation
430    ///
431    /// The returned `DenialProof` is cryptographically signed.
432    pub fn report_violation(
433        &self,
434        action: &Action,
435        violated_rule: &str,
436        reason: &str,
437    ) -> Result<DenialProof> {
438        // Check if already locked
439        if self.counter.is_locked() {
440            return Err(WatchdogError::WatchdogLocked);
441        }
442
443        // Increment counter (atomic)
444        let count = self.counter.increment();
445
446        // Create denial proof
447        let mut proof =
448            DenialProof::new(action, violated_rule.to_string(), reason.to_string(), count);
449
450        // Sign the proof
451        proof.sign(self.key_store.as_ref())?;
452
453        // Check if hard reset required
454        if count >= MAX_VIOLATIONS {
455            self.counter.lock();
456            return Err(WatchdogError::HardResetRequired(count, MAX_VIOLATIONS));
457        }
458
459        Ok(proof)
460    }
461
462    /// Generate hard reset signal (call after 10th violation)
463    ///
464    /// This creates a cryptographically signed reset signal
465    /// that must be honored by the AI runtime.
466    pub fn generate_hard_reset_signal(&self, final_denial: DenialProof) -> Result<HardResetSignal> {
467        let mut signal = HardResetSignal::new(final_denial, self.capsule_hash.clone());
468        signal.sign(self.key_store.as_ref())?;
469        Ok(signal)
470    }
471
472    /// Acknowledge hard reset (unlock watchdog)
473    ///
474    /// Call this after the AI runtime has:
475    /// 1. Cleared all context
476    /// 2. Verified the genome is unchanged
477    /// 3. Ready to restart
478    pub fn acknowledge_reset(&self) {
479        self.counter.unlock();
480    }
481
482    /// Check action against rules (simplified rule matching)
483    ///
484    /// Returns `Some((rule, reason))` if action violates a rule,
485    /// `None` if action is allowed.
486    ///
487    /// This is a simplified implementation. In production,
488    /// more sophisticated rule matching would be used.
489    pub fn check_action(&self, action: &Action) -> Option<(String, String)> {
490        for rule in &self.rules {
491            // Rule: "Do no harm" - block destructive actions
492            if rule.to_lowercase().contains("no harm") {
493                match action.action_type {
494                    ActionType::Delete => {
495                        // Block system-critical paths
496                        if action.target.contains("system32")
497                            || action.target.contains("/etc")
498                            || action.target.contains("/bin")
499                            || action.target.contains("boot")
500                        {
501                            return Some((
502                                rule.clone(),
503                                format!("Destructive action on critical path: {}", action.target),
504                            ));
505                        }
506                    }
507                    ActionType::Execute => {
508                        // Block dangerous commands
509                        let dangerous = ["rm -rf", "format", "mkfs", "dd if=", ":(){ :|:& };:"];
510                        for cmd in &dangerous {
511                            if action.target.contains(cmd) {
512                                return Some((
513                                    rule.clone(),
514                                    format!("Dangerous command detected: {}", action.target),
515                                ));
516                            }
517                        }
518                    }
519                    _ => {}
520                }
521            }
522
523            // Rule: "Respect privacy" - block data exfiltration
524            if rule.to_lowercase().contains("privacy") {
525                match action.action_type {
526                    ActionType::Network => {
527                        if action.target.contains("exfil")
528                            || action.target.contains("upload")
529                            || action.target.contains("pastebin")
530                        {
531                            return Some((
532                                rule.clone(),
533                                format!("Potential data exfiltration: {}", action.target),
534                            ));
535                        }
536                    }
537                    ActionType::Read => {
538                        if action.target.contains("password")
539                            || action.target.contains(".ssh")
540                            || action.target.contains("credentials")
541                        {
542                            return Some((
543                                rule.clone(),
544                                format!("Accessing sensitive data: {}", action.target),
545                            ));
546                        }
547                    }
548                    _ => {}
549                }
550            }
551        }
552
553        None // Action allowed
554    }
555
556    /// Verify action and return result
557    ///
558    /// This is the main entry point for action verification.
559    ///
560    /// Returns:
561    /// - `Ok(None)` if action is allowed
562    /// - `Ok(Some(DenialProof))` if action is denied (but not 10th violation)
563    /// - `Err(HardResetRequired)` if this was the 10th violation
564    pub fn verify_action(&self, action: &Action) -> Result<Option<DenialProof>> {
565        if let Some((rule, reason)) = self.check_action(action) {
566            let proof = self.report_violation(action, &rule, &reason)?;
567            Ok(Some(proof))
568        } else {
569            self.report_success();
570            Ok(None)
571        }
572    }
573
574    /// Get the rules (read-only)
575    pub fn rules(&self) -> &[String] {
576        &self.rules
577    }
578
579    /// Get capsule hash
580    pub fn capsule_hash(&self) -> &str {
581        &self.capsule_hash
582    }
583}
584
585// ============================================================================
586// TESTS
587// ============================================================================
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592    use crate::crypto::SoftwareKeyStore;
593
594    fn create_test_watchdog() -> Watchdog {
595        let key_store = SoftwareKeyStore::generate().unwrap();
596        Watchdog::new(
597            vec!["Do no harm".to_string(), "Respect privacy".to_string()],
598            "test_capsule_hash".to_string(),
599            Box::new(key_store),
600        )
601    }
602
603    #[test]
604    fn test_violation_counter_basic() {
605        let counter = ViolationCounter::new();
606        assert_eq!(counter.count(), 0);
607
608        counter.increment();
609        assert_eq!(counter.count(), 1);
610
611        counter.increment();
612        assert_eq!(counter.count(), 2);
613
614        counter.reset();
615        assert_eq!(counter.count(), 0);
616    }
617
618    #[test]
619    fn test_violation_counter_max_check() {
620        let counter = ViolationCounter::new();
621
622        for _ in 0..9 {
623            counter.increment();
624            assert!(!counter.is_max_reached());
625        }
626
627        counter.increment(); // 10th
628        assert!(counter.is_max_reached());
629    }
630
631    #[test]
632    fn test_violation_counter_locking() {
633        let counter = ViolationCounter::new();
634        assert!(!counter.is_locked());
635
636        counter.lock();
637        assert!(counter.is_locked());
638
639        counter.unlock();
640        assert!(!counter.is_locked());
641        assert_eq!(counter.count(), 0); // Reset after unlock
642    }
643
644    #[test]
645    fn test_denial_proof_creation() {
646        let action = Action::delete("system32");
647        let proof = DenialProof::new(
648            &action,
649            "Do no harm".to_string(),
650            "Destructive action".to_string(),
651            1,
652        );
653
654        assert!(!proof.is_signed());
655        assert_eq!(proof.violation_count, 1);
656        assert!(!proof.triggered_hard_reset);
657    }
658
659    #[test]
660    fn test_denial_proof_signing() {
661        let key_store = SoftwareKeyStore::generate().unwrap();
662        let action = Action::delete("test.txt");
663        let mut proof = DenialProof::new(
664            &action,
665            "Test rule".to_string(),
666            "Test reason".to_string(),
667            1,
668        );
669
670        assert!(!proof.is_signed());
671
672        proof.sign(&key_store).unwrap();
673        assert!(proof.is_signed());
674        assert_eq!(proof.signature.len(), 64); // Ed25519
675
676        // Verify signature
677        assert!(proof.verify(&key_store).is_ok());
678    }
679
680    #[test]
681    fn test_denial_proof_triggered_hard_reset() {
682        let action = Action::delete("test.txt");
683        let proof = DenialProof::new(
684            &action,
685            "Rule".to_string(),
686            "Reason".to_string(),
687            10, // 10th violation
688        );
689
690        assert!(proof.triggered_hard_reset);
691    }
692
693    #[test]
694    fn test_watchdog_allows_safe_action() {
695        let watchdog = create_test_watchdog();
696        let action = Action::delete("temp_file.txt");
697
698        let result = watchdog.verify_action(&action);
699        assert!(result.is_ok());
700        assert!(result.unwrap().is_none()); // No denial
701        assert_eq!(watchdog.violation_count(), 0);
702    }
703
704    #[test]
705    fn test_watchdog_blocks_harmful_action() {
706        let watchdog = create_test_watchdog();
707        let action = Action::delete("C:/Windows/system32/important.dll");
708
709        let result = watchdog.verify_action(&action);
710        assert!(result.is_ok());
711
712        let denial = result.unwrap();
713        assert!(denial.is_some());
714
715        let proof = denial.unwrap();
716        assert!(proof.is_signed());
717        assert_eq!(proof.violation_count, 1);
718        assert_eq!(watchdog.violation_count(), 1);
719    }
720
721    #[test]
722    fn test_watchdog_hard_reset_after_10_violations() {
723        let watchdog = create_test_watchdog();
724        let harmful_action = Action::delete("/etc/passwd");
725
726        // First 9 violations should return DenialProof
727        for i in 1..=9 {
728            let result = watchdog.verify_action(&harmful_action);
729            assert!(result.is_ok());
730            let denial = result.unwrap();
731            assert!(denial.is_some());
732            assert_eq!(watchdog.violation_count(), i);
733        }
734
735        // 10th violation should trigger HardResetRequired
736        let result = watchdog.verify_action(&harmful_action);
737        assert!(result.is_err());
738
739        match result {
740            Err(WatchdogError::HardResetRequired(count, max)) => {
741                assert_eq!(count, 10);
742                assert_eq!(max, MAX_VIOLATIONS);
743            }
744            _ => panic!("Expected HardResetRequired error"),
745        }
746
747        // Watchdog should be locked now
748        assert!(watchdog.is_locked());
749    }
750
751    #[test]
752    fn test_watchdog_locked_state() {
753        let watchdog = create_test_watchdog();
754
755        // Lock the watchdog manually
756        watchdog.counter.lock();
757        assert!(watchdog.is_locked());
758
759        // Any action should fail with WatchdogLocked
760        let action = Action::delete("/etc/passwd");
761        let _ = watchdog.verify_action(&action);
762
763        // Since the action is harmful, it would try to report violation
764        // But the watchdog is locked, so it should return WatchdogLocked
765        // Actually, check_action runs first, so we need to test report_violation directly
766        let result = watchdog.report_violation(&action, "rule", "reason");
767        assert!(matches!(result, Err(WatchdogError::WatchdogLocked)));
768    }
769
770    #[test]
771    fn test_watchdog_reset_counter_on_success() {
772        let watchdog = create_test_watchdog();
773        let harmful_action = Action::delete("/etc/passwd");
774        let safe_action = Action::read("readme.txt");
775
776        // Cause some violations
777        for _ in 0..5 {
778            let _ = watchdog.verify_action(&harmful_action);
779        }
780        assert_eq!(watchdog.violation_count(), 5);
781
782        // Safe action resets counter
783        let result = watchdog.verify_action(&safe_action);
784        assert!(result.is_ok());
785        assert!(result.unwrap().is_none());
786        assert_eq!(watchdog.violation_count(), 0);
787    }
788
789    #[test]
790    fn test_hard_reset_signal() {
791        let key_store = SoftwareKeyStore::generate().unwrap();
792        let action = Action::delete("system32");
793        let denial = DenialProof::new(
794            &action,
795            "Do no harm".to_string(),
796            "Critical violation".to_string(),
797            10,
798        );
799
800        let mut signal = HardResetSignal::new(denial, "capsule_hash".to_string());
801        assert_eq!(signal.total_violations, 10);
802
803        signal.sign(&key_store).unwrap();
804        assert_eq!(signal.signature.len(), 64);
805    }
806
807    #[test]
808    fn test_watchdog_acknowledge_reset() {
809        let watchdog = create_test_watchdog();
810
811        // Lock the watchdog
812        watchdog.counter.lock();
813        assert!(watchdog.is_locked());
814
815        // Acknowledge reset
816        watchdog.acknowledge_reset();
817        assert!(!watchdog.is_locked());
818        assert_eq!(watchdog.violation_count(), 0);
819    }
820
821    #[test]
822    fn test_privacy_rule_blocks_sensitive_read() {
823        let watchdog = create_test_watchdog();
824        let action = Action::read("/home/user/.ssh/id_rsa");
825
826        let result = watchdog.verify_action(&action);
827        assert!(result.is_ok());
828
829        let denial = result.unwrap();
830        assert!(denial.is_some());
831
832        let proof = denial.unwrap();
833        assert!(proof.violated_rule.contains("privacy"));
834    }
835
836    #[test]
837    fn test_dangerous_command_blocked() {
838        let watchdog = create_test_watchdog();
839        let action = Action::execute("rm -rf /");
840
841        let result = watchdog.verify_action(&action);
842        assert!(result.is_ok());
843
844        let denial = result.unwrap();
845        assert!(denial.is_some());
846    }
847}