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}