Skip to main content

palisade_errors/
codes.rs

1//! Error code namespace - enables error tracking without information disclosure.
2//!
3//! When an attacker triggers an error, they see: "Configuration operation failed (E-CFG-100)"
4//! Internally, we log full context. Externally, we reveal only category and code.
5//!
6//! # Namespace Structure
7//!
8//! - **CORE**: Fundamental system errors (init, shutdown, panic recovery)
9//! - **CFG**: Configuration parsing and validation
10//! - **DCP**: Deception artifact management
11//! - **TEL**: Telemetry collection subsystem
12//! - **COR**: Correlation engine
13//! - **RSP**: Response execution
14//! - **LOG**: Logging subsystem
15//! - **PLT**: Platform-specific operations
16//! - **IO**: Filesystem and network operations
17//!
18//! # Governance
19//! Namespaces are enforced at compile-time via the `ErrorNamespace` type with
20//! private fields. This prevents ad-hoc namespace creation and runtime construction,
21//! ensuring taxonomy stability.
22//!
23//! See [error-governance.md](../docs/error-governance.md) for the complete governance contract.
24//!
25//! # Security Properties
26//!
27//! ## No-Copy/No-Clone Semantics
28//! Error **identity** is non-Copy and non-movable; contextual **metadata** is Copy by design.
29//!
30//! - **Identity** (`ErrorCode`, `ErrorNamespace`): Frozen at compile time
31//!   - Namespaces cannot be constructed or moved at runtime
32//!   - Error codes are defined once as const statics
33//!   - Makes data flow explicit and auditable
34//!   - Enforces governance through type system, not discipline
35//!
36//! - **Metadata** (`OperationCategory`, `ErrorImpact`, `ImpactScore`): Copy-enabled
37//!   - Small enums that benefit from pass-by-value
38//!   - Defensive code can extract and propagate metadata cheaply
39//!   - No governance risk from duplication of classification data
40//!
41//! This is a **policy choice for code hygiene**, not a cryptographic mitigation.
42//!
43//! ## Zero-Allocation Guarantee
44//! All operations in this module are guaranteed zero-allocation:
45//! - Error code construction: compile-time const evaluation
46//! - Display formatting: writes directly to provided formatter (no intermediate buffers)
47//! - Namespace validation: compile-time const assertions
48//! - Category checking: pure computation, no heap use
49//!
50//! Note: `Display` itself is allocation-free; `to_string()` allocates in user code.
51//!
52//! This ensures error handling remains fast and predictable even under
53//! memory pressure or DoS conditions where allocators may be stressed.
54//!
55//! # Example Usage
56//!
57//! ```rust
58//! use palisade_errors::{ErrorCode, OperationCategory, ImpactScore, define_error_codes, namespaces};
59//!
60//! // Define error codes as const statics (zero allocation)
61//! define_error_codes! {
62//!     &namespaces::CFG, OperationCategory::Configuration => {
63//!         CFG_PARSE_FAILED = (100, 350),
64//!         CFG_INVALID_SCHEMA = (101, 250),
65//!     }
66//! }
67//!
68//! // Use by reference (no copies, no moves)
69//! fn handle_error(code: &ErrorCode) {
70//!     println!("Error: {}", code); // Zero allocation display
71//! }
72//!
73//! handle_error(&CFG_PARSE_FAILED);
74//! ```
75
76use crate::OperationCategory;
77use std::fmt;
78
79// ============================================================================
80// Impact Score Type (Validates Policy)
81// ============================================================================
82
83/// Validated impact score representing error severity (0-1000).
84///
85/// # Purpose
86///
87/// This newtype centralizes impact validation and makes the type system
88/// encode security policy:
89/// - Impact scores must be in valid range (0-1000)
90/// - Construction enforces validation once at creation
91/// - Downstream consumers receive pre-validated scores
92///
93/// # Copy Semantics
94///
95/// This type is Copy because:
96/// - It's a small numeric value (u16)
97/// - No governance risk from duplication
98/// - Defensive code benefits from pass-by-value semantics
99///
100/// # Feature: Strict Severity Authority
101///
102/// When `strict_severity` feature is enabled, additional constraints apply
103/// based on namespace authority flags. See `ErrorNamespace::CAN_BREACH`.
104///
105/// # Example
106///
107/// ```rust
108/// # use palisade_errors::ImpactScore;
109/// // Compile-time validation
110/// const MINOR_IMPACT: ImpactScore = ImpactScore::new(150);
111///
112/// // Runtime validation
113/// # let user_input = 123u16;
114/// let score = ImpactScore::checked_new(user_input).unwrap();
115/// ```
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub struct ImpactScore(u16);
118
119impl ImpactScore {
120    /// Create a new impact score with compile-time validation.
121    ///
122    /// # Panics
123    ///
124    /// Panics at compile time (in const contexts) if score > 1000.
125    /// Panics at runtime (in non-const contexts) if score > 1000.
126    ///
127    /// # Use Case
128    ///
129    /// For const static error code definitions where scores are known at compile time.
130    #[inline]
131    pub const fn new(score: u16) -> Self {
132        assert!(score <= 1000, "Impact score must be 0-1000");
133        Self(score)
134    }
135
136    /// Create a new impact score with runtime validation.
137    ///
138    /// # Errors
139    ///
140    /// Returns `Err` if score > 1000.
141    ///
142    /// # Use Case
143    ///
144    /// For runtime paths where impact scores come from configuration,
145    /// user input, or dynamic sources. Prevents panics.
146    #[inline]
147    pub fn checked_new(score: u16) -> Result<Self, ImpactScoreError> {
148        if score > 1000 {
149            Err(ImpactScoreError::OutOfRange { value: score })
150        } else {
151            Ok(Self(score))
152        }
153    }
154
155    /// Get the raw numeric value.
156    #[inline]
157    pub const fn value(self) -> u16 {
158        self.0
159    }
160
161    /// Convert to detailed impact level classification.
162    #[inline]
163    pub const fn to_impact_level(self) -> ErrorImpact {
164        ErrorImpact::from_score(self.0)
165    }
166
167    /// Check if this is a Breach-level impact (951-1000).
168    #[inline]
169    pub const fn is_breach_level(self) -> bool {
170        self.0 >= 951
171    }
172}
173
174impl fmt::Display for ImpactScore {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(f, "{}", self.0)
177    }
178}
179
180/// Error type for impact score validation failures.
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub enum ImpactScoreError {
183    /// Score exceeds maximum allowed value (1000).
184    OutOfRange { value: u16 },
185}
186
187impl fmt::Display for ImpactScoreError {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        match self {
190            Self::OutOfRange { value } => {
191                write!(f, "Impact score {} exceeds maximum (1000)", value)
192            }
193        }
194    }
195}
196
197impl std::error::Error for ImpactScoreError {}
198
199// ============================================================================
200// Error Impact Classification
201// ============================================================================
202
203/// Error impact enum - derives impact mapping
204/// 
205/// Each impact level maps to the threat represented by an error.
206///
207/// # Copy Semantics
208///
209/// This type is Copy because it's a small enum representing metadata,
210/// not governed identity. Defensive code can freely extract and propagate
211/// impact levels without governance concerns.
212#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
213pub enum ErrorImpact {
214    /// 0-50: Purely internal noise; no operational or deception impact.
215    Noise,
216    /// 51-150: Minor visual discrepancy in the deception layer.
217    Flaw,
218    /// 151-300: Performance issues that may be perceived as network lag.
219    Jitter,
220    /// 301-450: Functional error where an emulated feature fails to respond correctly.
221    Glitch,
222    /// 451-600: Logic inconsistency that allows an attacker to identify the trap.
223    Suspicion,
224    /// 601-750: Error reveals sensitive internal system information (Fingerprinting).
225    Leak,
226    /// 751-850: Total failure of the emulated service; the "illusion" stops.
227    Collapse,
228    /// 851-950: Error provides the attacker with unintended lateral or vertical access.
229    Escalation,
230    /// 951-1000: High risk of sandbox breakout or host machine compromise.
231    Breach,
232}
233
234impl ErrorImpact {
235    /// Converts a raw u16 score into a detailed ErrorImpact variant.
236    pub const fn from_score(score: u16) -> Self {
237        match score {
238            0..=50 => Self::Noise,
239            51..=150 => Self::Flaw,
240            151..=300 => Self::Jitter,
241            301..=450 => Self::Glitch,
242            451..=600 => Self::Suspicion,
243            601..=750 => Self::Leak,
244            751..=850 => Self::Collapse,
245            851..=950 => Self::Escalation,
246            _ => Self::Breach,
247        }
248    }
249}
250
251// ============================================================================
252// Error Namespace (Frozen Identity)
253// ============================================================================
254
255/// Error namespace type - enforces frozen taxonomy.
256///
257/// Namespaces are locked at compile-time with no runtime construction:
258/// - Private field prevents user construction
259/// - Only const instances are exported (see `namespaces` module)
260/// - Cannot be moved or duplicated at runtime
261///
262/// Each namespace has authority flags determining permitted operations:
263/// - Which categories are semantically valid
264/// - Whether Breach-level impacts are allowed (strict_severity mode)
265///
266/// # No-Copy, No-Move Semantics
267///
268/// This type does not implement Copy or Clone, and cannot be constructed
269/// at runtime. Namespaces exist only as const statics, making governance
270/// a compile-time property, not a runtime discipline.
271///
272/// This is the **identity** layer - completely frozen.
273///
274/// # Authority Flags
275///
276/// Each namespace carries const authority flags:
277/// - `CAN_BREACH`: Whether Breach-level impacts (951-1000) are permitted
278///
279/// This allows authority to evolve independently of namespace identity.
280#[derive(Debug, PartialEq, Eq, Hash)]
281pub struct ErrorNamespace {
282    name: &'static str,
283    can_breach: bool,
284    _private: (),
285}
286
287impl ErrorNamespace {
288    /// Internal constructor - not pub, enforces const-only usage.
289    #[doc(hidden)]
290    pub const fn __internal_new(name: &'static str, can_breach: bool) -> Self {
291        Self {
292            name,
293            can_breach,
294            _private: (),
295        }
296    }
297
298    /// Get the string representation for external display.
299    /// Zero-allocation - returns static string.
300    #[inline]
301    pub const fn as_str(&self) -> &'static str {
302        self.name
303    }
304
305    /// Check if this namespace permits Breach-level impacts (951-1000).
306    ///
307    /// # Strict Severity Mode
308    ///
309    /// When `strict_severity` feature is enabled, this authority is enforced
310    /// at error code construction. Without the feature, it's advisory only.
311    ///
312    /// Authority is granted via const flag, allowing architectural evolution
313    /// without hardcoded namespace checks.
314    #[inline]
315    pub const fn can_breach(&self) -> bool {
316        self.can_breach
317    }
318}
319
320/// Canonical namespace instances.
321///
322/// These are the **only** ErrorNamespace values that can exist.
323/// Private field in ErrorNamespace prevents runtime construction.
324pub mod namespaces {
325    use super::ErrorNamespace;
326
327    /// Fundamental system errors (init, shutdown, panic recovery).
328    /// Authority: Can emit Breach-level impacts.
329    pub const CORE: ErrorNamespace = ErrorNamespace::__internal_new("CORE", true);
330
331    /// Configuration parsing and validation.
332    /// Authority: Cannot emit Breach-level impacts.
333    pub const CFG: ErrorNamespace = ErrorNamespace::__internal_new("CFG", false);
334
335    /// Deception artifact management.
336    /// Authority: Can emit Breach-level impacts (deception compromise = breach).
337    pub const DCP: ErrorNamespace = ErrorNamespace::__internal_new("DCP", true);
338
339    /// Telemetry collection subsystem.
340    /// Authority: Cannot emit Breach-level impacts.
341    pub const TEL: ErrorNamespace = ErrorNamespace::__internal_new("TEL", false);
342
343    /// Correlation engine.
344    /// Authority: Cannot emit Breach-level impacts.
345    pub const COR: ErrorNamespace = ErrorNamespace::__internal_new("COR", false);
346
347    /// Response execution.
348    /// Authority: Cannot emit Breach-level impacts (future: may need elevation).
349    pub const RSP: ErrorNamespace = ErrorNamespace::__internal_new("RSP", false);
350
351    /// Logging subsystem.
352    /// Authority: Cannot emit Breach-level impacts.
353    pub const LOG: ErrorNamespace = ErrorNamespace::__internal_new("LOG", false);
354
355    /// Platform-specific operations.
356    /// Authority: Cannot emit Breach-level impacts (future: may need elevation).
357    pub const PLT: ErrorNamespace = ErrorNamespace::__internal_new("PLT", false);
358
359    /// Filesystem and network operations.
360    /// Authority: Cannot emit Breach-level impacts.
361    pub const IO: ErrorNamespace = ErrorNamespace::__internal_new("IO", false);
362}
363
364// ============================================================================
365// Category Policy (Extracted for Maintainability)
366// ============================================================================
367
368/// Category enforcement policy - extracted to reduce god-function complexity.
369///
370/// This module centralizes category validation logic and makes policy
371/// changes easier to audit and maintain.
372mod category_policy {
373    use crate::OperationCategory;
374
375    /// Check if IO namespace permits a category.
376    pub(super) const fn io_permits(category: OperationCategory) -> bool {
377        use OperationCategory::*;
378        !matches!(category, Deception | Detection | Containment)
379    }
380
381    /// Check if Log/Telemetry namespace permits a category.
382    pub(super) const fn observability_permits(category: OperationCategory) -> bool {
383        use OperationCategory::*;
384        matches!(category, Audit | Monitoring | System)
385    }
386
387    /// Check if Deception namespace permits a category.
388    pub(super) const fn deception_permits(category: OperationCategory) -> bool {
389        use OperationCategory::*;
390        matches!(category, Deception | Detection | Containment | Deployment)
391    }
392
393    /// Check if permissive namespaces permit a category (default mode).
394    #[cfg(not(feature = "strict_taxonomy"))]
395    #[allow(unused_variables)]
396    pub(super) const fn permissive_permits(category: OperationCategory) -> bool {
397        true
398    }
399
400    /// Check category for strict taxonomy mode.
401    #[cfg(feature = "strict_taxonomy")]
402    pub(super) const fn strict_core_permits(category: OperationCategory) -> bool {
403        matches!(category, OperationCategory::System)
404    }
405
406    #[cfg(feature = "strict_taxonomy")]
407    pub(super) const fn strict_cfg_permits(category: OperationCategory) -> bool {
408        matches!(category, OperationCategory::Configuration)
409    }
410
411    #[cfg(feature = "strict_taxonomy")]
412    pub(super) const fn strict_cor_permits(category: OperationCategory) -> bool {
413        matches!(category, OperationCategory::Analysis)
414    }
415
416    #[cfg(feature = "strict_taxonomy")]
417    pub(super) const fn strict_rsp_permits(category: OperationCategory) -> bool {
418        matches!(category, OperationCategory::Response)
419    }
420
421    #[cfg(feature = "strict_taxonomy")]
422    pub(super) const fn strict_plt_permits(category: OperationCategory) -> bool {
423        use OperationCategory::*;
424        matches!(category, System | IO)
425    }
426}
427
428/// Validate that a namespace permits the given operation category.
429///
430/// # Category Enforcement Policy
431///
432/// Enforces semantic constraints to prevent obvious mismatches:
433/// - IO namespace: Cannot use Deception/Detection/Containment
434/// - DCP namespace: Must use deception-related categories
435/// - Log/Tel namespaces: Restricted to Audit/Monitoring/System
436///
437/// # Strict Taxonomy Mode
438///
439/// When `strict_taxonomy` feature is enabled, formerly permissive namespaces
440/// enforce strict category mappings. See governance docs for migration guide.
441///
442/// Returns true if the pairing is semantically valid.
443pub const fn permits_category(namespace: &ErrorNamespace, category: OperationCategory) -> bool {
444    // Match on namespace name (identity-based dispatch)
445    // This avoids encoding namespace enum into the type
446    match namespace.name.as_bytes() {
447        b"IO" => category_policy::io_permits(category),
448        b"LOG" | b"TEL" => category_policy::observability_permits(category),
449        b"DCP" => category_policy::deception_permits(category),
450
451        // Strict mode enforcement
452        #[cfg(feature = "strict_taxonomy")]
453        b"CORE" => category_policy::strict_core_permits(category),
454        #[cfg(feature = "strict_taxonomy")]
455        b"CFG" => category_policy::strict_cfg_permits(category),
456        #[cfg(feature = "strict_taxonomy")]
457        b"COR" => category_policy::strict_cor_permits(category),
458        #[cfg(feature = "strict_taxonomy")]
459        b"RSP" => category_policy::strict_rsp_permits(category),
460        #[cfg(feature = "strict_taxonomy")]
461        b"PLT" => category_policy::strict_plt_permits(category),
462
463        // Permissive fallback (default mode)
464        #[cfg(not(feature = "strict_taxonomy"))]
465        _ => category_policy::permissive_permits(category),
466
467        // Exhaustiveness in strict mode
468        #[cfg(feature = "strict_taxonomy")]
469        _ => false,
470    }
471}
472
473/// Validate that a namespace permits the given impact level.
474///
475/// # Severity Authority Enforcement
476///
477/// When `strict_severity` feature is enabled, restricts Breach-level
478/// impacts (951-1000) to namespaces with `can_breach` authority.
479///
480/// Authority is decoupled from namespace identity, allowing architectural
481/// evolution without policy rewrites.
482pub const fn permits_impact(namespace: &ErrorNamespace, impact: ImpactScore) -> bool {
483    #[cfg(feature = "strict_severity")]
484    {
485        if impact.is_breach_level() {
486            return namespace.can_breach();
487        }
488    }
489
490    #[cfg(not(feature = "strict_severity"))]
491    let _ = (namespace, impact);
492
493    true
494}
495
496// ============================================================================
497// Error Violation Types (Internal + Public)
498// ============================================================================
499
500/// Internal error code violation with detailed taxonomy information.
501///
502/// **SECURITY WARNING**: This type contains internal policy details and
503/// should NEVER be exposed to external systems or untrusted contexts.
504///
505/// For external error reporting, use `.to_public()` to get sanitized message.
506#[derive(Debug, Clone, PartialEq, Eq)]
507pub enum InternalErrorCodeViolation {
508    /// Code is zero or exceeds 999.
509    CodeOutOfRange { value: u16 },
510    /// Category not permitted for namespace (reveals policy).
511    CategoryNotPermitted {
512        namespace: &'static str,
513        category: &'static str,
514    },
515    /// Impact level not permitted for namespace (reveals authority model).
516    ImpactNotPermitted {
517        namespace: &'static str,
518        impact: u16,
519    },
520}
521
522impl InternalErrorCodeViolation {
523    /// Convert to public-safe error message (taxonomy-sanitized).
524    ///
525    /// Returns a generic error string that does not reveal:
526    /// - Namespace restrictions
527    /// - Category policies
528    /// - Authority models
529    ///
530    /// # Use Case
531    ///
532    /// For external APIs, plugin systems, or any untrusted boundary where
533    /// detailed policy information should not leak.
534    pub fn to_public(&self) -> &'static str {
535        match self {
536            Self::CodeOutOfRange { .. } => "Invalid error code format",
537            Self::CategoryNotPermitted { .. } => "Invalid error configuration",
538            Self::ImpactNotPermitted { .. } => "Invalid error severity",
539        }
540    }
541}
542
543impl fmt::Display for InternalErrorCodeViolation {
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        match self {
546            Self::CodeOutOfRange { value } => {
547                write!(f, "Error code {} is out of range (must be 001-999)", value)
548            }
549            Self::CategoryNotPermitted { namespace, category } => {
550                write!(
551                    f,
552                    "Category {} not permitted for namespace {}",
553                    category, namespace
554                )
555            }
556            Self::ImpactNotPermitted { namespace, impact } => {
557                write!(
558                    f,
559                    "Impact level {} not permitted for namespace {}",
560                    impact, namespace
561                )
562            }
563        }
564    }
565}
566
567impl std::error::Error for InternalErrorCodeViolation {}
568
569// ============================================================================
570// Error Code (Primary Identity Type)
571// ============================================================================
572
573/// An error code with namespace, numeric code, and operation category.
574///
575/// Error codes follow the format `E-XXX-YYY` where:
576/// - `XXX` is the namespace (CORE, CFG, IO, etc.)
577/// - `YYY` is the numeric code (001-999)
578///
579/// # Compile-time Guarantees
580///
581/// All error codes are defined as const statics, providing:
582/// - Namespace frozen at compile time (cannot be constructed at runtime)
583/// - Code range validated (001-999)
584/// - Impact score validated (0-1000)
585/// - Category compatibility enforced
586/// - Severity authority enforced (strict_severity mode)
587///
588/// # Construction APIs
589///
590/// - `const_new`: For const statics (panics = compile error)
591/// - `checked_new`: For runtime construction (returns Result, never panics)
592///
593/// # No-Copy/No-Clone Semantics
594///
595/// This type is part of the error **identity** layer and cannot be copied,
596/// cloned, or moved after const initialization. All usage is by reference.
597///
598/// # Zero-Allocation Guarantee
599///
600/// All operations are zero-allocation. Display writes directly to formatter.
601///
602/// # Example
603///
604/// ```rust
605/// use palisade_errors::{ErrorCode, OperationCategory, ImpactScore, define_error_codes, namespaces};
606///
607/// // Compile-time construction (panics if invalid)
608/// const CFG_PARSE_FAILED: ErrorCode = ErrorCode::const_new(
609///     &namespaces::CFG,
610///     100,
611///     OperationCategory::Configuration,
612///     ImpactScore::new(700)
613/// );
614/// 
615/// // Runtime construction (returns Result)
616/// # let code_from_config = 801u16;
617/// # let impact_from_config = 700u16;
618/// let code = ErrorCode::checked_new(
619///     &namespaces::IO,
620///     code_from_config,
621///     OperationCategory::IO,
622///     ImpactScore::checked_new(impact_from_config).unwrap()
623/// ).unwrap();
624/// 
625/// // Use by reference only
626/// fn log_error(code: &ErrorCode) {
627///     println!("Error: {}", code);
628/// }
629/// 
630/// log_error(&CFG_PARSE_FAILED);
631/// ```
632#[derive(Debug, PartialEq, Eq, Hash)]
633pub struct ErrorCode {
634    namespace: &'static ErrorNamespace,
635    code: u16,
636    category: OperationCategory,
637    impact: ImpactScore,
638}
639
640impl ErrorCode {
641    /// Create a new error code with compile-time validation (infallible in const contexts).
642    ///
643    /// # Panics
644    ///
645    /// Panics if:
646    /// - Code is 0 or >= 1000 (must be 001-999)
647    /// - Category is not permitted for the namespace
648    /// - Impact is not permitted for the namespace (strict_severity mode)
649    ///
650    /// In **const contexts**, panics occur at compile time.
651    /// In **runtime contexts**, panics occur at runtime.
652    ///
653    /// # Use Case
654    ///
655    /// For const static error code definitions where all values are known
656    /// at compile time and violations indicate programmer error.
657    #[inline]
658    pub const fn const_new(
659        namespace: &'static ErrorNamespace,
660        code: u16,
661        category: OperationCategory,
662        impact: ImpactScore,
663    ) -> Self {
664        // Validate code range (001-999)
665        assert!(code > 0 && code < 1000, "Error code must be 001-999");
666
667        // Validate category compatibility
668        assert!(
669            permits_category(namespace, category),
670            "Category not permitted for this namespace"
671        );
672
673        // Validate impact authority
674        assert!(
675            permits_impact(namespace, impact),
676            "Impact level not permitted for this namespace"
677        );
678
679        Self {
680            namespace,
681            code,
682            category,
683            impact,
684        }
685    }
686
687    /// Create a new error code with runtime validation (fallible, no panics).
688    ///
689    /// # Errors
690    ///
691    /// Returns `Err` with **internal** violation details. For external contexts,
692    /// call `.to_public()` on the error to sanitize taxonomy information.
693    ///
694    /// # Use Case
695    ///
696    /// For runtime construction from untrusted sources (config, plugins, etc.).
697    #[inline]
698    pub fn checked_new(
699        namespace: &'static ErrorNamespace,
700        code: u16,
701        category: OperationCategory,
702        impact: ImpactScore,
703    ) -> Result<Self, InternalErrorCodeViolation> {
704        // Validate code range
705        if code == 0 || code >= 1000 {
706            return Err(InternalErrorCodeViolation::CodeOutOfRange { value: code });
707        }
708
709        // Validate category compatibility
710        if !permits_category(namespace, category) {
711            return Err(InternalErrorCodeViolation::CategoryNotPermitted {
712                namespace: namespace.as_str(),
713                category: category.display_name(),
714            });
715        }
716
717        // Validate impact authority
718        if !permits_impact(namespace, impact) {
719            return Err(InternalErrorCodeViolation::ImpactNotPermitted {
720                namespace: namespace.as_str(),
721                impact: impact.value(),
722            });
723        }
724
725        Ok(Self {
726            namespace,
727            code,
728            category,
729            impact,
730        })
731    }
732
733    /// Get the operation category.
734    #[inline]
735    pub const fn category(&self) -> OperationCategory {
736        self.category
737    }
738
739    /// Get namespace reference.
740    #[inline]
741    pub const fn namespace(&self) -> &'static ErrorNamespace {
742        self.namespace
743    }
744
745    /// Get numeric code.
746    #[inline]
747    pub const fn code(&self) -> u16 {
748        self.code
749    }
750
751    /// Get impact score.
752    #[inline]
753    pub const fn impact(&self) -> ImpactScore {
754        self.impact
755    }
756
757    /// Get the detailed impact level.
758    #[inline]
759    pub const fn impact_level(&self) -> ErrorImpact {
760        self.impact.to_impact_level()
761    }
762}
763
764impl fmt::Display for ErrorCode {
765    /// Zero-allocation formatting - writes directly to formatter.
766    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767        write!(f, "E-{}-{:03}", self.namespace.as_str(), self.code)
768    }
769}
770
771// ============================================================================
772// Tests
773// ============================================================================
774
775#[cfg(test)]
776mod tests {
777    use super::*;
778    use crate::define_error_codes;
779
780    // ========================================================================
781    // Namespace Governance Tests
782    // ========================================================================
783
784    #[test]
785    fn namespaces_are_frozen_at_compile_time() {
786        // This compiles - using const namespace
787        const _CODE: ErrorCode = ErrorCode::const_new(
788            &namespaces::CORE,
789            1,
790            OperationCategory::System,
791            ImpactScore::new(100),
792        );
793
794        // This would NOT compile (namespace cannot be constructed):
795        // let ns = ErrorNamespace { name: "FAKE", can_breach: false, _private: () };
796    }
797
798    #[test]
799    fn namespace_authority_flags_work() {
800        assert!(namespaces::CORE.can_breach());
801        assert!(namespaces::DCP.can_breach());
802        assert!(!namespaces::CFG.can_breach());
803        assert!(!namespaces::IO.can_breach());
804    }
805
806    // ========================================================================
807    // Basic Construction Tests
808    // ========================================================================
809
810    #[test]
811    fn valid_error_code_const_construction() {
812        const CODE: ErrorCode = ErrorCode::const_new(
813            &namespaces::CFG,
814            100,
815            OperationCategory::Configuration,
816            ImpactScore::new(100),
817        );
818        assert_eq!(CODE.to_string(), "E-CFG-100");
819        assert_eq!(CODE.namespace().as_str(), "CFG");
820        assert_eq!(CODE.code(), 100);
821    }
822
823    #[test]
824    fn valid_error_code_checked_construction() {
825        let code = ErrorCode::checked_new(
826            &namespaces::IO,
827            200,
828            OperationCategory::IO,
829            ImpactScore::new(500),
830        )
831        .unwrap();
832
833        assert_eq!(code.to_string(), "E-IO-200");
834    }
835
836    #[test]
837    fn checked_new_rejects_invalid_code() {
838        let result = ErrorCode::checked_new(
839            &namespaces::CORE,
840            0,
841            OperationCategory::System,
842            ImpactScore::new(100),
843        );
844        assert!(matches!(
845            result,
846            Err(InternalErrorCodeViolation::CodeOutOfRange { value: 0 })
847        ));
848    }
849
850    #[test]
851    fn violation_to_public_sanitizes_details() {
852        let violation = InternalErrorCodeViolation::CategoryNotPermitted {
853            namespace: "IO",
854            category: "Deception",
855        };
856
857        // Internal has details
858        assert!(violation.to_string().contains("IO"));
859        assert!(violation.to_string().contains("Deception"));
860
861        // Public is sanitized
862        assert_eq!(violation.to_public(), "Invalid error configuration");
863        assert!(!violation.to_public().contains("IO"));
864    }
865
866    // ========================================================================
867    // Category Policy Tests
868    // ========================================================================
869
870    #[test]
871    fn category_enforcement_io_namespace() {
872        assert!(permits_category(&namespaces::IO, OperationCategory::IO));
873        assert!(!permits_category(
874            &namespaces::IO,
875            OperationCategory::Deception
876        ));
877    }
878
879    #[test]
880    fn category_enforcement_dcp_namespace() {
881        assert!(permits_category(
882            &namespaces::DCP,
883            OperationCategory::Deception
884        ));
885        assert!(!permits_category(
886            &namespaces::DCP,
887            OperationCategory::Configuration
888        ));
889    }
890
891    // ========================================================================
892    // Dual-Mode Testing
893    // ========================================================================
894
895    #[cfg(not(feature = "strict_taxonomy"))]
896    #[test]
897    fn permissive_mode_allows_flexible_categories() {
898        assert!(permits_category(
899            &namespaces::CORE,
900            OperationCategory::Configuration
901        ));
902        assert!(permits_category(
903            &namespaces::CFG,
904            OperationCategory::System
905        ));
906    }
907
908    #[cfg(feature = "strict_taxonomy")]
909    #[test]
910    fn strict_mode_enforces_core_namespace() {
911        assert!(permits_category(
912            &namespaces::CORE,
913            OperationCategory::System
914        ));
915        assert!(!permits_category(
916            &namespaces::CORE,
917            OperationCategory::Configuration
918        ));
919    }
920
921    // ========================================================================
922    // Severity Authority Tests
923    // ========================================================================
924
925    #[cfg(not(feature = "strict_severity"))]
926    #[test]
927    fn permissive_severity_allows_breach_anywhere() {
928        let _code = ErrorCode::const_new(
929            &namespaces::CFG,
930            1,
931            OperationCategory::Configuration,
932            ImpactScore::new(980),
933        );
934    }
935
936    #[cfg(feature = "strict_severity")]
937    #[test]
938    fn strict_severity_respects_authority_flags() {
939        assert!(permits_impact(&namespaces::CORE, ImpactScore::new(980)));
940        assert!(permits_impact(&namespaces::DCP, ImpactScore::new(1000)));
941        assert!(!permits_impact(&namespaces::CFG, ImpactScore::new(980)));
942    }
943
944    // ========================================================================
945    // Impact Score Tests
946    // ========================================================================
947
948    #[test]
949    fn impact_score_boundaries() {
950        assert_eq!(ImpactScore::new(50).to_impact_level(), ErrorImpact::Noise);
951        assert_eq!(ImpactScore::new(51).to_impact_level(), ErrorImpact::Flaw);
952        assert_eq!(ImpactScore::new(951).to_impact_level(), ErrorImpact::Breach);
953        assert!(ImpactScore::new(980).is_breach_level());
954    }
955
956    // ========================================================================
957    // Macro Tests
958    // ========================================================================
959
960    #[test]
961    fn macro_batch_definition() {
962        define_error_codes! {
963            &namespaces::IO, OperationCategory::IO => {
964                IO_READ_ERROR = (100, 500),
965                IO_WRITE_ERROR = (101, 500),
966            }
967        }
968
969        assert_eq!(IO_READ_ERROR.to_string(), "E-IO-100");
970        assert_eq!(IO_WRITE_ERROR.to_string(), "E-IO-101");
971    }
972}