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, WorkflowReviewError, 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    /// An application-owned reviewer failed to evaluate a generated candidate.
114    #[error("workflow step `{step}` review failed: {source}")]
115    Review {
116        /// Stable repairable step identity.
117        step: StepId,
118        /// Typed reviewer failure.
119        #[source]
120        source: WorkflowReviewError,
121    },
122    /// A reviewer permanently rejected a generated candidate.
123    #[error("workflow step `{step}` was rejected after {attempts} generation attempt(s): {reason}")]
124    RemediationRejected {
125        /// Stable repairable step identity.
126        step: StepId,
127        /// Number of generated candidates reviewed.
128        attempts: u32,
129        /// Safe reviewer explanation.
130        reason: String,
131    },
132    /// A repairable step consumed every configured repair attempt.
133    #[error("workflow step `{step}` exhausted remediation after {attempts} generation attempt(s)")]
134    RemediationExhausted {
135        /// Stable repairable step identity.
136        step: StepId,
137        /// Number of generated candidates reviewed.
138        attempts: u32,
139    },
140    /// One branch of a parallel node failed.
141    #[error("parallel branch `{branch}` in workflow step `{step}` failed: {source}")]
142    ParallelBranch {
143        /// Stable parallel node identifier.
144        step: StepId,
145        /// Stable failed branch identifier.
146        branch: StepId,
147        /// Typed branch failure.
148        #[source]
149        source: Box<WorkflowStepError>,
150    },
151    /// Every branch in a first-success race failed.
152    #[error("every branch in workflow race step `{step}` failed")]
153    RaceAllFailed {
154        /// Stable race node identifier.
155        step: StepId,
156        /// Safe branch failure explanations.
157        failures: BTreeMap<StepId, String>,
158    },
159    /// The workflow was cancelled.
160    #[error("workflow execution was cancelled")]
161    Cancelled,
162    /// The workflow deadline elapsed.
163    #[error("workflow execution deadline elapsed")]
164    DeadlineExceeded,
165    /// A durable wait was executed outside the distributed worker boundary.
166    #[error("durable workflow wait requires a distributed WorkflowWorker")]
167    DurableWaitRequiresWorker,
168    /// Stored wake data does not match the checkpoint's durable wait.
169    #[error("workflow wake does not match its persisted wait condition")]
170    WakeMismatch,
171    /// A durable wait or human decision violated its domain invariants.
172    #[error("invalid workflow wait: {0}")]
173    Wait(#[from] WorkflowWaitError),
174    /// A durable wait output could not be represented as canonical JSON.
175    #[error("workflow wait output serialization failed: {0}")]
176    Serialization(#[from] serde_json::Error),
177    /// Parallel budget reservation or consumption exceeded a hard limit.
178    #[error("workflow budget exceeded: {0}")]
179    Budget(#[from] BudgetExceeded),
180    /// A child Run rejected its capability or budget scope.
181    #[error("workflow child Run is invalid: {0}")]
182    ChildRun(#[from] ChildRunError),
183    /// Recovery would silently retry a possibly partial step.
184    #[error("checkpoint contains ambiguous in-flight workflow step `{step}`")]
185    AmbiguousCheckpoint {
186        /// Interrupted step identifier.
187        step: StepId,
188    },
189    /// Persisted state does not belong to this workflow definition.
190    #[error("workflow checkpoint does not match the current workflow definition")]
191    CheckpointIdentityMismatch,
192    /// Persisted usage is incompatible with the supplied run context.
193    #[error("workflow checkpoint usage is incompatible with the supplied run")]
194    CheckpointUsageMismatch,
195    /// Structured event recording failed.
196    #[error("workflow observability failed: {0}")]
197    Journal(#[from] JournalError),
198    /// Checkpoint persistence or validation failed.
199    #[error("workflow checkpoint failed: {0}")]
200    Checkpoint(#[from] CheckpointError),
201}