Skip to main content

oris_kernel/kernel/
determinism_guard.rs

1//! Determinism guard: trap handlers for clock, randomness, and thread spawn in Replay/Verify.
2//!
3//! When the kernel is in [crate::kernel::kernel_mode::KernelMode::Replay] or
4//! [crate::kernel::kernel_mode::KernelMode::Verify], these traps immediately fail
5//! execution if code attempts clock access, hardware randomness, or uncontrolled
6//! thread spawning, so the same run guarantees an identical event stream.
7
8use sha2::{Digest, Sha256};
9
10use crate::kernel::event::{EventStore, SequencedEvent};
11use crate::kernel::identity::{RunId, Seq};
12use crate::kernel::kernel_mode::KernelMode;
13use crate::kernel::KernelError;
14
15/// Guard that enforces determinism in Replay/Verify mode.
16///
17/// Call [DeterminismGuard::check_clock_access], [DeterminismGuard::check_random_access],
18/// or [DeterminismGuard::check_spawn_access] before performing the corresponding
19/// operation; they return [Err] in Replay/Verify mode.
20#[derive(Clone, Debug)]
21pub struct DeterminismGuard {
22    pub mode: KernelMode,
23}
24
25impl DeterminismGuard {
26    pub fn new(mode: KernelMode) -> Self {
27        Self { mode }
28    }
29
30    /// Call before reading the system clock. Fails in Replay/Verify.
31    pub fn check_clock_access(&self) -> Result<(), KernelError> {
32        if self.mode.traps_nondeterminism() {
33            return Err(KernelError::Driver(
34                "clock access not allowed in Replay/Verify mode (determinism guard)".into(),
35            ));
36        }
37        Ok(())
38    }
39
40    /// Call before using hardware randomness. Fails in Replay/Verify.
41    pub fn check_random_access(&self) -> Result<(), KernelError> {
42        if self.mode.traps_nondeterminism() {
43            return Err(KernelError::Driver(
44                "hardware randomness not allowed in Replay/Verify mode (determinism guard)".into(),
45            ));
46        }
47        Ok(())
48    }
49
50    /// Call before spawning a new thread. Fails in Replay/Verify.
51    pub fn check_spawn_access(&self) -> Result<(), KernelError> {
52        if self.mode.traps_nondeterminism() {
53            return Err(KernelError::Driver(
54                "thread spawn not allowed in Replay/Verify mode (determinism guard)".into(),
55            ));
56        }
57        Ok(())
58    }
59}
60
61/// Computes a deterministic SHA-256 hash of the event stream for a run.
62///
63/// Used in Record mode to store the hash and in Verify mode to detect replay mismatches.
64pub fn event_stream_hash(store: &dyn EventStore, run_id: &RunId) -> Result<[u8; 32], KernelError> {
65    const FROM: Seq = 1;
66    let events = store.scan(run_id, FROM)?;
67    Ok(compute_event_stream_hash(&events))
68}
69
70/// Computes SHA-256 hash of the canonical serialized event sequence.
71pub fn compute_event_stream_hash(events: &[SequencedEvent]) -> [u8; 32] {
72    let mut hasher = Sha256::new();
73    for se in events {
74        let canonical = serde_json::to_string(&se).unwrap_or_default();
75        hasher.update(canonical.as_bytes());
76    }
77    hasher.finalize().into()
78}
79
80/// Verifies that the current event stream for `run_id` matches `expected_hash`.
81/// Returns [Err] if the hash does not match (replay mismatch).
82pub fn verify_event_stream_hash(
83    store: &dyn EventStore,
84    run_id: &RunId,
85    expected_hash: &[u8; 32],
86) -> Result<(), KernelError> {
87    let actual = event_stream_hash(store, run_id)?;
88    if actual != *expected_hash {
89        return Err(KernelError::Driver(format!(
90            "replay mismatch: event stream hash differs from expected (determinism verify)"
91        )));
92    }
93    Ok(())
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::kernel::event::Event;
100    use crate::kernel::event_store::InMemoryEventStore;
101
102    #[test]
103    fn guard_normal_allows_clock() {
104        let g = DeterminismGuard::new(KernelMode::Normal);
105        assert!(g.check_clock_access().is_ok());
106    }
107
108    #[test]
109    fn guard_replay_traps_clock() {
110        let g = DeterminismGuard::new(KernelMode::Replay);
111        let e = g.check_clock_access().unwrap_err();
112        assert!(e.to_string().contains("Replay"));
113    }
114
115    #[test]
116    fn guard_verify_traps_spawn() {
117        let g = DeterminismGuard::new(KernelMode::Verify);
118        let e = g.check_spawn_access().unwrap_err();
119        assert!(e.to_string().contains("spawn"));
120    }
121
122    #[test]
123    fn event_stream_hash_deterministic() {
124        let store = InMemoryEventStore::new();
125        let run_id: RunId = "r1".into();
126        store
127            .append(
128                &run_id,
129                &[
130                    Event::StateUpdated {
131                        step_id: Some("a".into()),
132                        payload: serde_json::json!([1]),
133                    },
134                    Event::Completed,
135                ],
136            )
137            .unwrap();
138        let h1 = event_stream_hash(&store, &run_id).unwrap();
139        let h2 = event_stream_hash(&store, &run_id).unwrap();
140        assert_eq!(h1, h2, "same stream must yield same hash");
141    }
142
143    #[test]
144    fn verify_event_stream_hash_mismatch_fails() {
145        let store = InMemoryEventStore::new();
146        let run_id: RunId = "r2".into();
147        store.append(&run_id, &[Event::Completed]).unwrap();
148        let expected = [0u8; 32];
149        let result = verify_event_stream_hash(&store, &run_id, &expected);
150        assert!(result.is_err());
151    }
152}