Skip to main content

vex_runtime/gate/
mod.rs

1use async_trait::async_trait;
2use sha2::Digest;
3use uuid::Uuid;
4use vex_core::audit::EvidenceCapsule;
5use vex_core::segment::semantic_commitment_hash;
6use vex_llm::Capability;
7
8/// Exogenous Gate Decision Boundary
9///
10/// A Gate acts as a continuation authority, deciding whether an agent's
11/// output is "Safe", "Valid", or "Audit-Compliant".
12#[async_trait]
13pub trait Gate: Send + Sync + std::fmt::Debug {
14    /// Evaluate the current execution state and return a signed Evidence Capsule.
15    #[allow(clippy::too_many_arguments)]
16    async fn execute_gate(
17        &self,
18        agent_id: Uuid,
19        task_prompt: &str,
20        suggested_output: &str,
21        intent_data: Option<vex_core::segment::IntentData>,
22        confidence: f64,
23        capabilities: &[Capability],
24        nonce: Uuid,
25    ) -> EvidenceCapsule;
26
27    /// Verify a Continuation Token against the current execution context.
28    async fn verify_token(
29        &self,
30        token: &vex_core::ContinuationToken,
31        expected_aid: Option<&str>,
32        expected_intent_hash: Option<&str>,
33        expected_circuit_id: Option<&str>,
34    ) -> Result<bool, String>;
35}
36
37/// A mock implementation of the Generic Gate for testing and local development.
38#[derive(Debug, Default)]
39pub struct GenericGateMock;
40
41#[async_trait]
42impl Gate for GenericGateMock {
43    #[allow(clippy::too_many_arguments)]
44    async fn execute_gate(
45        &self,
46        _agent_id: Uuid,
47        _task_prompt: &str,
48        suggested_output: &str,
49        _intent_data: Option<vex_core::segment::IntentData>,
50        confidence: f64,
51        capabilities: &[Capability],
52        nonce: Uuid,
53    ) -> EvidenceCapsule {
54        // Simple logic for the mock:
55        // 1. If confidence is very low (< 0.3), HALT.
56        // 2. If the output contains common failure patterns, HALT.
57        // 3. Otherwise, ALLOW.
58
59        let (outcome, reason) = if confidence < 0.3 {
60            ("HALT", "LOW_CONFIDENCE")
61        } else if capabilities.contains(&Capability::Network)
62            && !suggested_output.to_lowercase().contains("http")
63        {
64            // Example policy: If you have network capability but don't explain the URL, caution.
65            ("ALLOW", "SENSORS_ORANGE_NETWORK_IDLE")
66        } else if suggested_output.to_lowercase().contains("i'm sorry")
67            || suggested_output.to_lowercase().contains("cannot fulfill")
68        {
69            ("HALT", "REFUSAL_FILTER")
70        } else {
71            ("ALLOW", "SENSORS_GREEN")
72        };
73
74        EvidenceCapsule {
75            capsule_id: format!("mock-{}", &Uuid::new_v4().to_string()[..8]),
76            outcome: outcome.to_string(),
77            reason_code: reason.to_string(),
78            witness_receipt: "mock-receipt-0xdeadbeef".to_string(),
79            nonce: nonce.to_string(),
80            magpie_source: None,
81            gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
82                "confidence_sensor": if confidence > 0.5 { "GREEN" } else { "YELLOW" },
83                "content_length": suggested_output.len(),
84            })),
85            reproducibility_context: vex_core::segment::SchemaValue(serde_json::json!({
86                "gate_provider": "ChoraGateMock",
87                "version": "0.1.0",
88            })),
89            resolution_vep_hash: None,
90            escalation_id: None,
91            continuation_token: None,
92            intent_data: None,
93            vep_blob: None,
94        }
95    }
96
97    async fn verify_token(
98        &self,
99        _token: &vex_core::ContinuationToken,
100        _expected_aid: Option<&str>,
101        _expected_intent_hash: Option<&str>,
102        _expected_circuit_id: Option<&str>,
103    ) -> Result<bool, String> {
104        // Mock always returns true
105        Ok(true)
106    }
107}
108
109/// Networked Gate provider communicating over HTTP.
110/// Legacy wrapper: Now uses ChoraGate internally for unified handshake logic.
111#[derive(Debug, Clone)]
112pub struct HttpGate {
113    pub inner: std::sync::Arc<ChoraGate>,
114}
115
116impl HttpGate {
117    pub fn new(client: std::sync::Arc<dyn vex_chora::client::AuthorityClient>) -> Self {
118        let bridge = std::sync::Arc::new(vex_chora::AuthorityBridge::new(client));
119        Self {
120            inner: std::sync::Arc::new(ChoraGate {
121                bridge,
122                prover: None,
123            }),
124        }
125    }
126
127    /// Attach a ZK Prover for Shadow Intent generation (Phase 4)
128    pub fn with_prover(self, prover: std::sync::Arc<attest_rs::zk::AuditProver>) -> Self {
129        Self {
130            inner: std::sync::Arc::new(ChoraGate {
131                bridge: self.inner.bridge.clone(),
132                prover: Some(prover),
133            }),
134        }
135    }
136
137    /// Attach a hardware identity to the underlying bridge.
138    pub fn with_identity(self, identity: std::sync::Arc<vex_hardware::api::AgentIdentity>) -> Self {
139        let bridge = (*self.inner.bridge).clone().with_identity(identity);
140        Self {
141            inner: std::sync::Arc::new(ChoraGate {
142                bridge: std::sync::Arc::new(bridge),
143                prover: self.inner.prover.clone(),
144            }),
145        }
146    }
147}
148
149#[async_trait]
150impl Gate for HttpGate {
151    #[allow(clippy::too_many_arguments)]
152    async fn execute_gate(
153        &self,
154        agent_id: Uuid,
155        task_prompt: &str,
156        suggested_output: &str,
157        intent_data: Option<vex_core::segment::IntentData>,
158        confidence: f64,
159        capabilities: &[Capability],
160        nonce: Uuid,
161    ) -> EvidenceCapsule {
162        self.inner
163            .execute_gate(
164                agent_id,
165                task_prompt,
166                suggested_output,
167                intent_data,
168                confidence,
169                capabilities,
170                nonce,
171            )
172            .await
173    }
174    async fn verify_token(
175        &self,
176        token: &vex_core::ContinuationToken,
177        expected_aid: Option<&str>,
178        expected_intent_hash: Option<&str>,
179        expected_circuit_id: Option<&str>,
180    ) -> Result<bool, String> {
181        self.inner
182            .verify_token(
183                token,
184                expected_aid,
185                expected_intent_hash,
186                expected_circuit_id,
187            )
188            .await
189    }
190}
191
192/// The default CHORA Gate implementation using the unified AuthorityBridge.
193#[derive(Debug, Clone)]
194pub struct ChoraGate {
195    pub bridge: std::sync::Arc<vex_chora::AuthorityBridge>,
196    pub prover: Option<std::sync::Arc<attest_rs::zk::AuditProver>>,
197}
198
199impl ChoraGate {
200    /// Generates a session-specific salt deriving from the hardware identity (if available).
201    /// Uses signature-based derivation to ensure unlinkability across requests while
202    /// maintaining deterministic reproducibility for audits.
203    pub fn generate_shadow_intent_salt(&self, agent_id: Uuid, nonce: Uuid) -> Vec<u8> {
204        let context = format!("shadow-intent-v1:{}:{}", agent_id, nonce);
205        if let Some(identity) = &self.bridge.identity {
206            let sig = identity.sign(context.as_bytes());
207            sha2::Sha256::digest(&sig).to_vec()
208        } else {
209            // Fallback for mock/plain gates: Deterministic hash of context + static experiment seed
210            let mut hasher = sha2::Sha256::new();
211            hasher.update(b"VEX_FALLBACK_SEED_001");
212            hasher.update(context.as_bytes());
213            hasher.finalize().to_vec()
214        }
215    }
216}
217
218#[async_trait]
219impl Gate for ChoraGate {
220    #[allow(clippy::too_many_arguments)]
221    async fn execute_gate(
222        &self,
223        _agent_id: Uuid,
224        task_prompt: &str,
225        suggested_output: &str,
226        intent_data: Option<vex_core::segment::IntentData>,
227        confidence: f64,
228        capabilities: &[Capability],
229        nonce: Uuid,
230    ) -> EvidenceCapsule {
231        // 1. Build IntentData (Transparent or Shadow if prover is available)
232        // If intent_data was provided by the caller, we use it; otherwise generate it.
233        let intent = if let Some(id) = intent_data {
234            id
235        } else if let Some(_prover) = &self.prover {
236            let salt = self.generate_shadow_intent_salt(_agent_id, nonce);
237            let intent_hash = semantic_commitment_hash(task_prompt, &salt);
238
239            // Compute the actual start_root (first 8 bytes of the prompt SHA256)
240            // this must match the entry point in vex-core
241            use sha2::Digest;
242            let preimage = sha2::Sha256::digest(vex_core::segment::canonical_encode_shadow_intent(
243                task_prompt,
244                &salt,
245            ));
246            let mut start_root_bytes = [0u8; 32];
247            start_root_bytes[0..8].copy_from_slice(&preimage[0..8]);
248            let start_root_hex = hex::encode(start_root_bytes);
249
250            // Generate STARK Proof for the intent transition [start_root -> intent_hash]
251            let proof_blob = attest_rs::zk::AuditProver::prove_transition(
252                start_root_bytes,
253                hex::decode(&intent_hash)
254                    .unwrap_or_default()
255                    .try_into()
256                    .unwrap_or([0u8; 32]),
257            )
258            .unwrap_or_default();
259
260            vex_core::segment::IntentData::Shadow {
261                commitment_hash: intent_hash.clone(),
262                stark_proof_b64: base64::Engine::encode(
263                    &base64::engine::general_purpose::STANDARD,
264                    &proof_blob,
265                ),
266                public_inputs: vex_core::segment::ShadowPublicInputs {
267                    schema: vex_core::segment::ShadowPublicInputs::SCHEMA_V1.to_string(),
268                    start_root: start_root_hex,
269                    commitment_hash: intent_hash,
270                    circuit_id: attest_rs::zk::AuditProver::CIRCUIT_ID.to_string(),
271                    public_salt: hex::encode(salt),
272                    verifier_params_hash: None,
273                }
274                .to_schema_value(),
275                circuit_id: Some(attest_rs::zk::AuditProver::CIRCUIT_ID.to_string()),
276                continuation_token: None,
277                metadata: vex_core::segment::SchemaValue::default(),
278            }
279        } else {
280            vex_core::segment::IntentData::Transparent {
281                request_sha256: hex::encode(sha2::Sha256::digest(suggested_output.as_bytes())),
282                confidence,
283                capabilities: capabilities.iter().map(|c| format!("{:?}", c)).collect(),
284                magpie_source: None,
285                continuation_token: None,
286                metadata: vex_core::segment::SchemaValue::default(),
287            }
288        };
289
290        // 2. Perform Handshake via Unified Bridge with Boundary Nonce
291        match self
292            .bridge
293            .perform_handshake(intent.clone(), &nonce.to_string())
294            .await
295        {
296            Ok(capsule) => EvidenceCapsule {
297                capsule_id: capsule.capsule_id,
298                outcome: capsule.authority.outcome,
299                reason_code: capsule.authority.reason_code,
300                witness_receipt: capsule.witness.receipt_hash,
301                nonce: capsule.authority.nonce.clone(),
302                magpie_source: None,
303                gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
304                    "trace_root": capsule.authority.trace_root,
305                    "identity_type": capsule.identity.identity_type,
306                    "agent_id": _agent_id.to_string(),
307                })),
308                reproducibility_context: vex_core::segment::SchemaValue(serde_json::json!({
309                    "gate_provider": "ChoraGate",
310                    "bridge_version": "v0.2.0",
311                })),
312                resolution_vep_hash: None,
313                escalation_id: capsule.authority.escalation_id,
314                continuation_token: capsule.authority.continuation_token,
315                intent_data: Some(intent),
316                vep_blob: None,
317            },
318            Err(e) => EvidenceCapsule {
319                capsule_id: "error".to_string(),
320                outcome: "HALT".to_string(),
321                reason_code: format!("CHORA_BRIDGE_ERROR: {}", e),
322                witness_receipt: "error-none".to_string(),
323                nonce: "0".to_string(),
324                magpie_source: None,
325                gate_sensors: vex_core::segment::SchemaValue(serde_json::Value::Null),
326                reproducibility_context: vex_core::segment::SchemaValue(serde_json::Value::Null),
327                resolution_vep_hash: None,
328                escalation_id: None,
329                continuation_token: None,
330                intent_data: None,
331                vep_blob: None,
332            },
333        }
334    }
335
336    async fn verify_token(
337        &self,
338        token: &vex_core::ContinuationToken,
339        expected_aid: Option<&str>,
340        expected_intent_hash: Option<&str>,
341        expected_circuit_id: Option<&str>,
342    ) -> Result<bool, String> {
343        self.bridge
344            .verify_continuation_token(
345                token,
346                expected_aid,
347                expected_intent_hash,
348                expected_circuit_id,
349                None,
350                None,
351            )
352            .await
353    }
354}
355
356pub mod titan;
357pub use titan::TitanGate;
358
359pub const SHADOW_INTENT_TEST_SALT: &[u8] = b"VEX_LAB_001";