1use thiserror::Error;
7use mempill_types::{AgentId, ClaimRef};
8
9#[derive(Debug, Error)]
12pub enum MemError {
13 #[error("Missing or untyped provenance on write: claim cannot be committed without a provenance label")]
16 MissingProvenance,
17
18 #[error("Caller does not hold write authority for agent_id {agent_id:?}")]
20 WriteAuthorityViolation {
21 agent_id: AgentId,
23 },
24
25 #[error("Malformed fact: {reason}")]
27 MalformedFact {
28 reason: String,
30 },
31
32 #[error("Unknown or invalid agent_id: {agent_id:?}")]
34 UnknownAgentId {
35 agent_id: AgentId,
37 },
38
39 #[error("Claim not found: {claim_ref:?}")]
41 ClaimNotFound {
42 claim_ref: ClaimRef,
44 },
45
46 #[error("Write lock for agent_id {agent_id:?} is already held (single-writer-per-agent-id violation)")]
49 WriteLockContention {
50 agent_id: AgentId,
52 },
53
54 #[error("spawn_blocking task failed: {reason}")]
57 SpawnBlocking {
58 reason: String,
60 },
61
62 #[error("Atomic commit unit violated: partial write detected for agent_id {agent_id:?}")]
65 AtomicCommitViolation {
66 agent_id: AgentId,
68 },
69
70 #[error(
72 "Fixed-history monotonicity violated: belief changed between reads without an intervening write \
73 for agent_id {agent_id:?}"
74 )]
75 MonotonicityViolation {
76 agent_id: AgentId,
78 },
79
80 #[error(
82 "Belief cache inconsistency: materialized belief cache disagrees with canonical fold \
83 (cache must be subordinate)"
84 )]
85 BeliefCacheInconsistency,
86
87 #[error(
90 "Temporal coherence failure: valid_time_start ({start}) is after valid_time_end ({end})"
91 )]
92 IncoherentTemporalWindow {
93 start: String,
95 end: String,
97 },
98
99 #[error("Persistence error: {source}")]
102 Persistence {
103 #[from]
105 source: Box<dyn std::error::Error + Send + Sync + 'static>,
106 },
107
108 #[error("SQLite PRAGMA initialization failed: {reason}")]
110 PragmaInitFailed {
111 reason: String,
113 },
114
115 #[error("Oracle port error: {reason}")]
120 OracleError {
121 reason: String,
123 },
124
125 #[error("Pending-adjudication store error: {source}")]
127 PendingStore {
128 source: Box<dyn std::error::Error + Send + Sync + 'static>,
130 },
131
132 #[error("Adjudication handle not found: {handle_id}")]
134 AdjudicationHandleNotFound {
135 handle_id: uuid::Uuid,
137 },
138
139 #[error("Engine calibration parameter invalid: {param} = {value}: {reason}")]
142 ConfigurationError {
143 param: String,
145 value: String,
147 reason: String,
149 },
150}
151
152pub type WriteResult = Result<mempill_types::WriteOutcome, MemError>;
156
157pub type BeliefResult = Result<mempill_types::BeliefProjection, MemError>;
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use mempill_types::AgentId;
164
165 #[test]
166 fn mem_error_missing_provenance_display() {
167 let e = MemError::MissingProvenance;
168 let s = e.to_string();
169 assert!(s.contains("provenance"));
170 }
171
172 #[test]
173 fn mem_error_malformed_fact_carries_reason() {
174 let e = MemError::MalformedFact { reason: "empty subject".into() };
175 assert!(e.to_string().contains("empty subject"));
176 }
177
178 #[test]
179 fn mem_error_spawn_blocking_present_and_displays() {
180 let e = MemError::SpawnBlocking { reason: "task panicked".into() };
181 let s = e.to_string();
182 assert!(s.contains("spawn_blocking"));
183 assert!(s.contains("task panicked"));
184 }
185
186 #[test]
187 fn mem_error_write_authority_violation_displays_agent_id() {
188 let e = MemError::WriteAuthorityViolation {
189 agent_id: AgentId("agent-42".into()),
190 };
191 assert!(e.to_string().contains("agent-42"));
192 }
193
194 #[test]
195 fn mem_error_claim_not_found_displays_claim_ref() {
196 let id = uuid::Uuid::new_v4();
197 let e = MemError::ClaimNotFound {
198 claim_ref: mempill_types::ClaimRef(id),
199 };
200 assert!(e.to_string().contains(&id.to_string()));
201 }
202
203 #[test]
204 fn mem_error_atomic_commit_violation_displays_agent_id() {
205 let e = MemError::AtomicCommitViolation {
206 agent_id: AgentId("agent-99".into()),
207 };
208 assert!(e.to_string().contains("agent-99"));
209 }
210
211 #[test]
212 fn mem_error_incoherent_temporal_window_displays_times() {
213 let e = MemError::IncoherentTemporalWindow {
214 start: "2025-01-02T00:00:00Z".into(),
215 end: "2025-01-01T00:00:00Z".into(),
216 };
217 let s = e.to_string();
218 assert!(s.contains("2025-01-02"));
219 assert!(s.contains("2025-01-01"));
220 }
221
222 #[test]
223 fn mem_error_oracle_error_carries_reason() {
224 let e = MemError::OracleError { reason: "timeout".into() };
225 assert!(e.to_string().contains("timeout"));
226 }
227
228 #[test]
229 fn mem_error_configuration_error_displays_all_fields() {
230 let e = MemError::ConfigurationError {
231 param: "valid_time_confidence_threshold".into(),
232 value: "-0.1".into(),
233 reason: "must be in [0.0, 1.0]".into(),
234 };
235 let s = e.to_string();
236 assert!(s.contains("valid_time_confidence_threshold"));
237 assert!(s.contains("-0.1"));
238 assert!(s.contains("must be in [0.0, 1.0]"));
239 }
240
241 #[test]
242 fn mem_error_adjudication_handle_not_found() {
243 let id = uuid::Uuid::new_v4();
244 let e = MemError::AdjudicationHandleNotFound { handle_id: id };
245 assert!(e.to_string().contains(&id.to_string()));
246 }
247
248 #[test]
249 fn mem_error_pragma_init_failed() {
250 let e = MemError::PragmaInitFailed { reason: "WAL failed".into() };
251 assert!(e.to_string().contains("WAL failed"));
252 }
253
254 #[test]
255 fn mem_error_is_debug() {
256 let e = MemError::MissingProvenance;
257 let s = format!("{e:?}");
258 assert!(s.contains("MissingProvenance"));
259 }
260}