a3s_flow/model/event.rs
1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use super::{
6 CancellationRequest, ChildOperationReference, ChildWorkflowCancellationPolicy, JsonValue,
7 RetryPolicy, WorkflowProgress, WorkflowSignal, WorkflowSpec, WorkflowTerminalOutcome,
8};
9
10/// Event persisted as the single source of truth for a workflow run.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[non_exhaustive]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum FlowEvent {
15 /// Creates a run with its immutable definition and initial input.
16 RunCreated {
17 /// Workflow definition pinned to the run.
18 spec: WorkflowSpec,
19 /// Initial JSON input supplied to the workflow.
20 input: JsonValue,
21 },
22 /// Marks the run as actively executing.
23 RunStarted,
24 /// Completes the run successfully.
25 RunCompleted {
26 /// Final JSON value returned by the workflow.
27 output: JsonValue,
28 },
29 /// Terminates the run with an application or runtime error.
30 RunFailed {
31 /// Human-readable failure description.
32 error: String,
33 },
34 /// Records a cleanup-aware cancellation request.
35 RunCancellationRequested {
36 /// Immutable cancellation request delivered during replay.
37 request: CancellationRequest,
38 },
39 /// Completes a requested or forced cancellation.
40 RunCancelled {
41 /// Optional operator- or application-supplied cancellation reason.
42 reason: Option<String>,
43 },
44 /// Terminates the run because its deadline elapsed.
45 RunTimedOut {
46 /// UTC deadline that caused the timeout.
47 deadline: DateTime<Utc>,
48 /// Optional context for the timeout decision.
49 reason: Option<String>,
50 },
51 /// Terminates the run after a step uses all permitted attempts.
52 RunRetryExhausted {
53 /// Stable identifier of the exhausted step.
54 step_id: String,
55 /// Final attempt number that failed.
56 attempt: u32,
57 /// Error returned by the final attempt.
58 error: String,
59 },
60 /// Terminates the run because its owning host shut down.
61 RunHostShutdown {
62 /// Optional host shutdown reason.
63 reason: Option<String>,
64 },
65 /// Closes this history and links it to a successor run.
66 RunContinuedAsNew {
67 /// Identifier assigned to the successor run.
68 successor_run_id: String,
69 /// Initial input persisted for the successor.
70 input: JsonValue,
71 },
72 /// Persists the workflow's latest durable progress report.
73 RunProgressRecorded {
74 /// Progress value made visible to inspectors and observers.
75 progress: WorkflowProgress,
76 },
77 /// Links an externally managed child operation to the run.
78 ChildOperationLinked {
79 /// Stable reference to the linked operation.
80 child: ChildOperationReference,
81 },
82 /// Requests a first-class child workflow.
83 ChildWorkflowRequested {
84 /// Parent-local stable identifier used during replay.
85 child_id: String,
86 /// Globally addressable run identifier assigned to the child.
87 child_run_id: String,
88 /// Workflow definition used to create the child.
89 spec: WorkflowSpec,
90 /// Initial JSON input supplied to the child.
91 input: JsonValue,
92 /// Policy applied when the parent is cancelled or terminated.
93 #[serde(default)]
94 cancellation_policy: ChildWorkflowCancellationPolicy,
95 },
96 /// Records the terminal result observed from a child workflow.
97 ChildWorkflowResolved {
98 /// Parent-local identifier from the matching request.
99 child_id: String,
100 /// Terminal outcome returned by the child.
101 outcome: WorkflowTerminalOutcome,
102 },
103 /// Persists one named asynchronous signal.
104 SignalReceived {
105 /// Signal identity, name, payload, and receipt metadata.
106 signal: WorkflowSignal,
107 },
108 /// Creates a replay-stable wait for a named signal.
109 SignalWaitCreated {
110 /// Stable identity of the wait command.
111 wait_id: String,
112 /// Signal contract accepted by the wait.
113 signal_name: String,
114 },
115 /// Pairs a waiting command with one received signal.
116 SignalWaitCompleted {
117 /// Stable identity of the completed wait.
118 wait_id: String,
119 /// Identifier of the signal consumed by the wait.
120 signal_id: String,
121 },
122 /// Creates a durable step invocation.
123 StepCreated {
124 /// Replay-stable identity of the step.
125 step_id: String,
126 /// Registered step implementation name.
127 step_name: String,
128 /// JSON input supplied to the step.
129 input: JsonValue,
130 /// Retry behavior pinned when the step is created.
131 #[serde(default)]
132 retry: RetryPolicy,
133 },
134 /// Marks one step attempt as started.
135 StepStarted {
136 /// Stable identity of the step.
137 step_id: String,
138 /// One-based attempt number.
139 attempt: u32,
140 },
141 /// Records the successful output of a step.
142 StepCompleted {
143 /// Stable identity of the step.
144 step_id: String,
145 /// JSON output returned by the step.
146 output: JsonValue,
147 },
148 /// Records a failed attempt that will be retried.
149 StepRetrying {
150 /// Stable identity of the step.
151 step_id: String,
152 /// Attempt number that failed.
153 attempt: u32,
154 /// Error returned by the attempt.
155 error: String,
156 /// Earliest UTC time for the next attempt, if delayed.
157 retry_after: Option<DateTime<Utc>>,
158 },
159 /// Records a step that exhausted its retry policy.
160 StepFailed {
161 /// Stable identity of the step.
162 step_id: String,
163 /// Final attempt number that failed.
164 attempt: u32,
165 /// Error returned by the final attempt.
166 error: String,
167 },
168 /// Creates a durable timer wait.
169 WaitCreated {
170 /// Replay-stable identity of the wait.
171 wait_id: String,
172 /// UTC time at which the wait becomes ready.
173 resume_at: DateTime<Utc>,
174 },
175 /// Marks a durable timer wait as ready.
176 WaitCompleted {
177 /// Stable identity of the completed wait.
178 wait_id: String,
179 },
180 /// Creates an externally completable hook.
181 HookCreated {
182 /// Replay-stable identity of the hook.
183 hook_id: String,
184 /// Secret bearer token required to deliver the hook.
185 token: String,
186 /// Application metadata persisted with the hook.
187 metadata: JsonValue,
188 },
189 /// Records a payload delivered to a hook.
190 HookReceived {
191 /// Stable identity of the receiving hook.
192 hook_id: String,
193 /// JSON payload supplied by the external caller.
194 payload: JsonValue,
195 },
196 /// Permanently closes a hook without another payload.
197 HookDisposed {
198 /// Stable identity of the disposed hook.
199 hook_id: String,
200 },
201}
202
203impl FlowEvent {
204 /// Dot-separated event key for A3S-wide event routing.
205 pub fn event_key(&self) -> &'static str {
206 match self {
207 Self::RunCreated { .. } => "flow.run.created",
208 Self::RunStarted => "flow.run.started",
209 Self::RunCompleted { .. } => "flow.run.completed",
210 Self::RunFailed { .. } => "flow.run.failed",
211 Self::RunCancellationRequested { .. } => "flow.run.cancellation.requested",
212 Self::RunCancelled { .. } => "flow.run.cancelled",
213 Self::RunTimedOut { .. } => "flow.run.timed_out",
214 Self::RunRetryExhausted { .. } => "flow.run.retry_exhausted",
215 Self::RunHostShutdown { .. } => "flow.run.host_shutdown",
216 Self::RunContinuedAsNew { .. } => "flow.run.continued_as_new",
217 Self::RunProgressRecorded { .. } => "flow.run.progress.recorded",
218 Self::ChildOperationLinked { .. } => "flow.child.operation.linked",
219 Self::ChildWorkflowRequested { .. } => "flow.child.workflow.requested",
220 Self::ChildWorkflowResolved { .. } => "flow.child.workflow.resolved",
221 Self::SignalReceived { .. } => "flow.signal.received",
222 Self::SignalWaitCreated { .. } => "flow.signal.wait.created",
223 Self::SignalWaitCompleted { .. } => "flow.signal.wait.completed",
224 Self::StepCreated { .. } => "flow.step.created",
225 Self::StepStarted { .. } => "flow.step.started",
226 Self::StepCompleted { .. } => "flow.step.completed",
227 Self::StepRetrying { .. } => "flow.step.retrying",
228 Self::StepFailed { .. } => "flow.step.failed",
229 Self::WaitCreated { .. } => "flow.wait.created",
230 Self::WaitCompleted { .. } => "flow.wait.completed",
231 Self::HookCreated { .. } => "flow.hook.created",
232 Self::HookReceived { .. } => "flow.hook.received",
233 Self::HookDisposed { .. } => "flow.hook.disposed",
234 }
235 }
236}
237
238/// Stored event with per-run sequence and timestamp.
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
240#[non_exhaustive]
241pub struct FlowEventEnvelope {
242 /// Run whose history owns this event.
243 pub run_id: String,
244 /// Monotonically increasing per-run sequence number.
245 pub sequence: u64,
246 /// Globally unique identity used for event deduplication.
247 pub event_id: Uuid,
248 /// UTC time at which the event was persisted.
249 pub timestamp: DateTime<Utc>,
250 /// Durable event payload.
251 pub event: FlowEvent,
252}
253
254impl FlowEventEnvelope {
255 /// Create an envelope from the identity assigned by a durable event store.
256 pub fn new(
257 run_id: impl Into<String>,
258 sequence: u64,
259 event_id: Uuid,
260 timestamp: DateTime<Utc>,
261 event: FlowEvent,
262 ) -> Self {
263 Self {
264 run_id: run_id.into(),
265 sequence,
266 event_id,
267 timestamp,
268 event,
269 }
270 }
271}