Skip to main content

palisade_errors/
models.rs

1//! Dual-context error handling for honeypot systems with type-enforced trust boundaries.
2//!
3//! # Architecture
4//!
5//! This module separates public-facing error messages from internal diagnostic data
6//! using distinct types that cannot be confused at compile time:
7//!
8//! - `PublicContext`: Contains data safe for external display (truthful or deceptive)
9//! - `InternalContext`: Contains diagnostic data restricted to authenticated SOC access
10//! - `DualContextError`: Pairs these contexts with enforced consistency rules
11//!
12//! # Trust Boundary Enforcement
13//!
14//! The type system prevents accidental cross-boundary leakage:
15//! - `PublicContext` implements `Display` for external rendering
16//! - `InternalContext` implements `Display` as redacted placeholder only
17//! - No implicit conversions exist between these types
18//!
19//! # Feature Gates
20//!
21//! When `external_signaling` is disabled, `PublicTruth` variant is unavailable at
22//! compile time. This forces all external outputs to use `DeceptiveLie`, ensuring
23//! honeypot deployments cannot accidentally expose truthful diagnostic information.
24//!
25//! # Memory Safety Strategy
26//!
27//! Sensitive data receives best-effort clearing from memory on drop:
28//!
29//! 1. **Owned strings**: Cleared via `zeroize` crate (handles heap buffers)
30//! 2. **Compiler optimization**: Volatile writes prevent LLVM dead-store elimination
31//! 3. **Instruction ordering**: Compiler fences prevent reordering across security boundaries
32//!
33//! ## What This Does NOT Guarantee
34//!
35//! - **Hardware cache visibility**: Compiler fences do not flush CPU caches
36//! - **Cross-thread guarantees**: Other threads may observe old values in cache
37//! - **Allocator-level security**: Memory may be reallocated before physical clearing
38//! - **DMA or swap**: OS/hardware may have copied data before zeroization
39//!
40//! This protects against compiler optimizations and casual memory inspection.
41//! It does NOT provide HSM-grade secure memory wiping. For that, use platform-specific
42//! APIs (mlock, SecureZeroMemory, etc.) and dedicated secure allocators.
43
44use std::borrow::Cow;
45use std::fmt;
46use std::ptr;
47use std::sync::atomic::{compiler_fence, Ordering};
48use zeroize::{Zeroize, ZeroizeOnDrop};
49
50// ============================================================================
51// Capability Token for Sensitive Access Control
52// ============================================================================
53
54/// Capability token for accessing sensitive internal context data.
55///
56/// # Purpose
57///
58/// This zero-sized type serves as a proof-of-authority for accessing sensitive
59/// information via `InternalContext::expose_sensitive()`. Requiring this token:
60///
61/// 1. Forces explicit privilege acquisition (cannot call accidentally)
62/// 2. Makes sensitive access grep-able in codebase
63/// 3. Enables future RBAC or audit hook integration
64/// 4. Documents authority requirement in the type system
65///
66/// # Construction
67///
68/// Only constructible via `SocAccess::acquire()`, which should be called only in
69/// controlled contexts (authenticated logging pipelines, SOC-exclusive endpoints, etc.).
70///
71/// # Security Model
72///
73/// This is not cryptographic. An attacker with code execution can trivially construct
74/// this type. The purpose is **organizational process safety**: preventing accidental
75/// misuse by well-meaning developers, not preventing malicious actors.
76///
77/// # Example
78///
79/// ```ignore
80/// // In SOC-restricted logging code:
81/// let access = SocAccess::acquire();
82/// if let Some(sensitive) = context.expose_sensitive(&access) {
83///     secure_log_to_encrypted_siem(sensitive);
84/// }
85/// ```
86pub struct SocAccess(());
87
88impl SocAccess {
89    /// Acquire SOC access capability for sensitive data exposure.
90    ///
91    /// # Security Contract
92    ///
93    /// Caller must ensure this is invoked only in contexts where sensitive data
94    /// disclosure is authorized:
95    /// - Authenticated SOC dashboards with RBAC
96    /// - Encrypted internal logging pipelines
97    /// - Forensic analysis tools with access controls
98    ///
99    /// # Audit Recommendation
100    ///
101    /// Calls to this method should be logged separately for compliance auditing.
102    /// Consider wrapping this in a macro that logs the caller's location:
103    ///
104    /// ```ignore
105    /// macro_rules! acquire_soc_access {
106    ///     () => {{
107    ///         audit_log!("SOC access acquired at {}:{}", file!(), line!());
108    ///         SocAccess::acquire()
109    ///     }}
110    /// }
111    /// ```
112    #[inline]
113    pub fn acquire() -> Self {
114        Self(())
115    }
116}
117
118// ============================================================================
119// Core Context Classification
120// ============================================================================
121
122/// Internal context field classification for SOC-visible error data.
123///
124/// This type is wrapped in `InternalContext` newtype and never directly exposed
125/// to external systems. The wrapper prevents accidental leakage via Display trait.
126///
127/// # Variants
128///
129/// - `Diagnostic`: Standard internal error information for SOC analysis
130/// - `Sensitive`: Contains PII, credentials, file paths, or other high-value data
131/// - `Lie`: Deceptive content tracked internally (marked to prevent analyst confusion)
132///
133/// # Memory Model
134///
135/// Uses `Cow<'static, str>` to allow both:
136/// - Zero-allocation storage of compile-time string literals (`Cow::Borrowed`)
137/// - Runtime-allocated sensitive data that can be zeroized (`Cow::Owned`)
138///
139/// ## Assumption About Borrowed Data
140///
141/// By convention, `Cow::Borrowed` is assumed to contain non-sensitive data
142/// (typically string literals embedded in the binary). This assumption can be
143/// violated if someone uses `Box::leak()` to create 'static references to sensitive
144/// data, but this is not a supported pattern.
145///
146/// Only `Cow::Owned` variants receive zeroization. Borrowed static data cannot
147/// and need not be cleared (it's in the binary).
148///
149/// # No Clone/Copy Policy
150///
151/// Prevents unintended duplication of potentially sensitive diagnostic data.
152/// All access requires borrowing from the single owner.
153enum InternalContextField {
154    Diagnostic(Cow<'static, str>),
155    Sensitive(Cow<'static, str>),
156    Lie(Cow<'static, str>),
157}
158
159impl Zeroize for InternalContextField {
160    fn zeroize(&mut self) {
161        match self {
162            Self::Diagnostic(cow) | Self::Sensitive(cow) | Self::Lie(cow) => {
163                if let Cow::Owned(s) = cow {
164                    s.zeroize();
165                }
166            }
167        }
168    }
169}
170
171impl ZeroizeOnDrop for InternalContextField {}
172
173impl Drop for InternalContextField {
174    fn drop(&mut self) {
175        // For sensitive variants with owned data, perform volatile write to prevent
176        // compiler from eliding the zeroization as a "dead store" optimization.
177        //
178        // GUARANTEES PROVIDED:
179        // - Prevents LLVM from removing the write as dead code
180        // - Ensures write completes before subsequent drop logic
181        //
182        // GUARANTEES NOT PROVIDED:
183        // - Does NOT guarantee CPU cache flushes
184        // - Does NOT prevent other threads from seeing old values
185        // - Does NOT prevent allocator from reusing memory before physical clear
186        // - Does NOT protect against swap, DMA, or hardware memory copies
187        //
188        // This is best-effort memory clearing for defense against casual inspection
189        // and compiler optimizations. Not suitable for cryptographic key material
190        // that requires HSM-grade wiping.
191        if let Self::Sensitive(cow) = &mut *self {
192            if let Cow::Owned(s) = cow {
193                // SAFETY:
194                // - We own this String and are in its Drop implementation
195                // - as_mut_ptr() returns valid pointer to the String's buffer
196                // - len() is correct and bounds-checked by Rust
197                // - We write only within allocated bounds (0..len)
198                // - Volatile writes prevent compiler optimization
199                unsafe {
200                    let ptr = s.as_mut_ptr();
201                    let len = s.len();
202                    for i in 0..len {
203                        ptr::write_volatile(ptr.add(i), 0u8);
204                    }
205                }
206            };
207        }
208        
209        // High-level zeroization via zeroize crate
210        self.zeroize();
211        
212        // Compiler fence prevents reordering of instructions across this boundary.
213        // Ensures zeroization completes before any subsequent destructor logic.
214        //
215        // GUARANTEES PROVIDED:
216        // - Prevents compiler from reordering instructions
217        // - Sequential consistency for this thread's view
218        //
219        // GUARANTEES NOT PROVIDED:
220        // - Does NOT imply hardware memory barriers
221        // - Does NOT force cache coherence across CPU cores
222        // - Other threads may still observe old values in their caches
223        compiler_fence(Ordering::SeqCst);
224    }
225}
226
227/// Public context field classification for externally-visible error data.
228///
229/// This type is wrapped in `PublicContext` newtype and can only display data
230/// explicitly marked as safe or intentionally deceptive for external consumption.
231///
232/// # Variants
233///
234/// - `Truth`: Minimal truthful message safe for external display (feature-gated)
235/// - `Lie`: Intentionally false narrative for misleading attackers
236///
237/// # Feature Gate Behavior
238///
239/// `Truth` variant only exists when `external_signaling` feature is enabled.
240/// Without this feature, attempting to construct `PublicContext::truth()` will
241/// fail at compile time, forcing all external outputs to be deceptive.
242///
243/// # Memory Model
244///
245/// Same as `InternalContextField` - uses `Cow<'static, str>` for efficient
246/// storage of both static and dynamic strings. Borrowed data assumed non-sensitive.
247///
248/// # No Clone/Copy Policy
249///
250/// Prevents accidental propagation of deceptive narratives across system boundaries.
251enum PublicContextField {
252    #[cfg(feature = "external_signaling")]
253    Truth(Cow<'static, str>),
254    Lie(Cow<'static, str>),
255}
256
257impl Zeroize for PublicContextField {
258    fn zeroize(&mut self) {
259        match self {
260            #[cfg(feature = "external_signaling")]
261            Self::Truth(cow) => {
262                if let Cow::Owned(s) = cow {
263                    s.zeroize();
264                }
265            }
266            Self::Lie(cow) => {
267                if let Cow::Owned(s) = cow {
268                    s.zeroize();
269                }
270            }
271        }
272    }
273}
274
275impl ZeroizeOnDrop for PublicContextField {}
276
277// Note: No custom Drop implementation here. Public contexts rarely contain
278// sensitive data, and if they did, they should be in InternalContext instead.
279// ZeroizeOnDrop provides sufficient cleanup for owned strings.
280
281// ============================================================================
282// Type-Safe Context Wrappers
283// ============================================================================
284
285/// Type-safe wrapper for public-facing error contexts.
286///
287/// # Trust Boundary Enforcement
288///
289/// This newtype prevents `InternalContextField` from being accidentally displayed
290/// externally. The type system ensures only `PublicContextField` variants can be
291/// wrapped here, and the `Display` implementation is the sole external rendering path.
292///
293/// # Construction
294///
295/// - `lie()`: Always available for deceptive public messages
296/// - `truth()`: Only available with `external_signaling` feature enabled
297///
298/// # Safety Properties
299///
300/// 1. Cannot be constructed from `InternalContext`
301/// 2. Cannot implicitly convert to string (must use `as_str()` or `Display`)
302/// 3. Implements `ZeroizeOnDrop` for owned string data
303///
304/// # No Clone/Copy Policy
305///
306/// Single-owner semantics prevent duplicate public messages from existing
307/// simultaneously, reducing risk of inconsistent external responses.
308pub struct PublicContext(PublicContextField);
309
310impl PublicContext {
311    /// Create a deceptive public context for external display.
312    ///
313    /// # Use Case
314    ///
315    /// Default constructor for honeypot deployments. Deceptive messages are
316    /// explicitly labeled and auditable in internal logs.
317    ///
318    /// # Performance
319    ///
320    /// Accepts `Cow<'static, str>` to allow zero-allocation when passed string
321    /// literals: `PublicContext::lie("error")` allocates nothing.
322    #[inline]
323    pub fn lie(message: impl Into<Cow<'static, str>>) -> Self {
324        Self(PublicContextField::Lie(message.into()))
325    }
326
327    /// Create a truthful public context for external display.
328    ///
329    /// # Availability
330    ///
331    /// This method only exists when `external_signaling` feature is enabled.
332    /// Without this feature, all public contexts must be deceptive, enforcing
333    /// operational security at compile time rather than runtime configuration.
334    ///
335    /// # Use Case
336    ///
337    /// For honeypots that intentionally signal some authentic errors to appear
338    /// more legitimate (e.g., benign input validation failures).
339    #[cfg(feature = "external_signaling")]
340    #[inline]
341    pub fn truth(message: impl Into<Cow<'static, str>>) -> Self {
342        Self(PublicContextField::Truth(message.into()))
343    }
344
345    /// Get the external-safe string representation.
346    ///
347    /// # Returns
348    ///
349    /// Borrowed string slice suitable for HTTP responses, external APIs, or any
350    /// untrusted display context. This string may be deceptive.
351    ///
352    /// # Lifetime
353    ///
354    /// Returned reference borrows from self, valid until this context is dropped.
355    #[inline]
356    pub fn as_str(&self) -> &str {
357        match &self.0 {
358            #[cfg(feature = "external_signaling")]
359            PublicContextField::Truth(c) => c.as_ref(),
360            PublicContextField::Lie(c) => c.as_ref(),
361        }
362    }
363
364    /// Get classification label for internal audit trails.
365    ///
366    /// # Returns
367    ///
368    /// Static string identifying context type without exposing payload.
369    /// Useful for metrics, SOC dashboards, and audit log indexing.
370    ///
371    /// # Values
372    ///
373    /// - `"PublicTruth"`: Authentic message (requires feature flag)
374    /// - `"DeceptiveLie"`: Intentionally false message
375    #[inline]
376    pub const fn classification(&self) -> &'static str {
377        match &self.0 {
378            #[cfg(feature = "external_signaling")]
379            PublicContextField::Truth(_) => "PublicTruth",
380            PublicContextField::Lie(_) => "DeceptiveLie",
381        }
382    }
383}
384
385impl Zeroize for PublicContext {
386    fn zeroize(&mut self) {
387        self.0.zeroize();
388    }
389}
390
391impl ZeroizeOnDrop for PublicContext {}
392
393impl fmt::Display for PublicContext {
394    /// Render public context for external display.
395    ///
396    /// This is the primary interface for converting error contexts into
397    /// externally-visible strings (HTTP responses, external APIs, etc.).
398    ///
399    /// # Security Note
400    ///
401    /// This implementation is intentionally simple and does not check context
402    /// classification. The type system guarantees only `PublicContextField`
403    /// variants can be wrapped in this type, so all outputs are safe by construction.
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        f.write_str(self.as_str())
406    }
407}
408
409impl fmt::Debug for PublicContext {
410    /// Debug representation for internal logging and diagnostics.
411    ///
412    /// # Redaction Strategy
413    ///
414    /// Deceptive payloads are redacted in debug output to prevent lies from
415    /// being aggregated as factual data in log analysis systems that may:
416    /// - Export logs to external SIEMs
417    /// - Send logs to cloud providers
418    /// - Aggregate metrics across trust boundaries
419    ///
420    /// This prevents deceptive error messages from polluting statistical analysis.
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422        match &self.0 {
423            #[cfg(feature = "external_signaling")]
424            PublicContextField::Truth(c) => write!(f, "PublicTruth({:?})", c),
425            PublicContextField::Lie(_) => write!(f, "DeceptiveLie([REDACTED])"),
426        }
427    }
428}
429
430/// Type-safe wrapper for internal-only error contexts.
431///
432/// # Trust Boundary Enforcement
433///
434/// This newtype ensures internal diagnostic data cannot be accidentally exposed
435/// externally. The `Display` implementation returns a redacted placeholder, not
436/// actual content, preventing misuse in external-facing code paths.
437///
438/// # Access Patterns
439///
440/// - `payload()`: Returns structured data for SOC logging (zero allocation)
441/// - `expose_sensitive()`: Returns raw sensitive content (requires `SocAccess` capability)
442///
443/// Both methods require conscious choice and cannot be used accidentally via
444/// generic string formatting.
445///
446/// # Memory Safety
447///
448/// Implements `ZeroizeOnDrop` to clear owned string data. Sensitive variants
449/// receive additional volatile write treatment in `InternalContextField::drop()`
450/// to prevent compiler optimization of the clearing operation.
451///
452/// # No Clone/Copy Policy
453///
454/// Single-owner semantics prevent sensitive diagnostic data from being duplicated
455/// across memory regions, reducing attack surface for memory inspection.
456pub struct InternalContext(InternalContextField);
457
458impl InternalContext {
459    /// Create a standard diagnostic internal context.
460    ///
461    /// # Use Case
462    ///
463    /// For typical internal error diagnostics that SOC analysts need but that
464    /// should never be exposed externally (stack traces, internal state, etc.).
465    ///
466    /// # Example
467    ///
468    /// ```ignore
469    /// InternalContext::diagnostic("SQL injection detected in /api/users?id=1' OR '1'='1")
470    /// ```
471    #[inline]
472    pub fn diagnostic(message: impl Into<Cow<'static, str>>) -> Self {
473        Self(InternalContextField::Diagnostic(message.into()))
474    }
475
476    /// Create a sensitive internal context with best-effort memory clearing.
477    ///
478    /// # Use Case
479    ///
480    /// For internal diagnostics containing:
481    /// - Personally identifiable information (PII)
482    /// - Credentials or API keys
483    /// - Filesystem paths that reveal system topology
484    /// - Database connection strings
485    /// - Any data that could aid an attacker
486    ///
487    /// # Memory Clearing Strategy
488    ///
489    /// When this context is dropped:
490    /// 1. Owned string data is cleared via `zeroize` crate
491    /// 2. Volatile writes prevent LLVM dead-store elimination
492    /// 3. Compiler fence prevents instruction reordering
493    ///
494    /// This provides best-effort clearing against casual memory inspection and
495    /// compiler optimizations. It does NOT provide guarantees against:
496    /// - Hardware cache persistence
497    /// - Allocator-level memory reuse
498    /// - Swap file or DMA copies
499    ///
500    /// For cryptographic key material, use dedicated secure allocators.
501    ///
502    /// # Example
503    ///
504    /// ```ignore
505    /// InternalContext::sensitive(format!("Failed login for username: {}", username))
506    /// ```
507    #[inline]
508    pub fn sensitive(message: impl Into<Cow<'static, str>>) -> Self {
509        Self(InternalContextField::Sensitive(message.into()))
510    }
511
512    /// Create an internal context marked as deceptive.
513    ///
514    /// # Use Case
515    ///
516    /// When internal logs themselves may be exfiltrated and you need to track
517    /// deceptive narratives without exposing them externally. The `payload()`
518    /// method will return this with a `Lie` marker to prevent SOC analysts from
519    /// treating it as authentic diagnostic data.
520    ///
521    /// # Distinction from PublicContext::lie()
522    ///
523    /// - `PublicContext::lie()`: For external consumption
524    /// - `InternalContext::lie()`: For internal tracking of deception operations
525    ///
526    /// # Example
527    ///
528    /// ```ignore
529    /// InternalContext::lie("Normal database query executed successfully")
530    /// ```
531    #[inline]
532    pub fn lie(message: impl Into<Cow<'static, str>>) -> Self {
533        Self(InternalContextField::Lie(message.into()))
534    }
535
536    /// Get classification label for logging and metrics.
537    ///
538    /// # Returns
539    ///
540    /// Static string identifying the context type:
541    /// - `"InternalDiagnostic"`: Standard internal error information
542    /// - `"Sensitive"`: Contains high-value data requiring special handling
543    /// - `"InternalLie"`: Deceptive content tracked internally
544    ///
545    /// # Use Case
546    ///
547    /// For indexing in log aggregation systems, metrics collection, or
548    /// routing different context types to different storage backends.
549    #[inline]
550    pub const fn classification(&self) -> &'static str {
551        match &self.0 {
552            InternalContextField::Diagnostic(_) => "InternalDiagnostic",
553            InternalContextField::Sensitive(_) => "Sensitive",
554            InternalContextField::Lie(_) => "InternalLie",
555        }
556    }
557
558    /// Get structured payload for internal logging without heap allocation.
559    ///
560    /// # Returns
561    ///
562    /// - `Some(InternalPayload::Truth(_))`: For diagnostic contexts
563    /// - `Some(InternalPayload::Lie(_))`: For lie contexts (marked for SOC awareness)
564    /// - `None`: For sensitive contexts (use `expose_sensitive()` instead)
565    ///
566    /// # Performance
567    ///
568    /// Zero allocation. The returned `InternalPayload` borrows directly from the
569    /// underlying `Cow<'static, str>`. Loggers can format this without heap use:
570    ///
571    /// ```ignore
572    /// match context.payload() {
573    ///     Some(payload) => println!("{}", payload),  // No allocation
574    ///     None => println!("[SENSITIVE REDACTED]"),
575    /// }
576    /// ```
577    ///
578    /// # Rationale
579    ///
580    /// Previous design allocated `format!("[LIE] {}", msg)` on every access.
581    /// This approach defers formatting to the logger, allowing:
582    /// - Zero-copy access to underlying data
583    /// - Logger-controlled formatting policy
584    /// - Better performance under high error rates
585    #[inline]
586    pub fn payload(&self) -> Option<InternalPayload<'_>> {
587        match &self.0 {
588            InternalContextField::Diagnostic(c) => Some(InternalPayload::Truth(c.as_ref())),
589            InternalContextField::Sensitive(_) => None,
590            InternalContextField::Lie(c) => Some(InternalPayload::Lie(c.as_ref())),
591        }
592    }
593
594    /// Expose raw sensitive content with capability-based access control.
595    ///
596    /// # Access Control
597    ///
598    /// Requires `SocAccess` capability token, which forces:
599    /// 1. Explicit privilege acquisition (cannot call accidentally)
600    /// 2. Grep-able sensitive data access points
601    /// 3. Future integration with RBAC or audit systems
602    ///
603    /// # Security Contract
604    ///
605    /// Caller must ensure returned data is sent ONLY to:
606    /// - Authenticated, encrypted, access-controlled endpoints
607    /// - SOC-exclusive dashboards with strict RBAC
608    /// - Encrypted internal logging with key rotation
609    /// - Forensic analysis workstations with air-gapped storage
610    ///
611    /// Never send to:
612    /// - External SIEMs or cloud logging services
613    /// - Unencrypted log files
614    /// - Monitoring services that aggregate across trust boundaries
615    ///
616    /// # Returns
617    ///
618    /// - `Some(&str)`: Raw sensitive content (if this is a Sensitive context)
619    /// - `None`: If this is not a Sensitive context
620    ///
621    /// # Why #[must_use]
622    ///
623    /// Forces caller to explicitly handle the returned sensitive data rather than
624    /// accidentally discarding it (which might indicate a logic error).
625    ///
626    /// # Example
627    ///
628    /// ```ignore
629    /// let access = SocAccess::acquire();
630    /// if let Some(sensitive) = context.expose_sensitive(&access) {
631    ///     send_to_encrypted_soc_siem(sensitive);
632    /// }
633    /// ```
634    #[must_use]
635    #[inline]
636    pub fn expose_sensitive(&self, _access: &SocAccess) -> Option<&str> {
637        match &self.0 {
638            InternalContextField::Sensitive(c) => Some(c.as_ref()),
639            _ => None,
640        }
641    }
642}
643
644impl Zeroize for InternalContext {
645    fn zeroize(&mut self) {
646        self.0.zeroize();
647    }
648}
649
650impl ZeroizeOnDrop for InternalContext {}
651
652impl fmt::Display for InternalContext {
653    /// Display implementation for internal contexts.
654    ///
655    /// # Security Policy
656    ///
657    /// This ALWAYS returns a redacted placeholder, never actual content.
658    /// Internal contexts should not be formatted for external display under
659    /// any circumstances. This implementation exists only to satisfy trait
660    /// bounds in generic code.
661    ///
662    /// # Correct Usage
663    ///
664    /// - Use `payload()` for SOC logging
665    /// - Use `expose_sensitive()` for controlled sensitive access
666    /// - Do NOT use `Display` or `ToString` on internal contexts
667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668        f.write_str("[INTERNAL CONTEXT REDACTED]")
669    }
670}
671
672impl fmt::Debug for InternalContext {
673    /// Debug representation for internal development and diagnostics.
674    ///
675    /// # Redaction Policy
676    ///
677    /// - Diagnostic: Shows full content (for debugging)
678    /// - Sensitive: Redacted (to prevent accidental logging)
679    /// - Lie: Redacted (to prevent aggregation as factual data)
680    ///
681    /// # Use Case
682    ///
683    /// Primarily for unit tests and local development. Production logging should
684    /// use `payload()` or `expose_sensitive()` for explicit control.
685    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
686        match &self.0 {
687            InternalContextField::Diagnostic(c) => write!(f, "InternalDiagnostic({:?})", c),
688            InternalContextField::Sensitive(_) => write!(f, "Sensitive([REDACTED])"),
689            InternalContextField::Lie(_) => write!(f, "InternalLie([REDACTED])"),
690        }
691    }
692}
693
694/// Zero-allocation internal payload for SOC logging.
695///
696/// Returned by `InternalContext::payload()`. This type borrows from the
697/// underlying context and allows loggers to format output without heap allocation.
698///
699/// # Variants
700///
701/// - `Truth(&str)`: Authentic diagnostic message
702/// - `Lie(&str)`: Deceptive message (should be prefixed in logs)
703///
704/// # Performance
705///
706/// The borrowed lifetime ties this to the parent `InternalContext`, preventing
707/// accidental persistence of sensitive data beyond the logging operation.
708///
709/// # No Copy Policy
710///
711/// While this type only contains `&str` (which is Copy), we deliberately do not
712/// derive Copy to maintain consistency with the module's no-duplication philosophy.
713/// Use `Clone` if you need to store the payload temporarily.
714///
715/// # Usage Pattern
716///
717/// ```ignore
718/// match context.payload() {
719///     Some(InternalPayload::Truth(msg)) => soc_log!("DIAG: {}", msg),
720///     Some(InternalPayload::Lie(msg)) => soc_log!("LIE: {}", msg),
721///     None => {
722///         // Sensitive - requires explicit access
723///         let access = SocAccess::acquire();
724///         if let Some(sensitive) = context.expose_sensitive(&access) {
725///             secure_log_encrypted(sensitive);
726///         }
727///     }
728/// }
729/// ```
730#[derive(Debug, Clone)]
731pub enum InternalPayload<'a> {
732    Truth(&'a str),
733    Lie(&'a str),
734}
735
736impl<'a> InternalPayload<'a> {
737    /// Get the raw message content without classification prefix.
738    ///
739    /// # Returns
740    ///
741    /// Borrowed string slice from the parent context. Valid until the
742    /// `InternalContext` is dropped.
743    ///
744    /// # Note
745    ///
746    /// This does NOT include `[LIE]` prefix. Use the `Display` implementation
747    /// if you want formatted output with classification markers.
748    #[inline]
749    pub const fn as_str(&self) -> &'a str {
750        match self {
751            Self::Truth(s) | Self::Lie(s) => s,
752        }
753    }
754
755    /// Check if this payload represents deceptive content.
756    ///
757    /// # Returns
758    ///
759    /// - `true`: This is a lie, should be marked in logs
760    /// - `false`: This is authentic diagnostic data
761    ///
762    /// # Use Case
763    ///
764    /// For conditional log routing or metrics collection based on deception status.
765    #[inline]
766    pub const fn is_lie(&self) -> bool {
767        matches!(self, Self::Lie(_))
768    }
769}
770
771impl<'a> fmt::Display for InternalPayload<'a> {
772    /// Format payload with classification prefix for logging.
773    ///
774    /// # Output Format
775    ///
776    /// - Truth: Raw message (no prefix)
777    /// - Lie: `[LIE] {message}`
778    ///
779    /// # Rationale
780    ///
781    /// The `[LIE]` prefix prevents SOC analysts from mistaking deceptive content
782    /// for authentic diagnostic data when reviewing logs. This is critical when
783    /// logs may be exported to systems that lack context classification.
784    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785        match self {
786            Self::Truth(s) => f.write_str(s),
787            Self::Lie(s) => write!(f, "[LIE] {}", s),
788        }
789    }
790}
791
792// ============================================================================
793// Operation Category
794// ============================================================================
795
796/// Operation category for contextualizing errors without revealing architecture.
797///
798/// Categories are intentionally broad to provide SOC operators with sufficient
799/// signal for triage and response while preventing attackers from mapping internal
800/// system topology through error analysis.
801///
802/// # Design Principle
803///
804/// Each category represents a functional domain rather than a specific component.
805/// This prevents errors from revealing:
806/// - Service boundaries
807/// - Technology stack details
808/// - Deployment architecture
809/// - Internal naming conventions
810///
811/// # Copy Semantics Exception
812///
813/// This is the only type in this module that implements Copy. Rationale:
814/// - Small fieldless enum (single byte in practice)
815/// - No owned or sensitive data
816/// - Copying is cheaper than reference passing (no indirection)
817/// - Frequently passed by value in error construction
818///
819/// # Deception Support
820///
821/// Honeypot-specific categories (Deception, Detection, Containment) have
822/// deceptive display names that mask defensive operations as routine work.
823#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
824pub enum OperationCategory {
825    /// Configuration parsing, validation, or application
826    Configuration,
827    /// Artifact deployment, management, or versioning
828    Deployment,
829    /// Event collection, monitoring, or observability
830    Monitoring,
831    /// Rule evaluation, scoring, or decision logic
832    Analysis,
833    /// Action execution, remediation, or enforcement
834    Response,
835    /// Logging, audit trail, or compliance recording
836    Audit,
837    /// System-level operations (process, memory, OS interaction)
838    System,
839    /// File system or network I/O operations
840    IO,
841    /// Honeypot luring, facade maintenance, or attacker engagement
842    Deception,
843    /// Intrusion detection, anomaly identification, or threat recognition
844    Detection,
845    /// Attack isolation, sandboxing, or containment operations
846    Containment,
847}
848
849impl OperationCategory {
850    /// Get the authentic display name for this category.
851    ///
852    /// # Returns
853    ///
854    /// Static string describing the operation domain. No allocation, no Unicode
855    /// edge cases. Safe for all internal logging and SOC dashboards.
856    ///
857    /// # Performance
858    ///
859    /// Const function compiled to direct pointer return. Zero runtime cost.
860    #[inline]
861    pub const fn display_name(&self) -> &'static str {
862        match self {
863            Self::Configuration => "Configuration",
864            Self::Deployment => "Deployment",
865            Self::Monitoring => "Monitoring",
866            Self::Analysis => "Analysis",
867            Self::Response => "Response",
868            Self::Audit => "Audit",
869            Self::System => "System",
870            Self::IO => "I/O",
871            Self::Deception => "Deception",
872            Self::Detection => "Detection",
873            Self::Containment => "Containment",
874        }
875    }
876
877    /// Get deceptive display name for external contexts.
878    ///
879    /// # Returns
880    ///
881    /// Static string that masks honeypot-specific operations as routine work:
882    /// - Deception → "Routine Operation"
883    /// - Detection → "Routine Operation"
884    /// - Containment → "Routine Operation"
885    /// - All others → Same as `display_name()`
886    ///
887    /// # Use Case
888    ///
889    /// For external error messages where revealing defensive operations would
890    /// compromise the honeypot's effectiveness. Generic labels prevent attackers
891    /// from identifying which interactions triggered defensive responses.
892    ///
893    /// # Performance
894    ///
895    /// Const function, zero runtime cost. Compiled to direct pointer return.
896    #[inline]
897    pub const fn deceptive_name(&self) -> &'static str {
898        match self {
899            Self::Deception | Self::Detection | Self::Containment => "Routine Operation",
900            _ => self.display_name(),
901        }
902    }
903}
904
905// ============================================================================
906// Dual-Context Error with Invariant Enforcement
907// ============================================================================
908
909/// Dual-context error model for honeypot systems with constructor-enforced invariants.
910///
911/// # Type Safety Guarantees
912///
913/// 1. Public and internal contexts use distinct wrapper types (cannot be confused)
914/// 2. Fields are private (all construction goes through validated constructors)
915/// 3. Constructors enforce semantic consistency rules at creation time
916///
917/// # Enforced Invariants
918///
919/// - Public truth requires internal truth (no internal lies when external truth)
920/// - Public lie allows any internal context (deception is flexible)
921/// - Sensitive data flows only through InternalContext (type system prevents external leakage)
922///
923/// # Constructor Selection
924///
925/// - `with_lie()`: Public deception + internal diagnostic (most common)
926/// - `with_lie_and_sensitive()`: Public deception + best-effort cleared sensitive internal
927/// - `with_truth()`: Public truth + internal truth (feature-gated, enforces consistency)
928/// - `with_double_lie()`: Public deception + internal deception (for log exfiltration scenarios)
929///
930/// # Memory Management
931///
932/// Implements `ZeroizeOnDrop` to clear all owned string data. Sensitive contexts
933/// receive additional volatile write treatment in `InternalContextField::drop()`
934/// to prevent LLVM from eliding the zeroization as a dead-store optimization.
935///
936/// This provides best-effort memory clearing but does not guarantee:
937/// - Hardware cache flushes
938/// - Cross-thread memory visibility
939/// - Protection against allocator reuse before physical clear
940///
941/// # No Clone/Copy Policy
942///
943/// Single-owner semantics prevent:
944/// - Duplicate error contexts in memory (reduced attack surface)
945/// - Inconsistent public/internal message pairs
946/// - Accidental persistence of sensitive data across scopes
947pub struct DualContextError {
948    public: PublicContext,
949    internal: InternalContext,
950    category: OperationCategory,
951}
952
953impl DualContextError {
954    /// Internal constructor from pre-built contexts.
955    ///
956    /// This is crate-private to preserve external API invariants.
957    #[inline]
958    pub(crate) fn new(
959        public: PublicContext,
960        internal: InternalContext,
961        category: OperationCategory,
962    ) -> Self {
963        Self {
964            public,
965            internal,
966            category,
967        }
968    }
969
970    /// Create error with public deception and internal diagnostic.
971    ///
972    /// # Use Case
973    ///
974    /// Standard constructor for honeypot deployments. External attackers see
975    /// deceptive error message while SOC analysts see actual diagnostic data.
976    ///
977    /// # Invariant
978    ///
979    /// Public message is explicitly marked as `DeceptiveLie`. Internal message
980    /// is authentic diagnostic data for SOC analysis.
981    ///
982    /// # Example
983    ///
984    /// ```ignore
985    /// DualContextError::with_lie(
986    ///     "Permission denied",  // Attacker sees generic error
987    ///     "Blocked SQL injection attempt: UNION SELECT detected in query parameter 'id'",
988    ///     OperationCategory::Detection,
989    /// )
990    /// ```
991    ///
992    /// # Performance
993    ///
994    /// Zero allocation if string literals are passed. `Into<Cow<'static, str>>`
995    /// allows both literals and owned strings without forcing allocation.
996    #[inline]
997    pub fn with_lie(
998        public_lie: impl Into<Cow<'static, str>>,
999        internal_diagnostic: impl Into<Cow<'static, str>>,
1000        category: OperationCategory,
1001    ) -> Self {
1002        Self {
1003            public: PublicContext::lie(public_lie),
1004            internal: InternalContext::diagnostic(internal_diagnostic),
1005            category,
1006        }
1007    }
1008
1009    /// Create error with public deception and sensitive internal data.
1010    ///
1011    /// # Use Case
1012    ///
1013    /// When internal diagnostic contains PII, credentials, file paths, or other
1014    /// high-value data requiring best-effort memory clearing on drop.
1015    ///
1016    /// # Memory Clearing Strategy
1017    ///
1018    /// When this error is dropped, sensitive data receives:
1019    /// 1. High-level clearing via `zeroize` crate
1020    /// 2. Volatile writes to prevent compiler optimization
1021    /// 3. Compiler fence to prevent instruction reordering
1022    ///
1023    /// This provides best-effort defense against casual memory inspection and
1024    /// compiler optimizations. See module-level docs for limitations.
1025    ///
1026    /// # Example
1027    ///
1028    /// ```ignore
1029    /// DualContextError::with_lie_and_sensitive(
1030    ///     "Resource not found",
1031    ///     format!("Attempted access to restricted path: /var/secrets/api_keys.txt by user {}", username),
1032    ///     OperationCategory::IO,
1033    /// )
1034    /// ```
1035    ///
1036    /// # Invariant
1037    ///
1038    /// Public message is deceptive. Internal message is marked sensitive and
1039    /// will be redacted in most logging contexts (requires explicit access via
1040    /// `expose_sensitive()` with `SocAccess` capability).
1041    #[inline]
1042    pub fn with_lie_and_sensitive(
1043        public_lie: impl Into<Cow<'static, str>>,
1044        internal_sensitive: impl Into<Cow<'static, str>>,
1045        category: OperationCategory,
1046    ) -> Self {
1047        Self {
1048            public: PublicContext::lie(public_lie),
1049            internal: InternalContext::sensitive(internal_sensitive),
1050            category,
1051        }
1052    }
1053
1054    /// Create error with public truth and internal diagnostic.
1055    ///
1056    /// # Availability
1057    ///
1058    /// Only available when `external_signaling` feature is enabled. Without this
1059    /// feature, all public contexts must be deceptive (compile-time enforcement).
1060    ///
1061    /// # Invariant Enforcement
1062    ///
1063    /// When telling truth externally, internal context must also be truthful.
1064    /// This constructor enforces semantic consistency - you cannot lie internally
1065    /// while being honest externally.
1066    ///
1067    /// # Use Case
1068    ///
1069    /// For honeypots that intentionally signal some authentic errors to appear
1070    /// more legitimate (e.g., benign validation failures that don't reveal
1071    /// defensive posture).
1072    ///
1073    /// # Example
1074    ///
1075    /// ```ignore
1076    /// DualContextError::with_truth(
1077    ///     "Invalid JSON format",
1078    ///     "JSON parse error at line 42, column 15: expected closing brace",
1079    ///     OperationCategory::Configuration,
1080    /// )
1081    /// ```
1082    #[cfg(feature = "external_signaling")]
1083    #[inline]
1084    pub fn with_truth(
1085        public_truth: impl Into<Cow<'static, str>>,
1086        internal_diagnostic: impl Into<Cow<'static, str>>,
1087        category: OperationCategory,
1088    ) -> Self {
1089        Self {
1090            public: PublicContext::truth(public_truth),
1091            internal: InternalContext::diagnostic(internal_diagnostic),
1092            category,
1093        }
1094    }
1095
1096    /// Create error where both public and internal contexts are deceptive.
1097    ///
1098    /// # Use Case
1099    ///
1100    /// Advanced deception scenarios where even internal logs may be exfiltrated
1101    /// by sophisticated attackers. Both contexts contain lies, but the internal
1102    /// lie is marked to prevent SOC analysts from treating it as authentic.
1103    ///
1104    /// # Behavior
1105    ///
1106    /// - External systems see the public lie (generic error)
1107    /// - Internal logs show internal lie prefixed with `[LIE]` marker
1108    /// - SOC analysts are warned not to trust this diagnostic data
1109    ///
1110    /// # Example
1111    ///
1112    /// ```ignore
1113    /// DualContextError::with_double_lie(
1114    ///     "Service temporarily unavailable",
1115    ///     "Routine maintenance window in progress",
1116    ///     OperationCategory::System,
1117    /// )
1118    /// ```
1119    ///
1120    /// # Rationale
1121    ///
1122    /// In environments where log exfiltration is a threat (compromised SIEM,
1123    /// malicious cloud provider, etc.), truthful internal logs become a liability.
1124    /// This constructor allows full deception while maintaining analyst awareness.
1125    #[inline]
1126    pub fn with_double_lie(
1127        public_lie: impl Into<Cow<'static, str>>,
1128        internal_lie: impl Into<Cow<'static, str>>,
1129        category: OperationCategory,
1130    ) -> Self {
1131        Self {
1132            public: PublicContext::lie(public_lie),
1133            internal: InternalContext::lie(internal_lie),
1134            category,
1135        }
1136    }
1137
1138    /// Get the public context (safe for external display).
1139    ///
1140    /// # Returns
1141    ///
1142    /// Borrowed reference to `PublicContext`, which can be used with `Display`
1143    /// trait to render external error messages.
1144    ///
1145    /// # Lifetime
1146    ///
1147    /// Reference borrows from self, valid until this error is dropped.
1148    #[inline]
1149    pub const fn public(&self) -> &PublicContext {
1150        &self.public
1151    }
1152
1153    /// Get the internal context (SOC-only visibility).
1154    ///
1155    /// # Returns
1156    ///
1157    /// Borrowed reference to `InternalContext`. Use `payload()` or
1158    /// `expose_sensitive()` methods for accessing content.
1159    ///
1160    /// # Security Note
1161    ///
1162    /// Do NOT use `Display` or `ToString` on this reference. Those implementations
1163    /// return redacted placeholders. Use explicit accessor methods instead.
1164    #[inline]
1165    pub const fn internal(&self) -> &InternalContext {
1166        &self.internal
1167    }
1168
1169    /// Get the operation category.
1170    ///
1171    /// # Returns
1172    ///
1173    /// Copy of the `OperationCategory` enum. Can be used with `display_name()`
1174    /// for internal logging or `deceptive_name()` for external contexts.
1175    #[inline]
1176    pub const fn category(&self) -> OperationCategory {
1177        self.category
1178    }
1179
1180    /// Get the external-facing error message as a string.
1181    ///
1182    /// # Returns
1183    ///
1184    /// Borrowed string slice suitable for HTTP responses, external APIs, or any
1185    /// untrusted context. May be deceptive depending on constructor used.
1186    ///
1187    /// # Use Case
1188    ///
1189    /// Primary method for rendering errors to external clients:
1190    /// ```ignore
1191    /// let error = DualContextError::with_lie(...);
1192    /// http_response.body(error.external_message());
1193    /// ```
1194    ///
1195    /// # Performance
1196    ///
1197    /// Returns borrowed reference, no allocation. Delegates to
1198    /// `PublicContext::as_str()` which in turn delegates to `Cow::as_ref()`.
1199    #[inline]
1200    pub fn external_message(&self) -> &str {
1201        self.public.as_str()
1202    }
1203
1204    /// Get the deceptive category name for external display.
1205    ///
1206    /// # Returns
1207    ///
1208    /// Static string that masks honeypot operations as generic activity.
1209    /// Honeypot-specific categories return "Routine Operation".
1210    ///
1211    /// # Use Case
1212    ///
1213    /// For structured error responses where category field is included:
1214    /// ```ignore
1215    /// json!({
1216    ///     "error": error.external_message(),
1217    ///     "category": error.external_category(),
1218    /// })
1219    /// ```
1220    ///
1221    /// # Performance
1222    ///
1223    /// Const function, compiles to direct pointer return. Zero runtime cost.
1224    #[inline]
1225    pub fn external_category(&self) -> &'static str {
1226        self.category.deceptive_name()
1227    }
1228}
1229
1230impl Zeroize for DualContextError {
1231    fn zeroize(&mut self) {
1232        self.public.zeroize();
1233        self.internal.zeroize();
1234        // category is Copy, contains no sensitive data, no zeroization needed
1235    }
1236}
1237
1238impl ZeroizeOnDrop for DualContextError {}
1239
1240// Note: No custom Drop implementation here. Zeroization is handled authoritatively
1241// in InternalContextField::drop() for sensitive data. This layer just delegates
1242// via ZeroizeOnDrop trait. Consolidating the volatile writes and fences to a single
1243// location (the base field type) reduces complexity and prevents redundant operations.
1244
1245impl fmt::Display for DualContextError {
1246    /// Render error for external display.
1247    ///
1248    /// # Behavior
1249    ///
1250    /// Displays only the public context. Internal diagnostic data is never
1251    /// included in this output.
1252    ///
1253    /// # Use Case
1254    ///
1255    /// For generic error handling where you want automatic string conversion:
1256    /// ```ignore
1257    /// println!("Error occurred: {}", error);
1258    /// ```
1259    ///
1260    /// # Security Note
1261    ///
1262    /// This is safe for external use. Only public context is rendered, which
1263    /// can only contain `PublicTruth` or `DeceptiveLie` - never internal diagnostics.
1264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1265        write!(f, "{}", self.public)
1266    }
1267}
1268
1269impl fmt::Debug for DualContextError {
1270    /// Debug representation for development and internal diagnostics.
1271    ///
1272    /// # Output Format
1273    ///
1274    /// Structured representation showing all three fields:
1275    /// - public: May be redacted if deceptive
1276    /// - internal: May be redacted if sensitive
1277    /// - category: Always shown
1278    ///
1279    /// # Use Case
1280    ///
1281    /// For unit tests, local development logging, and internal debugging.
1282    /// Not intended for production SOC logging (use explicit accessors instead).
1283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1284        f.debug_struct("DualContextError")
1285            .field("public", &self.public)
1286            .field("internal", &self.internal)
1287            .field("category", &self.category)
1288            .finish()
1289    }
1290}
1291
1292// ============================================================================
1293// Tests
1294// ============================================================================
1295
1296#[cfg(test)]
1297mod tests {
1298    use super::*;
1299
1300    #[test]
1301    fn test_public_context_lie() {
1302        let ctx = PublicContext::lie("Permission denied");
1303        assert_eq!(ctx.as_str(), "Permission denied");
1304        assert_eq!(ctx.classification(), "DeceptiveLie");
1305    }
1306
1307    #[cfg(feature = "external_signaling")]
1308    #[test]
1309    fn test_public_context_truth() {
1310        let ctx = PublicContext::truth("Invalid input format");
1311        assert_eq!(ctx.as_str(), "Invalid input format");
1312        assert_eq!(ctx.classification(), "PublicTruth");
1313    }
1314
1315    #[test]
1316    fn test_internal_context_diagnostic() {
1317        let ctx = InternalContext::diagnostic("SQL injection detected in /api/users");
1318        assert_eq!(ctx.classification(), "InternalDiagnostic");
1319        
1320        match ctx.payload() {
1321            Some(InternalPayload::Truth(msg)) => {
1322                assert_eq!(msg, "SQL injection detected in /api/users");
1323            }
1324            _ => panic!("Expected truth payload"),
1325        }
1326    }
1327
1328    #[test]
1329    fn test_internal_context_sensitive() {
1330        let ctx = InternalContext::sensitive("/etc/passwd accessed by user:admin");
1331        assert_eq!(ctx.classification(), "Sensitive");
1332        assert!(ctx.payload().is_none());
1333        
1334        let access = SocAccess::acquire();
1335        let exposed = ctx.expose_sensitive(&access);
1336        assert_eq!(exposed, Some("/etc/passwd accessed by user:admin"));
1337    }
1338
1339    #[test]
1340    fn test_internal_context_lie() {
1341        let ctx = InternalContext::lie("Normal database query");
1342        assert_eq!(ctx.classification(), "InternalLie");
1343        
1344        match ctx.payload() {
1345            Some(InternalPayload::Lie(msg)) => {
1346                assert_eq!(msg, "Normal database query");
1347            }
1348            _ => panic!("Expected lie payload"),
1349        }
1350    }
1351
1352    #[test]
1353    fn test_internal_payload_display() {
1354        let truth = InternalPayload::Truth("test message");
1355        assert_eq!(format!("{}", truth), "test message");
1356        
1357        let lie = InternalPayload::Lie("test message");
1358        assert_eq!(format!("{}", lie), "[LIE] test message");
1359    }
1360
1361    #[test]
1362    fn test_dual_context_with_lie() {
1363        let err = DualContextError::with_lie(
1364            "Access forbidden",
1365            "Honeypot triggered: multiple failed auth attempts",
1366            OperationCategory::Detection,
1367        );
1368        
1369        assert_eq!(err.external_message(), "Access forbidden");
1370        assert_eq!(err.external_category(), "Routine Operation");
1371    }
1372
1373    #[test]
1374    fn test_dual_context_with_sensitive() {
1375        let err = DualContextError::with_lie_and_sensitive(
1376            "Resource not found",
1377            "File path: /var/secrets/api_keys.txt",
1378            OperationCategory::IO,
1379        );
1380        
1381        assert_eq!(err.external_message(), "Resource not found");
1382        assert!(err.internal().payload().is_none());
1383    }
1384
1385    #[cfg(feature = "external_signaling")]
1386    #[test]
1387    fn test_dual_context_with_truth() {
1388        let err = DualContextError::with_truth(
1389            "Invalid JSON format",
1390            "JSON parse error at line 42: unexpected token",
1391            OperationCategory::Configuration,
1392        );
1393        
1394        assert_eq!(err.external_message(), "Invalid JSON format");
1395    }
1396
1397    #[test]
1398    fn test_operation_category_deceptive_names() {
1399        assert_eq!(OperationCategory::Deception.deceptive_name(), "Routine Operation");
1400        assert_eq!(OperationCategory::Detection.deceptive_name(), "Routine Operation");
1401        assert_eq!(OperationCategory::Containment.deceptive_name(), "Routine Operation");
1402        assert_eq!(OperationCategory::Configuration.deceptive_name(), "Configuration");
1403    }
1404
1405    #[test]
1406    fn test_soc_access_capability() {
1407        let ctx = InternalContext::sensitive("secret data".to_string());
1408        
1409        // Without capability, cannot access
1410        // (This is enforced by requiring SocAccess parameter)
1411        
1412        // With capability, can access
1413        let access = SocAccess::acquire();
1414        assert_eq!(ctx.expose_sensitive(&access), Some("secret data"));
1415    }
1416
1417    #[test]
1418    fn test_zeroization() {
1419        let mut ctx = InternalContext::sensitive("secret data".to_string());
1420        
1421        // Verify data exists before zeroization
1422        let access = SocAccess::acquire();
1423        assert_eq!(ctx.expose_sensitive(&access), Some("secret data"));
1424        
1425        // Explicit zeroize call
1426        ctx.zeroize();
1427        
1428        // Note: Actual verification of memory clearing would require unsafe inspection.
1429        // This test demonstrates the API contract. In production, zeroization happens
1430        // automatically on drop via ZeroizeOnDrop trait and InternalContextField::drop().
1431    }
1432
1433    #[test]
1434    fn test_volatile_write_on_drop() {
1435        // This test verifies the drop behavior exists and compiles correctly.
1436        // Actual verification of volatile writes would require memory inspection tools.
1437        let ctx = InternalContext::sensitive("highly sensitive data".to_string());
1438        drop(ctx);
1439        // If this compiles and runs without panicking, the Drop impl is correct
1440    }
1441
1442    #[test]
1443    fn test_display_never_leaks_internal() {
1444        let err = DualContextError::with_lie(
1445            "Generic error",
1446            "Actual internal diagnostic with sensitive details",
1447            OperationCategory::System,
1448        );
1449        
1450        let display_output = format!("{}", err);
1451        assert_eq!(display_output, "Generic error");
1452        assert!(!display_output.contains("internal"));
1453        assert!(!display_output.contains("sensitive"));
1454    }
1455
1456    #[test]
1457    fn test_internal_display_always_redacts() {
1458        let ctx = InternalContext::diagnostic("secret diagnostic info");
1459        let display_output = format!("{}", ctx);
1460        assert_eq!(display_output, "[INTERNAL CONTEXT REDACTED]");
1461        assert!(!display_output.contains("secret"));
1462    }
1463
1464    #[test]
1465    fn test_internal_payload_not_copy() {
1466        // This test verifies InternalPayload does not implement Copy
1467        // If it did, this would compile with just moving instead of cloning
1468        let payload = InternalPayload::Truth("test");
1469        let _payload2 = payload.clone();
1470        // If we try to use payload again without Clone, it would fail to compile
1471        // (proving it's not Copy)
1472    }
1473}