#[derive(Debug, thiserror::Error)]
pub enum MemoryError {
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
#[error("Embedding request failed: {0}")]
EmbeddingRequest(#[from] reqwest::Error),
#[error("Embedding provider returned {actual} dimensions, expected {expected}")]
DimensionMismatch { expected: usize, actual: usize },
#[error("Embedding provider returned {returned} vectors, expected {requested}")]
EmbeddingBatchCountMismatch { requested: usize, returned: usize },
#[error("Embedding vector has {actual} dimensions, expected {expected}")]
EmbeddingDimensionMismatch { expected: usize, actual: usize },
#[error("Embedding vector contains non-finite value at index {index}")]
NonFiniteEmbeddingValue { index: usize },
#[error("Vector blob length mismatch: expected {expected_bytes} bytes, got {actual_bytes}")]
VectorBlobLengthMismatch {
expected_bytes: usize,
actual_bytes: usize,
},
#[error("Vector codec profile mismatch: expected {expected_digest}, got {actual_digest}")]
VectorCodecProfileMismatch {
expected_digest: String,
actual_digest: String,
},
#[error("Search receipt ID conflict for {receipt_id}")]
SearchReceiptConflict {
receipt_id: String,
},
#[error("Digest error: {0}")]
DigestError(String),
#[error("Search receipt not found: {receipt_id}")]
SearchReceiptNotFound {
receipt_id: String,
},
#[error("Invalid embedding data: expected {expected_bytes} bytes, got {actual_bytes}")]
InvalidEmbedding {
expected_bytes: usize,
actual_bytes: usize,
},
#[error("Embedding model mismatch: database has '{stored}', config specifies '{configured}'")]
ModelMismatch { stored: String, configured: String },
#[error("Session not found: {0}")]
SessionNotFound(String),
#[error("Fact not found: {0}")]
FactNotFound(String),
#[error("Document not found: {0}")]
DocumentNotFound(String),
#[error("Embedding provider unavailable: {0}")]
EmbedderUnavailable(String),
#[error("Migration failed at version {version}: {reason}")]
MigrationFailed { version: u32, reason: String },
#[error("HNSW index error: {0}")]
HnswError(String),
#[error("Not implemented: {0}")]
NotImplemented(String),
#[error("Invalid HNSW key format: {0}")]
InvalidKey(String),
#[error("Quantization error: {0}")]
QuantizationError(String),
#[error("Storage path error: {0}")]
StorageError(String),
#[error("Index integrity check failed: {in_sqlite_not_hnsw} items in SQLite but not HNSW, {in_hnsw_not_sqlite} items in HNSW but not SQLite")]
IntegrityError {
in_sqlite_not_hnsw: usize,
in_hnsw_not_sqlite: usize,
},
#[error(
"Schema version {found} is ahead of max supported {supported} — upgrade semantic-memory"
)]
SchemaAhead {
found: u32,
supported: u32,
},
#[error("Content too large: {size} bytes exceeds limit of {limit} bytes")]
ContentTooLarge {
size: usize,
limit: usize,
},
#[error("Namespace '{namespace}' has {count} facts, limit is {limit}")]
NamespaceFull {
namespace: String,
count: usize,
limit: usize,
},
#[error("Database size limit exceeded: current footprint is {current} bytes, limit is {limit} bytes")]
DatabaseSizeLimitExceeded {
current: u64,
limit: u64,
},
#[error("Episode not found: {0}")]
EpisodeNotFound(String),
#[error("Pool reader acquisition timed out after {elapsed_ms}ms (pool size: {pool_size})")]
PoolTimeout {
elapsed_ms: u64,
pool_size: usize,
},
#[error(
"Vector scan hard limit exceeded for {table}: scanned {scanned} rows, limit is {limit}"
)]
VectorScanLimitExceeded {
table: String,
scanned: usize,
limit: usize,
},
#[error("Invalid configuration for '{field}': {reason}")]
InvalidConfig {
field: &'static str,
reason: String,
},
#[error("Corrupt data in {table} ({row_id}): {detail}")]
CorruptData {
table: &'static str,
row_id: String,
detail: String,
},
#[error("Invalid import envelope: {reason}")]
ImportInvalid {
reason: String,
},
#[error("Import envelope already ingested: {envelope_id}")]
ImportDuplicate {
envelope_id: String,
},
#[error(
"Import requires digest migration or receipt repair for {source_envelope_id}: {detail}"
)]
ImportMigrationRequired {
source_envelope_id: String,
detail: String,
},
#[error("{0}")]
Other(String),
}
impl MemoryError {
pub fn kind(&self) -> &'static str {
match self {
Self::Database(_) => "database",
Self::EmbeddingRequest(_) => "embedding_request",
Self::DimensionMismatch { .. } => "dimension_mismatch",
Self::EmbeddingBatchCountMismatch { .. } => "embedding_batch_count_mismatch",
Self::EmbeddingDimensionMismatch { .. } => "embedding_dimension_mismatch",
Self::NonFiniteEmbeddingValue { .. } => "non_finite_embedding_value",
Self::VectorBlobLengthMismatch { .. } => "vector_blob_length_mismatch",
Self::VectorCodecProfileMismatch { .. } => "vector_codec_profile_mismatch",
Self::SearchReceiptConflict { .. } => "search_receipt_conflict",
Self::DigestError(_) => "digest_error",
Self::SearchReceiptNotFound { .. } => "search_receipt_not_found",
Self::InvalidEmbedding { .. } => "invalid_embedding",
Self::ModelMismatch { .. } => "model_mismatch",
Self::SessionNotFound(_) => "session_not_found",
Self::FactNotFound(_) => "fact_not_found",
Self::DocumentNotFound(_) => "document_not_found",
Self::EpisodeNotFound(_) => "episode_not_found",
Self::PoolTimeout { .. } => "pool_timeout",
Self::VectorScanLimitExceeded { .. } => "vector_scan_limit_exceeded",
Self::EmbedderUnavailable(_) => "embedder_unavailable",
Self::MigrationFailed { .. } => "migration_failed",
Self::HnswError(_) => "hnsw_error",
Self::NotImplemented(_) => "not_implemented",
Self::InvalidKey(_) => "invalid_key",
Self::QuantizationError(_) => "quantization_error",
Self::StorageError(_) => "storage_error",
Self::IntegrityError { .. } => "integrity_error",
Self::SchemaAhead { .. } => "schema_ahead",
Self::ContentTooLarge { .. } => "content_too_large",
Self::NamespaceFull { .. } => "namespace_full",
Self::DatabaseSizeLimitExceeded { .. } => "database_size_limit_exceeded",
Self::InvalidConfig { .. } => "invalid_config",
Self::CorruptData { .. } => "corrupt_data",
Self::ImportInvalid { .. } => "import_invalid",
Self::ImportDuplicate { .. } => "import_duplicate",
Self::ImportMigrationRequired { .. } => "import_migration_required",
Self::Other(_) => "other",
}
}
}