oris_kernel/kernel/
determinism_guard.rs1use 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#[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 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 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 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
61pub 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
70pub 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
80pub 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}