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