use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
SessionObserved,
TaskCreated,
LeaseAcquired,
LeaseReleased,
MemoryWritten,
InterruptRaised,
InterruptDelivered,
InterruptAcknowledged,
HandoffCreated,
HandoffAccepted,
BlockerOpened,
BlockerResolved,
}
impl EventType {
pub fn as_str(&self) -> &'static str {
match self {
Self::SessionObserved => "session_observed",
Self::TaskCreated => "task_created",
Self::LeaseAcquired => "lease_acquired",
Self::LeaseReleased => "lease_released",
Self::MemoryWritten => "memory_written",
Self::InterruptRaised => "interrupt_raised",
Self::InterruptDelivered => "interrupt_delivered",
Self::InterruptAcknowledged => "interrupt_acknowledged",
Self::HandoffCreated => "handoff_created",
Self::HandoffAccepted => "handoff_accepted",
Self::BlockerOpened => "blocker_opened",
Self::BlockerResolved => "blocker_resolved",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"session_observed" => Some(Self::SessionObserved),
"task_created" => Some(Self::TaskCreated),
"lease_acquired" => Some(Self::LeaseAcquired),
"lease_released" => Some(Self::LeaseReleased),
"memory_written" => Some(Self::MemoryWritten),
"interrupt_raised" => Some(Self::InterruptRaised),
"interrupt_delivered" => Some(Self::InterruptDelivered),
"interrupt_acknowledged" => Some(Self::InterruptAcknowledged),
"handoff_created" => Some(Self::HandoffCreated),
"handoff_accepted" => Some(Self::HandoffAccepted),
"blocker_opened" => Some(Self::BlockerOpened),
"blocker_resolved" => Some(Self::BlockerResolved),
_ => None,
}
}
}
impl std::fmt::Display for EventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoordEvent {
pub id: Option<i64>,
pub event_type: EventType,
pub timestamp: String,
pub session_id: Option<String>,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LeaseMode {
Exclusive,
SharedRead,
SharedAppend,
Advisory,
}
impl LeaseMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Exclusive => "exclusive",
Self::SharedRead => "shared_read",
Self::SharedAppend => "shared_append",
Self::Advisory => "advisory",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"exclusive" => Some(Self::Exclusive),
"shared_read" => Some(Self::SharedRead),
"shared_append" => Some(Self::SharedAppend),
"advisory" => Some(Self::Advisory),
_ => None,
}
}
}
impl std::fmt::Display for LeaseMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LeaseStatus {
Active,
Released,
Expired,
}
impl LeaseStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Active => "active",
Self::Released => "released",
Self::Expired => "expired",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"active" => Some(Self::Active),
"released" => Some(Self::Released),
"expired" => Some(Self::Expired),
_ => None,
}
}
}
impl std::fmt::Display for LeaseStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lease {
pub id: String,
pub owner_session_id: String,
pub owner_agent: String,
pub resource_kind: String,
pub resource_value: String,
pub mode: LeaseMode,
pub reason: String,
pub acquired_at: String,
pub expires_at: Option<String>,
pub status: LeaseStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockerStatus {
Open,
Resolved,
}
impl BlockerStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Open => "open",
Self::Resolved => "resolved",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"open" => Some(Self::Open),
"resolved" => Some(Self::Resolved),
_ => None,
}
}
}
impl std::fmt::Display for BlockerStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Blocker {
pub id: String,
pub task_id: String,
pub depends_on: Option<String>,
pub waiting_for: String,
pub status: BlockerStatus,
pub owner_session_id: String,
pub created_at: String,
pub resolved_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandoffState {
pub goal: String,
pub artifacts: Vec<String>,
pub attempted: Vec<String>,
pub next_steps: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Handoff {
pub id: String,
pub from_session_id: String,
pub to_session_id: Option<String>,
pub task_id: String,
pub summary: String,
pub state: HandoffState,
pub priority: String,
pub created_at: String,
pub acknowledged_at: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InterruptType {
Nudge,
RequestInput,
Pause,
Compact,
Reroute,
ReleaseOwnership,
Stop,
Resume,
DependencyUnblocked,
HandoffReady,
}
impl InterruptType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Nudge => "nudge",
Self::RequestInput => "request_input",
Self::Pause => "pause",
Self::Compact => "compact",
Self::Reroute => "reroute",
Self::ReleaseOwnership => "release_ownership",
Self::Stop => "stop",
Self::Resume => "resume",
Self::DependencyUnblocked => "dependency_unblocked",
Self::HandoffReady => "handoff_ready",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"nudge" => Some(Self::Nudge),
"request_input" => Some(Self::RequestInput),
"pause" => Some(Self::Pause),
"compact" => Some(Self::Compact),
"reroute" => Some(Self::Reroute),
"release_ownership" => Some(Self::ReleaseOwnership),
"stop" => Some(Self::Stop),
"resume" => Some(Self::Resume),
"dependency_unblocked" => Some(Self::DependencyUnblocked),
"handoff_ready" => Some(Self::HandoffReady),
_ => None,
}
}
}
impl std::fmt::Display for InterruptType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InterruptState {
Pending,
Delivered,
Acknowledged,
Resolved,
Expired,
Dismissed,
}
impl InterruptState {
pub fn as_str(&self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Delivered => "delivered",
Self::Acknowledged => "acknowledged",
Self::Resolved => "resolved",
Self::Expired => "expired",
Self::Dismissed => "dismissed",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"pending" => Some(Self::Pending),
"delivered" => Some(Self::Delivered),
"acknowledged" => Some(Self::Acknowledged),
"resolved" => Some(Self::Resolved),
"expired" => Some(Self::Expired),
"dismissed" => Some(Self::Dismissed),
_ => None,
}
}
}
impl std::fmt::Display for InterruptState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Interrupt {
pub id: String,
pub interrupt_type: InterruptType,
pub priority: String,
pub target_session_id: String,
pub reason: String,
pub payload: Option<serde_json::Value>,
pub delivery_mode: String,
pub max_retries: u32,
#[serde(default)]
pub retry_count: u32,
#[serde(default)]
pub next_retry_at: Option<String>,
pub expires_at: Option<String>,
pub dedupe_key: Option<String>,
pub state: InterruptState,
pub created_at: String,
pub delivered_at: Option<String>,
pub acknowledged_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subject {
pub kind: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryRecord {
pub id: String,
pub mem_type: String,
pub scope: serde_json::Value,
pub subjects: Vec<Subject>,
pub summary: String,
pub evidence: Vec<Subject>,
pub source: Option<serde_json::Value>,
pub confidence: f64,
pub created_at: String,
pub updated_at: String,
pub expires_at: Option<String>,
pub tags: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_type_serde_roundtrip() {
let val = EventType::LeaseAcquired;
let json = serde_json::to_string(&val).unwrap();
assert_eq!(json, "\"lease_acquired\"");
let back: EventType = serde_json::from_str(&json).unwrap();
assert_eq!(back, val);
}
#[test]
fn event_type_all_variants_roundtrip() {
let variants = [
EventType::SessionObserved,
EventType::TaskCreated,
EventType::LeaseAcquired,
EventType::LeaseReleased,
EventType::MemoryWritten,
EventType::InterruptRaised,
EventType::InterruptDelivered,
EventType::InterruptAcknowledged,
EventType::HandoffCreated,
EventType::HandoffAccepted,
EventType::BlockerOpened,
EventType::BlockerResolved,
];
for v in variants {
let s = v.as_str();
assert_eq!(EventType::parse(s), Some(v));
let json = serde_json::to_string(&v).unwrap();
let back: EventType = serde_json::from_str(&json).unwrap();
assert_eq!(back, v);
}
}
#[test]
fn lease_mode_serde_roundtrip() {
let val = LeaseMode::SharedRead;
let json = serde_json::to_string(&val).unwrap();
assert_eq!(json, "\"shared_read\"");
let back: LeaseMode = serde_json::from_str(&json).unwrap();
assert_eq!(back, val);
}
#[test]
fn interrupt_type_display() {
assert_eq!(
InterruptType::DependencyUnblocked.to_string(),
"dependency_unblocked"
);
assert_eq!(
InterruptType::ReleaseOwnership.to_string(),
"release_ownership"
);
}
#[test]
fn interrupt_state_lifecycle_ordering() {
let states = [
"pending",
"delivered",
"acknowledged",
"resolved",
"expired",
"dismissed",
];
for s in states {
assert!(InterruptState::parse(s).is_some(), "failed to parse: {s}");
}
}
#[test]
fn unknown_strings_return_none() {
assert_eq!(EventType::parse("bogus"), None);
assert_eq!(LeaseMode::parse("bogus"), None);
assert_eq!(LeaseStatus::parse("bogus"), None);
assert_eq!(BlockerStatus::parse("bogus"), None);
assert_eq!(InterruptType::parse("bogus"), None);
assert_eq!(InterruptState::parse("bogus"), None);
}
#[test]
fn coord_event_json_roundtrip() {
let event = CoordEvent {
id: Some(1),
event_type: EventType::HandoffCreated,
timestamp: "2026-04-20T10:00:00Z".into(),
session_id: Some("sess_1".into()),
payload: serde_json::json!({"task": "fix_tests"}),
};
let json = serde_json::to_string(&event).unwrap();
let back: CoordEvent = serde_json::from_str(&json).unwrap();
assert_eq!(back.event_type, EventType::HandoffCreated);
assert_eq!(back.session_id.as_deref(), Some("sess_1"));
}
}