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 thiserror::Error;
10
11use crate::context::Context;
12use crate::invariant::InvariantClass;
13
14/// Top-level error type for Converge operations.
15///
16/// Note: Context is boxed in error variants to keep the error type small,
17/// as recommended by clippy. Access via `error.context()` method.
18#[derive(Debug, Error)]
19pub enum ConvergeError {
20 /// Budget limit exceeded (cycles, facts, or time).
21 #[error("budget exhausted: {kind}")]
22 BudgetExhausted { kind: String },
23
24 /// An invariant was violated during execution.
25 #[error("{class:?} invariant '{name}' violated: {reason}")]
26 InvariantViolation {
27 /// Name of the violated invariant.
28 name: String,
29 /// Class of the invariant (Structural, Semantic, Acceptance).
30 class: InvariantClass,
31 /// Reason for the violation.
32 reason: String,
33 /// Final context state (including diagnostic facts). Boxed to reduce error size.
34 context: Box<Context>,
35 },
36
37 /// Agent execution failed.
38 #[error("agent failed: {agent_id}")]
39 AgentFailed { agent_id: String },
40
41 /// Conflicting facts detected for the same ID.
42 #[error("conflict detected for fact '{id}': existing content '{existing}' vs new content '{new}'")]
43 Conflict {
44 /// ID of the conflicting fact.
45 id: String,
46 /// Existing content.
47 existing: String,
48 /// New conflicting content.
49 new: String,
50 /// Final context state. Boxed to reduce error size.
51 context: Box<Context>,
52 },
53}
54
55impl ConvergeError {
56 /// Returns a reference to the context if this error variant carries one.
57 #[must_use]
58 pub fn context(&self) -> Option<&Context> {
59 match self {
60 Self::InvariantViolation { context, .. } | Self::Conflict { context, .. } => {
61 Some(context)
62 }
63 Self::BudgetExhausted { .. } | Self::AgentFailed { .. } => None,
64 }
65 }
66}