Skip to main content

forge_engine/
error.rs

1use std::path::PathBuf;
2
3pub use forge_policy::{Violation, ViolationKind};
4
5/// All errors produced by forge-engine.
6#[derive(Debug, thiserror::Error)]
7pub enum ForgeError {
8    #[error("refuse to open database: {reason}")]
9    RefuseToOpenDb { reason: String },
10
11    #[error("database error: {0}")]
12    Database(#[from] rusqlite::Error),
13
14    #[error("IO error: {0}")]
15    Io(#[from] std::io::Error),
16
17    #[error("serialization error: {0}")]
18    Serialization(#[from] serde_json::Error),
19
20    #[error("patch validation failed: {violations:?}")]
21    PatchValidation { violations: Vec<Violation> },
22
23    #[error("anchor resolution failed: {0}")]
24    AnchorResolution(String),
25
26    #[error("patch apply failed: {0}")]
27    PatchApply(String),
28
29    #[error("command timeout after {timeout_secs}s: {command}")]
30    CommandTimeout { command: String, timeout_secs: u64 },
31
32    #[error("command failed (exit {exit_code}): {command}")]
33    CommandFailed { command: String, exit_code: i32 },
34
35    #[error("sealed mode unsupported for runtime: {runtime}")]
36    SealedModeUnsupported { runtime: String },
37
38    #[error("remote model forbidden in sealed mode")]
39    RemoteModelForbiddenInSealedMode,
40
41    #[error("no container runtime found")]
42    NoContainerRuntime,
43
44    #[error("promotion failed: {criterion} = {value}")]
45    PromotionFailed { criterion: String, value: String },
46
47    #[error("golden mindstate mismatch: version={version_id}, input_hash={input_hash}")]
48    GoldenMindStateMismatch {
49        version_id: String,
50        input_hash: String,
51    },
52
53    #[error("CEA: raw source detected in node")]
54    CeaRawSourceDetected,
55
56    #[error("fixture error: {0}")]
57    Fixture(String),
58
59    #[error("config error: {0}")]
60    Config(String),
61
62    #[error("not found: {0}")]
63    NotFound(String),
64
65    #[error("workspace path error: {0}")]
66    WorkspacePath(PathBuf),
67
68    #[error("experiment failed: {0}")]
69    ExperimentFailed(String),
70
71    #[error("limit exceeded: {limit} (value={value}, max={max})")]
72    LimitExceeded { limit: String, value: u64, max: u64 },
73
74    #[error("export error: {0}")]
75    Export(String),
76
77    #[error("write-through blocked: danger-sm-write feature not enabled")]
78    WriteThroughBlocked,
79
80    #[error("pair incomparable: {reasons:?}")]
81    PairIncomparable { reasons: Vec<String> },
82
83    #[error("sealed bundle cannot be mutated")]
84    SealedBundle,
85
86    #[error("{0}")]
87    Other(String),
88}
89
90impl ForgeError {
91    /// Returns a stable string discriminant for programmatic matching.
92    pub fn kind(&self) -> &'static str {
93        match self {
94            Self::RefuseToOpenDb { .. } => "refuse_to_open_db",
95            Self::Database(_) => "database",
96            Self::Io(_) => "io",
97            Self::Serialization(_) => "serialization",
98            Self::PatchValidation { .. } => "patch_validation",
99            Self::AnchorResolution(_) => "anchor_resolution",
100            Self::PatchApply(_) => "patch_apply",
101            Self::CommandTimeout { .. } => "command_timeout",
102            Self::CommandFailed { .. } => "command_failed",
103            Self::SealedModeUnsupported { .. } => "sealed_mode_unsupported",
104            Self::RemoteModelForbiddenInSealedMode => "remote_model_forbidden",
105            Self::NoContainerRuntime => "no_container_runtime",
106            Self::PromotionFailed { .. } => "promotion_failed",
107            Self::GoldenMindStateMismatch { .. } => "golden_mindstate_mismatch",
108            Self::CeaRawSourceDetected => "cea_raw_source",
109            Self::Fixture(_) => "fixture",
110            Self::Config(_) => "config",
111            Self::NotFound(_) => "not_found",
112            Self::WorkspacePath(_) => "workspace_path",
113            Self::ExperimentFailed(_) => "experiment_failed",
114            Self::LimitExceeded { .. } => "limit_exceeded",
115            Self::Export(_) => "export",
116            Self::WriteThroughBlocked => "write_through_blocked",
117            Self::PairIncomparable { .. } => "pair_incomparable",
118            Self::SealedBundle => "sealed_bundle",
119            Self::Other(_) => "other",
120        }
121    }
122}
123
124pub type ForgeResult<T> = Result<T, ForgeError>;
125
126impl From<sandbox_workspace::WorkspaceError> for ForgeError {
127    fn from(error: sandbox_workspace::WorkspaceError) -> Self {
128        match error {
129            sandbox_workspace::WorkspaceError::Io(error) => Self::Io(error),
130            sandbox_workspace::WorkspaceError::Policy(error) => Self::Other(error.to_string()),
131        }
132    }
133}
134
135impl From<mindstate_core::MindStateError> for ForgeError {
136    fn from(error: mindstate_core::MindStateError) -> Self {
137        Self::Other(error.to_string())
138    }
139}
140
141impl From<stabilizer_core::StabilizerError> for ForgeError {
142    fn from(error: stabilizer_core::StabilizerError) -> Self {
143        Self::Other(error.to_string())
144    }
145}
146
147impl From<typed_patch::PatchError> for ForgeError {
148    fn from(error: typed_patch::PatchError) -> Self {
149        match error {
150            typed_patch::PatchError::Validation { violations } => {
151                Self::PatchValidation { violations }
152            }
153            typed_patch::PatchError::AnchorResolution(message) => Self::AnchorResolution(message),
154            typed_patch::PatchError::Apply(message) => Self::PatchApply(message),
155            typed_patch::PatchError::Io(error) => Self::Io(error),
156            typed_patch::PatchError::Workspace(error) => Self::from(error),
157            typed_patch::PatchError::Policy(error) => Self::Other(error.to_string()),
158            typed_patch::PatchError::Other(message) => Self::Other(message),
159        }
160    }
161}
162
163impl From<check_runner::RunnerError> for ForgeError {
164    fn from(error: check_runner::RunnerError) -> Self {
165        match error {
166            check_runner::RunnerError::Io(error) => Self::Io(error),
167            check_runner::RunnerError::Policy(error) => Self::Other(error.to_string()),
168            check_runner::RunnerError::Workspace(error) => Self::from(error),
169            check_runner::RunnerError::CommandTimeout {
170                command,
171                timeout_secs,
172            } => Self::CommandTimeout {
173                command,
174                timeout_secs,
175            },
176            check_runner::RunnerError::CommandFailed { command, exit_code } => {
177                Self::CommandFailed { command, exit_code }
178            }
179            check_runner::RunnerError::SealedModeUnsupported { runtime } => {
180                Self::SealedModeUnsupported { runtime }
181            }
182            check_runner::RunnerError::NoContainerRuntime => Self::NoContainerRuntime,
183            check_runner::RunnerError::Other(message) => Self::Other(message),
184        }
185    }
186}
187
188impl From<cea_core::CeaCoreError> for ForgeError {
189    fn from(error: cea_core::CeaCoreError) -> Self {
190        Self::Other(error.to_string())
191    }
192}
193
194impl From<cea_store::CeaStoreError> for ForgeError {
195    fn from(error: cea_store::CeaStoreError) -> Self {
196        match error {
197            cea_store::CeaStoreError::Serialization(error) => Self::Serialization(error),
198            cea_store::CeaStoreError::Backend(message) => Self::Other(message),
199        }
200    }
201}
202
203impl From<semantic_memory_forge::ExportEnvelopeError> for ForgeError {
204    fn from(error: semantic_memory_forge::ExportEnvelopeError) -> Self {
205        Self::Export(error.to_string())
206    }
207}
208
209impl From<forge_memory_bridge::BridgeError> for ForgeError {
210    fn from(error: forge_memory_bridge::BridgeError) -> Self {
211        Self::Export(error.to_string())
212    }
213}
214
215impl From<semantic_memory::MemoryError> for ForgeError {
216    fn from(error: semantic_memory::MemoryError) -> Self {
217        Self::Other(format!("semantic-memory write: {error}"))
218    }
219}