use serde::{Deserialize, Serialize};
use crate::kernel::event::Event;
use crate::kernel::identity::{RunId, Seq, StepId};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionLog {
pub thread_id: RunId,
pub step_id: Option<StepId>,
pub event_index: Seq,
pub event: Event,
pub state_hash: Option<[u8; 32]>,
}
impl ExecutionLog {
pub fn from_sequenced(
thread_id: RunId,
se: &crate::kernel::event::SequencedEvent,
state_hash: Option<[u8; 32]>,
) -> Self {
let step_id = step_id_from_event(&se.event);
Self {
thread_id,
step_id,
event_index: se.seq,
event: se.event.clone(),
state_hash,
}
}
}
fn step_id_from_event(event: &Event) -> Option<StepId> {
match event {
Event::StateUpdated { step_id, .. } => step_id.clone(),
_ => None,
}
}
pub fn scan_execution_log(
store: &dyn crate::kernel::event::EventStore,
run_id: &RunId,
from: Seq,
) -> Result<Vec<ExecutionLog>, crate::kernel::KernelError> {
let sequenced = store.scan(run_id, from)?;
Ok(sequenced
.iter()
.map(|se| ExecutionLog::from_sequenced(run_id.clone(), se, None))
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::event::{EventStore, SequencedEvent};
use crate::kernel::event_store::InMemoryEventStore;
#[test]
fn scan_execution_log_returns_canonical_entries() {
let store = InMemoryEventStore::new();
let run_id: RunId = "run-scan".into();
store
.append(
&run_id,
&[
Event::StateUpdated {
step_id: Some("n1".into()),
payload: serde_json::json!([1]),
},
Event::Completed,
],
)
.unwrap();
let log = scan_execution_log(&store, &run_id, 1).unwrap();
assert_eq!(log.len(), 2);
assert_eq!(log[0].thread_id, run_id);
assert_eq!(log[0].event_index, 1);
assert_eq!(log[0].step_id.as_deref(), Some("n1"));
assert_eq!(log[1].event_index, 2);
assert!(matches!(log[1].event, Event::Completed));
}
#[test]
fn from_sequenced_state_updated_has_step_id() {
let thread_id: RunId = "run-1".into();
let se = SequencedEvent {
seq: 1,
event: Event::StateUpdated {
step_id: Some("node-a".into()),
payload: serde_json::json!([1]),
},
};
let log = ExecutionLog::from_sequenced(thread_id.clone(), &se, None);
assert_eq!(log.thread_id, thread_id);
assert_eq!(log.step_id.as_deref(), Some("node-a"));
assert_eq!(log.event_index, 1);
assert!(log.state_hash.is_none());
}
#[test]
fn from_sequenced_completed_has_no_step_id() {
let thread_id: RunId = "run-2".into();
let se = SequencedEvent {
seq: 2,
event: Event::Completed,
};
let log = ExecutionLog::from_sequenced(thread_id.clone(), &se, None);
assert_eq!(log.step_id, None);
assert_eq!(log.event_index, 2);
}
}