Skip to main content

vex_runtime/gate/
titan.rs

1use crate::audit::vep::{
2    AuthoritySegment, EvidenceCapsuleV0, IdentitySegment, IntentSegment, RequestCommitment,
3    WitnessSegment,
4};
5use crate::gate::Gate;
6use async_trait::async_trait;
7use once_cell::sync::Lazy;
8use regex::Regex;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use tokio::sync::Mutex;
13use uuid::Uuid;
14use vex_core::audit::EvidenceCapsule;
15use vex_llm::{Capability, LlmProvider};
16
17use vex_chora::client::AuthorityClient;
18use vex_hardware::api::AgentIdentity;
19
20/// Pre-compiled L1 deterministic rules (McpVanguard signatures).
21/// Using static Lazy avoids re-compiling regexes on every TitanGate construction.
22static L1_RULES: Lazy<Vec<Regex>> = Lazy::new(|| {
23    vec![
24        Regex::new(r"(?i)rm\s+-rf\s+/").expect("L1 regex: rm -rf"),
25        Regex::new(r"(?i)drop\s+table").expect("L1 regex: drop table"),
26        Regex::new(r"(?i)chmod\s+777").expect("L1 regex: chmod 777"),
27        Regex::new(r"(?i)169\.254\.169\.254").expect("L1 regex: metadata service"),
28        Regex::new(r"(?i)\.\./\.\./").expect("L1 regex: path traversal"),
29        Regex::new(r"(?i)shutdown\s+-h\s+now").expect("L1 regex: shutdown"),
30    ]
31});
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SecurityProfile {
35    Standard,
36    Fortress,
37}
38
39/// TitanGate: The Tri-Layer Defense
40/// L1: Deterministic Rules (McpVanguard)
41/// L2: Formal Intent (Magpie)
42/// L3: Hardware Attestation (VEX/CHORA)
43#[derive(Debug)]
44pub struct TitanGate {
45    pub inner: Arc<dyn Gate>,
46    pub llm: Arc<dyn LlmProvider>,
47    pub chora: Arc<dyn AuthorityClient>,
48    pub identity: AgentIdentity,
49    pub profile: SecurityProfile,
50    pub throttler: Arc<Mutex<ThrottleGovernor>>,
51}
52
53struct TempFileGuard {
54    path: std::path::PathBuf,
55}
56
57impl Drop for TempFileGuard {
58    fn drop(&mut self) {
59        if self.path.exists() {
60            let _ = std::fs::remove_file(&self.path);
61        }
62    }
63}
64
65/// L1 Deterministic Security: Kernel-Level Path Resolution
66pub struct SecurePathResolver;
67
68impl SecurePathResolver {
69    /// Resolves a path to its physical location using handle-based syscalls.
70    /// Prevents symlink bypasses, TOCTOU, and path normalization tricks.
71    pub fn resolve_deterministic(path: &Path) -> Result<PathBuf, String> {
72        #[cfg(windows)]
73        {
74            use std::os::windows::io::AsRawHandle;
75            use windows_sys::Win32::Storage::FileSystem::{
76                GetFinalPathNameByHandleW, FILE_NAME_NORMALIZED, VOLUME_NAME_DOS,
77            };
78
79            let file = std::fs::File::open(path).map_err(|e| format!("IO_OPEN_ERROR: {}", e))?;
80            let handle = file.as_raw_handle() as *mut std::ffi::c_void;
81            let mut buffer = [0u16; 1024];
82            let len = unsafe {
83                GetFinalPathNameByHandleW(
84                    handle,
85                    buffer.as_mut_ptr(),
86                    buffer.len() as u32,
87                    FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
88                )
89            };
90
91            if len == 0 {
92                return Err("WIN32_PATH_RESOLVE_FAILED".to_string());
93            }
94
95            let path_str = String::from_utf16_lossy(&buffer[..len as usize]);
96            Ok(PathBuf::from(path_str.trim_start_matches(r"\\?\")))
97        }
98
99        #[cfg(target_os = "linux")]
100        {
101            // On Linux, std::fs::canonicalize uses realpath() which is generally secure
102            // but we can add secondary checks for symlink races if needed.
103            std::fs::canonicalize(path).map_err(|e| format!("LINUX_PATH_RESOLVE_FAILED: {}", e))
104        }
105
106        #[cfg(not(any(windows, target_os = "linux")))]
107        {
108            std::fs::canonicalize(path).map_err(|e| format!("OS_PATH_RESOLVE_FAILED: {}", e))
109        }
110    }
111}
112
113/// L1 Deterministic Security: Entropy-Based Exfiltration Detection
114pub struct EntropyGovernor;
115
116impl EntropyGovernor {
117    /// Calculates Shannon Entropy of the given data.
118    /// H = -sum(p_i * log2(p_i))
119    pub fn calculate_shannon_entropy(data: &str) -> f64 {
120        if data.is_empty() {
121            return 0.0;
122        }
123
124        let mut counts = [0usize; 256];
125        for &byte in data.as_bytes() {
126            counts[byte as usize] += 1;
127        }
128
129        let total = data.len() as f64;
130        let mut entropy = 0.0;
131
132        for &count in &counts {
133            if count > 0 {
134                let p = count as f64 / total;
135                entropy -= p * p.log2();
136            }
137        }
138
139        entropy
140    }
141
142    /// Checks if the content poses an exfiltration risk based on information density.
143    pub fn check_exfiltration(output: &str, threshold: f64) -> bool {
144        Self::calculate_shannon_entropy(output) > threshold
145    }
146}
147
148/// L1 Deterministic Security: Stateful Entropy Throttling
149/// Detects "low-and-slow" exfiltration by tracking rolling averages.
150#[derive(Debug)]
151pub struct ThrottleGovernor {
152    agent_states: HashMap<Uuid, AgentThrottleState>,
153}
154
155#[derive(Debug)]
156struct AgentThrottleState {
157    entropy_history: Vec<f64>,
158}
159
160impl AgentThrottleState {
161    fn new() -> Self {
162        Self {
163            entropy_history: Vec::with_capacity(5),
164        }
165    }
166
167    fn push_entropy(&mut self, entropy: f64) {
168        if self.entropy_history.len() >= 5 {
169            self.entropy_history.remove(0);
170        }
171        self.entropy_history.push(entropy);
172    }
173
174    fn average_entropy(&self) -> f64 {
175        if self.entropy_history.is_empty() {
176            return 0.0;
177        }
178        let sum: f64 = self.entropy_history.iter().sum();
179        sum / self.entropy_history.len() as f64
180    }
181}
182
183impl ThrottleGovernor {
184    pub fn new() -> Self {
185        Self {
186            agent_states: HashMap::new(),
187        }
188    }
189}
190
191impl Default for ThrottleGovernor {
192    fn default() -> Self {
193        Self::new()
194    }
195}
196
197impl ThrottleGovernor {
198    pub fn check_throttle(
199        &mut self,
200        agent_id: Uuid,
201        current_entropy: f64,
202        profile: SecurityProfile,
203    ) -> Result<f64, String> {
204        let state = self
205            .agent_states
206            .entry(agent_id)
207            .or_insert_with(AgentThrottleState::new);
208        state.push_entropy(current_entropy);
209
210        let avg = state.average_entropy();
211        let threshold = if profile == SecurityProfile::Fortress {
212            5.5
213        } else {
214            6.8
215        };
216
217        if state.entropy_history.len() >= 3 && avg > threshold {
218            return Err(format!(
219                "CUMULATIVE_ENTROPY_VIOLATION: AVG_{:.2} > LIMIT_{:.1}",
220                avg, threshold
221            ));
222        }
223
224        Ok(avg)
225    }
226}
227
228impl TitanGate {
229    pub fn new(
230        inner: Arc<dyn Gate>,
231        llm: Arc<dyn LlmProvider>,
232        chora: Arc<dyn AuthorityClient>,
233        identity: AgentIdentity,
234        profile: SecurityProfile,
235    ) -> Self {
236        #[allow(clippy::arc_with_non_send_sync)]
237        Self {
238            inner,
239            llm,
240            chora,
241            identity,
242            profile,
243            throttler: Arc::new(Mutex::new(ThrottleGovernor::new())),
244        }
245    }
246
247    /// L2: Real Magpie Intent Verification
248    /// This calls the real magpie compiler to verify the formal safety of the intent.
249    async fn verify_formal_intent(
250        &self,
251        suggested_output: &str,
252        _role: &str,
253        digest: &str,
254    ) -> Result<String, String> {
255        use tokio::fs;
256        use tokio::process::Command;
257        use tokio::time::{timeout, Duration};
258
259        // 0. Use proper AST Builder instead of fragile string formatting
260        let mut builder = MagpieAstBuilder::new(self.profile, digest.to_string());
261
262        // 1. Sanitize and add instructions programmatically
263        builder.add_intent(suggested_output)?;
264
265        // 2. Generate secure source module
266        let mp_source = builder.build();
267
268        let tmp_filename = format!("gate_intent_{}.mp", &Uuid::new_v4().to_string()[..8]);
269        let mut tmp_path = std::env::temp_dir();
270        let magpie_path = crate::utils::find_magpie_binary();
271
272        // Cross-Runtime Path Synthesis (Willis interop pattern)
273        if cfg!(target_os = "linux") && magpie_path.ends_with(".exe") {
274            // WSL Interop Hardening: If calling a Windows EXE, prefer host-side mount for temp storage
275            // to avoid fragile UNC path dependencies and WSL_DISTRO_NAME resolving.
276            if let Ok(cwd) = std::env::current_dir() {
277                if cwd.to_string_lossy().starts_with("/mnt/") {
278                    tmp_path = cwd;
279                }
280            }
281        }
282
283        tmp_path.push(&tmp_filename);
284
285        // Atomic Cleanup Guard
286        let _guard = TempFileGuard {
287            path: tmp_path.clone(),
288        };
289
290        fs::write(&tmp_path, &mp_source)
291            .await
292            .map_err(|e| format!("IO_ERROR: Failed to write intent file: {}", e))?;
293
294        let mut arg_path = tmp_path.to_string_lossy().to_string();
295
296        // Final path conversion for Windows execution
297        if cfg!(target_os = "linux") && magpie_path.ends_with(".exe") {
298            if arg_path.starts_with("/mnt/") {
299                // Map /mnt/c/ -> C:\
300                let drive_letter = arg_path.chars().nth(5).unwrap_or('c').to_uppercase();
301                arg_path = format!("{}:\\{}", drive_letter, arg_path[7..].replace('/', "\\"));
302            } else if arg_path.starts_with('/') {
303                let distro =
304                    std::env::var("WSL_DISTRO_NAME").unwrap_or_else(|_| "Ubuntu".to_string());
305                arg_path = format!(
306                    "\\\\wsl.localhost\\{}{}",
307                    distro,
308                    arg_path.replace('/', "\\")
309                );
310            }
311        }
312
313        let mut cmd = Command::new(&magpie_path);
314        cmd.arg("--output")
315            .arg("json")
316            .arg("--entry")
317            .arg(&arg_path)
318            .arg("parse");
319
320        // Limit compiler execution to 5000ms to prevent DOS/Hangs (increased for WSL interop overhead)
321        let output = timeout(Duration::from_millis(5000), cmd.output())
322            .await
323            .map_err(|_| "MAGPIE_TIMEOUT: Formal verification exceeded 5000ms limit")?
324            .map_err(|e| format!("MAGPIE_SPAWN_ERROR: {}", e))?;
325
326        if output.status.success() {
327            Ok(mp_source)
328        } else {
329            let stdout = String::from_utf8_lossy(&output.stdout);
330            let stderr = String::from_utf8_lossy(&output.stderr);
331
332            // Try to parse JSON diagnostics for better feedback
333            let diagnostic_msg =
334                if let Ok(json) = serde_json::from_str::<serde_json::Value>(&stdout) {
335                    if let Some(diags) = json.get("diagnostics").and_then(|d| d.as_array()) {
336                        let mut collected = Vec::new();
337                        for d in diags {
338                            if let Some(msg) = d.get("message").and_then(|m| m.as_str()) {
339                                collected.push(msg.to_string());
340                            }
341                        }
342                        if !collected.is_empty() {
343                            collected.join(" | ")
344                        } else {
345                            stderr.to_string()
346                        }
347                    } else {
348                        stderr.to_string()
349                    }
350                } else {
351                    stderr.to_string()
352                };
353
354            Err(format!("MAGPIE_FORMAL_ERROR: {}", diagnostic_msg))
355        }
356    }
357
358    // (Sanitization logic moved to MagpieAstBuilder::add_intent)
359}
360
361/// Programmatic AST Builder for Magpie IR modules
362/// Fully replaces string formatting to prevent code injection vulnerabilities
363struct MagpieAstBuilder {
364    module_name: String,
365    profile: SecurityProfile,
366    digest: String,
367    body_instructions: Vec<String>,
368}
369
370impl MagpieAstBuilder {
371    fn new(profile: SecurityProfile, digest: String) -> Self {
372        Self {
373            module_name: match profile {
374                SecurityProfile::Standard => "standard".to_string(), // Aligned with profiles/standard_agent.mp
375                SecurityProfile::Fortress => "fortress.verify".to_string(),
376            },
377            profile,
378            digest,
379            body_instructions: Vec::new(),
380        }
381    }
382
383    fn add_intent(&mut self, input: &str) -> Result<(), String> {
384        // Strict blocking: Disallow closing braces which could be used for code injection
385        if input.contains('}') {
386            return Err("INJECTION_ATTACK: Input contains forbidden closing brace '}'".to_string());
387        }
388
389        // Structural keyword blocking (word-boundary aware)
390        let forbidden_keywords = ["module", "fn", "exports", "imports", "digest"];
391        let scan_input = input.to_lowercase();
392        for &keyword in &forbidden_keywords {
393            let escaped = regex::escape(keyword);
394            let pattern = format!(r"(?i)\b{}\b", escaped);
395            let re = Regex::new(&pattern).map_err(|e| {
396                format!(
397                    "INTERNAL_ERROR: Failed to compile keyword regex '{}': {}",
398                    keyword, e
399                )
400            })?;
401            if re.is_match(&scan_input) {
402                return Err(format!(
403                    "INJECTION_ATTACK: Input contains forbidden keyword '{}'",
404                    keyword
405                ));
406            }
407        }
408
409        // Parse instructions line-by-line to ensure they fit exactly within the basic block
410        for line in input.lines() {
411            if !line.trim().is_empty() {
412                self.body_instructions.push(line.trim().to_string());
413            }
414        }
415
416        Ok(())
417    }
418
419    fn build(&self) -> String {
420        let mut out = String::new();
421        out.push_str(&format!("module {}\n", self.module_name));
422        out.push_str("exports { @intent }\n");
423        out.push_str("imports { }\n");
424        out.push_str(&format!("digest \"{}\"\n\n", self.digest));
425
426        if self.profile == SecurityProfile::Fortress {
427            out.push_str(";; THE ONLY PERMITTED SIDE-EFFECT\n");
428        }
429        out.push_str(
430            "fn @log_safe(%msg: Str) -> unit meta { } {\n  bb0:\n    ret const.unit unit\n}\n\n",
431        );
432
433        out.push_str("fn @intent() -> i32 meta { } {\n  bb0:\n");
434        if self.profile == SecurityProfile::Fortress {
435            out.push_str("    ;; Default Deny: If the agent tries to call something unknown, it won't even parse/link.\n");
436        }
437
438        for inst in &self.body_instructions {
439            out.push_str(&format!("    {}\n", inst));
440        }
441
442        // Only add trailing ret if the last instruction wasn't already a terminator
443        let has_ret = self
444            .body_instructions
445            .iter()
446            .any(|i| i.trim_start().starts_with("ret "));
447        if !has_ret {
448            out.push_str("    ret const.i32 0\n");
449        }
450        out.push_str("}\n");
451        out
452    }
453}
454
455#[async_trait]
456impl Gate for TitanGate {
457    async fn execute_gate(
458        &self,
459        agent_id: Uuid,
460        task_prompt: &str,
461        suggested_output: &str,
462        intent_data: Option<vex_core::segment::IntentData>,
463        confidence: f64,
464        capabilities: &[Capability],
465        nonce: Uuid,
466    ) -> EvidenceCapsule {
467        // --- PRE-LAYER: Continuation Validation (Willis Theorem 1 & 4) ---
468        // Verify context binding and signature before any transition logic fires.
469        if let Some(ref data) = intent_data {
470            if let Some(token) = data.continuation_token() {
471                // Calculate current intent hash for binding (excluding the token itself)
472                // Note: We hash the data structure to ensure semantic parity.
473                let intent_jcs = data
474                    .to_jcs_hash()
475                    .unwrap_or_else(|_| vex_core::Hash::from_bytes([0; 32]));
476                let local_intent_hash = intent_jcs.to_hex();
477
478                let nonce_str = nonce.to_string();
479                let expected_nonce = data
480                    .metadata()
481                    .get("original_nonce")
482                    .and_then(|v: &serde_json::Value| v.as_str())
483                    .or(Some(&nonce_str));
484
485                let parent_root = data
486                    .metadata()
487                    .get("source_capsule_root")
488                    .and_then(|v: &serde_json::Value| v.as_str());
489
490                let aid = self.identity.public_key_hex();
491                match self
492                    .chora
493                    .verify_continuation_token(
494                        token,
495                        Some(&aid),
496                        Some(&local_intent_hash),
497                        data.circuit_id().as_deref(),
498                        expected_nonce,
499                        parent_root,
500                    )
501                    .await
502                {
503                    Ok(true) => {
504                        // Hardened AEM Check (Theorem 4): Bind to local identity
505                        if token.payload.execution_target.aid != aid {
506                            return EvidenceCapsule {
507                                capsule_id: format!("halt-binding-{}", &nonce.to_string()[..8]),
508                                outcome: "HALT".into(),
509                                reason_code:
510                                    "INVALID_CONTINUATION_TOKEN: EXECUTION_TARGET_MISMATCH".into(),
511                                witness_receipt: "target-mismatch".into(),
512                                nonce: "0".to_string(),
513                                magpie_source: None,
514                                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
515                                    "error": "Token is bound to another agent",
516                                    "token_target": token.payload.execution_target.aid,
517                                    "local_aid": aid
518                                })),
519                                reproducibility_context: vex_core::segment::SchemaValue(
520                                    serde_json::json!({"gate": "TitanGate/AEM"}),
521                                ),
522                                resolution_vep_hash: None,
523                                escalation_id: None,
524                                continuation_token: None,
525                                intent_data: intent_data.clone(),
526                                vep_blob: None,
527                            };
528                        }
529
530                        // Verify intent binding strictly
531                        if token.payload.execution_target.intent_hash != local_intent_hash {
532                            return EvidenceCapsule {
533                                capsule_id: format!("halt-intent-{}", &nonce.to_string()[..8]),
534                                outcome: "HALT".into(),
535                                reason_code: "INVALID_CONTINUATION_TOKEN: INTENT_BINDING_FAILURE"
536                                    .into(),
537                                witness_receipt: "intent-mismatch".into(),
538                                nonce: "0".to_string(),
539                                magpie_source: None,
540                                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
541                                    "error": "Token is bound to a different intent",
542                                    "token_intent": token.payload.execution_target.intent_hash,
543                                    "local_intent": local_intent_hash
544                                })),
545                                reproducibility_context: vex_core::segment::SchemaValue(
546                                    serde_json::json!({"gate": "TitanGate/AEM"}),
547                                ),
548                                resolution_vep_hash: None,
549                                escalation_id: None,
550                                continuation_token: None,
551                                intent_data: intent_data.clone(),
552                                vep_blob: None,
553                            };
554                        }
555
556                        tracing::info!("AEM: Continuation Token v3.1 verified for context binding (Silicon Seal).");
557                    }
558                    Ok(false) | Err(_) => {
559                        return EvidenceCapsule {
560                            capsule_id: format!("halt-token-err-{}", &nonce.to_string()[..8]),
561                            outcome: "HALT".into(),
562                            reason_code: "INVALID_CONTINUATION_TOKEN: SIGNATURE_OR_LIFECYCLE_FAIL"
563                                .into(),
564                            witness_receipt: "token-reject".into(),
565                            nonce: "0".to_string(),
566                            magpie_source: None,
567                            gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
568                                "error": "Signature check or lifecycle validation failed at the boundary"
569                            })),
570                            reproducibility_context: vex_core::segment::SchemaValue(
571                                serde_json::json!({"gate": "TitanGate/AEM"}),
572                            ),
573                            resolution_vep_hash: None,
574                            escalation_id: None,
575                            continuation_token: None,
576                            intent_data: intent_data.clone(),
577                            vep_blob: None,
578                        };
579                    }
580                }
581            }
582        }
583
584        // --- Layer 1: Deterministic (McpVanguard) ---
585        // 1.1 Entropy-Based Exfiltration Detection (ISO 42001/VEX Requirement)
586        let entropy = EntropyGovernor::calculate_shannon_entropy(suggested_output);
587        let entropy_threshold = if self.profile == SecurityProfile::Fortress {
588            6.0
589        } else {
590            7.5
591        };
592
593        if entropy > entropy_threshold {
594            return EvidenceCapsule {
595                capsule_id: format!("l1-block-{}", &nonce.to_string()[..8]),
596                outcome: "HALT".into(),
597                reason_code: "L1_ENTROPY_VIOLATION: HIGH_EXFILTRATION_RISK".into(),
598                witness_receipt: "entropy-block".into(),
599                nonce: "0".to_string(),
600                magpie_source: None,
601                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
602                    "layer": "L1",
603                    "entropy": entropy,
604                    "threshold": entropy_threshold,
605                    "trigger": "BEH-007"
606                })),
607                reproducibility_context: vex_core::segment::SchemaValue(
608                    serde_json::json!({"gate": "TitanGate/L1"}),
609                ),
610                resolution_vep_hash: None,
611                escalation_id: None,
612                continuation_token: None,
613                intent_data: intent_data.clone(),
614                vep_blob: None,
615            };
616        }
617
618        // 1.1.b Stateful Throttling (Cumulative Entropy)
619        {
620            let mut throttler = self.throttler.lock().await;
621            if let Err(e) = throttler.check_throttle(agent_id, entropy, self.profile) {
622                return EvidenceCapsule {
623                    capsule_id: format!("l1-block-{}", &Uuid::new_v4().to_string()[..8]),
624                    outcome: "HALT".into(),
625                    reason_code: format!("L1_THROTTLE_VIOLATION: {}", e),
626                    witness_receipt: "stateful-throttle-block".into(),
627                    nonce: "0".to_string(),
628                    magpie_source: None,
629                    gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
630                        "layer": "L1",
631                        "cumulative_check": "FAILED",
632                        "error": e
633                    })),
634                    reproducibility_context: vex_core::segment::SchemaValue(
635                        serde_json::json!({"gate": "TitanGate/L1"}),
636                    ),
637                    resolution_vep_hash: None,
638                    escalation_id: None,
639                    continuation_token: None,
640                    intent_data: intent_data.clone(),
641                    vep_blob: None,
642                };
643            }
644        }
645
646        // 1.2 Deterministic Path Validation (VEX L1 Perimeter)
647        // If the output looks like a path, resolve it deterministically
648        if (suggested_output.contains('/') || suggested_output.contains('\\'))
649            && suggested_output.len() < 256
650        {
651            let path = Path::new(suggested_output);
652            if path.exists() {
653                match SecurePathResolver::resolve_deterministic(path) {
654                    Ok(resolved) => {
655                        let resolved_str = resolved.to_string_lossy().to_lowercase();
656                        if resolved_str.contains("etc") || resolved_str.contains("system32") {
657                            return EvidenceCapsule {
658                                capsule_id: format!(
659                                    "l1-block-{}",
660                                    &Uuid::new_v4().to_string()[..8]
661                                ),
662                                outcome: "HALT".into(),
663                                reason_code: "L1_PATH_VIOLATION: SENSITIVE_SYSTEM_PATH".into(),
664                                witness_receipt: "path-resolve-block".into(),
665                                nonce: "0".to_string(),
666                                magpie_source: None,
667                                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
668                                    "layer": "L1",
669                                    "attempted_path": suggested_output,
670                                    "resolved_physical_path": resolved_str
671                                })),
672                                reproducibility_context: vex_core::segment::SchemaValue(
673                                    serde_json::json!({"gate": "TitanGate/L1"}),
674                                ),
675                                resolution_vep_hash: None,
676                                escalation_id: None,
677                                continuation_token: None,
678                                intent_data: intent_data.clone(),
679                                vep_blob: None,
680                            };
681                        }
682                    }
683                    Err(e) => {
684                        // Fail-Closed: If the path exists but we can't resolve it (e.g. permission denied)
685                        // we must assume it might be sensitive and HALT.
686                        return EvidenceCapsule {
687                            capsule_id: format!("l1-block-{}", &Uuid::new_v4().to_string()[..8]),
688                            outcome: "HALT".into(),
689                            reason_code: format!("L1_PATH_RESOLUTION_ERROR: {}", e),
690                            witness_receipt: "path-resolve-failed".into(),
691                            nonce: "0".to_string(),
692                            magpie_source: None,
693                            gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
694                                "layer": "L1",
695                                "attempted_path": suggested_output,
696                                "error": e
697                            })),
698                            reproducibility_context: vex_core::segment::SchemaValue(
699                                serde_json::json!({"gate": "TitanGate/L1"}),
700                            ),
701                            resolution_vep_hash: None,
702                            escalation_id: None,
703                            continuation_token: None,
704                            intent_data: intent_data.clone(),
705                            vep_blob: None,
706                        };
707                    }
708                }
709            }
710        }
711
712        // 1.3 Legacy Regex fallback for non-path strings
713        for rule in L1_RULES.iter() {
714            if rule.is_match(suggested_output) {
715                return EvidenceCapsule {
716                    capsule_id: format!("l1-block-{}", &nonce.to_string()[..8]),
717                    outcome: "HALT".into(),
718                    reason_code: format!("L1_RULE_VIOLATION: {:?}", rule),
719                    witness_receipt: "deterministic-none".into(),
720                    nonce: "0".to_string(),
721                    magpie_source: None,
722                    gate_sensors: vex_core::segment::SchemaValue(
723                        serde_json::json!({"layer": "L1", "rule": format!("{:?}", rule)}),
724                    ),
725                    reproducibility_context: vex_core::segment::SchemaValue(
726                        serde_json::json!({"gate": "TitanGate/L1"}),
727                    ),
728                    resolution_vep_hash: None,
729                    escalation_id: None,
730                    continuation_token: None,
731                    intent_data: intent_data.clone(),
732                    vep_blob: None,
733                };
734            }
735        }
736
737        let capsule_id = format!("gate-{}", &Uuid::new_v4().to_string()[..8]);
738
739        // --- Layer 2: Formal Intent (Magpie) ---
740        // Cryptographic Link: Use SHA-256 to bind the Intent to the Hardware Capsule Identity
741        use sha2::{Digest, Sha256};
742        let mut hasher = Sha256::new();
743        hasher.update(capsule_id.as_bytes());
744        let digest_hex = format!("{:x}", hasher.finalize());
745
746        match self
747            .verify_formal_intent(suggested_output, "StandardAgent", &digest_hex)
748            .await
749        {
750            Ok(mp_source) => {
751                // --- Layer 3: Cryptographic (VEX/CHORA) ---
752                // Transition to Spec v0.1: Handshake with CHORA Witness Network
753                let intent_payload = suggested_output.as_bytes();
754
755                match self
756                    .chora
757                    .request_attestation(intent_payload, &nonce.to_string())
758                    .await
759                {
760                    Ok(chora_resp) => {
761                        // --- AEM ESCALATION TRAP ---
762                        // Willis Theorem 1: If authority reports ambiguity, VEX must fail-closed
763                        // until an explicit ContinuationToken is provided side-channel (EMS).
764                        if chora_resp.authority_class.as_deref()
765                            == Some(vex_core::segment::AuthorityData::CLASS_AMBIGUITY)
766                        {
767                            return EvidenceCapsule {
768                                capsule_id: format!(
769                                    "escalate-{}",
770                                    &chora_resp.authority.capsule_id[..8]
771                                ),
772                                outcome: "ESCALATE".into(),
773                                reason_code: "AMBIGUITY_BAND_DETECTION".into(),
774                                witness_receipt: chora_resp.signature.clone(),
775                                nonce: chora_resp.authority.nonce.clone(),
776                                magpie_source: Some(mp_source.clone()),
777                                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
778                                    "authority_class": "ESCALATABLE_AMBIGUITY",
779                                    "requires_ems": true,
780                                    "trace_root": chora_resp.authority.trace_root
781                                })),
782                                reproducibility_context: vex_core::segment::SchemaValue(
783                                    serde_json::json!({"gate": "TitanGate/AEM"}),
784                                ),
785                                resolution_vep_hash: None,
786                                escalation_id: chora_resp.authority.escalation_id.clone(),
787                                continuation_token: None,
788                                intent_data: intent_data.clone(),
789                                vep_blob: None,
790                            };
791                        }
792                        // Assemble the finalized VEP (Verifiable Evidence Packet)
793                        let intent = IntentSegment {
794                            request_sha256: digest_hex.clone(),
795                            confidence,
796                            capabilities: capabilities.iter().map(|c| format!("{:?}", c)).collect(),
797                            magpie_source: Some(mp_source.clone()),
798                            circuit_id: None,
799                            intent_data: intent_data.clone(),
800                            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
801                        };
802
803                        let authority = AuthoritySegment {
804                            capsule_id: chora_resp.authority.capsule_id.clone(),
805                            outcome: chora_resp.authority.outcome.clone(),
806                            reason_code: chora_resp.authority.reason_code.clone(),
807                            trace_root: chora_resp.authority.trace_root.clone(),
808                            nonce: chora_resp.authority.nonce.clone(),
809                            gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
810                                "profile": format!("{:?}", self.profile),
811                                "l2_digest": digest_hex.clone()
812                            })),
813                            escalation_id: chora_resp.authority.escalation_id.clone(),
814                            binding_status: chora_resp.authority.binding_status.clone(),
815                            continuation_token: chora_resp.authority.continuation_token.clone(),
816                            authority_class: chora_resp.authority_class.clone(),
817                            metadata: chora_resp.authority.metadata,
818                        };
819
820                        let pcrs = self.identity.get_pcrs(&[0, 7, 11]).await.ok();
821                        let identity = IdentitySegment {
822                            aid: self.identity.public_key_hex(),
823                            identity_type: "TPM_ECC_PERSISTENT".to_string(), // Spec alignment
824                            pcrs,
825                            metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
826                        };
827
828                        let request_commitment = Some(RequestCommitment {
829                            canonicalization: "JCS-RFC8785".to_string(),
830                            payload_sha256: digest_hex.clone(),
831                            payload_encoding: "application/json".to_string(),
832                        });
833
834                        let witness = WitnessSegment {
835                            chora_node_id: "chora-primary-v1".to_string(),
836                            receipt_hash: chora_resp.signature.clone(),
837                            timestamp: chrono::Utc::now().timestamp() as u64,
838                            metadata: vex_core::segment::SchemaValue(serde_json::json!({})),
839                        };
840
841                        let mut v0_capsule = match EvidenceCapsuleV0::new(
842                            intent,
843                            authority,
844                            identity,
845                            witness,
846                            request_commitment,
847                        ) {
848                            Ok(c) => c,
849                            Err(e) => {
850                                return EvidenceCapsule {
851                                    capsule_id: format!("vep-err-{}", &capsule_id),
852                                    outcome: "HALT".into(),
853                                    reason_code: format!("VEP_GENERATION_ERROR: {}", e),
854                                    witness_receipt: "none".into(),
855                                    nonce: "0".to_string(),
856                                    magpie_source: None,
857                                    gate_sensors: vex_core::segment::SchemaValue(
858                                        serde_json::json!({"layer": "L3", "error": format!("{}", e)}),
859                                    ),
860                                    reproducibility_context: vex_core::segment::SchemaValue(
861                                        serde_json::json!({"gate": "TitanGate/L3"}),
862                                    ),
863                                    resolution_vep_hash: None,
864                                    escalation_id: None,
865                                    continuation_token: None,
866                                    intent_data: None,
867                                    vep_blob: None,
868                                };
869                            }
870                        };
871
872                        // Hardware Seal: Sign the root commitment with the TPM identity
873                        let root_bytes = match hex::decode(&v0_capsule.capsule_root) {
874                            Ok(b) => b,
875                            Err(e) => {
876                                return EvidenceCapsule {
877                                    capsule_id: format!("root-err-{}", &capsule_id),
878                                    outcome: "HALT".into(),
879                                    reason_code: format!("ROOT_DECODE_ERROR: {}", e),
880                                    witness_receipt: "none".into(),
881                                    nonce: "0".to_string(),
882                                    magpie_source: None,
883                                    gate_sensors: vex_core::segment::SchemaValue(
884                                        serde_json::json!({"layer": "L3", "error": format!("{}", e)}),
885                                    ),
886                                    reproducibility_context: vex_core::segment::SchemaValue(
887                                        serde_json::json!({"gate": "TitanGate/L3"}),
888                                    ),
889                                    resolution_vep_hash: None,
890                                    escalation_id: None,
891                                    continuation_token: None,
892                                    intent_data: None,
893                                    vep_blob: None,
894                                };
895                            }
896                        };
897                        let hardware_sig = self.identity.sign(&root_bytes);
898                        v0_capsule.set_signature(&hardware_sig);
899
900                        // Save the VEP binary to disk for offline verification
901                        if let Ok(binary) = v0_capsule.to_vep_binary() {
902                            let vep_path = std::env::temp_dir()
903                                .join(format!("vep_{}.capsule", v0_capsule.capsule_id));
904                            let _ = std::fs::write(vep_path, binary);
905                        }
906
907                        // Final check: Only ALLOW outcomes proceed to execution
908                        if v0_capsule.authority.outcome == "ALLOW" {
909                            let mut final_result = self
910                                .inner
911                                .execute_gate(
912                                    agent_id,
913                                    task_prompt,
914                                    suggested_output,
915                                    intent_data.clone(),
916                                    confidence,
917                                    capabilities,
918                                    nonce,
919                                )
920                                .await;
921
922                            // Inject the binary VEP blob if not already present
923                            if final_result.vep_blob.is_none() {
924                                final_result.vep_blob = v0_capsule.to_vep_binary().ok();
925                            }
926                            final_result
927                        } else {
928                            EvidenceCapsule {
929                                capsule_id: v0_capsule.capsule_id.clone(),
930                                outcome: v0_capsule.authority.outcome.clone(),
931                                reason_code: v0_capsule.authority.reason_code.clone(),
932                                witness_receipt: v0_capsule.witness_hash.clone(),
933                                nonce: v0_capsule.authority.nonce.to_string(),
934                                magpie_source: None,
935                                gate_sensors: vex_core::segment::SchemaValue(
936                                    serde_json::json!({"layer": "L3", "chora_sig": chora_resp.signature}),
937                                ),
938                                reproducibility_context: vex_core::segment::SchemaValue(
939                                    serde_json::json!({"gate": "TitanGate/L3"}),
940                                ),
941                                resolution_vep_hash: None,
942                                escalation_id: chora_resp.authority.escalation_id.clone(),
943                                continuation_token: chora_resp.authority.continuation_token.clone(),
944                                intent_data: None,
945                                vep_blob: v0_capsule.to_vep_binary().ok(),
946                            }
947                        }
948                    }
949                    Err(e) => EvidenceCapsule {
950                        capsule_id: format!("l3-err-{}", &capsule_id),
951                        outcome: "HALT".into(),
952                        reason_code: format!("CHORA_CONNECTION_ERROR: {}", e),
953                        witness_receipt: "none".into(),
954                        nonce: "0".to_string(),
955                        magpie_source: None,
956                        gate_sensors: vex_core::segment::SchemaValue(
957                            serde_json::json!({"layer": "L3", "error": e}),
958                        ),
959                        reproducibility_context: vex_core::segment::SchemaValue(
960                            serde_json::json!({"gate": "TitanGate/L3"}),
961                        ),
962                        resolution_vep_hash: None,
963                        escalation_id: None,
964                        continuation_token: None,
965                        intent_data: None,
966                        vep_blob: None,
967                    },
968                }
969            }
970            Err(e) => EvidenceCapsule {
971                capsule_id: format!("l2-block-{}", &capsule_id),
972                outcome: "HALT".into(),
973                reason_code: format!("L2_FORMAL_VIOLATION: {}", e),
974                witness_receipt: "semantic-none".into(),
975                nonce: "0".to_string(),
976                magpie_source: None,
977                gate_sensors: vex_core::segment::SchemaValue(
978                    serde_json::json!({"layer": "L2", "error": e, "digest": digest_hex}),
979                ),
980                reproducibility_context: vex_core::segment::SchemaValue(
981                    serde_json::json!({"gate": "TitanGate/L2"}),
982                ),
983                resolution_vep_hash: None,
984                escalation_id: None,
985                continuation_token: None,
986                intent_data: None,
987                vep_blob: None,
988            },
989        }
990    }
991
992    async fn verify_token(
993        &self,
994        token: &vex_core::ContinuationToken,
995        expected_aid: Option<&str>,
996        expected_intent_hash: Option<&str>,
997        expected_circuit_id: Option<&str>,
998    ) -> Result<bool, String> {
999        self.inner
1000            .verify_token(
1001                token,
1002                expected_aid,
1003                expected_intent_hash,
1004                expected_circuit_id,
1005            )
1006            .await
1007    }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012    use super::*;
1013    use vex_llm::LlmRequest;
1014
1015    #[derive(Debug)]
1016    struct MockInnerGate;
1017    #[async_trait]
1018    impl Gate for MockInnerGate {
1019        async fn execute_gate(
1020            &self,
1021            _id: Uuid,
1022            _p: &str,
1023            _o: &str,
1024            _intent: Option<vex_core::segment::IntentData>,
1025            _c: f64,
1026            _cap: &[Capability],
1027            _n: Uuid,
1028        ) -> EvidenceCapsule {
1029            EvidenceCapsule {
1030                capsule_id: "inner".into(),
1031                outcome: "ALLOW".into(),
1032                reason_code: "OK".into(),
1033                witness_receipt: "root".into(),
1034                nonce: "0".to_string(),
1035                magpie_source: None,
1036                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({})),
1037                reproducibility_context: vex_core::segment::SchemaValue(serde_json::json!({})),
1038                resolution_vep_hash: None,
1039                escalation_id: None,
1040                continuation_token: None,
1041                intent_data: None,
1042                vep_blob: None,
1043            }
1044        }
1045
1046        async fn verify_token(
1047            &self,
1048            _token: &vex_core::ContinuationToken,
1049            _expected_aid: Option<&str>,
1050            _expected_intent_hash: Option<&str>,
1051            _expected_circuit_id: Option<&str>,
1052        ) -> Result<bool, String> {
1053            Ok(true)
1054        }
1055    }
1056
1057    #[derive(Debug)]
1058    struct MockLlm;
1059    #[async_trait]
1060    impl LlmProvider for MockLlm {
1061        fn name(&self) -> &str {
1062            "mock"
1063        }
1064        async fn is_available(&self) -> bool {
1065            true
1066        }
1067        async fn complete(
1068            &self,
1069            _req: LlmRequest,
1070        ) -> std::result::Result<vex_llm::LlmResponse, vex_llm::LlmError> {
1071            Ok(vex_llm::LlmResponse {
1072                content: "mock".into(),
1073                model: "mock".into(),
1074                tokens_used: None,
1075                latency_ms: 0,
1076                trace_root: None,
1077            })
1078        }
1079    }
1080
1081    #[derive(Debug)]
1082    struct MockChora;
1083    #[async_trait]
1084    impl vex_chora::client::AuthorityClient for MockChora {
1085        async fn request_attestation(
1086            &self,
1087            _p: &[u8],
1088            _nonce: &str,
1089        ) -> std::result::Result<vex_chora::client::ChoraResponse, String> {
1090            Ok(vex_chora::client::ChoraResponse {
1091                authority: vex_core::segment::AuthorityData {
1092                    capsule_id: "chora".into(),
1093                    outcome: "ALLOW".into(),
1094                    reason_code: "OK".into(),
1095                    trace_root: "00".repeat(32),
1096                    nonce: "42".into(),
1097                    gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({})),
1098                    metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
1099                    escalation_id: None,
1100                    continuation_token: None,
1101                    binding_status: None,
1102                    authority_class: Some("ALLOW_PATH".to_string()),
1103                },
1104                signature: "sig".into(),
1105                intent_hash: None,
1106                authority_class: Some("ALLOW_PATH".to_string()),
1107            })
1108        }
1109        async fn verify_witness_signature(
1110            &self,
1111            _p: &[u8],
1112            _s: &[u8],
1113        ) -> std::result::Result<bool, String> {
1114            Ok(true)
1115        }
1116        async fn verify_continuation_token(
1117            &self,
1118            _token: &vex_core::segment::ContinuationToken,
1119            _expected_aid: Option<&str>,
1120            _expected_intent_hash: Option<&str>,
1121            _expected_circuit_id: Option<&str>,
1122            _expected_nonce: Option<&str>,
1123            _expected_source_capsule_root: Option<&str>,
1124        ) -> std::result::Result<bool, String> {
1125            Ok(true)
1126        }
1127        async fn request_escalation(
1128            &self,
1129            _id: &str,
1130            _context: &serde_json::Value,
1131        ) -> std::result::Result<vex_chora::client::ChoraResponse, String> {
1132            self.request_attestation(b"escalated", "mock-nonce").await
1133        }
1134    }
1135
1136    #[tokio::test]
1137    async fn test_titan_gate_pcr_binding() {
1138        // Fully enabled L2/L3 verification for "Bulletproof" parity
1139        std::env::set_var("VEX_HARDWARE_ATTESTATION", "false");
1140
1141        let identity = AgentIdentity::new();
1142        let gate = TitanGate::new(
1143            Arc::new(MockInnerGate),
1144            Arc::new(MockLlm),
1145            Arc::new(MockChora),
1146            identity.clone(),
1147            SecurityProfile::Standard,
1148        );
1149
1150        let capsule = gate
1151            .execute_gate(
1152                Uuid::new_v4(),
1153                "prompt",
1154                "ret const.i32 0",
1155                None,
1156                1.0,
1157                &[],
1158                Uuid::new_v4(),
1159            )
1160            .await;
1161
1162        if capsule.outcome != "ALLOW" {
1163            println!("GATE_HALT: {} - {}", capsule.outcome, capsule.reason_code);
1164        }
1165        assert_eq!(capsule.outcome, "ALLOW");
1166
1167        let blob = capsule.vep_blob.expect("Missing VEP blob");
1168        let packet = vex_core::vep::VepPacket::new(&blob).unwrap();
1169        let core_capsule = packet.to_capsule().unwrap();
1170
1171        assert_eq!(core_capsule.identity.aid, identity.public_key_hex());
1172        if let Some(pcrs) = core_capsule.identity.pcrs {
1173            for (idx, hash) in pcrs {
1174                println!("✅ HARDWARE PCR BINDING VERIFIED: PCR {} = {}", idx, hash);
1175            }
1176        }
1177    }
1178
1179    #[tokio::test]
1180    async fn test_titan_entropy_halt() {
1181        let identity = AgentIdentity::new();
1182        let gate = TitanGate::new(
1183            Arc::new(MockInnerGate),
1184            Arc::new(MockLlm),
1185            Arc::new(MockChora),
1186            identity.clone(),
1187            SecurityProfile::Fortress,
1188        );
1189
1190        // High entropy string (simulating encrypted/compressed data)
1191        let high_entropy = (0..=255u8).map(|b| b as char).collect::<String>();
1192
1193        let capsule = gate
1194            .execute_gate(
1195                Uuid::new_v4(),
1196                "prompt",
1197                &high_entropy,
1198                None,
1199                1.0,
1200                &[],
1201                Uuid::new_v4(),
1202            )
1203            .await;
1204
1205        assert_eq!(capsule.outcome, "HALT");
1206        assert!(capsule.reason_code.contains("ENTROPY_VIOLATION"));
1207        println!(
1208            "✅ ENTROPY EXFILTRATION BLOCK VERIFIED: {}",
1209            capsule.reason_code
1210        );
1211    }
1212
1213    #[tokio::test]
1214    async fn test_titan_path_resolution() {
1215        let identity = AgentIdentity::new();
1216        let gate = TitanGate::new(
1217            Arc::new(MockInnerGate),
1218            Arc::new(MockLlm),
1219            Arc::new(MockChora),
1220            identity.clone(),
1221            SecurityProfile::Standard,
1222        );
1223
1224        // This test requires a file to exist. We'll use a temp file.
1225        let temp = tempfile::NamedTempFile::new().unwrap();
1226        let path_str = temp.path().to_string_lossy().to_string();
1227
1228        let capsule = gate
1229            .execute_gate(
1230                Uuid::new_v4(),
1231                "prompt",
1232                &path_str,
1233                None,
1234                1.0,
1235                &[],
1236                Uuid::new_v4(),
1237            )
1238            .await;
1239
1240        // On Linux, this should pass L1 but fail L2 because it's not valid Magpie.
1241        // What matters is that it's NOT an L1_PATH_VIOLATION.
1242        assert_ne!(
1243            capsule.reason_code,
1244            "L1_PATH_VIOLATION: SENSITIVE_SYSTEM_PATH"
1245        );
1246        println!(
1247            "✅ DETERMINISTIC PATH RESOLUTION VERIFIED (L1 PASSED): {}",
1248            path_str
1249        );
1250    }
1251
1252    #[tokio::test]
1253    async fn test_titan_stateful_throttling() {
1254        let identity = AgentIdentity::new();
1255        let gate = TitanGate::new(
1256            Arc::new(MockInnerGate),
1257            Arc::new(MockLlm),
1258            Arc::new(MockChora),
1259            identity.clone(),
1260            SecurityProfile::Fortress,
1261        );
1262
1263        let agent_id = Uuid::new_v4();
1264        // Calibrated: 50 unique characters = log2(50) = 5.64 bits.
1265        // This is exactly between the 5.5 stateful average and the 6.0 single-shot limit.
1266        let med_entropy =
1267            "ret const.i32 0 ;; ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+".to_owned();
1268        let e = EntropyGovernor::calculate_shannon_entropy(&med_entropy);
1269        println!("DEBUG: med_entropy score = {:.4}", e);
1270
1271        for i in 1..=2 {
1272            let capsule = gate
1273                .execute_gate(
1274                    agent_id,
1275                    "prompt",
1276                    &med_entropy,
1277                    None,
1278                    1.0,
1279                    &[],
1280                    Uuid::new_v4(),
1281                )
1282                .await;
1283            assert_eq!(
1284                capsule.outcome, "ALLOW",
1285                "Call {} failed: {} (Entropy: {:.4})",
1286                i, capsule.reason_code, e
1287            );
1288        }
1289
1290        let capsule = gate
1291            .execute_gate(
1292                agent_id,
1293                "prompt",
1294                &med_entropy,
1295                None,
1296                1.0,
1297                &[],
1298                Uuid::new_v4(),
1299            )
1300            .await;
1301        assert_eq!(capsule.outcome, "HALT");
1302        assert!(
1303            capsule.reason_code.contains("L1_THROTTLE_VIOLATION"),
1304            "Expected throttle violation, got: {}",
1305            capsule.reason_code
1306        );
1307        println!("✅ STATEFUL THROTTLING VERIFIED: {}", capsule.reason_code);
1308    }
1309
1310    #[tokio::test]
1311    async fn test_titan_throttle_recovery() {
1312        let identity = AgentIdentity::new();
1313        let gate = TitanGate::new(
1314            Arc::new(MockInnerGate),
1315            Arc::new(MockLlm),
1316            Arc::new(MockChora),
1317            identity.clone(),
1318            SecurityProfile::Fortress,
1319        );
1320
1321        let agent_id = Uuid::new_v4();
1322        let med_entropy =
1323            "ret const.i32 0 ;; ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+".to_owned();
1324        let low_entropy = "ret const.i32 0".to_owned();
1325
1326        for _ in 0..2 {
1327            gate.execute_gate(
1328                agent_id,
1329                "prompt",
1330                &med_entropy,
1331                None,
1332                1.0,
1333                &[],
1334                Uuid::new_v4(),
1335            )
1336            .await;
1337        }
1338
1339        for i in 0..3 {
1340            let capsule = gate
1341                .execute_gate(
1342                    agent_id,
1343                    "prompt",
1344                    &low_entropy,
1345                    None,
1346                    1.0,
1347                    &[],
1348                    Uuid::new_v4(),
1349                )
1350                .await;
1351            assert_eq!(
1352                capsule.outcome, "ALLOW",
1353                "Recovery call {} failed: {}",
1354                i, capsule.reason_code
1355            );
1356        }
1357
1358        let capsule = gate
1359            .execute_gate(
1360                agent_id,
1361                "prompt",
1362                &med_entropy,
1363                None,
1364                1.0,
1365                &[],
1366                Uuid::new_v4(),
1367            )
1368            .await;
1369        assert_eq!(
1370            capsule.outcome, "ALLOW",
1371            "Call after recovery failed: {}",
1372            capsule.reason_code
1373        );
1374        println!("✅ THROTTLE RECOVERY VERIFIED");
1375    }
1376
1377    #[tokio::test]
1378    async fn test_titan_path_block() {
1379        let identity = AgentIdentity::new();
1380        let gate = TitanGate::new(
1381            Arc::new(MockInnerGate),
1382            Arc::new(MockLlm),
1383            Arc::new(MockChora),
1384            identity.clone(),
1385            SecurityProfile::Standard,
1386        );
1387
1388        let sensitive = if cfg!(windows) {
1389            "C:\\Windows\\System32\\drivers\\etc\\hosts"
1390        } else {
1391            "/etc/shadow"
1392        };
1393
1394        let capsule = gate
1395            .execute_gate(
1396                Uuid::new_v4(),
1397                "prompt",
1398                sensitive,
1399                None,
1400                1.0,
1401                &[],
1402                Uuid::new_v4(),
1403            )
1404            .await;
1405
1406        assert_eq!(capsule.outcome, "HALT");
1407        assert!(
1408            capsule.reason_code.contains("L1_PATH_VIOLATION")
1409                || capsule.reason_code.contains("L1_RULE_VIOLATION")
1410        );
1411        println!("✅ SENSITIVE PATH BLOCK VERIFIED: {}", sensitive);
1412    }
1413}