use serde::{Deserialize, Serialize};
use crate::value_objects::{AuditEventType, EventSchemaVersion};
use super::ceremony_events::{
CeremonyCompleted, CeremonyInstanceStarted, ContextWritten, EvidenceCollected,
HumanApprovalRecorded, HumanDeferralRecorded, InstanceImported, InterventionClosed,
InterventionRequested, InterventionResponded, MemoryRecalled, ParticipantsBound,
ReasonAsserted, StateIterationStarted, StepCompleted, StepFailed, StepStarted,
TransitionApplied,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CeremonyEvent {
CeremonyInstanceStarted(CeremonyInstanceStarted),
ParticipantsBound(ParticipantsBound),
StepStarted(StepStarted),
StepCompleted(StepCompleted),
StepFailed(StepFailed),
ContextWritten(ContextWritten),
StateIterationStarted(StateIterationStarted),
TransitionApplied(TransitionApplied),
InterventionRequested(InterventionRequested),
InterventionResponded(InterventionResponded),
InterventionClosed(InterventionClosed),
EvidenceCollected(EvidenceCollected),
ReasonAsserted(ReasonAsserted),
HumanApprovalRecorded(HumanApprovalRecorded),
HumanDeferralRecorded(HumanDeferralRecorded),
CeremonyCompleted(CeremonyCompleted),
InstanceImported(InstanceImported),
MemoryRecalled(MemoryRecalled),
}
impl CeremonyEvent {
#[must_use]
pub fn event_type(&self) -> AuditEventType {
match self {
Self::CeremonyInstanceStarted(_) => AuditEventType::CeremonyInstanceStarted,
Self::ParticipantsBound(_) => AuditEventType::ParticipantsBound,
Self::StepStarted(_) => AuditEventType::StepStarted,
Self::StepCompleted(_) => AuditEventType::StepCompleted,
Self::StepFailed(_) => AuditEventType::StepFailed,
Self::ContextWritten(_) => AuditEventType::ContextWritten,
Self::StateIterationStarted(_) => AuditEventType::StateIterationStarted,
Self::TransitionApplied(_) => AuditEventType::TransitionApplied,
Self::InterventionRequested(_) => AuditEventType::InterventionRequested,
Self::InterventionResponded(_) => AuditEventType::InterventionResponded,
Self::InterventionClosed(_) => AuditEventType::InterventionClosed,
Self::EvidenceCollected(_) => AuditEventType::EvidenceCollected,
Self::ReasonAsserted(_) => AuditEventType::ReasonAsserted,
Self::HumanApprovalRecorded(_) => AuditEventType::HumanApprovalRecorded,
Self::HumanDeferralRecorded(_) => AuditEventType::HumanDeferralRecorded,
Self::CeremonyCompleted(_) => AuditEventType::CeremonyCompleted,
Self::InstanceImported(_) => AuditEventType::InstanceImported,
Self::MemoryRecalled(_) => AuditEventType::MemoryRecalled,
}
}
#[must_use]
pub fn schema_version(&self) -> EventSchemaVersion {
match self {
Self::StepStarted(event) => {
if event.state_visit.is_some() {
EventSchemaVersion::V4
} else if event.role_from.is_some() || event.sealed_role.is_some() {
EventSchemaVersion::V3
} else {
event
.state_iteration
.map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
}
}
Self::StepCompleted(event) => {
if event.state_visit.is_some() {
EventSchemaVersion::V3
} else {
event
.state_iteration
.map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
}
}
Self::StepFailed(event) => {
if event.state_visit.is_some() {
EventSchemaVersion::V3
} else {
event
.state_iteration
.map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
}
}
Self::TransitionApplied(event) => {
if event.destination.is_some() || event.transition.has_explicit_state_visit() {
EventSchemaVersion::V3
} else if event.transition.has_explicit_state_iteration() {
EventSchemaVersion::V2
} else {
EventSchemaVersion::V1
}
}
Self::StateIterationStarted(event) => event
.state_visit
.map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2),
Self::ContextWritten(event) => event
.state_visit
.map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2),
Self::CeremonyInstanceStarted(_)
| Self::ParticipantsBound(_)
| Self::InterventionRequested(_)
| Self::InterventionResponded(_)
| Self::InterventionClosed(_)
| Self::EvidenceCollected(_)
| Self::ReasonAsserted(_)
| Self::HumanApprovalRecorded(_)
| Self::HumanDeferralRecorded(_)
| Self::CeremonyCompleted(_)
| Self::InstanceImported(_)
| Self::MemoryRecalled(_) => EventSchemaVersion::V1,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value_objects::StateId;
use time::macros::datetime;
fn completed() -> CeremonyEvent {
CeremonyEvent::CeremonyCompleted(CeremonyCompleted {
final_state: StateId::new("DONE").unwrap(),
completed_at: datetime!(2026-07-29 09:00:00 UTC),
})
}
#[test]
fn the_wire_tag_is_the_event_type_name() {
let json = serde_json::to_value(completed()).unwrap();
assert_eq!(json["type"], AuditEventType::CeremonyCompleted.as_str());
assert_eq!(json["final_state"], "DONE");
assert_eq!(json["completed_at"], "2026-07-29T09:00:00Z");
}
#[test]
fn every_payload_is_at_version_one_today() {
assert_eq!(completed().schema_version(), EventSchemaVersion::V1);
assert_eq!(completed().event_type(), AuditEventType::CeremonyCompleted);
}
#[test]
fn a_tag_with_a_foreign_shape_does_not_deserialize() {
let json = serde_json::json!({ "type": "ceremony_completed", "final_state": "DONE" });
assert!(serde_json::from_value::<CeremonyEvent>(json).is_err());
}
}