1use super::budget::{ConditionErrorPolicy, DispatchKind, TaskBudget};
34use super::cancel::{Limits, Stop};
35use super::error_context::ErrorContext;
36use super::fan_out::fan_out;
37use super::host::call_host;
38use super::message::{AuditEntry, AuditMode};
39use super::observers::Observers;
40use super::policy::{self, Flow};
41use super::record::{self, Completed, TaskIds};
42use super::trace::Capture;
43use crate::base::{
44 AuthoredPath, Change, Error, ErrorKind, ErrorRecord, ErrorSite, ExprCause, Instant, ProgramId,
45 SiteKind, TaskIx, WorkflowIx,
46};
47use crate::def::WorkflowFailurePolicy;
48use crate::expr::path::kind;
49use crate::expr::{Expr, ExprEngine, Path};
50use crate::function::builtins::Builtin;
51use crate::function::{CallLayer, SyncCtx, eval, test};
52use crate::observe::{Event, Selection, StepResult, TraceStep};
53use crate::program::{CallSite, Callee, CompiledWorkflow, CondKind, CondSite, LoopSite, Op};
54use crate::state::{Owner, RunContext};
55use chrono::{DateTime, Utc};
56use datavalue::{DataValue, OwnedDataValue};
57use std::borrow::Cow;
58use std::fmt;
59use std::sync::Arc;
60
61#[derive(Debug)]
63pub(crate) enum Exit {
64 Completed,
66 Halted,
68 Failed,
71 Stopped(Error),
75 Limited(Stop),
80}
81
82#[derive(Debug, Default)]
84pub(crate) struct Frame {
85 pub(crate) loop_counter: Option<i64>,
88 pub(crate) sweeps: u32,
91 counter: i64,
94 items: Option<Vec<OwnedDataValue>>,
98 begun: bool,
100 pub(crate) started: Option<Instant>,
103}
104
105impl Frame {
106 fn in_sweep(&self, wf: &CompiledWorkflow) -> bool {
109 wf.loops.is_empty() || self.loop_counter.is_some()
110 }
111}
112
113pub(crate) struct RunEnv<'r, 'p> {
117 pub(crate) program: ProgramId,
119 pub(crate) workflow_ix: WorkflowIx,
121 pub(crate) task_base: u32,
123 pub(crate) channel: Option<&'r str>,
125 pub(crate) routing_bucket: Option<u8>,
127 pub(crate) layers: &'r [Arc<dyn CallLayer>],
129 pub(crate) limits: Limits<'r>,
131 pub(crate) condition_error_policy: ConditionErrorPolicy,
133 pub(crate) budget: &'r TaskBudget<'r>,
135 pub(crate) ops: u32,
138 pub(crate) exprs: &'p ExprEngine,
140 pub(crate) payload: &'r OwnedDataValue,
142 pub(crate) errors: &'r mut Vec<ErrorRecord>,
144 pub(crate) audit: &'r mut Vec<AuditEntry>,
146 pub(crate) mode: AuditMode,
148 pub(crate) capture: CaptureMode,
150 pub(crate) watch: &'r mut Watch<'p>,
152 pub(crate) now: DateTime<Utc>,
154 pub(crate) now_text: &'r mut Option<Arc<str>>,
158 pub(crate) changes: Vec<Change>,
160 pub(crate) workflow_id: &'p Arc<str>,
162 pub(crate) task_ids: &'p [Arc<str>],
164 pub(crate) failed: bool,
166 pub(crate) frame: Frame,
168}
169
170pub(crate) struct Watch<'p> {
175 pub(crate) observers: Observers<'p>,
177 pub(crate) timed: bool,
180 pub(crate) trace: Option<Box<Capture>>,
183 pub(crate) error_context: Option<&'p ErrorContext>,
186}
187
188#[derive(Clone, Copy, Debug)]
196pub(crate) enum CaptureMode {
197 Plain { audit: bool },
201 Watched { audit: bool, traced: bool },
206}
207
208impl CaptureMode {
209 pub(crate) fn new(capture_audit: bool, watch: &Watch<'_>) -> Self {
212 let traced = watch.trace.as_deref().is_some_and(Capture::changes);
213 if watch.trace.is_some() || watch.error_context.is_some() || watch.observers.tasks() {
214 Self::Watched {
215 audit: capture_audit,
216 traced,
217 }
218 } else {
219 debug_assert!(!watch.timed, "only a task observer or a trace times a call");
220 Self::Plain {
221 audit: capture_audit,
222 }
223 }
224 }
225
226 #[inline(always)]
229 pub(crate) fn captures(self) -> bool {
230 match self {
231 Self::Plain { audit } => audit,
232 Self::Watched { audit, traced } => audit || traced,
233 }
234 }
235
236 #[inline(always)]
238 pub(crate) fn audit(self) -> bool {
239 match self {
240 Self::Plain { audit } | Self::Watched { audit, .. } => audit,
241 }
242 }
243
244 #[inline(always)]
246 pub(crate) fn traced(self) -> bool {
247 matches!(self, Self::Watched { traced: true, .. })
248 }
249
250 #[inline(always)]
252 pub(crate) fn is_watched(self) -> bool {
253 matches!(self, Self::Watched { .. })
254 }
255}
256
257pub const CHECK_EVERY: u32 = 64;
264
265impl<'p> RunEnv<'_, 'p> {
266 fn tick(&mut self, rc: &mut RunContext<'_, '_>) -> Result<(), Stop> {
269 rc.tick();
270 if self.limits.is_set() {
271 self.ops = self.ops.wrapping_add(1);
272 if self.ops.is_multiple_of(CHECK_EVERY) {
273 self.limits.check()?;
274 }
275 }
276 Ok(())
277 }
278
279 pub(crate) fn now_text(&mut self) -> &Arc<str> {
281 let now = self.now;
282 self.now_text
283 .get_or_insert_with(|| Arc::from(now.to_rfc3339()))
284 }
285
286 pub(crate) fn traces_changes(&self) -> bool {
289 self.capture.traced()
290 }
291
292 pub(crate) fn ids(&self, task: u32) -> TaskIds<'p> {
295 TaskIds {
296 workflow: self.workflow_id,
297 task: &self.task_ids[task as usize],
298 }
299 }
300
301 pub(crate) fn workflow_record(&self, error: &Error) -> ErrorRecord {
304 let mut record = ErrorRecord::from(error);
305 record.workflow_id = Some(Arc::clone(self.workflow_id));
306 record
307 }
308
309 pub(crate) fn task_error_record(&mut self, task: u32, error: &Error) -> ErrorRecord {
312 let mut record = ErrorRecord::from(error);
313 let ids = self.ids(task);
314 let now = self.now_text();
315 record.stamp(ids.workflow, Some(ids.task), now, None);
316 record
317 }
318
319 pub(crate) fn site(&self, kind: SiteKind, authored_path: AuthoredPath) -> ErrorSite {
321 ErrorSite {
322 kind,
323 workflow_id: Arc::clone(self.workflow_id),
324 task_id: None,
325 group_id: None,
326 rule_index: None,
327 mapping_index: None,
328 target_path: None,
329 authored_path: Some(authored_path),
330 loop_counter: self.frame.loop_counter,
331 }
332 }
333
334 pub(crate) fn task_site(
337 &self,
338 kind: SiteKind,
339 task: u32,
340 authored_path: AuthoredPath,
341 ) -> ErrorSite {
342 ErrorSite {
343 task_id: Some(Arc::clone(self.ids(task).task)),
344 ..self.site(kind, authored_path)
345 }
346 }
347
348 pub(crate) fn workflow_step(&self, result: StepResult) -> TraceStep {
350 TraceStep::new(self.workflow_ix, self.workflow_id, result)
351 }
352
353 pub(crate) fn task_step(&self, task: u32, result: StepResult) -> TraceStep {
356 let mut step = self.workflow_step(result);
357 step.task = Some(TaskIx::new(self.task_base + task));
358 step.task_id = Some(Arc::clone(&self.task_ids[task as usize]));
359 step.loop_counter = self.frame.loop_counter;
360 step
361 }
362
363 pub(crate) fn push_step(&mut self, step: TraceStep) {
367 if let Some(trace) = self.watch.trace.as_deref_mut() {
368 trace.push(step);
369 if let CaptureMode::Watched { traced, .. } = &mut self.capture {
370 *traced = trace.changes();
371 }
372 }
373 }
374}
375
376fn fail_to(op: Op) -> u32 {
378 match op {
379 Op::Call { fail_to, .. } | Op::FanOut { fail_to, .. } => fail_to,
380 Op::LoopInit { exit_to, .. } => exit_to,
381 Op::Cond { .. } | Op::LoopNext { .. } | Op::Halt => {
382 unreachable!("only a call, a fan-out or a loop's start fails")
383 }
384 }
385}
386
387pub(crate) async fn run_workflow<'p>(
406 rc: &mut RunContext<'_, 'p>,
407 wf: &'p CompiledWorkflow,
408 env: &mut RunEnv<'_, 'p>,
409) -> (Exit, Selection) {
410 let mut selection = Selection::Ran;
411 let mut unjudged = true;
414 env.frame.sweeps = u32::from(wf.loops.is_empty());
415 if !wf.judged_first() {
416 begin(env);
417 }
418 let mut pc = 0;
419 let exit = loop {
420 let Some(&op) = wf.ops.get(pc) else {
421 break if env.failed {
422 Exit::Failed
423 } else {
424 Exit::Completed
425 };
426 };
427 if let Err(e) = env.tick(rc) {
428 break Exit::Limited(e);
429 }
430 let flow = match op {
431 Op::Cond {
432 cond,
433 kind,
434 else_to,
435 } => {
436 let cond_site = &wf.conds[cond as usize];
437 let passed = test(rc, env.exprs, env.budget, &cond_site.expr)
438 .map_err(|e| attach_cond_site(env, cond_site, kind, e));
439 if env.condition_error_policy == ConditionErrorPolicy::Stop
440 && let Err(e) = passed
441 {
442 match kind {
443 CondKind::Workflow => {
444 if env.frame.in_sweep(wf) {
445 env.frame.sweeps -= 1;
446 }
447 if std::mem::take(&mut unjudged) {
448 selection = Selection::ConditionFailed(e.clone());
449 }
450 record::workflow_failure(env, &e);
451 }
452 CondKind::Group => {
453 record::workflow_failure(env, &e);
454 }
455 CondKind::Task => {
456 if let Some(Op::Call { site, .. } | Op::FanOut { site, .. }) =
457 wf.ops.get(pc + 1)
458 {
459 record::task_condition_failure(
460 env,
461 wf.calls[*site as usize].task,
462 &e,
463 );
464 } else {
465 record::workflow_failure(env, &e);
466 }
467 }
468 }
469 break Exit::Stopped(e);
470 }
471 let admitted = matches!(passed, Ok(true));
472 if kind == CondKind::Workflow {
473 if !admitted && env.frame.in_sweep(wf) {
474 env.frame.sweeps -= 1;
475 }
476 if std::mem::take(&mut unjudged) {
477 selection = match passed {
478 Ok(true) => {
479 begin(env);
480 Selection::Ran
481 }
482 Ok(false) => {
483 skipped_workflow(env, None);
484 Selection::SkippedByCondition
485 }
486 Err(e) => {
487 skipped_workflow(env, Some(&e));
488 Selection::ConditionFailed(e)
489 }
490 };
491 }
492 } else if !admitted && env.watch.trace.is_some() {
493 skipped_calls(env, wf, kind, pc, else_to, passed.as_ref().err());
494 }
495 if admitted {
496 Flow::Next
497 } else {
498 Flow::Jump(else_to)
499 }
500 }
501 Op::Call { site, .. } => {
502 let site = &wf.calls[site as usize];
503 match &site.callee {
504 Callee::Builtin(builtin) => call(rc, env, wf, site, builtin),
505 Callee::Host(bound) => call_host(rc, env, wf, site, &**bound).await,
506 }
507 }
508 Op::FanOut { site, fan, .. } => {
509 let site = &wf.calls[site as usize];
510 let Callee::Host(bound) = &site.callee else {
511 unreachable!("compile_one checks that a fan-out calls a host function")
512 };
513 Box::pin(fan_out(rc, env, wf, site, &wf.fans[fan as usize], &**bound)).await
516 }
517 Op::LoopInit { lp, exit_to } => loop_init(rc, env, &wf.loops[lp as usize], exit_to),
518 Op::LoopNext {
519 lp,
520 body_to,
521 exit_to,
522 } => loop_next(rc, env, &wf.loops[lp as usize], body_to, exit_to),
523 Op::Halt => Flow::Halt,
524 };
525 pc = match flow {
526 Flow::Next => pc + 1,
527 Flow::Jump(to) => to as usize,
528 Flow::Halt => break Exit::Halted,
529 Flow::Stop(e) => break Exit::Limited(e),
530 Flow::Abort(e) => break Exit::Stopped(e),
531 Flow::Fail(e) => match wf.on_failure {
532 WorkflowFailurePolicy::Continue => {
533 env.failed = true;
534 fail_to(op) as usize
535 }
536 WorkflowFailurePolicy::Fail => break Exit::Stopped(e),
537 },
538 };
539 };
540 if matches!(selection, Selection::Ran) {
541 begin(env);
542 }
543 (exit, selection)
544}
545
546fn begin(env: &mut RunEnv<'_, '_>) {
549 if !env.watch.observers.workflows() || std::mem::replace(&mut env.frame.begun, true) {
550 return;
551 }
552 if env.watch.observers.timed_workflows() {
555 env.frame.started = Some(record::now());
556 }
557 env.watch.observers.emit(Event::WorkflowStarted {
558 workflow: env.workflow_ix,
559 id: env.workflow_id,
560 });
561}
562
563fn skipped_workflow(env: &mut RunEnv<'_, '_>, error: Option<&Error>) {
569 if env.watch.trace.is_none() {
570 return;
571 }
572 let mut step = env.workflow_step(StepResult::Skipped);
573 if let Some(e) = error {
574 step.errors.push(env.workflow_record(e));
575 }
576 env.push_step(step);
577}
578
579fn attach_cond_site(
581 env: &RunEnv<'_, '_>,
582 cond_site: &CondSite,
583 kind: CondKind,
584 error: Error,
585) -> Error {
586 let site_kind = match kind {
587 CondKind::Workflow => SiteKind::WorkflowCondition,
588 CondKind::Group => SiteKind::GroupCondition,
589 CondKind::Task => SiteKind::TaskCondition,
590 };
591 let step_arc = cond_site.step.as_deref().map(Arc::<str>::from);
592 let (task_id, group_id) = match kind {
593 CondKind::Workflow => (None, None),
594 CondKind::Group => (None, step_arc),
595 CondKind::Task => (step_arc, None),
596 };
597 error.with_site(ErrorSite {
598 task_id,
599 group_id,
600 ..env.site(site_kind, cond_site.path.clone())
601 })
602}
603
604fn skipped_calls(
608 env: &mut RunEnv<'_, '_>,
609 wf: &CompiledWorkflow,
610 kind: CondKind,
611 pc: usize,
612 else_to: u32,
613 error: Option<&Error>,
614) {
615 let span = match kind {
616 CondKind::Group => wf.ops.get(pc + 1..else_to as usize),
617 CondKind::Task => wf.ops.get(pc + 1..pc + 2),
618 CondKind::Workflow => None,
619 };
620 for op in span.unwrap_or_default() {
621 if let Op::Call { site, .. } | Op::FanOut { site, .. } = *op {
622 let task = wf.calls[site as usize].task;
623 let mut step = env.task_step(task, StepResult::Skipped);
624 if let Some(e) = error {
625 step.errors.push(env.task_error_record(task, e));
626 }
627 env.push_step(step);
628 }
629 }
630}
631
632fn loop_init<'p>(
638 rc: &mut RunContext<'_, 'p>,
639 env: &mut RunEnv<'_, 'p>,
640 site: &'p LoopSite,
641 exit_to: u32,
642) -> Flow {
643 if let Some(over) = &site.over {
644 let over_site = ErrorSite {
645 loop_counter: None,
646 ..env.site(
647 SiteKind::LoopOver,
648 AuthoredPath::root().join("loop").join("over"),
649 )
650 };
651 match elements(
652 rc,
653 env.exprs,
654 env.budget,
655 over,
656 Over::Loop(env.workflow_id),
657 over_site,
658 ) {
659 Ok(items) => env.frame.items = Some(items.into_owned()),
661 Err(e) => {
662 record::workflow_failure(env, &e);
663 return if *e.kind() == ErrorKind::Budget && env.budget.is_run_limited() {
664 Flow::Abort(e)
665 } else {
666 Flow::Fail(e)
667 };
668 }
669 }
670 }
671 env.frame.counter = site.init;
672 enter(rc, env, site, Flow::Next, exit_to)
673}
674
675fn loop_next<'p>(
679 rc: &mut RunContext<'_, 'p>,
680 env: &mut RunEnv<'_, 'p>,
681 site: &'p LoopSite,
682 body_to: u32,
683 exit_to: u32,
684) -> Flow {
685 env.frame.counter = env.frame.counter.saturating_add(site.increment);
686 enter(rc, env, site, Flow::Jump(body_to), exit_to)
687}
688
689fn enter<'p>(
702 rc: &mut RunContext<'_, 'p>,
703 env: &mut RunEnv<'_, 'p>,
704 site: &'p LoopSite,
705 sweep: Flow,
706 exit_to: u32,
707) -> Flow {
708 let traced = env.traces_changes();
709 let mut changes = Vec::new();
710 let counter = env.frame.counter;
711 if let Some(path) = &site.counter {
712 engine_write(
713 rc,
714 path,
715 traced,
716 &mut changes,
717 || OwnedDataValue::from_i64(counter),
718 |_| DataValue::from_i64(counter),
719 );
720 }
721 let element = match &env.frame.items {
723 None => None,
724 Some(items) => usize::try_from(counter).ok().filter(|&i| i < items.len()),
725 };
726 let ends = if counter >= site.max || (env.frame.items.is_some() && element.is_none()) {
727 Some(Flow::Jump(exit_to))
728 } else {
729 env.limits.check().err().map(Flow::Stop)
732 };
733 if let Some(flow) = ends {
734 if site.counter.is_some() {
735 sweep_step(env, counter, changes);
736 }
737 return flow;
738 }
739 let _guard = match env.budget.charge_dispatch(DispatchKind::LoopStep {
740 workflow_id: env.workflow_id,
741 iteration: u64::from(env.frame.sweeps),
742 }) {
743 Ok(guard) => guard,
744 Err(e) => {
745 if site.counter.is_some() {
746 sweep_step(env, counter, changes);
747 }
748 let e = e.with_site(ErrorSite {
749 loop_counter: Some(counter),
750 ..env.site(SiteKind::LoopOver, AuthoredPath::root().join("loop"))
751 });
752 record::workflow_failure(env, &e);
753 return Flow::Abort(e);
754 }
755 };
756 if let Some(item) = element.and_then(|i| env.frame.items.as_mut()?.get_mut(i)) {
757 let item = std::mem::take(item);
759 if let Some(path) = &site.r#as {
760 rc.set_recorded(path, item, None, traced.then_some(&mut changes));
761 }
762 }
763 if let Some(path) = &site.scratch {
764 engine_write(
765 rc,
766 path,
767 traced,
768 &mut changes,
769 || OwnedDataValue::Object(Vec::new()),
770 |_| DataValue::Object(&[]),
771 );
772 }
773 env.frame.loop_counter = Some(counter);
774 env.frame.sweeps = env.frame.sweeps.saturating_add(1);
775 sweep_step(env, counter, changes);
776 sweep
777}
778
779fn engine_write<'p>(
782 rc: &mut RunContext<'_, 'p>,
783 path: &Path,
784 traced: bool,
785 changes: &mut Vec<Change>,
786 owned: impl FnOnce() -> OwnedDataValue,
787 view: impl for<'q> FnOnce(&'q Owner<'p>) -> DataValue<'q>,
788) {
789 let old = traced.then(|| rc.get_or_null(path));
790 if let Some(old) = old {
791 let new = owned();
792 let new_clone = new.clone();
793 rc.engine_set(path, || new_clone, view);
794 changes.push(Change::write(Arc::clone(path.dotted()), old, new));
795 } else {
796 rc.engine_set(path, owned, view);
797 }
798}
799
800fn sweep_step(env: &mut RunEnv<'_, '_>, counter: i64, changes: Vec<Change>) {
803 if env.watch.trace.is_none() {
804 return;
805 }
806 let mut step = env.workflow_step(StepResult::Executed);
807 step.changes = changes;
808 step.loop_counter = Some(counter);
809 env.push_step(step);
810}
811
812#[derive(Clone, Copy, Debug)]
814pub(super) enum Over<'a> {
815 Loop(&'a str),
817 FanOut(&'a str),
819}
820
821impl fmt::Display for Over<'_> {
822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823 match self {
824 Self::Loop(id) => write!(f, "Workflow {id}: loop.over"),
825 Self::FanOut(id) => write!(f, "Task {id}: for_each.over"),
826 }
827 }
828}
829
830pub(super) fn elements<'e>(
835 rc: &mut RunContext<'_, '_>,
836 exprs: &ExprEngine,
837 budget: &TaskBudget<'_>,
838 over: &'e Expr,
839 owner: Over<'_>,
840 site: ErrorSite,
841) -> Result<Cow<'e, [OwnedDataValue]>, Error> {
842 let value = eval(rc, exprs, budget, over).map_err(|e| {
843 let msg = format!("{owner} failed to evaluate: {}", e.record_text());
844 e.with_message(msg).with_site(site.clone())
845 })?;
846 match value {
847 Cow::Owned(OwnedDataValue::Array(items)) => Ok(Cow::Owned(items)),
848 Cow::Borrowed(OwnedDataValue::Array(items)) => Ok(Cow::Borrowed(items)),
849 other => Err(Error::new(
850 ErrorKind::Eval,
851 format!("{owner} must evaluate to an array, got {}", kind(&other)),
852 )
853 .with_expr_cause(ExprCause::type_mismatch(None))
854 .with_site(site)),
855 }
856}
857
858fn call<'p>(
861 rc: &mut RunContext<'_, 'p>,
862 env: &mut RunEnv<'_, 'p>,
863 wf: &'p CompiledWorkflow,
864 site: &'p CallSite,
865 builtin: &'p Builtin,
866) -> Flow {
867 let errors_before = env.errors.len();
868 let meta = &wf.tasks[site.task as usize];
869 let guard = match env.budget.charge_dispatch(DispatchKind::InlineBuiltin {
870 workflow_id: &wf.id,
871 task_id: &meta.id,
872 function: &meta.function,
873 }) {
874 Ok(guard) => guard,
875 Err(e) => {
876 let e = e.with_site(env.task_site(SiteKind::Task, site.task, meta.path.clone()));
877 let decision = policy::forced_abort(env.ids(site.task).task, e);
878 let done = Completed::new(env, site.task, None, None, errors_before, false);
879 return record::record(rc, env, wf, done, decision);
880 }
881 };
882 let (traced, started) = if env.capture.is_watched() {
884 (env.traces_changes(), env.watch.timed.then(record::now))
885 } else {
886 (false, None)
887 };
888 let mapping_ends = if traced {
890 env.watch.trace.as_deref_mut().map(Capture::mapping_ends)
891 } else {
892 None
893 };
894 let mut cx = SyncCtx {
895 rc: &mut *rc,
896 exprs: env.exprs,
897 budget: env.budget,
898 condition_error_policy: env.condition_error_policy,
899 payload: env.payload,
900 workflow_id: env.workflow_id,
901 task_id: &env.task_ids[site.task as usize],
902 task_path: &meta.path,
903 loop_counter: env.frame.loop_counter,
904 capture: env.capture.captures(),
905 traced,
906 changes: &mut env.changes,
907 marked: false,
908 mapping_ends,
909 errors: &mut *env.errors,
910 now: env.now,
911 now_text: &mut *env.now_text,
912 };
913 let result = builtin.call(&mut cx);
914 let mut marked = cx.marked;
915 drop(guard);
916 let elapsed = started.map(record::since);
917 if result.is_err() {
921 if traced {
922 for change in &mut env.changes {
923 change.trace_only = true;
924 }
925 marked = true;
926 } else {
927 env.changes.clear();
928 }
929 }
930 let decision = match result {
931 Err(mut e) => {
932 if e.site().is_none() {
933 e = e.with_site(env.task_site(SiteKind::Task, site.task, meta.path.clone()));
934 }
935 let force_stop = (env.condition_error_policy == ConditionErrorPolicy::Stop
936 && e.site()
937 .is_some_and(|s| s.kind == SiteKind::FilterCondition))
938 || (*e.kind() == ErrorKind::Budget && env.budget.is_run_limited());
939 if force_stop {
940 policy::forced_abort(env.ids(site.task).task, e)
941 } else {
942 policy::decide(site.policy, env.ids(site.task).task, Err(e))
943 }
944 }
945 Ok(outcome) => policy::decide(site.policy, env.ids(site.task).task, Ok(outcome)),
946 };
947 let done = Completed::new(env, site.task, None, elapsed, errors_before, marked);
948 record::record(rc, env, wf, done, decision)
949}