Skip to main content

runifold_workflow/
error.rs

1use runifold_agent::AgentError;
2use std::collections::BTreeMap;
3
4use runifold_core::{BudgetExceeded, BudgetReservationMismatch, CheckpointError, JournalError};
5use thiserror::Error;
6
7use crate::StepId;
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 parallel group requires at least two branches.
29    #[error("parallel workflow step `{0}` requires at least two branches")]
30    TooFewParallelBranches(StepId),
31    /// A first-success race requires at least two branches.
32    #[error("race workflow step `{0}` requires at least two branches")]
33    TooFewRaceBranches(StepId),
34    /// A parallel branch identifier is invalid.
35    #[error("invalid parallel branch identifier `{0}`")]
36    InvalidParallelBranchId(String),
37    /// A parallel branch identifier occurs more than once in its group.
38    #[error("parallel branch `{branch}` is registered more than once in step `{step}`")]
39    DuplicateParallelBranch {
40        /// Parallel group identity.
41        step: StepId,
42        /// Duplicate branch identity.
43        branch: StepId,
44    },
45    /// A race branch requested a capability that may mutate external state.
46    #[error(
47        "race branch `{branch}` in step `{step}` cannot abandon capability `{capability}` with external write effects"
48    )]
49    UnsafeRaceCapability {
50        /// Race node identity.
51        step: StepId,
52        /// Unsafe branch identity.
53        branch: StepId,
54        /// Rejected capability name.
55        capability: String,
56    },
57}
58
59/// Failure produced by one workflow step.
60#[derive(Debug, Error)]
61#[non_exhaustive]
62pub enum WorkflowStepError {
63    /// An Agent step failed.
64    #[error("Agent execution failed: {0}")]
65    Agent(#[from] AgentError),
66    /// The step rejected its canonical JSON input.
67    #[error("invalid step input: {0}")]
68    InvalidInput(String),
69    /// The step could not produce a canonical downstream value.
70    #[error("invalid step output: {0}")]
71    InvalidOutput(String),
72    /// The step failed while converting its output.
73    #[error("step output serialization failed: {0}")]
74    Serialization(#[from] serde_json::Error),
75    /// A custom step failed with a safe application-facing explanation.
76    #[error("step execution failed: {0}")]
77    Execution(String),
78}
79
80/// Failure of workflow execution or recovery.
81#[derive(Debug, Error)]
82#[non_exhaustive]
83pub enum WorkflowError {
84    /// A workflow definition is invalid.
85    #[error(transparent)]
86    Build(#[from] WorkflowBuildError),
87    /// A child requested authority absent from its parent run.
88    #[error("workflow step `{step}` requested capability `{capability}` not held by its parent")]
89    AuthorityEscalation {
90        /// Step whose grant was rejected.
91        step: StepId,
92        /// Missing capability name.
93        capability: String,
94    },
95    /// A step failed.
96    #[error("workflow step `{step}` failed: {source}")]
97    Step {
98        /// Stable failed step identifier.
99        step: StepId,
100        /// Typed step failure.
101        #[source]
102        source: Box<WorkflowStepError>,
103    },
104    /// One branch of a parallel node failed.
105    #[error("parallel branch `{branch}` in workflow step `{step}` failed: {source}")]
106    ParallelBranch {
107        /// Stable parallel node identifier.
108        step: StepId,
109        /// Stable failed branch identifier.
110        branch: StepId,
111        /// Typed branch failure.
112        #[source]
113        source: Box<WorkflowStepError>,
114    },
115    /// Every branch in a first-success race failed.
116    #[error("every branch in workflow race step `{step}` failed")]
117    RaceAllFailed {
118        /// Stable race node identifier.
119        step: StepId,
120        /// Safe branch failure explanations.
121        failures: BTreeMap<StepId, String>,
122    },
123    /// The workflow was cancelled.
124    #[error("workflow execution was cancelled")]
125    Cancelled,
126    /// The workflow deadline elapsed.
127    #[error("workflow execution deadline elapsed")]
128    DeadlineExceeded,
129    /// Parallel budget reservation or consumption exceeded a hard limit.
130    #[error("workflow budget exceeded: {0}")]
131    Budget(#[from] BudgetExceeded),
132    /// An internal reservation did not belong to this workflow's run tree.
133    #[error("workflow budget reservation is invalid: {0}")]
134    BudgetReservation(#[from] BudgetReservationMismatch),
135    /// Recovery would silently retry a possibly partial step.
136    #[error("checkpoint contains ambiguous in-flight workflow step `{step}`")]
137    AmbiguousCheckpoint {
138        /// Interrupted step identifier.
139        step: StepId,
140    },
141    /// Persisted state does not belong to this workflow definition.
142    #[error("workflow checkpoint does not match the current workflow definition")]
143    CheckpointIdentityMismatch,
144    /// Persisted usage is incompatible with the supplied run context.
145    #[error("workflow checkpoint usage is incompatible with the supplied run")]
146    CheckpointUsageMismatch,
147    /// Structured event recording failed.
148    #[error("workflow observability failed: {0}")]
149    Journal(#[from] JournalError),
150    /// Checkpoint persistence or validation failed.
151    #[error("workflow checkpoint failed: {0}")]
152    Checkpoint(#[from] CheckpointError),
153}