use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum FoldError {
#[error("event at seq {seq} belongs to run {actual}, fold input is for run {expected}")]
RunIdMismatch {
seq: u64,
expected: String,
actual: String,
},
#[error("event seq {seq} does not increase monotonically (previous {prev})")]
NonMonotonicSeq {
prev: u64,
seq: u64,
},
#[error("event {event_type} at seq {seq} precedes runStarted")]
EventBeforeRunStarted {
seq: u64,
event_type: &'static str,
},
#[error("duplicate runStarted at seq {seq} (later segments start with runResumed)")]
DuplicateRunStarted {
seq: u64,
},
#[error("{event_type} at seq {seq} has no in-flight step (missing stepEntered)")]
EventOutsideStep {
seq: u64,
event_type: &'static str,
},
#[error("stepExited at seq {seq} without a matching stepEntered")]
StepExitedWithoutEntry {
seq: u64,
},
#[error("verdictRecorded at seq {seq} targets neither the in-flight step nor a completed step")]
VerdictWithoutTarget {
seq: u64,
},
#[error("callFramePopped at seq {seq} would pop the root frame")]
PoppedRootFrame {
seq: u64,
},
#[error("stepExited at seq {seq} with no active call frame")]
NoActiveFrame {
seq: u64,
},
#[error(
"callFramePushed(rebase) at seq {seq} targets frame level {level}, stack depth is {depth}"
)]
RebaseWithoutFrame {
seq: u64,
level: usize,
depth: usize,
},
#[error("humanResponded at seq {seq} pairs no pending request (requestId {request_id})")]
UnpairedHumanResponse {
seq: u64,
request_id: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("sqlite error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("i/o error: {0}")]
Io(#[from] std::io::Error),
#[error("serialization error: {0}")]
Serde(#[from] serde_json::Error),
#[error("unknown run {0}")]
UnknownRun(String),
#[error("run {0} already exists")]
DuplicateRun(String),
#[error("run {0} has no materialized checkpoint")]
NoCheckpoint(String),
#[error(transparent)]
Fold(#[from] FoldError),
#[error(
"checkpoint for run {run_id} is stale: materialized at seq \
{materialized_seq}, log head is {log_seq}"
)]
StaleCheckpoint {
run_id: String,
materialized_seq: u64,
log_seq: u64,
},
#[error(
"materialized checkpoint for run {run_id} (log_seq {log_seq}) differs from the rebuilt fold"
)]
CheckpointMismatch {
run_id: String,
log_seq: u64,
materialized: String,
rebuilt: String,
},
#[error("run {run_id} status '{stored}' differs from folded status '{folded}'")]
StatusMismatch {
run_id: String,
stored: String,
folded: String,
},
#[error("corrupt stored data for run {run_id}: {reason}")]
Corrupt {
run_id: String,
reason: String,
},
#[error("human response for request {request_id} of run {run_id} rejected: {reason}")]
HumanResponseRejected {
run_id: String,
request_id: String,
reason: HumanResponseRejection,
},
#[error("run {run_id} has no step instance at '{path}'")]
UnknownStepInstance {
run_id: String,
path: String,
},
#[error("step '{step}' of run {run_id} is ambiguous; candidates: {}", candidates.join(", "))]
AmbiguousStep {
run_id: String,
step: String,
candidates: Vec<String>,
},
#[error("run path '{input}' does not parse: {message}")]
BadRunPath {
input: String,
message: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HumanResponseRejection {
UnknownRequest,
AlreadyResponded,
DeadlineExpired {
deadline_at_ms: u64,
received_at_ms: u64,
},
Settled,
InvalidShape {
reason: String,
},
}
impl fmt::Display for HumanResponseRejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HumanResponseRejection::UnknownRequest => write!(f, "unknown request"),
HumanResponseRejection::AlreadyResponded => {
write!(f, "already responded (first response wins)")
}
HumanResponseRejection::DeadlineExpired {
deadline_at_ms,
received_at_ms,
} => write!(
f,
"deadline expired (deadlineAtMs {deadline_at_ms}, received at {received_at_ms})"
),
HumanResponseRejection::Settled => {
write!(f, "the request is no longer pending (already settled)")
}
HumanResponseRejection::InvalidShape { reason } => {
write!(f, "invalid response shape: {reason}")
}
}
}
}