minerva 0.2.0

Causal ordering for distributed systems
use thiserror::Error;

use crate::kairos::DecodeError as KairosDecodeError;
use crate::metis::HaveSetDecodeError;

/// Decode failure for a [`Rhapsody`](crate::metis::Rhapsody) *snapshot*
/// frame (PRD 0023), the run-compressed sibling codec of the canonical v2
/// rhapsody frame.
///
/// A sibling codec owns its own error surface exactly as it owns its own
/// version space (ruling R-8): the snapshot's canonical form has laws the
/// v2 frame does not (the maximal-chain factoring, the rank-step
/// canonicality), so its refusals name states the v2 vocabulary cannot.
/// `#[non_exhaustive]` so a future version's validation failure is
/// additive.
#[non_exhaustive]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum SnapshotDecodeError {
    /// The leading version byte is not a snapshot version this build
    /// understands.
    #[error("unknown rhapsody snapshot version: {0:#04x}")]
    UnknownVersion(u8),
    /// The input was shorter than the declared counts require, or (for
    /// [`Rhapsody::from_snapshot_bytes`](crate::metis::Rhapsody::from_snapshot_bytes))
    /// carried trailing bytes past the frame.
    #[error("unexpected snapshot frame length: expected at least {expected}, found {found}")]
    UnexpectedLength {
        /// A lower bound on the frame length the declared counts require.
        expected: usize,
        /// The actual length of the supplied input.
        found: usize,
    },
    /// The frame materializes more loci than the identity plane's
    /// live-entry ceiling (`2^31 - 2`) can hold: decode refuses rather
    /// than construct a truncated skeleton (S199). Reaching this arm
    /// takes a genuinely multi-gibibyte input (every element is backed by
    /// at least six frame bytes), so it is a typed belt, not a hot path.
    #[error("snapshot frame carries {count} loci, past the plane's {capacity} ceiling")]
    TooManyLoci {
        /// The materialized locus count.
        count: u64,
        /// The plane's live-entry ceiling.
        capacity: u64,
    },
    /// A run base or free locus carried `index == 0`. The non-dot `0` is
    /// refused by [`Rhapsody::weave`](crate::metis::Rhapsody::weave), so no
    /// encoder produces one.
    #[error("non-canonical snapshot: zero index for station {station}")]
    ZeroDot {
        /// The station of the offending zero-index dot.
        station: u32,
    },
    /// A chain run declared fewer than two elements; a one-element chain is
    /// spelled as a free locus, so canonical form has exactly one spelling.
    #[error(
        "non-canonical snapshot: run of fewer than two elements at station {station}, base {base}"
    )]
    RunTooShort {
        /// The station of the offending run.
        station: u32,
        /// The run's base dot index.
        base: u64,
    },
    /// A run's last dot (`base + len - 1`) exceeds [`u64::MAX`]; no dot
    /// space holds it, so no encoder produces one.
    #[error("snapshot run overflow: station {station}, base {base}, len {len}")]
    RunOverflow {
        /// The station of the offending run.
        station: u32,
        /// The run's base dot index.
        base: u64,
        /// The run's declared element count (the four-byte field, widened).
        len: u64,
    },
    /// An anchor tag above `0x02` (only origin, after, and before exist).
    #[error("unknown snapshot anchor tag: {tag:#04x}")]
    BadAnchorTag {
        /// The unrecognized tag byte.
        tag: u8,
    },
    /// An origin-anchored run head carried non-zero anchor padding in its
    /// fixed-width row; canonical form zeroes the unused bytes, so a padded
    /// row is a second spelling of the same value.
    #[error("non-canonical snapshot: padded origin anchor at station {station}, index {index}")]
    NonCanonicalOriginAnchor {
        /// The station of the offending run head.
        station: u32,
        /// The dot index of the offending run head.
        index: u64,
    },
    /// A rank step would advance the physical component past [`u64::MAX`];
    /// the encoder breaks the chain there instead, so no encoder produces
    /// one.
    #[error("snapshot rank step overflow at station {station}, index {index}")]
    RankOverflow {
        /// The station of the offending element.
        station: u32,
        /// The dot index of the offending element.
        index: u64,
    },
    /// A positive rank step decoded to exactly the clock successor of its
    /// predecessor (the logical-rollover overlap); canonical form spells
    /// the successor as step zero.
    #[error("non-canonical snapshot rank step at station {station}, index {index}")]
    NonCanonicalRankStep {
        /// The station of the offending element.
        station: u32,
        /// The dot index of the offending element.
        index: u64,
    },
    /// The merged dot order (chain runs and free loci interleaved) was not
    /// strictly ascending: unsorted sections, overlapping runs, or a
    /// duplicated dot.
    #[error(
        "non-canonical snapshot: locus ({found_station}, {found_index}) does not ascend past ({previous_station}, {previous_index})"
    )]
    NonAscendingLoci {
        /// The preceding dot's station.
        previous_station: u32,
        /// The preceding dot's index.
        previous_index: u64,
        /// The offending dot's station.
        found_station: u32,
        /// The offending dot's index.
        found_index: u64,
    },
    /// A dot-consecutive pair satisfying the chain law was encoded split
    /// across a chain boundary; canonical form factors maximal chains, so
    /// the pair has exactly one spelling (inside one run).
    #[error("non-canonical snapshot: split chain at station {station}, index {index}")]
    SplitChain {
        /// The station of the pair.
        station: u32,
        /// The dot index of the element that should have continued its
        /// predecessor's chain.
        index: u64,
    },
    /// A rank frame inside the snapshot failed to decode (an unknown stamp
    /// version; length failures surface as
    /// [`UnexpectedLength`](Self::UnexpectedLength)).
    #[error("snapshot rank frame: {0}")]
    Rank(#[source] KairosDecodeError),
    /// The visible have-set suffix failed to decode.
    #[error("snapshot visible suffix: {0}")]
    Visible(#[source] HaveSetDecodeError),
    /// The visible suffix named a dot the locus sections did not carry.
    /// Visibility is a subset of the skeleton by construction, so no
    /// encoder produces such a frame (the v2 structural law, PRD 0017).
    #[error("snapshot visible dot without locus: station {station}, index {index}")]
    VisibleWithoutLocus {
        /// The station of the offending visible dot.
        station: u32,
        /// The index of the offending visible dot.
        index: u64,
    },
}