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) => format!("{}/{key}", state.id),
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 => format!("{}/{key}", state.id),
224 };
225 Event::StepScheduled {
226 step: key.into(),
227 attempt: 1,
228 input,
229 idempotency_key,
230 redelivery: false,
231 }
232}
233
234fn after_failure(
236 state: &ExecutionState,
237 key: &str,
238 u: &UnitState,
239 inv: Option<&Invocation>,
240 now: u64,
241) -> Event {
242 let error = u
243 .last_error
244 .clone()
245 .unwrap_or_else(|| TaskError::new("UNKNOWN", "attempt failed", true));
246 let aborting = state.abort.is_some() && !is_cleanup_key(key);
247 if let Some(inv) = inv {
248 let policy = &inv.retry;
249 let retryable = error.retryable && !policy.non_retryable.contains(&error.code);
250 if retryable && !aborting && u.attempt < policy.max_attempts {
251 let next = u.attempt + 1;
252 let delay = policy.delay_before(next);
253 return Event::RetryScheduled {
254 step: key.into(),
255 next_attempt: next,
256 delay_ms: delay,
257 fire_at_ms: now + delay,
258 };
259 }
260 if retryable && u.attempt >= policy.max_attempts && policy.max_attempts > 1 {
261 let mut e = error.clone();
262 e.message = format!("{} (gave up after {} attempts)", e.message, u.attempt);
263 return Event::StepFailed {
264 step: key.into(),
265 error: e,
266 };
267 }
268 }
269 Event::StepFailed {
270 step: key.into(),
271 error,
272 }
273}
274
275fn finish(state: &ExecutionState, outcome: CompensationOutcome) -> Event {
276 match &state.abort {
277 Some(AbortReason::Cancelled { .. }) => Event::WorkflowCancelled {
278 compensation: outcome,
279 },
280 Some(AbortReason::TimedOut) => Event::WorkflowTimedOut {
281 compensation: outcome,
282 },
283 Some(AbortReason::Failed { error }) => Event::WorkflowFailed {
284 error: error.clone(),
285 compensation: outcome,
286 },
287 None => Event::WorkflowFailed {
288 error: TaskError::new("UNKNOWN", "workflow aborted", false),
289 compensation: outcome,
290 },
291 }
292}
293
294pub fn decide(state: &ExecutionState, def: &WorkflowIr, now: u64) -> Vec<Event> {
296 if state.status.is_terminal() {
297 return vec![];
298 }
299 let mut out = vec![];
300
301 if state.status == WorkflowStatus::Running && state.abort.is_none() {
303 if let Some(t) = def.timeout_ms {
304 if now >= state.created_at_ms + t {
305 return vec![Event::WorkflowTimeoutReached {}];
306 }
307 }
308 }
309
310 let mut taken: Vec<u64> = vec![];
313 for (key, u) in &state.units {
314 let inv = invocation(def, key);
315 match u.status() {
316 UnitStatus::Dispatched => {
317 if u.deadline_ms.is_some_and(|d| now >= d) {
318 out.push(Event::StepAttemptFailed {
319 step: key.clone(),
320 attempt: u.attempt,
321 error: TaskError::new(
322 codes::TIMEOUT,
323 format!("attempt {} exceeded its timeout", u.attempt),
324 true,
325 ),
326 });
327 }
328 }
329 UnitStatus::Scheduled => {
330 let qt = inv.and_then(|i| i.queue_timeout_ms);
331 if let (Some(qt), Some(at)) = (qt, u.scheduled_at_ms) {
332 if !u.redelivery && now >= at + qt {
333 out.push(Event::StepAttemptFailed {
334 step: key.clone(),
335 attempt: u.attempt,
336 error: TaskError::new(
337 codes::QUEUE_TIMEOUT,
338 "no worker picked up the task before queue_timeout",
339 false,
340 ),
341 });
342 }
343 }
344 }
345 UnitStatus::Waiting => {
346 if let Some(signal) = awaited_signal(def, key) {
347 let next = state.signals.iter().find(|r| {
349 r.name == signal && r.delivered_to.is_none() && !taken.contains(&r.seq)
350 });
351 if let Some(r) = next {
352 taken.push(r.seq);
353 out.push(Event::SignalDelivered {
354 step: key.clone(),
355 signal_seq: r.seq,
356 });
357 } else if u.wake_at_ms.is_some_and(|w| now >= w) {
358 out.push(Event::StepFailed {
359 step: key.clone(),
360 error: TaskError::new(
361 codes::SIGNAL_TIMEOUT,
362 format!("no `{signal}` signal arrived before the step's timeout"),
363 false,
364 ),
365 });
366 }
367 } else if u.wake_at_ms.is_some_and(|w| now >= w) {
368 out.push(Event::TimerFired { step: key.clone() });
369 }
370 }
371 UnitStatus::RetryWait => {
372 if u.wake_at_ms.is_some_and(|w| now >= w) {
373 if state.abort.is_some() && !is_cleanup_key(key) {
374 out.push(Event::StepFailed {
375 step: key.clone(),
376 error: u.last_error.clone().unwrap_or_else(|| {
377 TaskError::new(codes::CANCELLED, "aborted", false)
378 }),
379 });
380 } else {
381 out.push(Event::StepScheduled {
382 step: key.clone(),
383 attempt: u.attempt,
384 input: u.input.clone().unwrap_or(Value::Null),
385 idempotency_key: u.idempotency_key.clone().unwrap_or_default(),
386 redelivery: false,
387 });
388 }
389 }
390 }
391 UnitStatus::AttemptFailed => out.push(after_failure(state, key, u, inv, now)),
392 UnitStatus::Uncertain => out.push(Event::StepScheduled {
393 step: key.clone(),
394 attempt: u.attempt,
395 input: u.input.clone().unwrap_or(Value::Null),
396 idempotency_key: u.idempotency_key.clone().unwrap_or_default(),
397 redelivery: true,
398 }),
399 _ => {}
400 }
401 }
402 if !out.is_empty() {
403 return out;
404 }
405
406 if let Some(phase) = &state.handlers {
407 return decide_handlers(state, def, phase, now);
408 }
409 match state.status {
410 WorkflowStatus::Running => decide_running(state, def, now),
411 WorkflowStatus::Compensating => decide_compensating(state, def),
412 _ => vec![],
413 }
414}
415
416fn event_instances<'a>(state: &'a ExecutionState, def: &'a WorkflowIr) -> Vec<(String, &'a str)> {
418 state
419 .signals
420 .iter()
421 .filter_map(|r| {
422 let key = r.delivered_to.as_ref()?;
423 let signal = key.strip_prefix("on_event:")?.split_once('@')?.0;
424 def.events
425 .get_key_value(signal)
426 .map(|(k, _)| (key.clone(), k.as_str()))
427 })
428 .collect()
429}
430
431fn awaited_signal<'a>(def: &'a WorkflowIr, key: &str) -> Option<&'a str> {
433 let k = parse_key(key);
434 if k.compensation {
435 return None;
436 }
437 match &def.step(k.step)?.action {
438 Action::Await { signal, .. } => Some(signal),
439 _ => None,
440 }
441}
442
443fn compute(state: &ExecutionState, def: &WorkflowIr, key: &str, expr: &orbit_ir::Expr) -> Event {
445 let mut scope = StateScope::new(state);
446 scope.local = element(state, def, key);
447 if parse_handler_key(key).is_some() {
448 scope.failure = Some(failure_value(state));
449 }
450 scope.event = event_scope(state, key);
451 match eval(expr, &scope) {
452 Ok(output) => Event::StepSucceeded {
453 step: key.into(),
454 attempt: 1,
455 output,
456 },
457 Err(e) => Event::StepFailed {
458 step: key.into(),
459 error: TaskError::new(
460 codes::EXPRESSION_ERROR,
461 format!("evaluating `compute`: {e}"),
462 false,
463 ),
464 },
465 }
466}
467
468fn deps_done(state: &ExecutionState, step: &StepIr) -> bool {
469 step.dependencies.iter().all(|d| {
470 matches!(
471 unit(state, d).status(),
472 UnitStatus::Succeeded | UnitStatus::Skipped
473 )
474 })
475}
476
477fn element_keys(state: &ExecutionState, step: &str) -> Vec<String> {
479 let n = state
480 .units
481 .get(step)
482 .and_then(|u| u.input.as_ref())
483 .and_then(Value::as_array)
484 .map(Vec::len)
485 .unwrap_or(0);
486 (0..n).map(|i| element_key(step, i)).collect()
487}
488
489fn in_flight(status: UnitStatus) -> bool {
490 matches!(
491 status,
492 UnitStatus::Scheduled
493 | UnitStatus::Dispatched
494 | UnitStatus::AttemptFailed
495 | UnitStatus::Uncertain
496 | UnitStatus::RetryWait
497 )
498}
499
500fn advance_fan_out(state: &ExecutionState, def: &WorkflowIr, step: &StepIr, out: &mut Vec<Event>) {
503 let keys = element_keys(state, &step.id);
504 let units: Vec<UnitState> = keys.iter().map(|k| unit(state, k)).collect();
505 if let Some(failed) = units.iter().find(|u| u.status() == UnitStatus::Failed) {
506 out.push(Event::StepFailed {
507 step: step.id.clone(),
508 error: failed
509 .error
510 .clone()
511 .unwrap_or_else(|| TaskError::new("UNKNOWN", "element failed", false)),
512 });
513 return;
514 }
515 if units.iter().all(|u| u.status() == UnitStatus::Succeeded) {
516 let output = Value::Array(
517 units
518 .iter()
519 .map(|u| u.output.clone().unwrap_or(Value::Null))
520 .collect(),
521 );
522 out.push(Event::StepSucceeded {
523 step: step.id.clone(),
524 attempt: 1,
525 output,
526 });
527 return;
528 }
529 if let Action::Compute { expr } = &step.action {
530 for (key, u) in keys.iter().zip(&units) {
531 if u.status() == UnitStatus::Pending {
532 out.push(compute(state, def, key, expr));
533 }
534 }
535 return;
536 }
537 let Some(inv) = step.invocation() else { return };
538 let limit = step
539 .for_each
540 .as_ref()
541 .and_then(|f| f.max_parallel)
542 .map(|n| n as usize)
543 .unwrap_or(usize::MAX);
544 let mut running = units.iter().filter(|u| in_flight(u.status())).count();
545 for (key, u) in keys.iter().zip(&units) {
546 if running >= limit {
547 break;
548 }
549 if u.status() == UnitStatus::Pending {
550 out.push(schedule(state, def, key, inv));
551 running += 1;
552 }
553 }
554}
555
556fn decide_running(state: &ExecutionState, def: &WorkflowIr, now: u64) -> Vec<Event> {
557 let mut out = vec![];
558 if state.abort.is_none() {
559 for step in &def.steps {
560 let u = unit(state, &step.id);
561 if u.status() == UnitStatus::Running {
562 advance_fan_out(state, def, step, &mut out);
563 continue;
564 }
565 if u.status() != UnitStatus::Pending || !deps_done(state, step) {
566 continue;
567 }
568 if let Some(cond) = &step.condition {
569 match eval(cond, &StateScope::new(state)) {
570 Ok(Value::Bool(true)) => {}
571 Ok(Value::Bool(false)) | Ok(Value::Null) => {
572 out.push(Event::StepSkipped {
573 step: step.id.clone(),
574 reason: "`when` condition was false".into(),
575 });
576 continue;
577 }
578 Ok(other) => {
579 out.push(Event::StepFailed {
580 step: step.id.clone(),
581 error: TaskError::new(
582 codes::EXPRESSION_ERROR,
583 format!("`when` evaluated to {other}, expected Bool"),
584 false,
585 ),
586 });
587 continue;
588 }
589 Err(e) => {
590 out.push(Event::StepFailed {
591 step: step.id.clone(),
592 error: TaskError::new(
593 codes::EXPRESSION_ERROR,
594 format!("evaluating `when`: {e}"),
595 false,
596 ),
597 });
598 continue;
599 }
600 }
601 }
602 if let Some(fe) = &step.for_each {
603 match eval(&fe.items, &StateScope::new(state)) {
604 Ok(Value::Array(items)) => out.push(Event::StepFanOut {
605 step: step.id.clone(),
606 items,
607 }),
608 Ok(Value::Null) => out.push(Event::StepFanOut {
609 step: step.id.clone(),
610 items: vec![],
611 }),
612 Ok(other) => out.push(Event::StepFailed {
613 step: step.id.clone(),
614 error: TaskError::new(
615 codes::EXPRESSION_ERROR,
616 format!("`for` expected a list, got {other}"),
617 false,
618 ),
619 }),
620 Err(e) => out.push(Event::StepFailed {
621 step: step.id.clone(),
622 error: TaskError::new(
623 codes::EXPRESSION_ERROR,
624 format!("evaluating `for`: {e}"),
625 false,
626 ),
627 }),
628 }
629 continue;
630 }
631 match &step.action {
632 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
633 step: step.id.clone(),
634 fire_at_ms: now + duration_ms,
635 }),
636 Action::Task(inv) | Action::Workflow(inv) => {
637 out.push(schedule(state, def, &step.id, inv))
638 }
639 Action::Await { signal, timeout_ms } => out.push(Event::SignalAwaitStarted {
640 step: step.id.clone(),
641 signal: signal.clone(),
642 deadline_ms: timeout_ms.map(|t| now + t),
643 }),
644 Action::Compute { expr } => out.push(compute(state, def, &step.id, expr)),
645 }
646 }
647 for r in &state.signals {
650 if r.delivered_to.is_none() && def.events.contains_key(&r.name) {
651 out.push(Event::SignalDelivered {
652 step: event_instance_key(&r.name, r.seq),
653 signal_seq: r.seq,
654 });
655 }
656 }
657 let mut events_done = true;
658 for (instance, signal) in event_instances(state, def) {
659 let steps = &def.events[signal];
660 for step in steps {
661 let key = format!("{instance}:{}", step.id);
662 let u = unit(state, &key);
663 if u.status().is_terminal() {
664 continue;
665 }
666 events_done = false;
667 if u.status() != UnitStatus::Pending {
668 continue;
669 }
670 let deps: Vec<UnitStatus> = step
671 .dependencies
672 .iter()
673 .map(|d| unit(state, &format!("{instance}:{d}")).status())
674 .collect();
675 if !deps
676 .iter()
677 .all(|s| matches!(s, UnitStatus::Succeeded | UnitStatus::Skipped))
678 {
679 continue;
680 }
681 let mut scope = StateScope::new(state);
682 scope.event = event_scope(state, &key);
683 if let Some(cond) = &step.condition {
684 match eval(cond, &scope) {
685 Ok(Value::Bool(true)) => {}
686 Ok(Value::Bool(false)) | Ok(Value::Null) => {
687 out.push(Event::StepSkipped {
688 step: key,
689 reason: "`when` condition was false".into(),
690 });
691 continue;
692 }
693 Ok(other) => {
694 out.push(Event::StepFailed {
695 step: key,
696 error: TaskError::new(
697 codes::EXPRESSION_ERROR,
698 format!("`when` evaluated to {other}, expected Bool"),
699 false,
700 ),
701 });
702 continue;
703 }
704 Err(e) => {
705 out.push(Event::StepFailed {
706 step: key,
707 error: TaskError::new(
708 codes::EXPRESSION_ERROR,
709 format!("evaluating `when`: {e}"),
710 false,
711 ),
712 });
713 continue;
714 }
715 }
716 }
717 match &step.action {
718 Action::Task(inv) | Action::Workflow(inv) => {
719 out.push(schedule(state, def, &key, inv))
720 }
721 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
722 step: key,
723 fire_at_ms: now + duration_ms,
724 }),
725 Action::Compute { expr } => out.push(compute(state, def, &key, expr)),
726 Action::Await { .. } => out.push(Event::StepFailed {
727 step: key,
728 error: TaskError::new(
729 "UNKNOWN",
730 "`await` is not allowed in an `on event` step",
731 false,
732 ),
733 }),
734 }
735 }
736 }
737 if !out.is_empty() {
738 return out;
739 }
740 let all_done = events_done
741 && def
742 .steps
743 .iter()
744 .all(|s| unit(state, &s.id).status().is_terminal());
745 if all_done {
746 let output = match &def.result {
747 Some(expr) => match eval(expr, &StateScope::new(state)) {
748 Ok(v) => v,
749 Err(e) => {
750 return vec![Event::WorkflowFailed {
751 error: TaskError::new(
752 codes::EXPRESSION_ERROR,
753 format!("evaluating `return`: {e}"),
754 false,
755 ),
756 compensation: CompensationOutcome::None,
757 }]
758 }
759 },
760 None => Value::Object(Default::default()),
761 };
762 if let Err(e) = def.output.validate(&output, false) {
763 return vec![Event::WorkflowFailed {
764 error: TaskError::new(
765 codes::OUTPUT_CONTRACT_VIOLATION,
766 format!("workflow output is invalid at {e}"),
767 false,
768 ),
769 compensation: CompensationOutcome::None,
770 }];
771 }
772 return vec![Event::WorkflowCompleted { output }];
773 }
774 return out;
775 }
776
777 let mut waiting = false;
780 let withdraw =
781 |key: &str, u: &UnitState, out: &mut Vec<Event>, waiting: &mut bool| match u.status() {
782 UnitStatus::Pending | UnitStatus::Waiting | UnitStatus::RetryWait => {
783 out.push(Event::StepCancelled { step: key.into() })
784 }
785 UnitStatus::Scheduled if !u.redelivery => {
786 out.push(Event::StepCancelled { step: key.into() })
787 }
788 UnitStatus::Scheduled
789 | UnitStatus::Dispatched
790 | UnitStatus::AttemptFailed
791 | UnitStatus::Uncertain => *waiting = true,
792 _ => {}
793 };
794 for step in &def.steps {
795 let u = unit(state, &step.id);
796 if u.status() == UnitStatus::Running {
797 let mut element_waiting = false;
798 let mut failed = None;
799 for key in element_keys(state, &step.id) {
800 let eu = unit(state, &key);
801 if eu.status() == UnitStatus::Failed && failed.is_none() {
802 failed = eu.error.clone();
803 }
804 withdraw(&key, &eu, &mut out, &mut element_waiting);
805 }
806 if element_waiting {
807 waiting = true;
808 } else {
809 out.push(match failed {
811 Some(error) => Event::StepFailed {
812 step: step.id.clone(),
813 error,
814 },
815 None => Event::StepCancelled {
816 step: step.id.clone(),
817 },
818 });
819 }
820 } else {
821 withdraw(&step.id, &u, &mut out, &mut waiting);
822 }
823 }
824 for (instance, signal) in event_instances(state, def) {
825 for step in &def.events[signal] {
826 let key = format!("{instance}:{}", step.id);
827 let u = unit(state, &key);
828 withdraw(&key, &u, &mut out, &mut waiting);
829 }
830 }
831 if !out.is_empty() || waiting {
832 return out;
833 }
834 let mut done: Vec<(String, u64)> = vec![];
837 for s in def.steps.iter().filter(|s| s.compensation.is_some()) {
838 let keys = if s.for_each.is_some() {
839 element_keys(state, &s.id)
840 } else {
841 vec![s.id.clone()]
842 };
843 for key in keys {
844 if let Some(u) = state.units.get(&key) {
845 if u.status() == UnitStatus::Succeeded {
846 done.push((key, u.succeeded_seq.unwrap_or(0)));
847 }
848 }
849 }
850 }
851 done.sort_by_key(|d| std::cmp::Reverse(d.1));
852 if done.is_empty() {
853 return conclude(state, def, CompensationOutcome::None);
854 }
855 let reason = match &state.abort {
856 Some(AbortReason::Failed { error }) => error.message.clone(),
857 Some(AbortReason::Cancelled { reason }) => format!("cancelled: {reason}"),
858 Some(AbortReason::TimedOut) => "workflow timed out".into(),
859 None => String::new(),
860 };
861 vec![Event::CompensationStarted {
862 reason,
863 steps: done.into_iter().map(|(s, _)| s).collect(),
864 }]
865}
866
867fn decide_compensating(state: &ExecutionState, def: &WorkflowIr) -> Vec<Event> {
868 let mut failures = vec![];
872 for step in &state.compensation_order {
873 let key = compensation_key(step);
874 let u = unit(state, &key);
875 match u.status() {
876 UnitStatus::Succeeded => continue,
877 UnitStatus::Failed | UnitStatus::Cancelled => {
878 let error = u
879 .error
880 .unwrap_or_else(|| TaskError::new("UNKNOWN", "compensation failed", false));
881 failures.push(Event::CompensationFailed {
882 step: step.clone(),
883 error,
884 });
885 continue;
886 }
887 UnitStatus::Pending => {
888 return match invocation(def, &key) {
889 Some(inv) => vec![schedule(state, def, &key, inv)],
890 None => vec![Event::StepFailed {
891 step: key,
892 error: TaskError::new("UNKNOWN", "missing compensation", false),
893 }],
894 };
895 }
896 _ => return vec![],
897 }
898 }
899 if !failures.is_empty() {
900 failures.extend(conclude(state, def, CompensationOutcome::Failed));
901 return failures;
902 }
903 let mut out = vec![Event::CompensationCompleted {}];
904 out.extend(conclude(state, def, CompensationOutcome::Completed));
905 out
906}
907
908fn handler_kind(state: &ExecutionState) -> &'static str {
910 match &state.abort {
911 Some(AbortReason::Cancelled { .. }) => "cancel",
912 Some(AbortReason::TimedOut) => "timeout",
913 _ => "failure",
914 }
915}
916
917fn conclude(state: &ExecutionState, def: &WorkflowIr, outcome: CompensationOutcome) -> Vec<Event> {
919 let kind = handler_kind(state);
920 match def.handlers.get(kind) {
921 Some(steps) if !steps.is_empty() => vec![Event::HandlersStarted {
922 kind: kind.to_string(),
923 compensation: outcome,
924 }],
925 _ => vec![finish(state, outcome)],
926 }
927}
928
929fn decide_handlers(
933 state: &ExecutionState,
934 def: &WorkflowIr,
935 phase: &HandlerPhase,
936 now: u64,
937) -> Vec<Event> {
938 let steps = def
939 .handlers
940 .get(&phase.kind)
941 .map(Vec::as_slice)
942 .unwrap_or(&[]);
943 let mut out = vec![];
944 let mut all_done = true;
945 for step in steps {
946 let key = handler_key(&phase.kind, &step.id);
947 let u = unit(state, &key);
948 if u.status().is_terminal() {
949 continue;
950 }
951 all_done = false;
952 if u.status() != UnitStatus::Pending {
953 continue;
954 }
955 let dep_status: Vec<UnitStatus> = step
956 .dependencies
957 .iter()
958 .map(|d| unit(state, &handler_key(&phase.kind, d)).status())
959 .collect();
960 if dep_status
961 .iter()
962 .any(|s| matches!(s, UnitStatus::Failed | UnitStatus::Cancelled))
963 {
964 out.push(Event::StepCancelled { step: key });
965 continue;
966 }
967 if !dep_status
968 .iter()
969 .all(|s| matches!(s, UnitStatus::Succeeded | UnitStatus::Skipped))
970 {
971 continue;
972 }
973 let mut scope = StateScope::new(state);
974 scope.failure = Some(failure_value(state));
975 if let Some(cond) = &step.condition {
976 match eval(cond, &scope) {
977 Ok(Value::Bool(true)) => {}
978 Ok(Value::Bool(false)) | Ok(Value::Null) => {
979 out.push(Event::StepSkipped {
980 step: key,
981 reason: "`when` condition was false".into(),
982 });
983 continue;
984 }
985 Ok(other) => {
986 out.push(Event::StepFailed {
987 step: key,
988 error: TaskError::new(
989 codes::EXPRESSION_ERROR,
990 format!("`when` evaluated to {other}, expected Bool"),
991 false,
992 ),
993 });
994 continue;
995 }
996 Err(e) => {
997 out.push(Event::StepFailed {
998 step: key,
999 error: TaskError::new(
1000 codes::EXPRESSION_ERROR,
1001 format!("evaluating `when`: {e}"),
1002 false,
1003 ),
1004 });
1005 continue;
1006 }
1007 }
1008 }
1009 match &step.action {
1010 Action::Task(inv) | Action::Workflow(inv) => out.push(schedule(state, def, &key, inv)),
1011 Action::Sleep { duration_ms } => out.push(Event::TimerStarted {
1012 step: key,
1013 fire_at_ms: now + duration_ms,
1014 }),
1015 Action::Compute { expr } => out.push(compute(state, def, &key, expr)),
1016 Action::Await { .. } => out.push(Event::StepFailed {
1017 step: key,
1018 error: TaskError::new("UNKNOWN", "`await` is not allowed in a handler", false),
1019 }),
1020 }
1021 }
1022 if all_done && out.is_empty() {
1023 return vec![finish(state, phase.compensation)];
1024 }
1025 out
1026}