soma-som-ring 0.1.0

Standalone ring execution engine for soma(som): cycle lifecycle, extension registration, boundary mediation
Documentation
// SPDX-License-Identifier: LGPL-3.0-only
#![allow(missing_docs)]

//! Error types for the ring execution engine.
//!
//! [`RingEngineError`] is the engine's own error enum. Per-processor
//! failures are captured as opaque strings via `ProcessorFailed` so the
//! engine never names the concrete error type of any registered
//! [`crate::RingProcessor`] implementation.

use crate::boundary::BoundaryError;
use soma_som_core::error::SomaError;
use soma_som_core::extension::GateRejection;
use soma_som_core::types::UnitId;

/// Errors produced by ring engine operations.
///
/// Marked `#[non_exhaustive]` so adding a variant in a future minor release
/// is not a breaking change. Consumers must include a wildcard arm in any
/// exhaustive `match`.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RingEngineError {
    // ── Lifecycle ────────────────────────────────────────────────────────
    /// Genesis has not been run yet.
    #[error("Ring engine not initialized — run genesis first")]
    NotInitialized,

    /// Genesis has already been completed.
    #[error("Genesis already completed — ring is in standard operation")]
    GenesisAlreadyComplete,

    // ── Processor failures ───────────────────────────────────────────────
    /// A unit's processor returned an error.
    ///
    /// The reason is an opaque string because the engine does not know
    /// the concrete error type of registered [`crate::RingProcessor`]
    /// implementations.
    #[error("Unit {unit} processing failed: {reason}")]
    ProcessorFailed { unit: UnitId, reason: String },

    /// A unit has no registered processor.
    #[error("Unit {unit} is disabled — no processor registered")]
    UnitDisabled { unit: UnitId },

    // ── Extension errors ─────────────────────────────────────────────────
    /// A BEFORE gate rejected the cycle.
    #[error("Cycle rejected by BEFORE gate: {0}")]
    GateRejected(#[from] GateRejection),

    // ── Delegation ───────────────────────────────────────────────────────
    /// A boundary operation failed.
    #[error("Boundary error: {0}")]
    Boundary(#[from] BoundaryError),

    /// A core structural operation failed.
    #[error("Core error: {0}")]
    Core(#[from] SomaError),

    // ── Cycle-level ──────────────────────────────────────────────────────
    /// The ring cycle failed (aggregated reason).
    #[error("Cycle {cycle_index} failed: {reason}")]
    CycleFailed { cycle_index: u64, reason: String },
}

/// Result type alias for ring engine operations.
pub type RingEngineResult<T> = Result<T, RingEngineError>;

// inline: exercises module-private items via super::*
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn error_display_not_initialized() {
        let e = RingEngineError::NotInitialized;
        assert!(e.to_string().contains("not initialized"));
    }

    #[test]
    fn error_display_processor_failed() {
        let e = RingEngineError::ProcessorFailed {
            unit: UnitId::FU,
            reason: "timeout".into(),
        };
        assert!(e.to_string().contains("FU"));
        assert!(e.to_string().contains("timeout"));
    }

    #[test]
    fn error_display_unit_disabled() {
        let e = RingEngineError::UnitDisabled { unit: UnitId::CU };
        assert!(e.to_string().contains("CU"));
        assert!(e.to_string().contains("disabled"));
    }

    #[test]
    fn error_display_gate_rejected() {
        let rejection = GateRejection {
            source: "guard".into(),
            reason: "rate limit".into(),
        };
        let e = RingEngineError::GateRejected(rejection);
        assert!(e.to_string().contains("guard"));
    }

    #[test]
    fn error_display_cycle_failed() {
        let e = RingEngineError::CycleFailed {
            cycle_index: 42,
            reason: "integrity".into(),
        };
        assert!(e.to_string().contains("42"));
    }
}