1use crate::model::*;
13use orbit_ir::{
14 eval, event_instance_key, handler_key, parse_event_key, parse_handler_key, Action, Invocation,
15 Scope, StepIr, WorkflowIr,
16};
17use serde_json::Value;
18
19pub struct StateScope<'a> {
23 pub state: &'a ExecutionState,
24 pub local: Option<(&'a str, &'a Value)>,
25 pub own_output: Option<(&'a str, &'a Value)>,
26 pub failure: Option<Value>,
28 pub event: Option<(String, Value)>,
31}
32
33impl<'a> StateScope<'a> {
34 pub fn new(state: &'a ExecutionState) -> Self {
35 StateScope {
36 state,
37 local: None,
38 own_output: None,
39 failure: None,
40 event: None,
41 }
42 }
43}
44
45impl Scope for StateScope<'_> {
46 fn input(&self) -> &Value {
47 &self.state.input
48 }
49
50 fn step_output(&self, step: &str) -> Option<&Value> {
51 if let Some((s, v)) = self.own_output {
52 if s == step {
53 return Some(v);
54 }
55 }
56 let handler_unit = match (&self.failure, &self.state.handlers, &self.event) {
59 (_, _, Some((instance, _))) => self.state.units.get(&format!("{instance}:{step}")),
60 (Some(_), Some(phase), _) => self.state.units.get(&handler_key(&phase.kind, step)),
61 _ => None,
62 };
63 let found = handler_unit
64 .or_else(|| self.state.units.get(step))
65 .and_then(|u| match u.status() {
66 UnitStatus::Succeeded | UnitStatus::Skipped => u.output.as_ref(),
67 _ => None,
68 });
69 static NULL: Value = Value::Null;
72 match (found, self.failure.is_some() || self.event.is_some()) {
73 (None, true) => Some(&NULL),
74 (found, _) => found,
75 }
76 }
77
78 fn execution_field(&self, field: &str) -> Option<Value> {
79 Some(Value::String(match field {
80 "id" => self.state.id.clone(),
81 "workflow" => self.state.workflow.clone(),
82 "version" => self.state.version.clone(),
83 _ => return None,
84 }))
85 }
86
87 fn local(&self, name: &str) -> Option<&Value> {
88 if name == "failure" {
89 if let Some(f) = &self.failure {
90 return Some(f);
91 }
92 }
93 if name == "event" {
94 if let Some((_, payload)) = &self.event {
95 return Some(payload);
96 }
97 }
98 self.local.filter(|(n, _)| *n == name).map(|(_, v)| v)
99 }
100}
101
102pub fn failure_value(state: &ExecutionState) -> Value {
104 let (reason, code, message) = match &state.abort {
105 Some(AbortReason::Failed { error }) => {
106 ("failed", error.code.clone(), error.message.clone())
107 }
108 Some(AbortReason::Cancelled { reason }) => {
109 ("cancelled", codes::CANCELLED.to_string(), reason.clone())
110 }
111 Some(AbortReason::TimedOut) => (
112 "timed_out",
113 codes::WORKFLOW_TIMEOUT.to_string(),
114 "workflow timed out".to_string(),
115 ),
116 None => ("failed", "UNKNOWN".to_string(), String::new()),
117 };
118 serde_json::json!({ "reason": reason, "code": code, "message": message })
119}
120
121fn event_scope(state: &ExecutionState, key: &str) -> Option<(String, Value)> {
123 let (instance, _, _) = parse_event_key(key)?;
124 let payload = state
125 .units
126 .get(instance)?
127 .output
128 .clone()
129 .unwrap_or(Value::Null);
130 Some((instance.to_string(), payload))
131}
132
133fn handler_step<'a>(def: &'a WorkflowIr, key: &str) -> Option<&'a StepIr> {
135 let (kind, id) = parse_handler_key(key)?;
136 def.handler_step(kind, id)
137}
138
139pub fn invocation<'a>(def: &'a WorkflowIr, key: &str) -> Option<&'a Invocation> {
141 if let Some(step) = handler_step(def, key) {
142 return step.invocation();
143 }
144 if let Some((_, signal, id)) = parse_event_key(key) {
145 return def.event_step(signal, id)?.invocation();
146 }
147 let k = parse_key(key);
148 let step = def.step(k.step)?;
149 if k.compensation {
150 step.compensation.as_ref()
151 } else {
152 step.invocation()
153 }
154}
155
156fn unit(state: &ExecutionState, key: &str) -> UnitState {
157 state.units.get(key).cloned().unwrap_or_default()
158}
159
160fn element<'a>(
162 state: &'a ExecutionState,
163 def: &'a WorkflowIr,
164 key: &str,
165) -> Option<(&'a str, &'a Value)> {
166 let k = parse_key(key);
167 let index = k.index?;
168 let fe = def.step(k.step)?.for_each.as_ref()?;
169 let items = state.units.get(k.step)?.input.as_ref()?.as_array()?;
170 Some((fe.variable.as_str(), items.get(index)?))
171}
172
173fn schedule(state: &ExecutionState, def: &WorkflowIr, key: &str, inv: &Invocation) -> Event {
175 let k = parse_key(key);
176 let mut scope = StateScope::new(state);
177 scope.local = element(state, def, key);
178 if parse_handler_key(key).is_some() {
179 scope.failure = Some(failure_value(state));
180 }
181 scope.event = event_scope(state, key);
182 let element_unit = if k.compensation && k.index.is_some() {
184 key.strip_prefix("compensate:")
185 } else {
186 None
187 };
188 let own = element_unit
189 .and_then(|e| state.units.get(e))
190 .and_then(|u| u.output.as_ref());
191 if let Some(v) = own {
192 scope.own_output = Some((k.step, v));
193 }
194 let fail = |code: &str, msg: String| Event::StepFailed {
195 step: key.into(),
196 error: TaskError::new(code, msg, false),
197 };
198 let mut input = match eval(&inv.input, &scope) {
199 Ok(v) => v,
200 Err(e) => return fail(codes::EXPRESSION_ERROR, format!("evaluating input: {e}")),
201 };
202 if let Some(contract) = def.contracts.get(&inv.task) {
203 contract.input.apply_defaults(&mut input);
204 if let Err(e) = contract.input.validate(&input, false) {
205 return fail(
206 codes::INPUT_CONTRACT_VIOLATION,
207 format!("input for `{}` is invalid at {e}", inv.task),
208 );
209 }
210 }
211 let idempotency_key = match &inv.idempotency_key {
212 Some(expr) => match eval(expr, &scope) {
213 Ok(Value::String(s)) => s,
214 Ok(Value::Null) => default_idempotency_key(state, key),
215 Ok(other) => other.to_string(),
216 Err(e) => {
217 return fail(
218 codes::EXPRESSION_ERROR,
219 format!("evaluating idempotency_key: {e}"),
220 )
221 }
222 },
223 None => default_idempotency_key(state, key),
224 };
225 Event::StepScheduled {
226 step: key.into(),
227 attempt: 1,
228 input,
229 idempotency_key,
230 redelivery: false,
231 }
232}
233
234fn default_idempotency_key(state: &ExecutionState, key: &str) -> String {
237 if state.run > 1 {
238 format!("{}/run{}/{key}", state.id, state.run)
239 } else {
240 format!("{}/{key}", state.id)
241 }
242}
243
244fn after_failure(
246 state: &ExecutionState,
247 key: &str,
248 u: &UnitState,
249 inv: Option<&Invocation>,
250 now: u64,
251) -> Event {
252 let error = u
253 .last_error
254 .clone()
255 .unwrap_or_else(|| TaskError::new("UNKNOWN", "attempt failed", true));
256 let aborting = state.abort.is_some() && !is_cleanup_key(key);
257 if let Some(inv) = inv {
258 let policy = &inv.retry;
259 let retryable = error.retryable && !policy.non_retryable.contains(&error.code);
260 if retryable && !aborting && u.attempt < policy.max_attempts {
261 let next = u.attempt + 1;
262 let delay = policy.delay_before(next);
263 return Event::RetryScheduled {
264 step: key.into(),
265 next_attempt: next,
266 delay_ms: delay,
267 fire_at_ms: now + delay,
268 };
269 }
270 if retryable && u.attempt >= policy.max_attempts && policy.max_attempts > 1 {
271 let mut e = error.clone();
272 e.message = format!("{} (gave up after {} attempts)", e.message, u.attempt);
273 return Event::StepFailed {
274 step: key.into(),
275 error: e,
276 };
277 }
278 }
279 Event::StepFailed {
280 step: key.into(),
281 error,
282 }
283}
284
285fn finish(state: &ExecutionState, outcome: CompensationOutcome) -> Event {
286 match &state.abort {
287 Some(AbortReason::Cancelled { .. }) => Event::WorkflowCancelled {
288 compensation: outcome,
289 },
290 Some(AbortReason::TimedOut) => Event::WorkflowTimedOut {
291 compensation: outcome,
292 },
293 Some(AbortReason::Failed { error }) => Event::WorkflowFailed {
294 error: error.clone(),
295 compensation: outcome,
296 },
297 None => Event::WorkflowFailed {
298 error: TaskError::new("UNKNOWN", "workflow aborted", false),
299 compensation: outcome,
300 },
301 }
302}
303
304pub fn decide(state: &ExecutionState, def: &WorkflowIr, now: u64) -> Vec<Event> {
306 if state.status.is_terminal() {
307 return vec![];
308 }
309 let mut out = vec![];
310
311 if state.status == WorkflowStatus::Running && state.abort.is_none() {
313 if let Some(t) = def.timeout_ms {
314 if now >= state.created_at_ms + t {
315 return vec![Event::WorkflowTimeoutReached {}];
316 }
317 }
318 }
319
320 let mut taken: Vec<u64> = vec![];
323 for (key, u) in &state.units {
324 let inv = invocation(def, key);
325 match u.status() {
326 UnitStatus::Dispatched => {
327 if u.deadline_ms.is_some_and(|d| now >= d) {
328 out.push(Event::StepAttemptFailed {
329 step: key.clone(),
330 attempt: u.attempt,
331 error: TaskError::new(
332 codes::TIMEOUT,
333 format!("attempt {} exceeded its timeout", u.attempt),
334 true,
335 ),
336 });
337 }
338 }
339 UnitStatus::Scheduled => {
340 let qt = inv.and_then(|i| i.queue_timeout_ms);
341 if let (Some(qt), Some(at)) = (qt, u.scheduled_at_ms) {
342 if !u.redelivery && now >= at + qt {
343 out.push(Event::StepAttemptFailed {
344 step: key.clone(),
345 attempt: u.attempt,
346 error: TaskError::new(
347 codes::QUEUE_TIMEOUT,
348 "no worker picked up the task before queue_timeout",
349 false,
350 ),
351 });
352 }
353 }
354 }
355 UnitStatus::Waiting => {
356 if let Some(signal) = awaited_signal(def, key) {
357 let next = state.signals.iter().find(|r| {
359 r.name == signal && r.delivered_to.is_none() && !taken.contains(&r.seq)
360 });
361 if let Some(r) = next {
362 taken.push(r.seq);
363 out.push(Event::SignalDelivered {
364 step: key.clone(),
365 signal_seq: r.seq,
366 });
367 } else if u.wake_at_ms.is_some_and(|w| now >= w) {
368 out.push(Event::StepFailed {
369 step: key.clone(),
370 error: TaskError::new(
371 codes::SIGNAL_TIMEOUT,
372 format!("no `{signal}` signal arrived before the step's timeout"),
373 false,
374 ),
375 });
376 }
377 } else if u.wake_at_ms.is_some_and(|w| now >= w) {
378 out.push(Event::TimerFired { step: key.clone() });
379 }
380 }
381 UnitStatus::RetryWait => {
382 if u.wake_at_ms.is_some_and(|w| now >= w) {
383 if state.abort.is_some() && !is_cleanup_key(key) {
384 out.push(Event::StepFailed {
385 step: key.clone(),
386 error: u.last_error.clone().unwrap_or_else(|| {
387 TaskError::new(codes::CANCELLED, "aborted", false)
388 }),
389 });
390 } else {
391 out.push(Event::StepScheduled {
392 step: key.clone(),
393 attempt: u.attempt,
394 input: u.input.clone().unwrap_or(Value::Null),
395 idempotency_key: u.idempotency_key.clone().unwrap_or_default(),
396 redelivery: false,
397 });
398 }
399 }
400 }
401 UnitStatus::AttemptFailed => out.push(after_failure(state, key, u, inv, now)),
402 UnitStatus::Uncertain => out.push(Event::StepScheduled {
403 step: key.clone(),
404 attempt: u.attempt,
405 input: u.input.clone().unwrap_or(Value::Null),
406 idempotency_key: u.idempotency_key.clone().unwrap_or_default(),
407 redelivery: true,
408 }),
409 _ => {}
410 }
411 }
412 if !out.is_empty() {
413 return out;
414 }
415
416 if let Some(phase) = &state.handlers {
417 return decide_handlers(state, def, phase, now);
418 }
419 match state.status {
420 WorkflowStatus::Running => decide_running(state, def, now),
421 WorkflowStatus::Compensating => decide_compensating(state, def),
422 _ => vec![],
423 }
424}
425
426fn event_instances<'a>(state: &'a ExecutionState, def: &'a WorkflowIr) -> Vec<(String, &'a str)> {
428 state
429 .signals
430 .iter()
431 .filter_map(|r| {
432 let key = r.delivered_to.as_ref()?;
433 let signal = key.strip_prefix("on_event:")?.split_once('@')?.0;
434 def.events
435 .get_key_value(signal)
436 .map(|(k, _)| (key.clone(), k.as_str()))
437 })
438 .collect()
439}
440
441fn awaited_signal<'a>(def: &'a WorkflowIr, key: &str) -> Option<&'a str> {
443 let k = parse_key(key);
444 if k.compensation {
445 return None;
446 }
447 match &def.step(k.step)?.action {
448 Action::Await { signal, .. } => Some(signal),
449 _ => None,
450 }
451}
452
453fn compute(state: &ExecutionState, def: &WorkflowIr, key: &str, expr: &orbit_ir::Expr) -> Event {
455 let mut scope = StateScope::new(state);
456 scope.local = element(state, def, key);
457 if parse_handler_key(key).is_some() {
458 scope.failure = Some(failure_value(state));
459 }
460 scope.event = event_scope(state, key);
461 match eval(expr, &scope) {
462 Ok(output) => Event::StepSucceeded {
463 step: key.into(),
464 attempt: 1,
465 output,
466 },
467 Err(e) => Event::StepFailed {
468 step: key.into(),
469 error: TaskError::new(
470 codes::EXPRESSION_ERROR,
471 format!("evaluating `compute`: {e}"),
472 false,
473 ),
474 },
475 }
476}
477
478fn deps_done(state: &ExecutionState, step: &StepIr) -> bool {
479 step.dependencies.iter().all(|d| {
480 matches!(
481 unit(state, d).status(),
482 UnitStatus::Succeeded | UnitStatus::Skipped
483 )
484 })
485}
486
487fn element_keys(state: &ExecutionState, step: &str) -> Vec<String> {
489 let n = state
490 .units
491 .get(step)
492 .and_then(|u| u.input.as_ref())
493 .and_then(Value::as_array)
494 .map(Vec::len)
495 .unwrap_or(0);
496 (0..n).map(|i| element_key(step, i)).collect()
497}
498
499fn in_flight(status: UnitStatus) -> bool {
500 matches!(
501 status,
502 UnitStatus::Scheduled
503 | UnitStatus::Dispatched
504 | UnitStatus::AttemptFailed
505 | UnitStatus::Uncertain
506 | UnitStatus::RetryWait
507 )
508}
509
510fn advance_fan_out(state: &ExecutionState, def: &WorkflowIr, step: &StepIr, out: &mut Vec<Event>) {
513 let keys = element_keys(state, &step.id);
514 let units: Vec<UnitState> = keys.iter().map(|k| unit(state, k)).collect();
515 if let Some(failed) = units.iter().find(|u| u.status() == UnitStatus::Failed) {
516 out.push(Event::StepFailed {
517 step: step.id.clone(),
518 error: failed
519 .error
520 .clone()
521 .unwrap_or_else(|| TaskError::new("UNKNOWN", "element failed", false)),
522 });
523 return;
524 }
525 if units.iter().all(|u| u.status() == UnitStatus::Succeeded) {
526 let output = Value::Array(
527 units
528 .iter()
529 .map(|u| u.output.clone().unwrap_or(Value::Null))
530 .collect(),
531 );
532 out.push(Event::StepSucceeded {
533 step: step.id.clone(),
534 attempt: 1,
535 output,
536 });
537 return;
538 }
539 if let Action::Compute { expr } = &step.action {
540 for (key, u) in keys.iter().zip(&units) {
541 if u.status() == UnitStatus::Pending {
542 out.push(compute(state, def, key, expr));
543 }
544 }
545 return;
546 }
547 let Some(inv) = step.invocation() else { return };
548 let limit = step
549 .for_each
550 .as_ref()
551 .and_then(|f| f.max_parallel)
552 .map(|n| n as usize)
553 .unwrap_or(usize::MAX);
554 let mut running = units.iter().filter(|u| in_flight(u.status())).count();
555 for (key, u) in keys.iter().zip(&units) {
556 if running >= limit {
557 break;
558 }
559 if u.status() == UnitStatus::Pending {
560 out.push(schedule(state, def, key, inv));
561 running += 1;
562 }
563 }
564}
565
566fn decide_running(state: &ExecutionState, def: &WorkflowIr, now: u64) -> Vec<Event> {
567 let mut out = vec![];
568 if state.abort.is_none() {
569 for step in &def.steps {
570 let u = unit(state, &step.id);
571 if u.status() == UnitStatus::Running {
572 advance_fan_out(state, def, step, &mut out);
573 continue;
574 }
575 if u.status() != UnitStatus::Pending || !deps_done(state, step) {
576 continue;
577 }
578 if let Some(cond) = &step.condition {
579 match eval(cond, &StateScope::new(state)) {
580 Ok(Value::Bool(true)) => {}
581 Ok(Value::Bool(false)) | Ok(Value::Null) => {
582 out.push(Event::StepSkipped {
583 step: step.id.clone(),
584 reason: "`when` condition was false".into(),
585 });
586 continue;
587 }
588 Ok(other) => {
589 out.push(Event::StepFailed {
590 step: step.id.clone(),
591 error: TaskError::new(
592 codes::EXPRESSION_ERROR,
593 format!("`when` evaluated to {other}, expected Bool"),
594 false,
595 ),
596 });
597 continue;
598 }
599 Err(e) => {
600 out.push(Event::StepFailed {
601 step: step.id.clone(),
602 error: TaskError::new(
603 codes::EXPRESSION_ERROR,
604 format!("evaluating `when`: {e}"),
605 false,
606 ),
607 });
608 continue;
609 }
610 }
611 }
612 if let Some(fe) = &step.for_each {
613 match eval(&fe.items, &StateScope::new(state)) {
614 Ok(Value::Array(items)) => out.push(Event::StepFanOut {
615 step: step.id.clone(),
616 items,
617 }),
618 Ok(Value::Null) => out.push(Event::StepFanOut {
619 step: step.id.clone(),
620 items: vec![],
621 }),
622 Ok(other) => out.push(Event::StepFailed {
623 step: step.id.clone(),
624 error: TaskError::new(
625 codes::EXPRESSION_ERROR,
626 format!("`for` expected a list, got {other}"),
627 false,
628 ),
629 }),
630 Err(e) => out.push(Event::StepFailed {
631 step: step.id.clone(),
632 error: TaskError::new(
633 codes::EXPRESSION_ERROR,
634 format!("evaluating `for`: {e}"),
635 false,
636 ),
637 }),
638 }
639 continue;
640 }
641 match &step.action {
642 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
643 step: step.id.clone(),
644 fire_at_ms: now + duration_ms,
645 }),
646 Action::Task(inv) | Action::Workflow(inv) => {
647 out.push(schedule(state, def, &step.id, inv))
648 }
649 Action::Await { signal, timeout_ms } => out.push(Event::SignalAwaitStarted {
650 step: step.id.clone(),
651 signal: signal.clone(),
652 deadline_ms: timeout_ms.map(|t| now + t),
653 }),
654 Action::Compute { expr } => out.push(compute(state, def, &step.id, expr)),
655 }
656 }
657 for r in &state.signals {
660 if r.delivered_to.is_none() && def.events.contains_key(&r.name) {
661 out.push(Event::SignalDelivered {
662 step: event_instance_key(&r.name, r.seq),
663 signal_seq: r.seq,
664 });
665 }
666 }
667 let mut events_done = true;
668 for (instance, signal) in event_instances(state, def) {
669 let steps = &def.events[signal];
670 for step in steps {
671 let key = format!("{instance}:{}", step.id);
672 let u = unit(state, &key);
673 if u.status().is_terminal() {
674 continue;
675 }
676 events_done = false;
677 if u.status() != UnitStatus::Pending {
678 continue;
679 }
680 let deps: Vec<UnitStatus> = step
681 .dependencies
682 .iter()
683 .map(|d| unit(state, &format!("{instance}:{d}")).status())
684 .collect();
685 if !deps
686 .iter()
687 .all(|s| matches!(s, UnitStatus::Succeeded | UnitStatus::Skipped))
688 {
689 continue;
690 }
691 let mut scope = StateScope::new(state);
692 scope.event = event_scope(state, &key);
693 if let Some(cond) = &step.condition {
694 match eval(cond, &scope) {
695 Ok(Value::Bool(true)) => {}
696 Ok(Value::Bool(false)) | Ok(Value::Null) => {
697 out.push(Event::StepSkipped {
698 step: key,
699 reason: "`when` condition was false".into(),
700 });
701 continue;
702 }
703 Ok(other) => {
704 out.push(Event::StepFailed {
705 step: key,
706 error: TaskError::new(
707 codes::EXPRESSION_ERROR,
708 format!("`when` evaluated to {other}, expected Bool"),
709 false,
710 ),
711 });
712 continue;
713 }
714 Err(e) => {
715 out.push(Event::StepFailed {
716 step: key,
717 error: TaskError::new(
718 codes::EXPRESSION_ERROR,
719 format!("evaluating `when`: {e}"),
720 false,
721 ),
722 });
723 continue;
724 }
725 }
726 }
727 match &step.action {
728 Action::Task(inv) | Action::Workflow(inv) => {
729 out.push(schedule(state, def, &key, inv))
730 }
731 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
732 step: key,
733 fire_at_ms: now + duration_ms,
734 }),
735 Action::Compute { expr } => out.push(compute(state, def, &key, expr)),
736 Action::Await { .. } => out.push(Event::StepFailed {
737 step: key,
738 error: TaskError::new(
739 "UNKNOWN",
740 "`await` is not allowed in an `on event` step",
741 false,
742 ),
743 }),
744 }
745 }
746 }
747 if !out.is_empty() {
748 return out;
749 }
750 let all_done = events_done
751 && def
752 .steps
753 .iter()
754 .all(|s| unit(state, &s.id).status().is_terminal());
755 if all_done {
756 if let Some(event) = continue_as_new(state, def) {
757 return vec![event];
758 }
759 let output = match &def.result {
760 Some(expr) => match eval(expr, &StateScope::new(state)) {
761 Ok(v) => v,
762 Err(e) => {
763 return vec![Event::WorkflowFailed {
764 error: TaskError::new(
765 codes::EXPRESSION_ERROR,
766 format!("evaluating `return`: {e}"),
767 false,
768 ),
769 compensation: CompensationOutcome::None,
770 }]
771 }
772 },
773 None => Value::Object(Default::default()),
774 };
775 if let Err(e) = def.output.validate(&output, false) {
776 return vec![Event::WorkflowFailed {
777 error: TaskError::new(
778 codes::OUTPUT_CONTRACT_VIOLATION,
779 format!("workflow output is invalid at {e}"),
780 false,
781 ),
782 compensation: CompensationOutcome::None,
783 }];
784 }
785 return vec![Event::WorkflowCompleted { output }];
786 }
787 return out;
788 }
789
790 let mut waiting = false;
793 let withdraw =
794 |key: &str, u: &UnitState, out: &mut Vec<Event>, waiting: &mut bool| match u.status() {
795 UnitStatus::Pending | UnitStatus::Waiting | UnitStatus::RetryWait => {
796 out.push(Event::StepCancelled { step: key.into() })
797 }
798 UnitStatus::Scheduled if !u.redelivery => {
799 out.push(Event::StepCancelled { step: key.into() })
800 }
801 UnitStatus::Scheduled
802 | UnitStatus::Dispatched
803 | UnitStatus::AttemptFailed
804 | UnitStatus::Uncertain => *waiting = true,
805 _ => {}
806 };
807 for step in &def.steps {
808 let u = unit(state, &step.id);
809 if u.status() == UnitStatus::Running {
810 let mut element_waiting = false;
811 let mut failed = None;
812 for key in element_keys(state, &step.id) {
813 let eu = unit(state, &key);
814 if eu.status() == UnitStatus::Failed && failed.is_none() {
815 failed = eu.error.clone();
816 }
817 withdraw(&key, &eu, &mut out, &mut element_waiting);
818 }
819 if element_waiting {
820 waiting = true;
821 } else {
822 out.push(match failed {
824 Some(error) => Event::StepFailed {
825 step: step.id.clone(),
826 error,
827 },
828 None => Event::StepCancelled {
829 step: step.id.clone(),
830 },
831 });
832 }
833 } else {
834 withdraw(&step.id, &u, &mut out, &mut waiting);
835 }
836 }
837 for (instance, signal) in event_instances(state, def) {
838 for step in &def.events[signal] {
839 let key = format!("{instance}:{}", step.id);
840 let u = unit(state, &key);
841 withdraw(&key, &u, &mut out, &mut waiting);
842 }
843 }
844 if !out.is_empty() || waiting {
845 return out;
846 }
847 let mut done: Vec<(String, u64)> = vec![];
850 for s in def.steps.iter().filter(|s| s.compensation.is_some()) {
851 let keys = if s.for_each.is_some() {
852 element_keys(state, &s.id)
853 } else {
854 vec![s.id.clone()]
855 };
856 for key in keys {
857 if let Some(u) = state.units.get(&key) {
858 if u.status() == UnitStatus::Succeeded {
859 done.push((key, u.succeeded_seq.unwrap_or(0)));
860 }
861 }
862 }
863 }
864 done.sort_by_key(|d| std::cmp::Reverse(d.1));
865 if done.is_empty() {
866 return conclude(state, def, CompensationOutcome::None);
867 }
868 let reason = match &state.abort {
869 Some(AbortReason::Failed { error }) => error.message.clone(),
870 Some(AbortReason::Cancelled { reason }) => format!("cancelled: {reason}"),
871 Some(AbortReason::TimedOut) => "workflow timed out".into(),
872 None => String::new(),
873 };
874 vec![Event::CompensationStarted {
875 reason,
876 steps: done.into_iter().map(|(s, _)| s).collect(),
877 }]
878}
879
880fn continue_as_new(state: &ExecutionState, def: &WorkflowIr) -> Option<Event> {
884 let can = def.continue_as_new.as_ref()?;
885 let scope = StateScope::new(state);
886 let fail = |code: &str, message: String| Event::WorkflowFailed {
887 error: TaskError::new(code, message, false),
888 compensation: CompensationOutcome::None,
889 };
890 if let Some(cond) = &can.condition {
891 match eval(cond, &scope) {
892 Ok(Value::Bool(true)) => {}
893 Ok(Value::Bool(false)) | Ok(Value::Null) => return None,
894 Ok(other) => {
895 return Some(fail(
896 codes::EXPRESSION_ERROR,
897 format!("the `continue_as_new` condition evaluated to {other}, expected Bool"),
898 ))
899 }
900 Err(e) => {
901 return Some(fail(
902 codes::EXPRESSION_ERROR,
903 format!("evaluating the `continue_as_new` condition: {e}"),
904 ))
905 }
906 }
907 }
908 let mut input = match eval(&can.input, &scope) {
909 Ok(v) => v,
910 Err(e) => {
911 return Some(fail(
912 codes::EXPRESSION_ERROR,
913 format!("evaluating the `continue_as_new` input: {e}"),
914 ))
915 }
916 };
917 def.input.apply_defaults(&mut input);
918 if let Err(e) = def.input.validate(&input, false) {
919 return Some(fail(
920 codes::INPUT_CONTRACT_VIOLATION,
921 format!("the `continue_as_new` input is invalid at {e}"),
922 ));
923 }
924 Some(Event::WorkflowContinuedAsNew {
925 next_run: state.run + 1,
926 input,
927 })
928}
929
930fn decide_compensating(state: &ExecutionState, def: &WorkflowIr) -> Vec<Event> {
931 let mut failures = vec![];
935 for step in &state.compensation_order {
936 let key = compensation_key(step);
937 let u = unit(state, &key);
938 match u.status() {
939 UnitStatus::Succeeded => continue,
940 UnitStatus::Failed | UnitStatus::Cancelled => {
941 let error = u
942 .error
943 .unwrap_or_else(|| TaskError::new("UNKNOWN", "compensation failed", false));
944 failures.push(Event::CompensationFailed {
945 step: step.clone(),
946 error,
947 });
948 continue;
949 }
950 UnitStatus::Pending => {
951 return match invocation(def, &key) {
952 Some(inv) => vec![schedule(state, def, &key, inv)],
953 None => vec![Event::StepFailed {
954 step: key,
955 error: TaskError::new("UNKNOWN", "missing compensation", false),
956 }],
957 };
958 }
959 _ => return vec![],
960 }
961 }
962 if !failures.is_empty() {
963 failures.extend(conclude(state, def, CompensationOutcome::Failed));
964 return failures;
965 }
966 let mut out = vec![Event::CompensationCompleted {}];
967 out.extend(conclude(state, def, CompensationOutcome::Completed));
968 out
969}
970
971fn handler_kind(state: &ExecutionState) -> &'static str {
973 match &state.abort {
974 Some(AbortReason::Cancelled { .. }) => "cancel",
975 Some(AbortReason::TimedOut) => "timeout",
976 _ => "failure",
977 }
978}
979
980fn conclude(state: &ExecutionState, def: &WorkflowIr, outcome: CompensationOutcome) -> Vec<Event> {
982 let kind = handler_kind(state);
983 match def.handlers.get(kind) {
984 Some(steps) if !steps.is_empty() => vec![Event::HandlersStarted {
985 kind: kind.to_string(),
986 compensation: outcome,
987 }],
988 _ => vec![finish(state, outcome)],
989 }
990}
991
992fn decide_handlers(
996 state: &ExecutionState,
997 def: &WorkflowIr,
998 phase: &HandlerPhase,
999 now: u64,
1000) -> Vec<Event> {
1001 let steps = def
1002 .handlers
1003 .get(&phase.kind)
1004 .map(Vec::as_slice)
1005 .unwrap_or(&[]);
1006 let mut out = vec![];
1007 let mut all_done = true;
1008 for step in steps {
1009 let key = handler_key(&phase.kind, &step.id);
1010 let u = unit(state, &key);
1011 if u.status().is_terminal() {
1012 continue;
1013 }
1014 all_done = false;
1015 if u.status() != UnitStatus::Pending {
1016 continue;
1017 }
1018 let dep_status: Vec<UnitStatus> = step
1019 .dependencies
1020 .iter()
1021 .map(|d| unit(state, &handler_key(&phase.kind, d)).status())
1022 .collect();
1023 if dep_status
1024 .iter()
1025 .any(|s| matches!(s, UnitStatus::Failed | UnitStatus::Cancelled))
1026 {
1027 out.push(Event::StepCancelled { step: key });
1028 continue;
1029 }
1030 if !dep_status
1031 .iter()
1032 .all(|s| matches!(s, UnitStatus::Succeeded | UnitStatus::Skipped))
1033 {
1034 continue;
1035 }
1036 let mut scope = StateScope::new(state);
1037 scope.failure = Some(failure_value(state));
1038 if let Some(cond) = &step.condition {
1039 match eval(cond, &scope) {
1040 Ok(Value::Bool(true)) => {}
1041 Ok(Value::Bool(false)) | Ok(Value::Null) => {
1042 out.push(Event::StepSkipped {
1043 step: key,
1044 reason: "`when` condition was false".into(),
1045 });
1046 continue;
1047 }
1048 Ok(other) => {
1049 out.push(Event::StepFailed {
1050 step: key,
1051 error: TaskError::new(
1052 codes::EXPRESSION_ERROR,
1053 format!("`when` evaluated to {other}, expected Bool"),
1054 false,
1055 ),
1056 });
1057 continue;
1058 }
1059 Err(e) => {
1060 out.push(Event::StepFailed {
1061 step: key,
1062 error: TaskError::new(
1063 codes::EXPRESSION_ERROR,
1064 format!("evaluating `when`: {e}"),
1065 false,
1066 ),
1067 });
1068 continue;
1069 }
1070 }
1071 }
1072 match &step.action {
1073 Action::Task(inv) | Action::Workflow(inv) => out.push(schedule(state, def, &key, inv)),
1074 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
1075 step: key,
1076 fire_at_ms: now + duration_ms,
1077 }),
1078 Action::Compute { expr } => out.push(compute(state, def, &key, expr)),
1079 Action::Await { .. } => out.push(Event::StepFailed {
1080 step: key,
1081 error: TaskError::new("UNKNOWN", "`await` is not allowed in a handler", false),
1082 }),
1083 }
1084 }
1085 if all_done && out.is_empty() {
1086 return vec![finish(state, phase.compensation)];
1087 }
1088 out
1089}