1use serde::{Deserialize, Serialize};
9
10use super::verify::hash_event;
11
12pub use lean_ctx_protocol::ContextBalanceV1 as ContextBalance;
13pub use lean_ctx_protocol::{AcceptanceState as TriState, ContextBalanceV1};
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum ExecutionEvent {
22 TaskStarted {
23 task_id: String,
24 trace_id: String,
25 envelope_ref: String,
26 timestamp: String,
27 sequence_number: u64,
28 prev_hash: String,
29 },
30 PlanCreated {
31 task_id: String,
32 trace_id: String,
33 plan_id: String,
34 plan_ref: String,
35 timestamp: String,
36 sequence_number: u64,
37 prev_hash: String,
38 },
39 ContextDelivered {
40 task_id: String,
41 trace_id: String,
42 context_balance: ContextBalanceV1,
43 timestamp: String,
44 sequence_number: u64,
45 prev_hash: String,
46 },
47 ModelInvoked {
48 task_id: String,
49 trace_id: String,
50 plan_id: String,
51 invocation_id: String,
52 invocation_ref: String,
53 model: String,
54 provider: String,
55 tokens_in: u64,
56 tokens_out: u64,
57 latency_ms: u64,
58 timestamp: String,
59 sequence_number: u64,
60 prev_hash: String,
61 },
62 EngineInvoked {
63 task_id: String,
64 trace_id: String,
65 plan_id: String,
66 invocation_id: String,
67 invocation_ref: String,
68 capability_id: String,
69 capability_version: String,
70 timestamp: String,
71 sequence_number: u64,
72 prev_hash: String,
73 },
74 ReceiptSigned {
75 task_id: String,
76 trace_id: String,
77 receipt_id: String,
78 receipt_hash: String,
79 signature: String,
80 timestamp: String,
81 sequence_number: u64,
82 prev_hash: String,
83 },
84 CanonicalReceiptRecorded {
85 task_id: String,
86 trace_id: String,
87 invocation_id: String,
88 receipt_id: String,
89 receipt_ref: String,
90 receipt_digest: String,
91 receipt_chain_id: String,
92 receipt_sequence_number: u64,
93 previous_receipt_id: Option<String>,
94 previous_signature_digest: Option<String>,
95 timestamp: String,
96 sequence_number: u64,
97 prev_hash: String,
98 entry_hash: String,
99 },
100 OutcomeRecorded {
101 task_id: String,
102 trace_id: String,
103 outcome_id: String,
104 receipt_id: String,
105 accepted: TriState,
106 timestamp: String,
107 sequence_number: u64,
108 prev_hash: String,
109 },
110 DecisionRecorded {
111 task_id: String,
112 trace_id: String,
113 decision_id: String,
114 kind: String,
115 selected: String,
116 timestamp: String,
117 sequence_number: u64,
118 prev_hash: String,
119 },
120}
121
122impl ExecutionEvent {
123 #[must_use]
125 pub(crate) fn idempotency_key(&self) -> Option<(&'static str, &str, &str)> {
126 match self {
127 Self::TaskStarted { task_id, .. } => Some(("task", task_id, "")),
128 Self::PlanCreated {
129 task_id, plan_id, ..
130 } => Some(("plan", task_id, plan_id)),
131 Self::ModelInvoked {
132 task_id,
133 invocation_id,
134 ..
135 }
136 | Self::EngineInvoked {
137 task_id,
138 invocation_id,
139 ..
140 } => Some(("invocation", task_id, invocation_id)),
141 Self::ReceiptSigned {
142 task_id,
143 receipt_id,
144 ..
145 } => Some(("receipt", task_id, receipt_id)),
146 Self::CanonicalReceiptRecorded { receipt_ref, .. } => {
147 Some(("canonical_receipt", receipt_ref, ""))
148 }
149 Self::OutcomeRecorded {
150 task_id,
151 outcome_id,
152 ..
153 } => Some(("outcome", task_id, outcome_id)),
154 Self::DecisionRecorded {
155 task_id,
156 decision_id,
157 ..
158 } => Some(("decision", task_id, decision_id)),
159 Self::ContextDelivered { task_id, .. } => Some(("context", task_id, "")),
160 }
161 }
162
163 pub(crate) fn payload_json(&self) -> serde_json::Result<String> {
165 let mut event = self.clone();
166 event.set_chain_fields(0, String::new());
167 event.set_entry_hash(String::new());
168 serde_json::to_string(&event)
169 }
170
171 #[must_use]
173 pub const fn sequence_number(&self) -> u64 {
174 match self {
175 Self::TaskStarted {
176 sequence_number, ..
177 }
178 | Self::PlanCreated {
179 sequence_number, ..
180 }
181 | Self::ContextDelivered {
182 sequence_number, ..
183 }
184 | Self::ModelInvoked {
185 sequence_number, ..
186 }
187 | Self::EngineInvoked {
188 sequence_number, ..
189 }
190 | Self::ReceiptSigned {
191 sequence_number, ..
192 }
193 | Self::CanonicalReceiptRecorded {
194 sequence_number, ..
195 }
196 | Self::OutcomeRecorded {
197 sequence_number, ..
198 }
199 | Self::DecisionRecorded {
200 sequence_number, ..
201 } => *sequence_number,
202 }
203 }
204
205 #[must_use]
207 pub fn prev_hash(&self) -> &str {
208 match self {
209 Self::TaskStarted { prev_hash, .. }
210 | Self::PlanCreated { prev_hash, .. }
211 | Self::ContextDelivered { prev_hash, .. }
212 | Self::ModelInvoked { prev_hash, .. }
213 | Self::EngineInvoked { prev_hash, .. }
214 | Self::ReceiptSigned { prev_hash, .. }
215 | Self::CanonicalReceiptRecorded { prev_hash, .. }
216 | Self::OutcomeRecorded { prev_hash, .. }
217 | Self::DecisionRecorded { prev_hash, .. } => prev_hash,
218 }
219 }
220
221 #[must_use]
223 pub fn task_id(&self) -> &str {
224 match self {
225 Self::TaskStarted { task_id, .. }
226 | Self::PlanCreated { task_id, .. }
227 | Self::ContextDelivered { task_id, .. }
228 | Self::ModelInvoked { task_id, .. }
229 | Self::EngineInvoked { task_id, .. }
230 | Self::ReceiptSigned { task_id, .. }
231 | Self::CanonicalReceiptRecorded { task_id, .. }
232 | Self::OutcomeRecorded { task_id, .. }
233 | Self::DecisionRecorded { task_id, .. } => task_id,
234 }
235 }
236
237 #[must_use]
239 pub fn trace_id(&self) -> &str {
240 match self {
241 Self::TaskStarted { trace_id, .. }
242 | Self::PlanCreated { trace_id, .. }
243 | Self::ContextDelivered { trace_id, .. }
244 | Self::ModelInvoked { trace_id, .. }
245 | Self::EngineInvoked { trace_id, .. }
246 | Self::ReceiptSigned { trace_id, .. }
247 | Self::CanonicalReceiptRecorded { trace_id, .. }
248 | Self::OutcomeRecorded { trace_id, .. }
249 | Self::DecisionRecorded { trace_id, .. } => trace_id,
250 }
251 }
252
253 #[must_use]
255 pub fn timestamp(&self) -> &str {
256 match self {
257 Self::TaskStarted { timestamp, .. }
258 | Self::PlanCreated { timestamp, .. }
259 | Self::ContextDelivered { timestamp, .. }
260 | Self::ModelInvoked { timestamp, .. }
261 | Self::EngineInvoked { timestamp, .. }
262 | Self::ReceiptSigned { timestamp, .. }
263 | Self::CanonicalReceiptRecorded { timestamp, .. }
264 | Self::OutcomeRecorded { timestamp, .. }
265 | Self::DecisionRecorded { timestamp, .. } => timestamp,
266 }
267 }
268
269 pub fn canonical_json(&self) -> serde_json::Result<String> {
275 serde_json::to_string(self)
276 }
277
278 pub(crate) fn hashable_json(&self) -> serde_json::Result<String> {
280 let mut event = self.clone();
281 event.set_entry_hash(String::new());
282 serde_json::to_string(&event)
283 }
284
285 #[must_use]
287 pub(crate) fn entry_hash(&self) -> Option<&str> {
288 match self {
289 Self::CanonicalReceiptRecorded { entry_hash, .. } => Some(entry_hash),
290 _ => None,
291 }
292 }
293
294 pub(crate) fn set_entry_hash(&mut self, value: String) {
295 if let Self::CanonicalReceiptRecorded { entry_hash, .. } = self {
296 *entry_hash = value;
297 }
298 }
299
300 pub fn event_hash(&self) -> serde_json::Result<String> {
302 hash_event(self)
303 }
304
305 pub(crate) fn set_chain_fields(&mut self, sequence_number: u64, prev_hash: String) {
307 match self {
308 Self::TaskStarted {
309 sequence_number: sequence,
310 prev_hash: previous,
311 ..
312 }
313 | Self::PlanCreated {
314 sequence_number: sequence,
315 prev_hash: previous,
316 ..
317 }
318 | Self::ContextDelivered {
319 sequence_number: sequence,
320 prev_hash: previous,
321 ..
322 }
323 | Self::ModelInvoked {
324 sequence_number: sequence,
325 prev_hash: previous,
326 ..
327 }
328 | Self::EngineInvoked {
329 sequence_number: sequence,
330 prev_hash: previous,
331 ..
332 }
333 | Self::ReceiptSigned {
334 sequence_number: sequence,
335 prev_hash: previous,
336 ..
337 }
338 | Self::CanonicalReceiptRecorded {
339 sequence_number: sequence,
340 prev_hash: previous,
341 ..
342 }
343 | Self::OutcomeRecorded {
344 sequence_number: sequence,
345 prev_hash: previous,
346 ..
347 }
348 | Self::DecisionRecorded {
349 sequence_number: sequence,
350 prev_hash: previous,
351 ..
352 } => {
353 *sequence = sequence_number;
354 *previous = prev_hash;
355 }
356 }
357 }
358}