Skip to main content

xlog_core/
error.rs

1//! Error types for XLOG
2
3use thiserror::Error;
4
5/// Primary error type for XLOG operations
6#[derive(Debug, Error)]
7#[non_exhaustive]
8pub enum XlogError {
9    /// Parse error from the Datalog frontend.
10    #[error("Parse error: {0}")]
11    Parse(String),
12
13    /// Stratification failed due to a cycle through negation.
14    #[error("Stratification failed: cycle through negation involving {0:?}")]
15    StratificationCycle(Vec<String>),
16
17    /// A variable is not bound in any positive body literal (domain safety violation).
18    #[error("Domain safety: variable {0} not bound in positive literal")]
19    UnsafeVariable(String),
20
21    /// A runtime configuration value was present but malformed.
22    #[error("Invalid configuration {name}={value:?}; expected {expected}")]
23    Configuration {
24        /// Configuration key or environment variable name.
25        name: String,
26        /// Rejected value, or a diagnostic placeholder when it is not Unicode.
27        value: String,
28        /// Human-readable description of the accepted value domain.
29        expected: &'static str,
30    },
31
32    /// GPU memory budget exceeded.
33    #[error("Resource exhausted: {context}, estimated {estimated_bytes} bytes, budget {budget_bytes} bytes")]
34    ResourceExhausted {
35        /// Description of the operation that exceeded the budget.
36        context: String,
37        /// Estimated memory required in bytes.
38        estimated_bytes: u64,
39        /// Available memory budget in bytes.
40        budget_bytes: u64,
41    },
42
43    /// A non-memory cardinality, dimension, or fixed-capacity limit was exceeded.
44    #[error("Capacity exceeded: {context}, required {required} {unit}, limit {limit} {unit}")]
45    CapacityExceeded {
46        /// Operation or data structure whose capacity was exceeded.
47        context: String,
48        /// Minimum observed or requested capacity.
49        required: u64,
50        /// Configured or representable capacity.
51        limit: u64,
52        /// Human-readable unit shared by `required` and `limit`.
53        unit: String,
54    },
55
56    /// A bounded iterative computation failed to reach its required fixpoint.
57    #[error(
58        "Convergence failed: {context}, {unconverged_worlds} of {total_worlds} worlds did not converge within {iteration_limit} iterations"
59    )]
60    ConvergenceFailure {
61        /// Iterative computation that did not converge.
62        context: String,
63        /// Number of worlds that remained unconverged.
64        unconverged_worlds: u64,
65        /// Total number of evaluated worlds.
66        total_worlds: u64,
67        /// Maximum iterations executed per world.
68        iteration_limit: u64,
69    },
70
71    /// The D4 **compile** phase declined a CNF too large to compile safely
72    /// (the knowledge-compilation emit buffers are fixed-capacity; a larger
73    /// instance would overrun them and fail with a context-poisoning CUDA
74    /// launch error). A typed, catchable decline distinct from the verify-phase
75    /// signal — "too big to compile", not "verify gave up". The caller can skip
76    /// the query or fall back to an approximate engine.
77    #[error("D4 compile declined: {context}: {detail}")]
78    CompileCapacityExceeded {
79        /// Description of the compile operation that was declined.
80        context: String,
81        /// Which capacity tripped and its measured/configured values.
82        detail: String,
83    },
84
85    /// The GPU CDCL equivalence **verifier** declined rather than risk a
86    /// CUDA launch failure that poisons the primary context: a per-verify
87    /// conflict budget ran out before the search reached a definite answer
88    /// (INDETERMINATE — declined fail-closed, never trusted as a proof).
89    /// Distinct from [`CompileCapacityExceeded`] so the two phases stay
90    /// diagnosably separate. The caller can skip the query or fall back to an
91    /// approximate engine.
92    #[error("D4 equivalence verify declined: {context}: {detail}")]
93    VerifyBudgetExceeded {
94        /// Description of the verify operation that was declined.
95        context: String,
96        /// Which budget tripped and its measured/configured values.
97        detail: String,
98    },
99
100    /// GPU kernel launch or execution error.
101    #[error("Kernel error: {0}")]
102    Kernel(String),
103
104    /// Type checking or inference error.
105    #[error("Type error: {0}")]
106    Type(String),
107
108    /// Compilation pipeline error.
109    #[error("Compilation error: {0}")]
110    Compilation(String),
111
112    /// Epistemic construct is known to the frontend but unsupported in this context.
113    #[error("Unsupported epistemic construct: {construct} ({context})")]
114    UnsupportedEpistemicConstruct {
115        /// Construct that was rejected.
116        construct: String,
117        /// Context where the construct was rejected.
118        context: String,
119    },
120
121    /// A compiler-generated integrity-constraint relation contains witness rows.
122    #[error(
123        "Constraint {constraint_index} violated: {relation_name} produced {witness_rows} witness row(s)"
124    )]
125    ConstraintViolation {
126        /// Source-order constraint index encoded by the compiler-generated relation.
127        constraint_index: usize,
128        /// Compiler-generated relation that contains the violation witnesses.
129        relation_name: String,
130        /// Number of materialized violation witnesses.
131        witness_rows: usize,
132    },
133
134    /// Runtime execution error.
135    #[error("Execution error: {0}")]
136    Execution(String),
137}
138
139impl XlogError {
140    /// Create a Kernel error with structured context: "op: detail: source".
141    pub fn kernel_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
142        XlogError::Kernel(format!("{op}: {detail}: {source}"))
143    }
144
145    /// Create an Execution error with structured context.
146    pub fn execution_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
147        XlogError::Execution(format!("{op}: {detail}: {source}"))
148    }
149
150    /// Create a Compilation error with structured context.
151    pub fn compilation_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
152        XlogError::Compilation(format!("{op}: {detail}: {source}"))
153    }
154}
155
156/// Result alias using XlogError
157pub type Result<T> = std::result::Result<T, XlogError>;
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_parse_error_display() {
165        let err = XlogError::Parse("unexpected token".to_string());
166        assert_eq!(err.to_string(), "Parse error: unexpected token");
167    }
168
169    #[test]
170    fn test_stratification_cycle_display() {
171        let err = XlogError::StratificationCycle(vec!["foo".to_string(), "bar".to_string()]);
172        assert!(err.to_string().contains("foo"));
173        assert!(err.to_string().contains("bar"));
174    }
175
176    #[test]
177    fn test_resource_exhausted_display() {
178        let err = XlogError::ResourceExhausted {
179            context: "join operation".to_string(),
180            estimated_bytes: 1024,
181            budget_bytes: 512,
182        };
183        assert!(err.to_string().contains("1024"));
184        assert!(err.to_string().contains("512"));
185    }
186
187    #[test]
188    fn test_capacity_exceeded_display_preserves_non_byte_units() {
189        let err = XlogError::CapacityExceeded {
190            context: "resident sparse rows".to_string(),
191            required: 65,
192            limit: 64,
193            unit: "rows per world".to_string(),
194        };
195        assert_eq!(
196            err.to_string(),
197            "Capacity exceeded: resident sparse rows, required 65 rows per world, limit 64 rows per world"
198        );
199    }
200
201    #[test]
202    fn test_convergence_failure_display_reports_affected_worlds_and_limit() {
203        let err = XlogError::ConvergenceFailure {
204            context: "resident Monte Carlo fixpoint".to_string(),
205            unconverged_worlds: 3,
206            total_worlds: 128,
207            iteration_limit: 7,
208        };
209        assert_eq!(
210            err.to_string(),
211            "Convergence failed: resident Monte Carlo fixpoint, 3 of 128 worlds did not converge within 7 iterations"
212        );
213    }
214
215    #[test]
216    fn test_constraint_violation_display() {
217        let err = XlogError::ConstraintViolation {
218            constraint_index: 3,
219            relation_name: "__xlog_constraint_3".to_string(),
220            witness_rows: 2,
221        };
222        assert_eq!(
223            err.to_string(),
224            "Constraint 3 violated: __xlog_constraint_3 produced 2 witness row(s)"
225        );
226    }
227
228    #[test]
229    fn test_kernel_ctx() {
230        let err = XlogError::kernel_ctx("download_column", "dtoh copy failed", &"device error 42");
231        assert_eq!(
232            err.to_string(),
233            "Kernel error: download_column: dtoh copy failed: device error 42"
234        );
235    }
236
237    #[test]
238    fn test_execution_ctx() {
239        let err = XlogError::execution_ctx("execute_node", "filter failed", &"type mismatch");
240        assert_eq!(
241            err.to_string(),
242            "Execution error: execute_node: filter failed: type mismatch"
243        );
244    }
245
246    #[test]
247    fn test_compilation_ctx() {
248        let err = XlogError::compilation_ctx("compile_d4", "frontier overflow", &"limit 1024");
249        assert_eq!(
250            err.to_string(),
251            "Compilation error: compile_d4: frontier overflow: limit 1024"
252        );
253    }
254}