use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalEntry {
pub seq: u64,
pub timestamp: DateTime<Utc>,
pub kind: WalEntryKind,
pub prev_hash: [u8; 32],
pub hash: [u8; 32],
}
impl WalEntry {
pub fn compute_hash(
seq: u64,
timestamp: &DateTime<Utc>,
kind: &WalEntryKind,
prev_hash: &[u8; 32],
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&seq.to_le_bytes());
hasher.update(timestamp.to_rfc3339().as_bytes());
if let Ok(kind_bytes) = serde_json::to_vec(kind) {
hasher.update(&kind_bytes);
}
hasher.update(prev_hash);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum WalEntryKind {
TripleInsert {
subject: String,
predicate: String,
object: serde_json::Value,
triple_id: [u8; 32],
},
TripleDelete { triple_id: [u8; 32] },
MemoryStore {
memory_id: String,
entry_type: String,
data: serde_json::Value,
importance: f32,
},
MemoryForget { memory_id: String },
MemoryConsolidate { consolidated_count: usize },
ProofSubmit {
proof_id: String,
proof_type: String,
},
Checkpoint {
graph_triple_count: usize,
ineru_stm_count: usize,
ineru_ltm_entity_count: usize,
},
LtmEntityCreate {
entity_id: String,
name: String,
entity_type: String,
},
LtmLinkCreate {
from_entity: String,
to_entity: String,
relation: String,
weight: f32,
},
LtmEntityDelete { entity_id: String },
RaftEntry {
index: u64,
term: u64,
data: Vec<u8>,
},
DagAction {
action_bytes: Vec<u8>,
},
Noop,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry_kind_serialization() {
let kind = WalEntryKind::TripleInsert {
subject: "alice".into(),
predicate: "knows".into(),
object: serde_json::json!("bob"),
triple_id: [0u8; 32],
};
let json = serde_json::to_string(&kind).unwrap();
let back: WalEntryKind = serde_json::from_str(&json).unwrap();
assert!(matches!(back, WalEntryKind::TripleInsert { .. }));
}
#[test]
fn test_compute_hash_deterministic() {
let ts = Utc::now();
let kind = WalEntryKind::TripleDelete {
triple_id: [1u8; 32],
};
let prev = [0u8; 32];
let h1 = WalEntry::compute_hash(1, &ts, &kind, &prev);
let h2 = WalEntry::compute_hash(1, &ts, &kind, &prev);
assert_eq!(h1, h2);
}
#[test]
fn test_compute_hash_differs_on_seq() {
let ts = Utc::now();
let kind = WalEntryKind::TripleDelete {
triple_id: [1u8; 32],
};
let prev = [0u8; 32];
let h1 = WalEntry::compute_hash(1, &ts, &kind, &prev);
let h2 = WalEntry::compute_hash(2, &ts, &kind, &prev);
assert_ne!(h1, h2);
}
}