1use async_trait::async_trait;
2use uuid::Uuid;
3use vex_core::audit::EvidenceCapsule;
4use vex_llm::Capability;
5
6#[async_trait]
11pub trait Gate: Send + Sync + std::fmt::Debug {
12 async fn execute_gate(
14 &self,
15 agent_id: Uuid,
16 task_prompt: &str,
17 suggested_output: &str,
18 confidence: f64,
19 capabilities: Vec<Capability>,
20 ) -> EvidenceCapsule;
21}
22
23#[derive(Debug, Default)]
25pub struct GenericGateMock;
26
27#[async_trait]
28impl Gate for GenericGateMock {
29 async fn execute_gate(
30 &self,
31 _agent_id: Uuid,
32 _task_prompt: &str,
33 suggested_output: &str,
34 confidence: f64,
35 capabilities: Vec<Capability>,
36 ) -> EvidenceCapsule {
37 let (outcome, reason) = if confidence < 0.3 {
43 ("HALT", "LOW_CONFIDENCE")
44 } else if capabilities.contains(&Capability::Network)
45 && !suggested_output.to_lowercase().contains("http")
46 {
47 ("ALLOW", "SENSORS_ORANGE_NETWORK_IDLE")
49 } else if suggested_output.to_lowercase().contains("i'm sorry")
50 || suggested_output.to_lowercase().contains("cannot fulfill")
51 {
52 ("HALT", "REFUSAL_FILTER")
53 } else {
54 ("ALLOW", "SENSORS_GREEN")
55 };
56
57 EvidenceCapsule {
58 capsule_id: format!("mock-{}", &Uuid::new_v4().to_string()[..8]),
59 outcome: outcome.to_string(),
60 reason_code: reason.to_string(),
61 sensors: serde_json::json!({
62 "confidence_sensor": if confidence > 0.5 { "GREEN" } else { "YELLOW" },
63 "content_length": suggested_output.len(),
64 }),
65 reproducibility_context: serde_json::json!({
66 "gate_provider": "ChoraGateMock",
67 "version": "0.1.0",
68 }),
69 }
70 }
71}
72
73#[derive(Debug)]
75pub struct HttpGate {
76 pub client: reqwest::Client,
77 pub url: String,
78 pub api_key: String,
79}
80
81impl HttpGate {
82 pub fn new(url: String, api_key: String) -> Self {
83 Self {
84 client: reqwest::Client::new(),
85 url,
86 api_key,
87 }
88 }
89}
90
91#[async_trait]
92impl Gate for HttpGate {
93 async fn execute_gate(
94 &self,
95 agent_id: Uuid,
96 task_prompt: &str,
97 suggested_output: &str,
98 confidence: f64,
99 capabilities: Vec<Capability>,
100 ) -> EvidenceCapsule {
101 let payload = serde_json::json!({
102 "agent_id": agent_id,
103 "task_prompt": task_prompt,
104 "suggested_output": suggested_output,
105 "confidence": confidence,
106 "capabilities": capabilities,
107 });
108
109 let gate_url = if self.url.ends_with('/') {
110 format!("{}gate", self.url)
111 } else {
112 format!("{}/gate", self.url)
113 };
114
115 match self
116 .client
117 .post(&gate_url)
118 .header("x-api-key", &self.api_key)
119 .json(&payload)
120 .send()
121 .await
122 {
123 Ok(resp) => {
124 if resp.status().is_success() {
125 resp.json::<EvidenceCapsule>()
126 .await
127 .unwrap_or_else(|e| EvidenceCapsule {
128 capsule_id: "error".to_string(),
129 outcome: "HALT".to_string(),
130 reason_code: format!("API_PARSE_ERROR: {}", e),
131 sensors: serde_json::Value::Null,
132 reproducibility_context: serde_json::Value::Null,
133 })
134 } else {
135 EvidenceCapsule {
136 capsule_id: "error".to_string(),
137 outcome: "HALT".to_string(),
138 reason_code: format!("API_STATUS_ERROR: {}", resp.status()),
139 sensors: serde_json::Value::Null,
140 reproducibility_context: serde_json::Value::Null,
141 }
142 }
143 }
144 Err(e) => EvidenceCapsule {
145 capsule_id: "error".to_string(),
146 outcome: "HALT".to_string(),
147 reason_code: format!("API_CONNECTION_ERROR: {}", e),
148 sensors: serde_json::Value::Null,
149 reproducibility_context: serde_json::Value::Null,
150 },
151 }
152 }
153}