use serde::{Deserialize, Serialize};
use crate::{error::StoreError, types::SessionId};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JournalEntry {
pub session_id: SessionId,
pub call_fingerprint: String,
pub tool_name: String,
pub arguments: serde_json::Value,
pub result: serde_json::Value,
pub is_error: bool,
pub timestamp_ms: u64,
}
impl JournalEntry {
#[must_use]
pub fn fingerprint(tool_name: &str, arguments: &serde_json::Value) -> String {
let args_canonical = serde_json::to_string(arguments).unwrap_or_default();
format!("{tool_name}:{args_canonical}")
}
}
#[async_trait::async_trait]
pub trait ToolJournalPort: Send + Sync {
async fn append(&self, entry: JournalEntry) -> Result<(), StoreError>;
async fn lookup(
&self,
session_id: &SessionId,
fingerprint: &str,
) -> Result<Option<JournalEntry>, StoreError>;
async fn entries(&self, session_id: &SessionId) -> Result<Vec<JournalEntry>, StoreError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fingerprint_is_deterministic() {
let args = serde_json::json!({"path": "/tmp/test.txt", "mode": "read"});
let fp1 = JournalEntry::fingerprint("read_file", &args);
let fp2 = JournalEntry::fingerprint("read_file", &args);
assert_eq!(fp1, fp2, "same inputs should produce same fingerprint");
}
#[test]
fn fingerprint_differs_for_different_tools() {
let args = serde_json::json!({"x": 1});
let fp1 = JournalEntry::fingerprint("tool_a", &args);
let fp2 = JournalEntry::fingerprint("tool_b", &args);
assert_ne!(fp1, fp2, "different tools should produce different fingerprints");
}
#[test]
fn fingerprint_differs_for_different_args() {
let args1 = serde_json::json!({"x": 1});
let args2 = serde_json::json!({"x": 2});
let fp1 = JournalEntry::fingerprint("tool_a", &args1);
let fp2 = JournalEntry::fingerprint("tool_a", &args2);
assert_ne!(fp1, fp2, "different args should produce different fingerprints");
}
}