use crate::drafts::{DraftId, DraftState};
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LibrarianError {
#[error("llm invocation failed: {message}")]
LlmInvocationFailed {
message: String,
},
#[error("llm produced non-JSON response: {parse_err}")]
LlmNonJsonResponse {
raw: String,
parse_err: String,
},
#[error("validation failed after {attempts} retries: {source}")]
ValidationFailedAfterRetry {
attempts: u32,
#[source]
source: mimir_core::PipelineError,
},
#[error("validation rejected candidate: {source}")]
ValidationRejected {
#[source]
source: mimir_core::PipelineError,
},
#[error("validation clock unavailable: {message}")]
ValidationClock {
message: String,
},
#[error("store open failed: {source}")]
StoreOpen {
#[source]
source: mimir_core::StoreError,
},
#[error("workspace write lock unavailable: {source}")]
WorkspaceLock {
#[source]
source: mimir_core::WorkspaceLockError,
},
#[error("store commit failed: {source}")]
StoreCommit {
#[source]
source: mimir_core::StoreError,
},
#[error("could not acquire workspace lease: {message}")]
LeaseContest {
message: String,
},
#[error("draft i/o error: {0}")]
DraftIo(#[from] std::io::Error),
#[error("draft JSON error: {0}")]
DraftJson(#[from] serde_json::Error),
#[error("unsupported draft schema version: {version}")]
UnsupportedDraftSchema {
version: u32,
},
#[error("draft id mismatch: declared {declared}, computed {computed}")]
DraftIdMismatch {
declared: String,
computed: String,
},
#[error("invalid draft transition: {from:?} -> {to:?}")]
InvalidDraftTransition {
from: DraftState,
to: DraftState,
},
#[error("draft {id} not found in {state:?}")]
DraftNotFound {
state: DraftState,
id: DraftId,
},
#[error("draft {id} already exists in {state:?}")]
DraftAlreadyExists {
state: DraftState,
id: DraftId,
},
#[error(
"quorum output already exists for episode {episode_id}, round {round}, participant {participant_id}"
)]
QuorumOutputAlreadyExists {
episode_id: String,
round: String,
participant_id: String,
},
#[error("quorum protocol violation for episode {episode_id}: {message}")]
QuorumProtocolViolation {
episode_id: String,
message: String,
},
#[error("retry budget exhausted after {attempts} attempts")]
RetryBudgetExhausted {
attempts: u32,
},
#[error("not yet implemented: {component} (see crate README § 'Roadmap within Category 1')")]
NotYetImplemented {
component: &'static str,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_is_informative_for_not_yet_implemented() {
let err = LibrarianError::NotYetImplemented {
component: "LlmInvoker::invoke",
};
let text = err.to_string();
assert!(text.contains("LlmInvoker::invoke"));
assert!(text.contains("not yet implemented"));
}
#[test]
fn draft_io_wraps_std_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing draft");
let wrapped: LibrarianError = io_err.into();
assert!(
matches!(wrapped, LibrarianError::DraftIo(_)),
"expected DraftIo, got something else",
);
}
#[test]
fn retry_budget_exhausted_reports_attempts() {
let err = LibrarianError::RetryBudgetExhausted { attempts: 3 };
assert!(err.to_string().contains("3 attempts"));
}
}