klieo-runlog 3.3.1

Tier 2 observability — RunLog aggregate + replay engine for klieo agents.
Documentation
//! `RunLogError` — the only error surface for this crate.

use crate::types::StepKind;
use klieo_core::ids::RunId;
use thiserror::Error;

/// Errors raised by `klieo-runlog` operations.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum RunLogError {
    /// Backend store failure (I/O, serialisation, SQL).
    #[error("runlog store error: {0}")]
    Store(String),

    /// `get`/`delete` requested a `run_id` that does not exist.
    #[error("runlog not found for run_id {0}")]
    NotFound(RunId),

    /// Projection from `Episode` stream into a `RunLog` aggregate failed.
    #[error("runlog projection error: {0}")]
    Projection(String),

    /// Free-form structural replay failure retained for back-compat; all
    /// in-crate paths now emit a typed variant instead.
    #[deprecated(note = "use ReplayMismatch, ReplayStep, or NoLlmCall")]
    #[error("runlog replay error: {0}")]
    Replay(String),

    /// The `RunLog` carried no `LlmCall` step, so strict `replay` has no final
    /// assistant text to return.
    #[error("runlog replay found no LlmCall step")]
    NoLlmCall,

    /// A replay double produced output that differs from the recording — the
    /// strict `replay` path's stop condition. Carries both sides so callers
    /// inspect the divergence without parsing the `Display` string.
    #[error("runlog replay mismatch at step {step} ({kind:?})")]
    ReplayMismatch {
        /// Zero-based index into the source `RunLog.steps`.
        step: u32,
        /// Copied from the diverging step; only `LlmCall`/`ToolCall` reach here,
        /// since other kinds re-issue no I/O on replay.
        kind: StepKind,
        /// Recorded output, normalized for comparison (see `flatten_output`).
        expected: String,
        /// Replayed output, normalized the same way as `expected`.
        actual: String,
    },

    /// A replay double failed to produce output for a step (the LLM/tool call
    /// errored or a scripted double exhausted) — distinct from a value
    /// mismatch, which surfaces as `ReplayMismatch` rather than an error.
    #[error("runlog replay step {step} failed")]
    ReplayStep {
        /// Zero-based index into the source `RunLog.steps`.
        step: u32,
        /// The `LlmError` or `ToolError` the double returned, boxed so the
        /// typed cause stays reachable via [`std::error::Error::source`].
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },

    /// Compaction policy failed (e.g. LLM summariser returned non-text).
    #[error("runlog compaction error: {0}")]
    Compaction(String),

    /// A capture-persistence backend operation (`KvStore` put/get/keys/delete)
    /// failed. Carries the backend error as a typed source.
    #[error("capture persistence backend error")]
    CaptureBackend {
        /// Downcast to `klieo_core::BusError` to branch on the backend failure
        /// kind (unsupported, conflict, transport).
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },

    /// A capture blob failed to serialize on persist or deserialize on load
    /// (e.g. a corrupt or truncated stored value).
    #[error("capture codec error")]
    CaptureCodec {
        /// Downcast to `serde_json::Error` for the byte offset of the
        /// malformed blob.
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },
}