1use chrono::{DateTime, Duration as ChronoDuration, Utc};
2use std::sync::Arc;
3use uuid::Uuid;
4
5use crate::error::{FlowError, Result};
6use crate::model::{
7 project_run, ActiveHookSnapshot, FlowEvent, FlowEventEnvelope, HookStatus, RetryPolicy,
8 RuntimeCommand, StepFailureAction, StepStatus, WaitStatus, WorkflowRunSnapshot,
9 WorkflowRunSummary, WorkflowRunSuspension, WorkflowSpec,
10};
11use crate::observe::{FlowEventObserver, NoopFlowEventObserver};
12use crate::runtime::{FlowRuntime, StepInvocation, WorkflowInvocation};
13use crate::store::{FlowEventStore, InMemoryEventStore};
14
15mod validation;
16use validation::{
17 ensure_hook_command_matches, ensure_same_start, ensure_step_batch_valid,
18 ensure_step_command_matches, ensure_wait_command_matches, is_event_conflict, validate_run_id,
19};
20
21pub struct FlowEngineBuilder {
23 store: Arc<dyn FlowEventStore>,
24 runtime: Arc<dyn FlowRuntime>,
25 observer: Arc<dyn FlowEventObserver>,
26 max_replay_iterations: usize,
27}
28
29impl FlowEngineBuilder {
30 pub fn new(runtime: Arc<dyn FlowRuntime>) -> Self {
31 Self {
32 store: Arc::new(InMemoryEventStore::new()),
33 runtime,
34 observer: Arc::new(NoopFlowEventObserver),
35 max_replay_iterations: 1024,
36 }
37 }
38
39 pub fn with_store(mut self, store: Arc<dyn FlowEventStore>) -> Self {
40 self.store = store;
41 self
42 }
43
44 pub fn with_observer(mut self, observer: Arc<dyn FlowEventObserver>) -> Self {
45 self.observer = observer;
46 self
47 }
48
49 pub fn with_max_replay_iterations(mut self, max_replay_iterations: usize) -> Self {
50 self.max_replay_iterations = max_replay_iterations.max(1);
51 self
52 }
53
54 pub fn build(self) -> FlowEngine {
55 FlowEngine {
56 store: self.store,
57 runtime: self.runtime,
58 observer: self.observer,
59 max_replay_iterations: self.max_replay_iterations,
60 }
61 }
62}
63
64#[derive(Clone)]
66pub struct FlowEngine {
67 store: Arc<dyn FlowEventStore>,
68 runtime: Arc<dyn FlowRuntime>,
69 observer: Arc<dyn FlowEventObserver>,
70 max_replay_iterations: usize,
71}
72
73impl FlowEngine {
74 pub fn builder(runtime: Arc<dyn FlowRuntime>) -> FlowEngineBuilder {
75 FlowEngineBuilder::new(runtime)
76 }
77
78 pub fn new(store: Arc<dyn FlowEventStore>, runtime: Arc<dyn FlowRuntime>) -> Self {
79 Self {
80 store,
81 runtime,
82 observer: Arc::new(NoopFlowEventObserver),
83 max_replay_iterations: 1024,
84 }
85 }
86
87 pub fn in_memory(runtime: Arc<dyn FlowRuntime>) -> Self {
88 Self::new(Arc::new(InMemoryEventStore::new()), runtime)
89 }
90
91 pub fn store(&self) -> Arc<dyn FlowEventStore> {
92 Arc::clone(&self.store)
93 }
94
95 pub fn observer(&self) -> Arc<dyn FlowEventObserver> {
96 Arc::clone(&self.observer)
97 }
98
99 pub async fn start(&self, spec: WorkflowSpec, input: serde_json::Value) -> Result<String> {
101 let run_id = Uuid::new_v4().to_string();
102 self.start_with_id(run_id, spec, input).await
103 }
104
105 pub async fn start_with_id(
110 &self,
111 run_id: impl Into<String>,
112 spec: WorkflowSpec,
113 input: serde_json::Value,
114 ) -> Result<String> {
115 spec.validate()?;
116 let run_id = run_id.into();
117 validate_run_id(&run_id)?;
118
119 for _ in 0..self.max_replay_iterations {
120 match self.store.list(&run_id).await {
121 Ok(history) => {
122 let snapshot = project_run(&run_id, &history)?;
123 ensure_same_start(&run_id, &snapshot, &spec, &input)?;
124 if !history
125 .iter()
126 .any(|event| matches!(event.event, FlowEvent::RunStarted))
127 {
128 match self
129 .record_event_at(&run_id, snapshot.last_sequence, FlowEvent::RunStarted)
130 .await
131 {
132 Ok(_) => {}
133 Err(err) if is_event_conflict(&err) => continue,
134 Err(err) => return Err(err),
135 }
136 }
137 match self.drive(&run_id).await {
138 Ok(_) => return Ok(run_id),
139 Err(err) if is_event_conflict(&err) => continue,
140 Err(err) => return Err(err),
141 }
142 }
143 Err(FlowError::RunNotFound(_)) => {
144 let created = match self
145 .record_event_at(
146 &run_id,
147 0,
148 FlowEvent::RunCreated {
149 spec: spec.clone(),
150 input: input.clone(),
151 },
152 )
153 .await
154 {
155 Ok(created) => created,
156 Err(err) if is_event_conflict(&err) => continue,
157 Err(err) => return Err(err),
158 };
159 match self
160 .record_event_at(&run_id, created.sequence, FlowEvent::RunStarted)
161 .await
162 {
163 Ok(_) => {}
164 Err(err) if is_event_conflict(&err) => continue,
165 Err(err) => return Err(err),
166 }
167 match self.drive(&run_id).await {
168 Ok(_) => return Ok(run_id),
169 Err(err) if is_event_conflict(&err) => continue,
170 Err(err) => return Err(err),
171 }
172 }
173 Err(err) => return Err(err),
174 }
175 }
176
177 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
178 }
179
180 pub async fn resume_wait(&self, run_id: &str, wait_id: &str) -> Result<()> {
182 for _ in 0..self.max_replay_iterations {
183 let snapshot = self.snapshot(run_id).await?;
184 if snapshot.status.is_terminal() {
185 return Err(FlowError::RunTerminal(run_id.to_string()));
186 }
187 match snapshot.waits.get(wait_id) {
188 Some(wait) if wait.status == WaitStatus::Waiting => {
189 match self
190 .record_event_at(
191 run_id,
192 snapshot.last_sequence,
193 FlowEvent::WaitCompleted {
194 wait_id: wait_id.to_string(),
195 },
196 )
197 .await
198 {
199 Ok(_) => {}
200 Err(err) if is_event_conflict(&err) => continue,
201 Err(err) => return Err(err),
202 }
203 match self.drive(run_id).await {
204 Ok(_) => return Ok(()),
205 Err(err) if is_event_conflict(&err) => continue,
206 Err(err) => return Err(err),
207 }
208 }
209 Some(_) => match self.drive(run_id).await {
210 Ok(_) => return Ok(()),
211 Err(err) if is_event_conflict(&err) => continue,
212 Err(err) => return Err(err),
213 },
214 None => {
215 return Err(FlowError::InvalidTransition(format!(
216 "wait {wait_id} does not exist for run {run_id}"
217 )))
218 }
219 }
220 }
221
222 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
223 }
224
225 pub async fn resume_hook(
227 &self,
228 run_id: &str,
229 hook_id: &str,
230 payload: serde_json::Value,
231 ) -> Result<()> {
232 for _ in 0..self.max_replay_iterations {
233 let snapshot = self.snapshot(run_id).await?;
234 if snapshot.status.is_terminal() {
235 return Err(FlowError::RunTerminal(run_id.to_string()));
236 }
237 match snapshot.hooks.get(hook_id) {
238 Some(hook) if hook.status == HookStatus::Active => {
239 match self
240 .record_event_at(
241 run_id,
242 snapshot.last_sequence,
243 FlowEvent::HookReceived {
244 hook_id: hook_id.to_string(),
245 payload: payload.clone(),
246 },
247 )
248 .await
249 {
250 Ok(_) => {}
251 Err(err) if is_event_conflict(&err) => continue,
252 Err(err) => return Err(err),
253 }
254 match self.drive(run_id).await {
255 Ok(_) => return Ok(()),
256 Err(err) if is_event_conflict(&err) => continue,
257 Err(err) => return Err(err),
258 }
259 }
260 Some(_) => match self.drive(run_id).await {
261 Ok(_) => return Ok(()),
262 Err(err) if is_event_conflict(&err) => continue,
263 Err(err) => return Err(err),
264 },
265 None => {
266 return Err(FlowError::InvalidTransition(format!(
267 "hook {hook_id} does not exist for run {run_id}"
268 )))
269 }
270 }
271 }
272
273 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
274 }
275
276 pub async fn dispose_hook(&self, run_id: &str, hook_id: &str) -> Result<()> {
284 for _ in 0..self.max_replay_iterations {
285 let snapshot = self.snapshot(run_id).await?;
286 if snapshot.status.is_terminal() {
287 return Err(FlowError::RunTerminal(run_id.to_string()));
288 }
289 match snapshot.hooks.get(hook_id) {
290 Some(hook) if hook.status == HookStatus::Active => {
291 match self
292 .record_event_at(
293 run_id,
294 snapshot.last_sequence,
295 FlowEvent::HookDisposed {
296 hook_id: hook_id.to_string(),
297 },
298 )
299 .await
300 {
301 Ok(_) => {}
302 Err(err) if is_event_conflict(&err) => continue,
303 Err(err) => return Err(err),
304 }
305 match self.drive(run_id).await {
306 Ok(_) => return Ok(()),
307 Err(err) if is_event_conflict(&err) => continue,
308 Err(err) => return Err(err),
309 }
310 }
311 Some(_) => match self.drive(run_id).await {
312 Ok(_) => return Ok(()),
313 Err(err) if is_event_conflict(&err) => continue,
314 Err(err) => return Err(err),
315 },
316 None => {
317 return Err(FlowError::InvalidTransition(format!(
318 "hook {hook_id} does not exist for run {run_id}"
319 )))
320 }
321 }
322 }
323
324 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
325 }
326
327 pub async fn resume_hook_by_token(
332 &self,
333 token: &str,
334 payload: serde_json::Value,
335 ) -> Result<(String, String)> {
336 let mut matches = Vec::new();
337 for run_id in self.store.list_run_ids().await? {
338 let snapshot = self.snapshot(&run_id).await?;
339 if snapshot.status.is_terminal() {
340 continue;
341 }
342 for hook in snapshot.hooks.values() {
343 if hook.status == HookStatus::Active && hook.token == token {
344 matches.push((run_id.clone(), hook.hook_id.clone()));
345 }
346 }
347 }
348
349 match matches.len() {
350 0 => Err(FlowError::HookTokenNotFound(token.to_string())),
351 1 => {
352 let (run_id, hook_id) = matches.remove(0);
353 self.resume_hook(&run_id, &hook_id, payload).await?;
354 Ok((run_id, hook_id))
355 }
356 _ => Err(FlowError::InvalidTransition(format!(
357 "hook token {token:?} is active in multiple runs"
358 ))),
359 }
360 }
361
362 pub async fn dispose_hook_by_token(&self, token: &str) -> Result<(String, String)> {
367 let mut matches = Vec::new();
368 for run_id in self.store.list_run_ids().await? {
369 let snapshot = self.snapshot(&run_id).await?;
370 if snapshot.status.is_terminal() {
371 continue;
372 }
373 for hook in snapshot.hooks.values() {
374 if hook.status == HookStatus::Active && hook.token == token {
375 matches.push((run_id.clone(), hook.hook_id.clone()));
376 }
377 }
378 }
379
380 match matches.len() {
381 0 => Err(FlowError::HookTokenNotFound(token.to_string())),
382 1 => {
383 let (run_id, hook_id) = matches.remove(0);
384 self.dispose_hook(&run_id, &hook_id).await?;
385 Ok((run_id, hook_id))
386 }
387 _ => Err(FlowError::InvalidTransition(format!(
388 "hook token {token:?} is active in multiple runs"
389 ))),
390 }
391 }
392
393 pub async fn list_due_waits(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
398 let mut due = Vec::new();
399 for run_id in self.store.list_run_ids().await? {
400 let snapshot = self.snapshot(&run_id).await?;
401 if snapshot.status.is_terminal() {
402 continue;
403 }
404 for wait in snapshot.waits.values() {
405 if wait.status == WaitStatus::Waiting && wait.resume_at <= now {
406 due.push((run_id.clone(), wait.wait_id.clone()));
407 }
408 }
409 }
410 due.sort();
411 Ok(due)
412 }
413
414 pub async fn resume_due_waits(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
419 let due = self.list_due_waits(now).await?;
420 let mut resumed = Vec::with_capacity(due.len());
421 for (run_id, wait_id) in due {
422 self.resume_wait(&run_id, &wait_id).await?;
423 resumed.push((run_id, wait_id));
424 }
425 Ok(resumed)
426 }
427
428 pub async fn list_due_retries(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
430 let mut due = Vec::new();
431 for run_id in self.store.list_run_ids().await? {
432 let snapshot = self.snapshot(&run_id).await?;
433 if snapshot.status.is_terminal() {
434 continue;
435 }
436 for (step_id, _) in snapshot.due_retries(now) {
437 due.push((run_id.clone(), step_id));
438 }
439 }
440 due.sort();
441 Ok(due)
442 }
443
444 pub async fn resume_due_retries(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
446 let due = self.list_due_retries(now).await?;
447 let mut run_ids = Vec::new();
448 for (run_id, _) in &due {
449 if !run_ids.contains(run_id) {
450 run_ids.push(run_id.clone());
451 }
452 }
453 for run_id in run_ids {
454 self.drive_at(&run_id, now).await?;
455 }
456 Ok(due)
457 }
458
459 pub async fn cancel(&self, run_id: &str, reason: Option<String>) -> Result<()> {
460 for _ in 0..self.max_replay_iterations {
461 let snapshot = self.snapshot(run_id).await?;
462 if snapshot.status.is_terminal() {
463 return Ok(());
464 }
465 match self
466 .record_event_at(
467 run_id,
468 snapshot.last_sequence,
469 FlowEvent::RunCancelled {
470 reason: reason.clone(),
471 },
472 )
473 .await
474 {
475 Ok(_) => return Ok(()),
476 Err(err) if is_event_conflict(&err) => continue,
477 Err(err) => return Err(err),
478 }
479 }
480
481 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
482 }
483
484 pub async fn snapshot(&self, run_id: &str) -> Result<WorkflowRunSnapshot> {
485 let history = self.store.list(run_id).await?;
486 project_run(run_id, &history)
487 }
488
489 pub async fn history(&self, run_id: &str) -> Result<Vec<FlowEventEnvelope>> {
490 self.store.list(run_id).await
491 }
492
493 pub async fn list_run_ids(&self) -> Result<Vec<String>> {
494 self.store.list_run_ids().await
495 }
496
497 pub async fn list_snapshots(&self) -> Result<Vec<WorkflowRunSnapshot>> {
498 let mut snapshots = Vec::new();
499 for run_id in self.store.list_run_ids().await? {
500 snapshots.push(self.snapshot(&run_id).await?);
501 }
502 Ok(snapshots)
503 }
504
505 pub async fn run_summary(&self) -> Result<WorkflowRunSummary> {
511 let snapshots = self.list_snapshots().await?;
512 Ok(WorkflowRunSummary::from_snapshots(&snapshots))
513 }
514
515 pub async fn list_open_suspensions(
521 &self,
522 now: DateTime<Utc>,
523 ) -> Result<Vec<WorkflowRunSuspension>> {
524 let mut suspensions = Vec::new();
525 for run_id in self.store.list_run_ids().await? {
526 let snapshot = self.snapshot(&run_id).await?;
527 if snapshot.status.is_terminal() {
528 continue;
529 }
530 for wait in snapshot.waits.values() {
531 if wait.status == WaitStatus::Waiting {
532 suspensions.push(WorkflowRunSuspension::Wait {
533 run_id: run_id.clone(),
534 wait: wait.clone(),
535 due: wait.resume_at <= now,
536 });
537 }
538 }
539 for hook in snapshot.hooks.values() {
540 if hook.status == HookStatus::Active {
541 suspensions.push(WorkflowRunSuspension::Hook {
542 run_id: run_id.clone(),
543 hook: hook.clone(),
544 });
545 }
546 }
547 for step in snapshot.steps.values() {
548 if step.status == StepStatus::Pending {
549 if let Some(retry_after) = step.retry_after {
550 suspensions.push(WorkflowRunSuspension::Retry {
551 run_id: run_id.clone(),
552 step: step.clone(),
553 due: retry_after <= now,
554 });
555 }
556 }
557 }
558 }
559 suspensions.sort_by(|left, right| {
560 (left.run_id(), left.kind_order(), left.subject_id()).cmp(&(
561 right.run_id(),
562 right.kind_order(),
563 right.subject_id(),
564 ))
565 });
566 Ok(suspensions)
567 }
568
569 pub async fn next_wakeup(&self, now: DateTime<Utc>) -> Result<Option<WorkflowRunSuspension>> {
575 let mut wakeups = self.list_open_suspensions(now).await?;
576 wakeups.retain(|suspension| suspension.scheduled_at().is_some());
577 wakeups.sort_by(|left, right| {
578 (
579 left.scheduled_at(),
580 left.run_id(),
581 left.kind_order(),
582 left.subject_id(),
583 )
584 .cmp(&(
585 right.scheduled_at(),
586 right.run_id(),
587 right.kind_order(),
588 right.subject_id(),
589 ))
590 });
591 Ok(wakeups.into_iter().next())
592 }
593
594 pub async fn list_active_hooks(&self) -> Result<Vec<ActiveHookSnapshot>> {
600 let mut hooks = Vec::new();
601 for run_id in self.store.list_run_ids().await? {
602 let snapshot = self.snapshot(&run_id).await?;
603 if snapshot.status.is_terminal() {
604 continue;
605 }
606 for hook in snapshot.hooks.values() {
607 if hook.status == HookStatus::Active {
608 hooks.push(ActiveHookSnapshot {
609 run_id: run_id.clone(),
610 hook: hook.clone(),
611 });
612 }
613 }
614 }
615 hooks.sort_by(|left, right| {
616 (left.run_id.as_str(), left.hook.hook_id.as_str())
617 .cmp(&(right.run_id.as_str(), right.hook.hook_id.as_str()))
618 });
619 Ok(hooks)
620 }
621
622 pub async fn drive(&self, run_id: &str) -> Result<WorkflowRunSnapshot> {
625 self.drive_at(run_id, Utc::now()).await
626 }
627
628 async fn drive_at(&self, run_id: &str, now: DateTime<Utc>) -> Result<WorkflowRunSnapshot> {
629 'replay: for _ in 0..self.max_replay_iterations {
630 let history = self.store.list(run_id).await?;
631 let snapshot = project_run(run_id, &history)?;
632 if snapshot.status.is_terminal()
633 || snapshot
634 .waits
635 .values()
636 .any(|wait| wait.status == WaitStatus::Waiting)
637 || snapshot
638 .hooks
639 .values()
640 .any(|hook| hook.status == HookStatus::Active)
641 || snapshot.has_future_retry(now)
642 {
643 return Ok(snapshot);
644 }
645
646 let command = self
647 .runtime
648 .run_workflow(WorkflowInvocation {
649 run_id: run_id.to_string(),
650 spec: snapshot.spec.clone(),
651 input: snapshot.input.clone(),
652 history,
653 })
654 .await?;
655
656 match command {
657 RuntimeCommand::Complete { output } => {
658 match self
659 .record_event_at(
660 run_id,
661 snapshot.last_sequence,
662 FlowEvent::RunCompleted { output },
663 )
664 .await
665 {
666 Ok(_) => {}
667 Err(err) if is_event_conflict(&err) => continue,
668 Err(err) => return Err(err),
669 }
670 return self.snapshot(run_id).await;
671 }
672 RuntimeCommand::Fail { error } => {
673 match self
674 .record_event_at(
675 run_id,
676 snapshot.last_sequence,
677 FlowEvent::RunFailed { error },
678 )
679 .await
680 {
681 Ok(_) => {}
682 Err(err) if is_event_conflict(&err) => continue,
683 Err(err) => return Err(err),
684 }
685 return self.snapshot(run_id).await;
686 }
687 RuntimeCommand::ScheduleStep {
688 step_id,
689 step_name,
690 input,
691 retry,
692 } => {
693 if let Some(step) = snapshot.steps.get(&step_id) {
694 ensure_step_command_matches(run_id, step, &step_name, &input, retry)?;
695 if matches!(step.status, StepStatus::Completed | StepStatus::Failed) {
696 return Err(FlowError::InvalidTransition(format!(
697 "workflow rescheduled terminal step {step_id} without progress"
698 )));
699 }
700 }
701 match self
702 .execute_step(run_id, &snapshot, step_id, step_name, input, retry)
703 .await
704 {
705 Ok(()) => {}
706 Err(err) if is_event_conflict(&err) => continue,
707 Err(err) => return Err(err),
708 }
709 }
710 RuntimeCommand::ScheduleSteps { steps } => {
711 ensure_step_batch_valid(&steps)?;
712 for step in &steps {
713 if let Some(existing) = snapshot.steps.get(&step.step_id) {
714 ensure_step_command_matches(
715 run_id,
716 existing,
717 &step.step_name,
718 &step.input,
719 step.retry,
720 )?;
721 }
722 }
723 if steps.iter().all(|step| {
724 snapshot.steps.get(&step.step_id).is_some_and(|existing| {
725 matches!(existing.status, StepStatus::Completed | StepStatus::Failed)
726 })
727 }) {
728 let step_ids = steps
729 .iter()
730 .map(|step| step.step_id.as_str())
731 .collect::<Vec<_>>()
732 .join(", ");
733 return Err(FlowError::InvalidTransition(format!(
734 "workflow rescheduled only terminal steps without progress: {step_ids}"
735 )));
736 }
737 for step in steps {
738 let current_snapshot = self.snapshot(run_id).await?;
739 match self
740 .execute_step(
741 run_id,
742 ¤t_snapshot,
743 step.step_id,
744 step.step_name,
745 step.input,
746 step.retry,
747 )
748 .await
749 {
750 Ok(()) => {}
751 Err(err) if is_event_conflict(&err) => continue 'replay,
752 Err(err) => return Err(err),
753 }
754 }
755 }
756 RuntimeCommand::WaitUntil { wait_id, resume_at } => {
757 match snapshot.waits.get(&wait_id) {
758 Some(wait) => {
759 ensure_wait_command_matches(run_id, wait, resume_at)?;
760 if wait.status == WaitStatus::Completed {
761 continue;
762 }
763 return self.snapshot(run_id).await;
764 }
765 None => {
766 match self
767 .record_event_at(
768 run_id,
769 snapshot.last_sequence,
770 FlowEvent::WaitCreated { wait_id, resume_at },
771 )
772 .await
773 {
774 Ok(_) => {}
775 Err(err) if is_event_conflict(&err) => continue,
776 Err(err) => return Err(err),
777 }
778 return self.snapshot(run_id).await;
779 }
780 }
781 }
782 RuntimeCommand::CreateHook {
783 hook_id,
784 token,
785 metadata,
786 } => match snapshot.hooks.get(&hook_id) {
787 Some(hook) => {
788 ensure_hook_command_matches(run_id, hook, &token, &metadata)?;
789 if matches!(hook.status, HookStatus::Received | HookStatus::Disposed) {
790 continue;
791 }
792 return self.snapshot(run_id).await;
793 }
794 None => {
795 self.ensure_hook_token_available(run_id, &hook_id, &token)
796 .await?;
797 match self
798 .record_event_at(
799 run_id,
800 snapshot.last_sequence,
801 FlowEvent::HookCreated {
802 hook_id,
803 token,
804 metadata,
805 },
806 )
807 .await
808 {
809 Ok(_) => {}
810 Err(err) if is_event_conflict(&err) => continue,
811 Err(err) => return Err(err),
812 }
813 return self.snapshot(run_id).await;
814 }
815 },
816 }
817 }
818
819 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
820 }
821
822 async fn record_event_at(
823 &self,
824 run_id: &str,
825 expected_sequence: u64,
826 event: FlowEvent,
827 ) -> Result<FlowEventEnvelope> {
828 let envelope = self
829 .store
830 .append_if_sequence(run_id, expected_sequence, event)
831 .await?;
832 self.observer.observe(envelope.clone()).await;
833 Ok(envelope)
834 }
835
836 async fn ensure_hook_token_available(
837 &self,
838 run_id: &str,
839 hook_id: &str,
840 token: &str,
841 ) -> Result<()> {
842 for existing_run_id in self.store.list_run_ids().await? {
843 let snapshot = self.snapshot(&existing_run_id).await?;
844 if snapshot.status.is_terminal() {
845 continue;
846 }
847 for hook in snapshot.hooks.values() {
848 if hook.status != HookStatus::Active || hook.token != token {
849 continue;
850 }
851 if existing_run_id == run_id && hook.hook_id == hook_id {
852 continue;
853 }
854 return Err(FlowError::HookTokenConflict {
855 token: token.to_string(),
856 existing_run_id,
857 existing_hook_id: hook.hook_id.clone(),
858 });
859 }
860 }
861 Ok(())
862 }
863
864 async fn execute_step(
865 &self,
866 run_id: &str,
867 snapshot: &WorkflowRunSnapshot,
868 step_id: String,
869 step_name: String,
870 input: serde_json::Value,
871 retry: RetryPolicy,
872 ) -> Result<()> {
873 let mut expected_sequence = snapshot.last_sequence;
874 if let Some(step) = snapshot.steps.get(&step_id) {
875 ensure_step_command_matches(run_id, step, &step_name, &input, retry)?;
876 if matches!(step.status, StepStatus::Completed | StepStatus::Failed) {
877 return Ok(());
878 }
879 } else {
880 let envelope = self
881 .record_event_at(
882 run_id,
883 expected_sequence,
884 FlowEvent::StepCreated {
885 step_id: step_id.clone(),
886 step_name: step_name.clone(),
887 input: input.clone(),
888 retry,
889 },
890 )
891 .await?;
892 expected_sequence = envelope.sequence;
893 }
894
895 let max_attempts = retry.max_attempts.max(1);
896 let mut attempt = snapshot
897 .steps
898 .get(&step_id)
899 .map(|step| step.attempt)
900 .unwrap_or(0);
901 let mut redelivering_running_step = snapshot
902 .steps
903 .get(&step_id)
904 .is_some_and(|step| step.status == StepStatus::Running);
905
906 loop {
907 if redelivering_running_step {
908 redelivering_running_step = false;
912 } else {
913 attempt = attempt.checked_add(1).ok_or_else(|| {
914 FlowError::InvalidTransition(format!("step attempt overflowed for {step_id}"))
915 })?;
916 let started = self
917 .record_event_at(
918 run_id,
919 expected_sequence,
920 FlowEvent::StepStarted {
921 step_id: step_id.clone(),
922 attempt,
923 },
924 )
925 .await?;
926 expected_sequence = started.sequence;
927 }
928
929 let history = self.store.list(run_id).await?;
930 let invocation = StepInvocation {
931 run_id: run_id.to_string(),
932 step_id: step_id.clone(),
933 step_name: step_name.clone(),
934 input: input.clone(),
935 history,
936 };
937
938 match self.runtime.run_step(invocation).await {
939 Ok(output) => {
940 self.record_event_at(
941 run_id,
942 expected_sequence,
943 FlowEvent::StepCompleted { step_id, output },
944 )
945 .await?;
946 return Ok(());
947 }
948 Err(err) if attempt < max_attempts => {
949 let retry_after = if retry.delay_ms > 0 {
950 Some(Utc::now() + ChronoDuration::milliseconds(retry.delay_ms as i64))
951 } else {
952 None
953 };
954 let retrying = self
955 .record_event_at(
956 run_id,
957 expected_sequence,
958 FlowEvent::StepRetrying {
959 step_id: step_id.clone(),
960 attempt,
961 error: err.to_string(),
962 retry_after,
963 },
964 )
965 .await?;
966 expected_sequence = retrying.sequence;
967 if retry_after.is_some() {
968 return Ok(());
969 }
970 }
971 Err(err) => {
972 let error = err.to_string();
973 let failed = self
974 .record_event_at(
975 run_id,
976 expected_sequence,
977 FlowEvent::StepFailed {
978 step_id: step_id.clone(),
979 attempt,
980 error: error.clone(),
981 },
982 )
983 .await?;
984 if retry.on_exhausted == StepFailureAction::ContinueWorkflow {
985 return Ok(());
986 }
987 self.record_event_at(run_id, failed.sequence, FlowEvent::RunFailed { error })
988 .await?;
989 return Ok(());
990 }
991 }
992 }
993 }
994}