1#![allow(unused)]
2#[cfg(not(target_arch = "wasm32"))]
3use std::fs::{File, OpenOptions};
4use std::io::{self, Read, Seek, SeekFrom, Write};
5use std::path::Path;
6
7use ed25519_dalek::{Signature, SigningKey};
8
9use crate::agency::{scrub_quin_volatile, sign_graph_mutation, stamp_fiduciary_metadata};
10use crate::crdt::SuspendedTransactionQueue;
11use crate::modalities::logic::core::WebizenOpcode;
12use crate::NQuin;
13use crate::PermissiveRoutingLane;
14
15const WAL_CHECKPOINT_MSG: &str = "wal:checkpoint";
28const WAL_HEADER_SIZE: u64 = 32;
30
31pub struct WriteAheadLog {
38 #[cfg(not(target_arch = "wasm32"))]
39 file: File,
40 #[cfg(target_arch = "wasm32")]
41 file: Vec<u8>,
42 pub prev_dag_hash: [u8; 32],
45}
46
47#[cfg(not(target_arch = "wasm32"))]
48impl WriteAheadLog {
49 pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
54 let mut file = OpenOptions::new()
55 .create(true)
56 .write(true)
57 .read(true)
58 .open(path)?;
59
60 let file_len = file.seek(SeekFrom::End(0))?;
61
62 let prev_dag_hash = if file_len >= WAL_HEADER_SIZE {
64 file.seek(SeekFrom::Start(0))?;
65 let mut h = [0u8; 32];
66 file.read_exact(&mut h)?;
67 file.seek(SeekFrom::End(0))?;
69 h
70 } else {
71 file.seek(SeekFrom::Start(0))?;
73 file.write_all(&[0u8; 32])?;
74 file.sync_all()?;
75 file.seek(SeekFrom::End(0))?;
76 [0u8; 32]
77 };
78
79 Ok(Self {
80 file,
81 prev_dag_hash,
82 })
83 }
84
85 pub fn append_mutation(&mut self, quin: &NQuin) -> std::io::Result<()> {
88 self.file.write_all(quin_as_bytes(quin))?;
90 self.file.sync_all()?;
91 Ok(())
92 }
93
94 pub fn append_mutation_volatile(&mut self, quin: &mut NQuin) -> std::io::Result<()> {
96 self.file.write_all(quin_as_bytes(quin))?;
97 self.file.sync_all()?;
98 scrub_quin_volatile(quin);
99 Ok(())
100 }
101
102 pub fn recover(&mut self) -> std::io::Result<Vec<NQuin>> {
104 self.file.seek(SeekFrom::Start(WAL_HEADER_SIZE))?;
105
106 let mut buffer = Vec::new();
107 self.file.read_to_end(&mut buffer)?;
108
109 let quin_size = std::mem::size_of::<NQuin>();
110 let mut recovered = Vec::with_capacity(buffer.len() / quin_size);
111
112 for chunk in buffer.chunks_exact(quin_size) {
114 let quin: NQuin = unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const NQuin) };
115 recovered.push(quin);
116 }
117
118 Ok(recovered)
119 }
120
121 pub fn truncate(&mut self) -> std::io::Result<()> {
125 self.file.set_len(WAL_HEADER_SIZE)?;
126 self.file.seek(SeekFrom::End(0))?;
127 self.file.sync_all()?;
128 Ok(())
129 }
130
131 pub fn checkpoint_to_dag(
142 &mut self,
143 dag_store: &mut crate::git_bridge::DagStore,
144 author_did: u64,
145 timestamp_ms: u64,
146 ) -> std::io::Result<[u8; 32]> {
147 let quins = self.recover()?;
148 if quins.is_empty() {
149 return Ok(self.prev_dag_hash);
150 }
151
152 let new_hash = if self.prev_dag_hash == [0u8; 32] {
153 dag_store.genesis_node(&quins, author_did, timestamp_ms, WAL_CHECKPOINT_MSG)
154 } else {
155 dag_store.commit_node(
156 self.prev_dag_hash,
157 &quins,
158 author_did,
159 timestamp_ms,
160 WAL_CHECKPOINT_MSG,
161 )
162 };
163
164 self.file.seek(SeekFrom::Start(0))?;
166 self.file.write_all(&new_hash)?;
167 self.file.sync_all()?;
168 self.file.seek(SeekFrom::End(0))?;
170
171 self.prev_dag_hash = new_hash;
172 Ok(new_hash)
173 }
174
175 pub fn buffered_count(&mut self) -> std::io::Result<usize> {
177 let file_len = self.file.seek(SeekFrom::End(0))?;
178 let data_len = file_len.saturating_sub(WAL_HEADER_SIZE);
179 Ok((data_len as usize) / std::mem::size_of::<NQuin>())
180 }
181}
182
183#[cfg(target_arch = "wasm32")]
184impl WriteAheadLog {
185 pub fn open<P: AsRef<Path>>(_path: P) -> std::io::Result<Self> {
186 Ok(Self {
187 file: Vec::new(),
188 prev_dag_hash: [0; 32],
189 })
190 }
191 pub fn append_mutation(&mut self, _quin: &NQuin) -> std::io::Result<()> {
192 Ok(())
193 }
194 pub fn append_mutation_volatile(&mut self, quin: &mut NQuin) -> std::io::Result<()> {
195 scrub_quin_volatile(quin);
196 Ok(())
197 }
198 pub fn recover(&mut self) -> std::io::Result<Vec<NQuin>> {
199 Ok(Vec::new())
200 }
201 pub fn truncate(&mut self) -> std::io::Result<()> {
202 Ok(())
203 }
204 pub fn checkpoint_to_dag(
205 &mut self,
206 _dag_store: &mut crate::git_bridge::DagStore,
207 _author_did: u64,
208 _timestamp_ms: u64,
209 ) -> std::io::Result<[u8; 32]> {
210 Ok(self.prev_dag_hash)
211 }
212 pub fn buffered_count(&mut self) -> std::io::Result<usize> {
213 Ok(0)
214 }
215}
216
217#[inline]
218fn quin_as_bytes(quin: &NQuin) -> &[u8] {
219 unsafe {
220 std::slice::from_raw_parts(
221 (quin as *const NQuin) as *const u8,
222 std::mem::size_of::<NQuin>(),
223 )
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum WalHandoffResult {
230 Committed,
231 Suspended { agreement_id: u64 },
232}
233
234pub fn commit_semantic_mutation(
236 wal: &mut WriteAheadLog,
237 quin: &mut NQuin,
238 principal_did_hash: u64,
239 agent_did_hash: u64,
240 signing_key: &SigningKey,
241 suspended: &mut SuspendedTransactionQueue,
242) -> io::Result<WalHandoffResult> {
243 stamp_fiduciary_metadata(quin, principal_did_hash, agent_did_hash);
244 let _sig: Signature = sign_graph_mutation(signing_key, quin);
245
246 if quin.identify_routing_lane() == PermissiveRoutingLane::EnforceBilateralMicroCommons {
247 let agreement_id = quin.context;
248 let tx = crate::crdt::SuspendedTransaction {
249 agreement_id,
250 threshold: 2,
251 collected_signatures: 1,
252 registers: [None; 16],
253 bytecode_buffer: [None; 64],
254 yielded_op: Some(WebizenOpcode::LoadModel(0)),
255 suspended_quin: *quin,
256 };
257 if suspended.push(tx).is_err() {
258 return Err(std::io::Error::new(
259 std::io::ErrorKind::Other,
260 "suspended transaction queue full",
261 ));
262 }
263 scrub_quin_volatile(quin);
264 return Ok(WalHandoffResult::Suspended { agreement_id });
265 }
266
267 wal.append_mutation_volatile(quin)?;
268 Ok(WalHandoffResult::Committed)
269}
270
271pub fn append_mutation(quin: &NQuin) -> io::Result<()> {
274 let mut wal = WriteAheadLog::open("qualia_global.wal")?;
277 wal.append_mutation(quin)
278}
279
280pub fn log_adversarial_conduct(
282 intent_quin: &NQuin,
283 violation_code: u8,
284 vm_cycles: u64,
285) -> io::Result<()> {
286 let violation_quin = NQuin {
287 subject: intent_quin.subject,
288 predicate: crate::q_hash("q42:conductViolation"),
289 object: intent_quin.object,
290 context: intent_quin.context,
291 metadata: (violation_code as u64) | (vm_cycles << 8),
292 parity: 0,
293 };
294 append_mutation(&violation_quin)
295}
296
297#[cfg(any(
310 not(target_arch = "wasm32"),
311 feature = "wasm-scientific",
312 feature = "wasm-full"
313))]
314pub fn log_rule_evaluation(
315 input_quin: &NQuin,
316 results: &[crate::modalities::logic::rules::RuleResult],
317 contract_hash: u64,
318) -> io::Result<()> {
319 let eval_predicate = crate::modalities::logic::rules::RULE_EVAL_PREDICATE;
320 let result_count = results.len().min(255) as u64;
321 for (i, result) in results.iter().enumerate().take(255) {
322 let rule_hash = crate::q_hash(&result.rule_name);
323 let mut metadata = if result.passed { 1u64 } else { 0u64 };
324 metadata |= (i as u64) << 16;
325 metadata |= result_count << 8;
326 let eval_quin = NQuin {
327 subject: input_quin.subject,
328 predicate: eval_predicate,
329 object: rule_hash,
330 context: contract_hash,
331 metadata,
332 parity: 0,
333 };
334 append_mutation(&eval_quin)?;
335 }
336 Ok(())
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use tempfile::NamedTempFile;
343
344 fn make_quin(subject: u64, object: u64) -> NQuin {
345 NQuin {
346 subject,
347 predicate: 2,
348 object,
349 context: 4,
350 metadata: 0,
351 parity: 0,
352 }
353 }
354
355 #[test]
356 fn wal_append_and_recover() {
357 let tmp = NamedTempFile::new().unwrap();
358 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
359
360 wal.append_mutation(&make_quin(1, 3)).unwrap();
361 wal.append_mutation(&make_quin(10, 30)).unwrap();
362
363 let recovered = wal.recover().unwrap();
364 assert_eq!(recovered.len(), 2, "WAL must recover 2 quins");
365 assert_eq!(recovered[0].subject, 1);
366 assert_eq!(recovered[1].object, 30);
367
368 wal.truncate().unwrap();
369 assert_eq!(wal.recover().unwrap().len(), 0, "WAL truncation failed");
370 }
371
372 #[test]
373 fn wal_header_persists_across_reopen() {
374 let tmp = NamedTempFile::new().unwrap();
375 {
376 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
377 assert_eq!(wal.prev_dag_hash, [0u8; 32]);
378 wal.append_mutation(&make_quin(42, 99)).unwrap();
379 }
380 {
382 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
383 assert_eq!(wal.prev_dag_hash, [0u8; 32]);
384 let quins = wal.recover().unwrap();
385 assert_eq!(quins.len(), 1);
386 assert_eq!(quins[0].subject, 42);
387 }
388 }
389
390 #[test]
391 fn wal_checkpoint_to_dag_chains_nodes() {
392 let tmp = NamedTempFile::new().unwrap();
393 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
394 let mut dag = crate::git_bridge::DagStore::new();
395 const AUTHOR: u64 = 0xA007_0001;
396
397 wal.append_mutation(&make_quin(1, 1)).unwrap();
399 wal.append_mutation(&make_quin(2, 2)).unwrap();
400 let hash1 = wal.checkpoint_to_dag(&mut dag, AUTHOR, 1000).unwrap();
401 assert_ne!(hash1, [0u8; 32], "genesis hash must be non-zero");
402 assert_eq!(wal.prev_dag_hash, hash1);
403 wal.truncate().unwrap();
404 assert_eq!(wal.recover().unwrap().len(), 0);
405
406 wal.append_mutation(&make_quin(3, 3)).unwrap();
408 let hash2 = wal.checkpoint_to_dag(&mut dag, AUTHOR, 2000).unwrap();
409 assert_ne!(hash2, hash1, "second checkpoint must have a different hash");
410 assert_eq!(wal.prev_dag_hash, hash2);
411 wal.truncate().unwrap();
412
413 let serialized = dag.serialize();
415 assert!(!serialized.is_empty());
416 }
417
418 #[test]
419 fn wal_checkpoint_empty_wal_is_noop() {
420 let tmp = NamedTempFile::new().unwrap();
421 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
422 let mut dag = crate::git_bridge::DagStore::new();
423
424 let hash = wal.checkpoint_to_dag(&mut dag, 0, 0).unwrap();
426 assert_eq!(hash, [0u8; 32]);
427 let serialized = dag.serialize();
429 let node_count = u64::from_le_bytes(serialized[..8].try_into().unwrap());
430 assert_eq!(node_count, 0, "no DagNodes should be created for empty WAL");
431 }
432
433 #[test]
434 fn wal_buffered_count() {
435 let tmp = NamedTempFile::new().unwrap();
436 let mut wal = WriteAheadLog::open(tmp.path()).unwrap();
437 assert_eq!(wal.buffered_count().unwrap(), 0);
438 wal.append_mutation(&make_quin(1, 1)).unwrap();
439 wal.append_mutation(&make_quin(2, 2)).unwrap();
440 assert_eq!(wal.buffered_count().unwrap(), 2);
441 }
442}