1use serde::{Deserialize, Serialize};
9
10use crate::value_objects::{AuditEventType, EventSchemaVersion};
11
12use super::ceremony_events::{
13 CeremonyCancelled, CeremonyCompleted, CeremonyDeadlineExceeded, CeremonyInstanceStarted,
14 CeremonyPaused, CeremonyResumed, ChildCompletionAccepted, ChildSpawnPlanAdopted,
15 ChildSpawnPlanned, ContextWritten, EvidenceCollected, ExecutionReceiptLinked,
16 HumanApprovalRecorded, HumanDeferralRecorded, InstanceImported, InterventionClosed,
17 InterventionRequested, InterventionResponded, LateStepResultObserved, MemoryRecalled,
18 ParticipantsBound, ReasonAsserted, StateDeadlineExceeded, StateIterationStarted, StepCompleted,
19 StepDeadlineExceeded, StepFailed, StepStarted, TransitionApplied,
20};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "type", rename_all = "snake_case")]
35pub enum CeremonyEvent {
36 CeremonyInstanceStarted(CeremonyInstanceStarted),
37 ParticipantsBound(ParticipantsBound),
38 StepStarted(StepStarted),
39 StepCompleted(StepCompleted),
40 StepFailed(StepFailed),
41 ContextWritten(ContextWritten),
42 StateIterationStarted(StateIterationStarted),
43 TransitionApplied(TransitionApplied),
44 InterventionRequested(InterventionRequested),
45 InterventionResponded(InterventionResponded),
46 InterventionClosed(InterventionClosed),
47 EvidenceCollected(EvidenceCollected),
48 ReasonAsserted(ReasonAsserted),
49 HumanApprovalRecorded(HumanApprovalRecorded),
50 HumanDeferralRecorded(HumanDeferralRecorded),
51 CeremonyCompleted(CeremonyCompleted),
52 InstanceImported(InstanceImported),
53 MemoryRecalled(MemoryRecalled),
54 ChildSpawnPlanned(ChildSpawnPlanned),
55 ChildSpawnPlanAdopted(ChildSpawnPlanAdopted),
56 ChildCompletionAccepted(ChildCompletionAccepted),
57 CeremonyPaused(CeremonyPaused),
58 CeremonyResumed(CeremonyResumed),
59 CeremonyCancelled(CeremonyCancelled),
60 CeremonyDeadlineExceeded(CeremonyDeadlineExceeded),
61 StateDeadlineExceeded(StateDeadlineExceeded),
62 StepDeadlineExceeded(StepDeadlineExceeded),
63 LateStepResultObserved(LateStepResultObserved),
64 ExecutionReceiptLinked(ExecutionReceiptLinked),
65}
66
67impl CeremonyEvent {
68 #[must_use]
70 pub fn event_type(&self) -> AuditEventType {
71 match self {
72 Self::CeremonyInstanceStarted(_) => AuditEventType::CeremonyInstanceStarted,
73 Self::ParticipantsBound(_) => AuditEventType::ParticipantsBound,
74 Self::StepStarted(_) => AuditEventType::StepStarted,
75 Self::StepCompleted(_) => AuditEventType::StepCompleted,
76 Self::StepFailed(_) => AuditEventType::StepFailed,
77 Self::ContextWritten(_) => AuditEventType::ContextWritten,
78 Self::StateIterationStarted(_) => AuditEventType::StateIterationStarted,
79 Self::TransitionApplied(_) => AuditEventType::TransitionApplied,
80 Self::InterventionRequested(_) => AuditEventType::InterventionRequested,
81 Self::InterventionResponded(_) => AuditEventType::InterventionResponded,
82 Self::InterventionClosed(_) => AuditEventType::InterventionClosed,
83 Self::EvidenceCollected(_) => AuditEventType::EvidenceCollected,
84 Self::ReasonAsserted(_) => AuditEventType::ReasonAsserted,
85 Self::HumanApprovalRecorded(_) => AuditEventType::HumanApprovalRecorded,
86 Self::HumanDeferralRecorded(_) => AuditEventType::HumanDeferralRecorded,
87 Self::CeremonyCompleted(_) => AuditEventType::CeremonyCompleted,
88 Self::InstanceImported(_) => AuditEventType::InstanceImported,
89 Self::MemoryRecalled(_) => AuditEventType::MemoryRecalled,
90 Self::ChildSpawnPlanned(_) => AuditEventType::ChildSpawnPlanned,
91 Self::ChildSpawnPlanAdopted(_) => AuditEventType::ChildSpawnPlanAdopted,
92 Self::ChildCompletionAccepted(_) => AuditEventType::ChildCompletionAccepted,
93 Self::CeremonyPaused(_) => AuditEventType::CeremonyPaused,
94 Self::CeremonyResumed(_) => AuditEventType::CeremonyResumed,
95 Self::CeremonyCancelled(_) => AuditEventType::CeremonyCancelled,
96 Self::CeremonyDeadlineExceeded(_) => AuditEventType::CeremonyDeadlineExceeded,
97 Self::StateDeadlineExceeded(_) => AuditEventType::StateDeadlineExceeded,
98 Self::StepDeadlineExceeded(_) => AuditEventType::StepDeadlineExceeded,
99 Self::LateStepResultObserved(_) => AuditEventType::LateStepResultObserved,
100 Self::ExecutionReceiptLinked(_) => AuditEventType::ExecutionReceiptLinked,
101 }
102 }
103
104 #[must_use]
109 pub fn schema_version(&self) -> EventSchemaVersion {
110 match self {
111 Self::StepStarted(event) => {
112 if event.budget_reservation_id.is_some() {
113 EventSchemaVersion::V6
114 } else if event.deadline.is_some() {
115 EventSchemaVersion::V5
116 } else if event.state_visit.is_some() {
117 EventSchemaVersion::V4
118 } else if event.role_from.is_some() || event.sealed_role.is_some() {
119 EventSchemaVersion::V3
120 } else {
121 event
122 .state_iteration
123 .map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
124 }
125 }
126 Self::StepCompleted(event) => {
127 if event.state_visit.is_some() {
128 EventSchemaVersion::V3
129 } else {
130 event
131 .state_iteration
132 .map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
133 }
134 }
135 Self::StepFailed(event) => {
136 if event.result.failure_kind().is_some() {
137 EventSchemaVersion::V4
138 } else if event.state_visit.is_some() {
139 EventSchemaVersion::V3
140 } else {
141 event
142 .state_iteration
143 .map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2)
144 }
145 }
146 Self::TransitionApplied(event) => {
147 if event
148 .destination
149 .as_ref()
150 .is_some_and(|destination| destination.deadline.is_some())
151 {
152 EventSchemaVersion::V4
153 } else if event.destination.is_some() || event.transition.has_explicit_state_visit()
154 {
155 EventSchemaVersion::V3
156 } else if event.transition.has_explicit_state_iteration() {
157 EventSchemaVersion::V2
158 } else {
159 EventSchemaVersion::V1
160 }
161 }
162 Self::StateIterationStarted(event) => event
163 .state_visit
164 .map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2),
165 Self::ContextWritten(event) => event
166 .state_visit
167 .map_or(EventSchemaVersion::V1, |_| EventSchemaVersion::V2),
168 Self::CeremonyInstanceStarted(event) if event.budget_account_id.is_some() => {
169 EventSchemaVersion::V4
170 }
171 Self::CeremonyInstanceStarted(event)
172 if event.ceremony_deadline.is_some() || event.state_deadline.is_some() =>
173 {
174 EventSchemaVersion::V3
175 }
176 Self::CeremonyInstanceStarted(event) if event.lineage.is_some() => {
177 EventSchemaVersion::V2
178 }
179 Self::CeremonyInstanceStarted(_)
180 | Self::ParticipantsBound(_)
181 | Self::InterventionRequested(_)
182 | Self::InterventionResponded(_)
183 | Self::InterventionClosed(_)
184 | Self::EvidenceCollected(_)
185 | Self::ReasonAsserted(_)
186 | Self::HumanApprovalRecorded(_)
187 | Self::HumanDeferralRecorded(_)
188 | Self::CeremonyCompleted(_)
189 | Self::InstanceImported(_)
190 | Self::MemoryRecalled(_)
191 | Self::ChildSpawnPlanned(_)
192 | Self::ChildSpawnPlanAdopted(_)
193 | Self::ChildCompletionAccepted(_)
194 | Self::CeremonyPaused(_)
195 | Self::CeremonyResumed(_)
196 | Self::CeremonyCancelled(_)
197 | Self::CeremonyDeadlineExceeded(_)
198 | Self::StateDeadlineExceeded(_)
199 | Self::StepDeadlineExceeded(_)
200 | Self::LateStepResultObserved(_)
201 | Self::ExecutionReceiptLinked(_) => EventSchemaVersion::V1,
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::value_objects::StateId;
210 use time::macros::datetime;
211
212 fn completed() -> CeremonyEvent {
213 CeremonyEvent::CeremonyCompleted(CeremonyCompleted {
214 final_state: StateId::new("DONE").unwrap(),
215 completed_at: datetime!(2026-07-29 09:00:00 UTC),
216 })
217 }
218
219 #[test]
220 fn the_wire_tag_is_the_event_type_name() {
221 let json = serde_json::to_value(completed()).unwrap();
222
223 assert_eq!(json["type"], AuditEventType::CeremonyCompleted.as_str());
224 assert_eq!(json["final_state"], "DONE");
225 assert_eq!(json["completed_at"], "2026-07-29T09:00:00Z");
226 }
227
228 #[test]
229 fn every_payload_is_at_version_one_today() {
230 assert_eq!(completed().schema_version(), EventSchemaVersion::V1);
231 assert_eq!(completed().event_type(), AuditEventType::CeremonyCompleted);
232 }
233
234 #[test]
235 fn a_tag_with_a_foreign_shape_does_not_deserialize() {
236 let json = serde_json::json!({ "type": "ceremony_completed", "final_state": "DONE" });
237
238 assert!(serde_json::from_value::<CeremonyEvent>(json).is_err());
239 }
240}