bistun-core 2.0.3

The authoritative Linguistic DNA models and DTOs for the Bistun LMS. Provides a high-performance, immutable contract layer for BCP 47 locale resolution, typographic traits, and linguistic metadata.
Documentation
// Copyright (C) 2026 Francis Xavier Wazeter IV
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of the Bistun Linguistic Metadata Service (LMS).
// See the LICENSE file in the workspace root for full license information.

//! # Global Error Definitions
//! **Crate**: `bistun-core`
//! **Ref**: `[LMS-PROCESS-ERROR]`
//! **Domain**: `Operations`
//! **Location**: `crates/bistun-core/src/error.rs`
//!
//! **Why**: This module provides a unified, strongly-typed error enumeration using the `thiserror` framework. It ensures all 5-phase pipeline failures are context-aware and bubble up gracefully to the telemetry phase.
//! **Impact**: If error contexts are lost or poorly formatted, observability sinks will lack actionable data, violating the system's operational `SLI` targets.
//!
//! ### Architectural Topology
//! * **Tier**: `0`
//! * **Dependencies**: `thiserror`
//! * **Consumers**: All `LMS` modules and the `Resolution Pipeline`
//! * **State Model**: `Stateless`
//! * **Design Patterns**: `Failure Narrative`
//!
//! ### Local Definitions
//! * **`Failure Narrative`**: The standard ensuring every error explains What failed, Where it failed, and Why it failed.
//! * **`thiserror`**: The framework used to derive descriptive, strongly-typed errors.

use thiserror::Error;

/// The universal error type for the Bistun `LMS` pipeline.
///
/// ### `OpenAPI` Schema
/// ```yaml
/// schema: LmsError
/// description: The unified, strongly-typed error enumeration representing all pipeline and resolution failures.
/// ```
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum LmsError {
    /// Raised when a linguistic algorithm fails to process the input string.
    #[error("[{pipeline_step}] Strategy Execution Failure ({context}): {reason}")]
    StrategyExecutionFailure {
        /// Where did it fail? (e.g., "Phase 4: Stemming Strategy")
        pipeline_step: String,
        /// What failed? (e.g., the input word or language tag)
        context: String,
        /// Why did it fail? (e.g., "Invalid `UTF-8` sequence")
        reason: String,
    },

    /// Raised when the `CapabilityManifest` lacks a required trait during resolution.
    #[error("[{pipeline_step}] Missing Trait ({trait_key}): {reason}")]
    MissingTrait {
        /// Where did it fail? (e.g., "Phase 2: Aggregation")
        pipeline_step: String,
        /// What failed? (e.g., "`PRIMARY_DIRECTION`")
        trait_key: String,
        /// Why did it fail? (e.g., "Script Definition lacks fallback")
        reason: String,
    },

    /// Raised when a `BCP 47` tag is mathematically invalid or unparseable.
    #[error("[{pipeline_step}] Invalid Tag ({tag}): {reason}")]
    InvalidTag {
        /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
        pipeline_step: String,
        /// What failed? (The raw tag, e.g., "en-US-u-foo-bar")
        tag: String,
        /// Why did it fail? (e.g., "Malformed Unicode extension subtag")
        reason: String,
    },

    /// Raised when the Taxonomic Engine fails to resolve a locale, exhausting the fallback chain.
    #[error("[{pipeline_step}] Resolution Failed ({tag}): {reason}")]
    ResolutionFailed {
        /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
        pipeline_step: String,
        /// What failed? (e.g., the missing canonical tag)
        tag: String,
        /// Why did it fail? (e.g., "Exhausted fallback chain without finding a registry entry")
        reason: String,
    },

    /// Raised when a capability manifest fails structural or typological integrity checks.
    #[error("[{pipeline_step}] Integrity Violation ({context}): {reason}")]
    IntegrityViolation {
        /// Where did it fail? (e.g., "Phase 4: Integrity Check")
        pipeline_step: String,
        /// What failed? (e.g., the canonical locale `ID` being verified)
        context: String,
        /// Why did it fail? (e.g., "Bidirectional text inherently requires complex shaping logic")
        reason: String,
    },

    /// Raised when registry signature verification or `WORM` state fails.
    #[error("[{pipeline_step}] Integrity Failure ({context}): {reason}")]
    SecurityFault {
        /// Where did it fail? (e.g., "Phase 0: `WORM` Hydration")
        pipeline_step: String,
        /// What failed? (e.g., the registry snapshot payload)
        context: String,
        /// Why did it fail? (e.g., "`Ed25519` signature mismatch")
        reason: String,
    },

    /// Raised when the `WORM` snapshot cannot be read or contains a structural persistence breach.
    #[error("[{pipeline_step}] Persistence Fault ({context}): {reason}")]
    PersistenceFault {
        /// Where did it fail? (e.g., "Phase 0: `WORM` Hydration")
        pipeline_step: String,
        /// What failed? (e.g., the file path or `URI`)
        context: String,
        /// Why did it fail? (e.g., "Snapshot checksum mismatch")
        reason: String,
    },

    /// Raised when Phase 2 (Aggregation) fails to synthesize algorithmic rules due to unresolvable conflicts.
    #[error("[{pipeline_step}] Rule Synthesis Failure ({context}): {reason}")]
    RuleSynthesisFailure {
        /// Where did it fail? (e.g., "Phase 2: Typology Aggregation")
        pipeline_step: String,
        /// What failed? (e.g., "`PLURAL_LOGIC`")
        context: String,
        /// Why did it fail? (e.g., "Conflicting plural categories without a clear High-Water Mark")
        reason: String,
    },

    /// Raised when Phase 2.5 (Resource Bridge) cannot map an abstract Resource `ID` to a physical `URI`.
    #[error("[{pipeline_step}] Resource Resolution Failure ({context}): {reason}")]
    ResourceResolutionFailure {
        /// Where did it fail? (e.g., "Phase 2.5: Resource Bridge")
        pipeline_step: String,
        /// What failed? (e.g., "`icu_arab`")
        context: String,
        /// Why did it fail? (e.g., "Resource `ID` not found in deployment map")
        reason: String,
    },

    /// Raised when Phase 3 (Override) encounters a malformed or unsupported `BCP 47` extension subtag.
    #[error("[{pipeline_step}] Extension Mapping Failure ({context}): {reason}")]
    ExtensionMappingFailure {
        /// Where did it fail? (e.g., "Phase 3: Extension Override")
        pipeline_step: String,
        /// What failed? (e.g., "-u-nu-invalid")
        context: String,
        /// Why did it fail? (e.g., "Unsupported numbering system requested")
        reason: String,
    },
}

#[cfg(test)]
mod tests {
    use super::*;

    /// **What is being proven**: Verifies that the `Failure Narrative` formatting correctly structures the `LmsError` context for observability sinks.
    #[test]
    fn test_error_narrative_formatting() {
        // [1] Set up Mock
        let error = LmsError::StrategyExecutionFailure {
            pipeline_step: "Phase 4: Strategy Execution".to_string(),
            context: "input_word".to_string(),
            reason: "Agglutinative stemmer encountered invalid suffix".to_string(),
        };

        // [2] Execute
        let output = error.to_string();

        // [3] Assert Equation
        assert_eq!(
            output,
            "[Phase 4: Strategy Execution] Strategy Execution Failure (input_word): Agglutinative stemmer encountered invalid suffix"
        );
    }
}