converge_core/error.rs
1// Copyright 2024-2025 Aprio One AB, Sweden
2// Author: Kenneth Pernyer, kenneth@aprio.one
3// SPDX-License-Identifier: LicenseRef-Proprietary
4// All rights reserved. This source code is proprietary and confidential.
5// Unauthorized copying, modification, or distribution is strictly prohibited.
6
7//! Error types for Converge.
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::context::Context;
13use crate::invariant::InvariantClass;
14
15/// Top-level error type for Converge operations.
16///
17/// Note: Context is boxed in error variants to keep the error type small,
18/// as recommended by clippy. Access via `error.context()` method.
19#[derive(Debug, Error, Serialize, Deserialize)]
20pub enum ConvergeError {
21 /// Budget limit exceeded (cycles, facts, or time).
22 #[error("budget exhausted: {kind}")]
23 BudgetExhausted { kind: String },
24
25 /// An invariant was violated during execution.
26 #[error("{class:?} invariant '{name}' violated: {reason}")]
27 InvariantViolation {
28 /// Name of the violated invariant.
29 name: String,
30 /// Class of the invariant (Structural, Semantic, Acceptance).
31 class: InvariantClass,
32 /// Reason for the violation.
33 reason: String,
34 /// Final context state (including diagnostic facts). Boxed to reduce error size.
35 context: Box<Context>,
36 },
37
38 /// Agent execution failed.
39 #[error("agent failed: {agent_id}")]
40 AgentFailed { agent_id: String },
41
42 /// Conflicting facts detected for the same ID.
43 #[error(
44 "conflict detected for fact '{id}': existing content '{existing}' vs new content '{new}'"
45 )]
46 Conflict {
47 /// ID of the conflicting fact.
48 id: String,
49 /// Existing content.
50 existing: String,
51 /// New conflicting content.
52 new: String,
53 /// Final context state. Boxed to reduce error size.
54 context: Box<Context>,
55 },
56}
57
58impl ConvergeError {
59 /// Returns a reference to the context if this error variant carries one.
60 #[must_use]
61 pub fn context(&self) -> Option<&Context> {
62 match self {
63 Self::InvariantViolation { context, .. } | Self::Conflict { context, .. } => {
64 Some(context)
65 }
66 Self::BudgetExhausted { .. } | Self::AgentFailed { .. } => None,
67 }
68 }
69}