Skip to main content

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}