Skip to main content

runifold_workflow/
error.rs

1use runifold_agent::AgentError;
2use std::collections::BTreeMap;
3
4use runifold_core::{BudgetExceeded, CheckpointError, ChildRunError, JournalError};
5use thiserror::Error;
6
7use crate::{StepId, WorkflowWaitError};
8
9/// Invalid workflow definition.
10#[derive(Clone, Debug, Error, Eq, PartialEq)]
11#[non_exhaustive]
12pub enum WorkflowBuildError {
13    /// The workflow name is blank.
14    #[error("workflow name cannot be empty")]
15    EmptyName,
16    /// A step identifier is invalid.
17    #[error("invalid workflow step identifier `{0}`")]
18    InvalidStepId(String),
19    /// The same step identifier occurs more than once.
20    #[error("workflow step `{0}` is registered more than once")]
21    DuplicateStep(StepId),
22    /// The workflow has no executable nodes.
23    #[error("workflow requires at least one step")]
24    NoSteps,
25    /// Version zero is reserved for invalid or unversioned definitions.
26    #[error("workflow version must be greater than zero")]
27    InvalidVersion,
28    /// A durable timer duration is invalid.
29    #[error("workflow durable timer must be a positive whole-millisecond duration")]
30    InvalidTimerDuration,
31    /// An external signal name is invalid.
32    #[error("workflow signal name must contain 1..=128 portable ASCII characters")]
33    InvalidSignalName,
34    /// A human-review prompt is blank or exceeds its durable limit.
35    #[error("workflow interrupt prompt must contain 1..=16384 bytes")]
36    InvalidInterruptPrompt,
37    /// A parallel group requires at least two branches.
38    #[error("parallel workflow step `{0}` requires at least two branches")]
39    TooFewParallelBranches(StepId),
40    /// A first-success race requires at least two branches.
41    #[error("race workflow step `{0}` requires at least two branches")]
42    TooFewRaceBranches(StepId),
43    /// A parallel branch identifier is invalid.
44    #[error("invalid parallel branch identifier `{0}`")]
45    InvalidParallelBranchId(String),
46    /// A parallel branch identifier occurs more than once in its group.
47    #[error("parallel branch `{branch}` is registered more than once in step `{step}`")]
48    DuplicateParallelBranch {
49        /// Parallel group identity.
50        step: StepId,
51        /// Duplicate branch identity.
52        branch: StepId,
53    },
54    /// A race branch requested a capability that may mutate external state.
55    #[error(
56        "race branch `{branch}` in step `{step}` cannot abandon capability `{capability}` with external write effects"
57    )]
58    UnsafeRaceCapability {
59        /// Race node identity.
60        step: StepId,
61        /// Unsafe branch identity.
62        branch: StepId,
63        /// Rejected capability name.
64        capability: String,
65    },
66}
67
68/// Failure produced by one workflow step.
69#[derive(Debug, Error)]
70#[non_exhaustive]
71pub enum WorkflowStepError {
72    /// An Agent step failed.
73    #[error("Agent execution failed: {0}")]
74    Agent(#[from] AgentError),
75    /// The step rejected its canonical JSON input.
76    #[error("invalid step input: {0}")]
77    InvalidInput(String),
78    /// The step could not produce a canonical downstream value.
79    #[error("invalid step output: {0}")]
80    InvalidOutput(String),
81    /// The step failed while converting its output.
82    #[error("step output serialization failed: {0}")]
83    Serialization(#[from] serde_json::Error),
84    /// A custom step failed with a safe application-facing explanation.
85    #[error("step execution failed: {0}")]
86    Execution(String),
87}
88
89/// Failure of workflow execution or recovery.
90#[derive(Debug, Error)]
91#[non_exhaustive]
92pub enum WorkflowError {
93    /// A workflow definition is invalid.
94    #[error(transparent)]
95    Build(#[from] WorkflowBuildError),
96    /// A child requested authority absent from its parent run.
97    #[error("workflow step `{step}` requested capability `{capability}` not held by its parent")]
98    AuthorityEscalation {
99        /// Step whose grant was rejected.
100        step: StepId,
101        /// Missing capability name.
102        capability: String,
103    },
104    /// A step failed.
105    #[error("workflow step `{step}` failed: {source}")]
106    Step {
107        /// Stable failed step identifier.
108        step: StepId,
109        /// Typed step failure.
110        #[source]
111        source: Box<WorkflowStepError>,
112    },
113    /// One branch of a parallel node failed.
114    #[error("parallel branch `{branch}` in workflow step `{step}` failed: {source}")]
115    ParallelBranch {
116        /// Stable parallel node identifier.
117        step: StepId,
118        /// Stable failed branch identifier.
119        branch: StepId,
120        /// Typed branch failure.
121        #[source]
122        source: Box<WorkflowStepError>,
123    },
124    /// Every branch in a first-success race failed.
125    #[error("every branch in workflow race step `{step}` failed")]
126    RaceAllFailed {
127        /// Stable race node identifier.
128        step: StepId,
129        /// Safe branch failure explanations.
130        failures: BTreeMap<StepId, String>,
131    },
132    /// The workflow was cancelled.
133    #[error("workflow execution was cancelled")]
134    Cancelled,
135    /// The workflow deadline elapsed.
136    #[error("workflow execution deadline elapsed")]
137    DeadlineExceeded,
138    /// A durable wait was executed outside the distributed worker boundary.
139    #[error("durable workflow wait requires a distributed WorkflowWorker")]
140    DurableWaitRequiresWorker,
141    /// Stored wake data does not match the checkpoint's durable wait.
142    #[error("workflow wake does not match its persisted wait condition")]
143    WakeMismatch,
144    /// A durable wait or human decision violated its domain invariants.
145    #[error("invalid workflow wait: {0}")]
146    Wait(#[from] WorkflowWaitError),
147    /// A durable wait output could not be represented as canonical JSON.
148    #[error("workflow wait output serialization failed: {0}")]
149    Serialization(#[from] serde_json::Error),
150    /// Parallel budget reservation or consumption exceeded a hard limit.
151    #[error("workflow budget exceeded: {0}")]
152    Budget(#[from] BudgetExceeded),
153    /// A child Run rejected its capability or budget scope.
154    #[error("workflow child Run is invalid: {0}")]
155    ChildRun(#[from] ChildRunError),
156    /// Recovery would silently retry a possibly partial step.
157    #[error("checkpoint contains ambiguous in-flight workflow step `{step}`")]
158    AmbiguousCheckpoint {
159        /// Interrupted step identifier.
160        step: StepId,
161    },
162    /// Persisted state does not belong to this workflow definition.
163    #[error("workflow checkpoint does not match the current workflow definition")]
164    CheckpointIdentityMismatch,
165    /// Persisted usage is incompatible with the supplied run context.
166    #[error("workflow checkpoint usage is incompatible with the supplied run")]
167    CheckpointUsageMismatch,
168    /// Structured event recording failed.
169    #[error("workflow observability failed: {0}")]
170    Journal(#[from] JournalError),
171    /// Checkpoint persistence or validation failed.
172    #[error("workflow checkpoint failed: {0}")]
173    Checkpoint(#[from] CheckpointError),
174}