Skip to main content

agent_effects/
error.rs

1//! Infrastructure errors.
2//!
3//! These are failures of the runtime itself: storage, configuration,
4//! serialization. What happened to an effect, including "unknown", is an
5//! [`EffectOutcome`](crate::EffectOutcome), never a `RuntimeError`.
6
7use crate::id::{EffectId, IdentityError};
8use crate::kind::EffectKind;
9use crate::store::StoreError;
10
11/// Why the runtime could not process an effect.
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum RuntimeError {
15    /// The effect committed externally but no replayable output was stored.
16    /// This does not mean the external action failed.
17    #[error("effect {id} committed, but its output is unavailable for replay")]
18    OutputUnavailable {
19        /// The committed effect.
20        id: EffectId,
21    },
22
23    /// An older committed output needs an unsupported or failing migration.
24    #[error("effect {id} committed, but its output schema cannot be read: {source}")]
25    OutputPayload {
26        /// The committed effect.
27        id: EffectId,
28        /// The schema or migration problem.
29        #[source]
30        source: crate::PayloadError,
31    },
32
33    /// Recovery cannot safely interpret the stored input schema.
34    #[error("effect {id}'s input schema cannot be read: {source}")]
35    InputPayload {
36        /// The existing effect.
37        id: EffectId,
38        /// The schema or migration problem.
39        #[source]
40        source: crate::PayloadError,
41    },
42
43    /// A caller supplied a different input schema for an existing key.
44    #[error("effect {id} was recorded with input schema {stored}, not {requested}")]
45    InputSchemaMismatch {
46        /// The existing effect.
47        id: EffectId,
48        /// The recorded schema.
49        stored: u32,
50        /// The caller's schema.
51        requested: u32,
52    },
53    /// Existing safety assumptions differ from this call. Nothing is run
54    /// or replayed; the authoritative record remains unchanged.
55    #[error("effect {id} has an incompatible execution contract")]
56    ContractMismatch {
57        /// The existing effect.
58        id: EffectId,
59    },
60
61    /// A pre-contract unresolved record needs explicit operator review.
62    /// Its original idempotency and verification assumptions are unknown.
63    #[error("effect {id} has no execution contract; reconcile the legacy effect before resuming")]
64    LegacyContract {
65        /// The existing effect.
66        id: EffectId,
67    },
68    /// The effect name or logical key was invalid.
69    #[error("invalid effect identity: {0}")]
70    Identity(#[from] IdentityError),
71
72    /// The key was used before with a different input. Returning the earlier
73    /// effect's result would answer a question nobody asked.
74    #[error("effect {id} was recorded with a different input")]
75    InputMismatch {
76        /// The existing effect.
77        id: EffectId,
78    },
79
80    /// The key was used before with a different [`EffectKind`].
81    #[error("effect {id} was recorded as {stored:?}, not {requested:?}")]
82    KindMismatch {
83        /// The existing effect.
84        id: EffectId,
85        /// The kind on record.
86        stored: EffectKind,
87        /// The kind of this call.
88        requested: EffectKind,
89    },
90
91    /// The input could not be serialized.
92    #[error("could not serialize the effect input: {0}")]
93    Input(#[source] serde_json::Error),
94
95    /// A committed effect's stored output does not deserialize into the
96    /// requested type, e.g. because the type changed since it was recorded.
97    #[error(
98        "effect {id} committed, but its stored output does not match the requested type: {source}"
99    )]
100    Output {
101        /// The committed effect.
102        id: EffectId,
103        /// The deserialization error.
104        #[source]
105        source: serde_json::Error,
106    },
107
108    /// The runtime's risk policy requires something the effect lacks, so
109    /// it was refused before anything was recorded.
110    #[error("effect `{key}` violates the risk policy: it requires {requirement}")]
111    PolicyViolation {
112        /// The effect's `name:key`.
113        key: String,
114        /// What the policy requires, e.g. `verification`.
115        requirement: &'static str,
116    },
117
118    /// No effect has this name and key.
119    #[error("no effect `{key}` exists")]
120    NoSuchEffect {
121        /// The effect's `name:key`.
122        key: String,
123    },
124
125    /// The handler is registered without `.compensable()`, so its effects
126    /// cannot be compensated.
127    #[error("the handler for effect `{name}` is not registered as compensable")]
128    NotCompensable {
129        /// The effect name.
130        name: &'static str,
131    },
132
133    /// No handler of the submitted type is registered under its name.
134    #[error("no handler registered for effect `{name}`")]
135    NotRegistered {
136        /// The effect name.
137        name: &'static str,
138    },
139
140    /// A stored input no longer deserializes into the handler's input type,
141    /// e.g. because the type changed since the effect was recorded.
142    #[error("effect {id}'s stored input does not match its handler's input type: {source}")]
143    StoredInput {
144        /// The effect.
145        id: EffectId,
146        /// The deserialization error.
147        #[source]
148        source: serde_json::Error,
149    },
150
151    /// The store failed. If this happens after the action ran, the effect
152    /// stays in doubt and recovery will treat its outcome as unknown.
153    #[error(transparent)]
154    Store(#[from] StoreError),
155
156    /// The runtime's own task failed (a bug in `agent-effects`).
157    #[error("effect task failed: {0}")]
158    Internal(String),
159}