pub struct NewEvent {
pub id: String,
pub project_id: String,
pub event_type: String,
pub actor_agent_id: Option<String>,
pub session_id: Option<String>,
pub task_id: Option<String>,
pub payload: serde_json::Value,
pub occurred_at: String,
}
#[derive(serde::Serialize)]
pub struct EventRecord {
pub id: String,
pub project_id: String,
pub event_type: String,
pub actor_agent_id: Option<String>,
pub session_id: Option<String>,
pub task_id: Option<String>,
pub payload: serde_json::Value,
pub occurred_at: String,
}
pub struct EventFilter {
pub project_id: String,
pub task_id: Option<String>,
pub agent_id: Option<String>,
pub session_id: Option<String>,
pub event_type: Option<String>,
pub since: Option<String>,
pub until: Option<String>,
pub limit: Option<u64>,
}
pub trait EventRepository {
fn append(&self, event: &NewEvent) -> Result<EventRecord, crate::error::CarryCtxError>;
fn find_by_id(
&self,
project_id: &str,
id: &str,
) -> Result<Option<EventRecord>, crate::error::CarryCtxError>;
fn list(&self, filter: &EventFilter) -> Result<Vec<EventRecord>, crate::error::CarryCtxError>;
fn list_task_events_by_type(
&self,
project_id: &str,
task_id: &str,
event_type: &str,
) -> Result<Vec<EventRecord>, crate::error::CarryCtxError> {
let _ = (project_id, task_id, event_type);
Err(crate::error::CarryCtxError::unsupported_operation(
"Unbounded task event history is not supported by this repository.",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct ExistingRepository;
impl EventRepository for ExistingRepository {
fn append(&self, _event: &NewEvent) -> Result<EventRecord, crate::error::CarryCtxError> {
unreachable!()
}
fn find_by_id(
&self,
_project_id: &str,
_id: &str,
) -> Result<Option<EventRecord>, crate::error::CarryCtxError> {
unreachable!()
}
fn list(
&self,
filter: &EventFilter,
) -> Result<Vec<EventRecord>, crate::error::CarryCtxError> {
assert_eq!(filter.project_id, "project");
assert_eq!(filter.task_id.as_deref(), Some("task"));
assert_eq!(filter.event_type.as_deref(), Some("task.completed"));
assert!(filter.limit.is_none());
Ok(Vec::new())
}
}
#[test]
fn default_task_event_history_method_rejects_unsupported_unbounded_lookup() {
let repository: &dyn EventRepository = &ExistingRepository;
let error = match repository.list_task_events_by_type("project", "task", "task.completed") {
Ok(_) => panic!("default implementation must not assume list(None) is unbounded"),
Err(error) => error,
};
assert_eq!(error.code, "UNSUPPORTED_OPERATION");
}
}