use thiserror::Error;
use mempill_types::{AgentId, ClaimRef};
#[derive(Debug, Error)]
pub enum MemError {
#[error("Missing or untyped provenance on write: claim cannot be committed without a provenance label")]
MissingProvenance,
#[error("Caller does not hold write authority for agent_id {agent_id:?}")]
WriteAuthorityViolation {
agent_id: AgentId,
},
#[error("Malformed fact: {reason}")]
MalformedFact {
reason: String,
},
#[error("Unknown or invalid agent_id: {agent_id:?}")]
UnknownAgentId {
agent_id: AgentId,
},
#[error("Claim not found: {claim_ref:?}")]
ClaimNotFound {
claim_ref: ClaimRef,
},
#[error("Write lock for agent_id {agent_id:?} is already held (single-writer-per-agent-id violation)")]
WriteLockContention {
agent_id: AgentId,
},
#[error("spawn_blocking task failed: {reason}")]
SpawnBlocking {
reason: String,
},
#[error("Atomic commit unit violated: partial write detected for agent_id {agent_id:?}")]
AtomicCommitViolation {
agent_id: AgentId,
},
#[error(
"Fixed-history monotonicity violated: belief changed between reads without an intervening write \
for agent_id {agent_id:?}"
)]
MonotonicityViolation {
agent_id: AgentId,
},
#[error(
"Belief cache inconsistency: materialized belief cache disagrees with canonical fold \
(cache must be subordinate)"
)]
BeliefCacheInconsistency,
#[error(
"Temporal coherence failure: valid_time_start ({start}) is after valid_time_end ({end})"
)]
IncoherentTemporalWindow {
start: String,
end: String,
},
#[error("Persistence error: {source}")]
Persistence {
#[from]
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error("SQLite PRAGMA initialization failed: {reason}")]
PragmaInitFailed {
reason: String,
},
#[error("Oracle port error: {reason}")]
OracleError {
reason: String,
},
#[error("Pending-adjudication store error: {source}")]
PendingStore {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error("Adjudication handle not found: {handle_id}")]
AdjudicationHandleNotFound {
handle_id: uuid::Uuid,
},
#[error("Engine calibration parameter invalid: {param} = {value}: {reason}")]
ConfigurationError {
param: String,
value: String,
reason: String,
},
}
pub type WriteResult = Result<mempill_types::WriteOutcome, MemError>;
pub type BeliefResult = Result<mempill_types::BeliefProjection, MemError>;
#[cfg(test)]
mod tests {
use super::*;
use mempill_types::AgentId;
#[test]
fn mem_error_missing_provenance_display() {
let e = MemError::MissingProvenance;
let s = e.to_string();
assert!(s.contains("provenance"));
}
#[test]
fn mem_error_malformed_fact_carries_reason() {
let e = MemError::MalformedFact { reason: "empty subject".into() };
assert!(e.to_string().contains("empty subject"));
}
#[test]
fn mem_error_spawn_blocking_present_and_displays() {
let e = MemError::SpawnBlocking { reason: "task panicked".into() };
let s = e.to_string();
assert!(s.contains("spawn_blocking"));
assert!(s.contains("task panicked"));
}
#[test]
fn mem_error_write_authority_violation_displays_agent_id() {
let e = MemError::WriteAuthorityViolation {
agent_id: AgentId("agent-42".into()),
};
assert!(e.to_string().contains("agent-42"));
}
#[test]
fn mem_error_claim_not_found_displays_claim_ref() {
let id = uuid::Uuid::new_v4();
let e = MemError::ClaimNotFound {
claim_ref: mempill_types::ClaimRef(id),
};
assert!(e.to_string().contains(&id.to_string()));
}
#[test]
fn mem_error_atomic_commit_violation_displays_agent_id() {
let e = MemError::AtomicCommitViolation {
agent_id: AgentId("agent-99".into()),
};
assert!(e.to_string().contains("agent-99"));
}
#[test]
fn mem_error_incoherent_temporal_window_displays_times() {
let e = MemError::IncoherentTemporalWindow {
start: "2025-01-02T00:00:00Z".into(),
end: "2025-01-01T00:00:00Z".into(),
};
let s = e.to_string();
assert!(s.contains("2025-01-02"));
assert!(s.contains("2025-01-01"));
}
#[test]
fn mem_error_oracle_error_carries_reason() {
let e = MemError::OracleError { reason: "timeout".into() };
assert!(e.to_string().contains("timeout"));
}
#[test]
fn mem_error_configuration_error_displays_all_fields() {
let e = MemError::ConfigurationError {
param: "valid_time_confidence_threshold".into(),
value: "-0.1".into(),
reason: "must be in [0.0, 1.0]".into(),
};
let s = e.to_string();
assert!(s.contains("valid_time_confidence_threshold"));
assert!(s.contains("-0.1"));
assert!(s.contains("must be in [0.0, 1.0]"));
}
#[test]
fn mem_error_adjudication_handle_not_found() {
let id = uuid::Uuid::new_v4();
let e = MemError::AdjudicationHandleNotFound { handle_id: id };
assert!(e.to_string().contains(&id.to_string()));
}
#[test]
fn mem_error_pragma_init_failed() {
let e = MemError::PragmaInitFailed { reason: "WAL failed".into() };
assert!(e.to_string().contains("WAL failed"));
}
#[test]
fn mem_error_is_debug() {
let e = MemError::MissingProvenance;
let s = format!("{e:?}");
assert!(s.contains("MissingProvenance"));
}
}