Skip to main content

highflame_shield/
types.rs

1//! Request and response types for the highflame-shield API.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6// ── Request context ──────────────────────────────────────────────────────────
7
8/// Tool-call context forwarded to the guard endpoint.
9#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct ToolContext {
11    /// Tool name (e.g. `"shell"`, `"read_file"`).
12    pub name: String,
13    /// Tool arguments as an arbitrary JSON object.
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub arguments: Option<HashMap<String, serde_json::Value>>,
16    /// MCP server ID the tool belongs to.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub server_id: Option<String>,
19    /// Whether this is a built-in (non-MCP) tool.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub is_builtin: Option<bool>,
22    /// Human-readable tool description.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub description: Option<String>,
25}
26
27/// LLM model context for the current request.
28#[derive(Debug, Clone, Serialize, Deserialize, Default)]
29pub struct ModelContext {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub provider: Option<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub model: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub temperature: Option<f64>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub tokens_used: Option<i64>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub max_tokens: Option<i64>,
40}
41
42/// File operation context.
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
44pub struct FileContext {
45    /// Absolute or relative file path.
46    pub path: String,
47    /// Operation being performed (`"read"`, `"write"`, `"delete"`, …).
48    pub operation: String,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub size: Option<i64>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub mime_type: Option<String>,
53}
54
55/// MCP server context.
56#[derive(Debug, Clone, Serialize, Deserialize, Default)]
57pub struct McpContext {
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub server_name: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub server_url: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub transport: Option<String>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub verified: Option<bool>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub capabilities: Option<Vec<String>>,
68}
69
70// ── Request ──────────────────────────────────────────────────────────────────
71
72/// Request body for `POST /v1/guard`.
73#[derive(Debug, Clone, Serialize, Default)]
74pub struct ShieldRequest {
75    /// Content to evaluate (prompt text, tool arguments, file content, …).
76    pub content: String,
77    /// Content type: `"prompt"`, `"response"`, `"tool_call"`, `"file"`.
78    pub content_type: String,
79    /// Action being performed: `"process_prompt"`, `"call_tool"`, `"read_file"`,
80    /// `"write_file"`, `"connect_server"`.
81    pub action: String,
82
83    // ── Agentic context ───────────────────────────────────────────────────────
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub tool: Option<ToolContext>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub model: Option<ModelContext>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub file: Option<FileContext>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub mcp: Option<McpContext>,
92
93    // ── Session ───────────────────────────────────────────────────────────────
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub session_id: Option<String>,
96
97    // ── Policy configuration ──────────────────────────────────────────────────
98    /// Execution mode: `"enforce"`, `"monitor"`, `"alert"`.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub mode: Option<String>,
101    /// Specific detectors to run. `None` runs all enabled detectors.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub detectors: Option<Vec<String>>,
104    /// Arbitrary key-value metadata forwarded to Cedar policies.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub metadata: Option<HashMap<String, serde_json::Value>>,
107    /// Additional text chunks for RAG context injection.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub contexts: Option<Vec<String>>,
110    /// Skip remaining tiers as soon as a deny is reached.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub early_exit: Option<bool>,
113    /// Include explanation data in the response.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub explain: Option<bool>,
116    /// Product namespace: `"guardrails"` (default) or `"overwatch"`.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub product: Option<String>,
119}
120
121// ── Response ─────────────────────────────────────────────────────────────────
122
123/// Cedar policy that contributed to the deny decision.
124#[derive(Debug, Clone, Deserialize)]
125pub struct DeterminingPolicy {
126    pub rule_id: String,
127    pub policy_id: String,
128    pub policy_name: String,
129    pub effect: String,
130    pub mode: String,
131    #[serde(default)]
132    pub annotations: HashMap<String, serde_json::Value>,
133}
134
135/// Per-detector result included in every [`ShieldResponse`].
136#[derive(Debug, Clone, Deserialize)]
137pub struct DetectorResult {
138    pub name: String,
139    pub tier: String,
140    pub latency_ms: Option<i64>,
141    #[serde(default)]
142    pub context: HashMap<String, serde_json::Value>,
143    pub status: String,
144    pub error: Option<String>,
145}
146
147/// Cross-turn session state delta returned with each response.
148#[derive(Debug, Clone, Deserialize)]
149pub struct SessionDelta {
150    pub turn_count: i32,
151    pub cumulative_risk: f64,
152    pub tokens_used_delta: Option<i64>,
153    pub new_action: Option<String>,
154}
155
156/// Root-cause entry explaining why a deny was issued.
157#[derive(Debug, Clone, Deserialize)]
158pub struct RootCause {
159    pub summary: String,
160    pub detector: String,
161    #[serde(default)]
162    pub labels: HashMap<String, serde_json::Value>,
163    #[serde(default)]
164    pub triggering_context: HashMap<String, serde_json::Value>,
165    #[serde(default)]
166    pub evidence: HashMap<String, serde_json::Value>,
167    #[serde(default)]
168    pub triggered_policies: Vec<String>,
169}
170
171/// Response from `POST /v1/guard`.
172#[derive(Debug, Clone, Deserialize)]
173pub struct ShieldResponse {
174    /// Enforced decision: `"allow"` or `"deny"`.
175    pub decision: String,
176    /// What Cedar would have decided before any mode override.
177    pub actual_decision: Option<String>,
178    /// `true` when a mode override changed the enforced decision.
179    pub mode_overridden: Option<bool>,
180    /// The effective policy mode that was applied.
181    pub effective_mode: Option<String>,
182    /// `true` when an alert-mode policy fired (action allowed but flagged).
183    pub alerted: Option<bool>,
184    /// Human-readable reason for the decision (deny path).
185    pub reason: Option<String>,
186    /// Audit trail reference.
187    pub audit_id: Option<String>,
188    /// Request correlation ID.
189    pub request_id: Option<String>,
190    /// ISO 8601 response timestamp.
191    pub timestamp: String,
192    /// Policies that drove a deny decision.
193    #[serde(default)]
194    pub determining_policies: Vec<DeterminingPolicy>,
195    /// Per-detector results.
196    #[serde(default)]
197    pub detectors: Vec<DetectorResult>,
198    /// Merged raw detector context.
199    #[serde(default)]
200    pub context: HashMap<String, serde_json::Value>,
201    /// Schema-normalised context sent to Cedar.
202    #[serde(default)]
203    pub projected_context: HashMap<String, serde_json::Value>,
204    /// Total request latency in milliseconds.
205    pub latency_ms: Option<i64>,
206    /// Cedar evaluation latency in milliseconds.
207    pub eval_latency_ms: Option<i64>,
208    /// Tiers that ran.
209    #[serde(default)]
210    pub tiers_evaluated: Vec<String>,
211    /// Tiers skipped due to early exit.
212    #[serde(default)]
213    pub tiers_skipped: Vec<String>,
214    /// Session state delta for this turn.
215    pub session_delta: Option<SessionDelta>,
216    /// Root causes for a deny decision.
217    #[serde(default)]
218    pub root_causes: Vec<RootCause>,
219}
220
221impl ShieldResponse {
222    /// Returns `true` when the decision is `"allow"`.
223    pub fn allowed(&self) -> bool {
224        self.decision == "allow"
225    }
226
227    /// Returns `true` when the decision is `"deny"`.
228    pub fn denied(&self) -> bool {
229        self.decision == "deny"
230    }
231}
232
233// ── Token exchange ────────────────────────────────────────────────────────────
234
235/// Response from the token exchange endpoint.
236#[derive(Debug, Clone, Deserialize)]
237pub struct TokenResponse {
238    pub access_token: String,
239    pub token_type: String,
240    pub expires_in: u64,
241    #[serde(default)]
242    pub account_id: String,
243    #[serde(default)]
244    pub gateway_id: String,
245    #[serde(default)]
246    pub project_id: String,
247}
248
249// ── Health & discovery ────────────────────────────────────────────────────────
250
251/// Response from `GET /v1/health`.
252#[derive(Debug, Clone, Deserialize)]
253pub struct HealthResponse {
254    /// Overall service status: `"healthy"` or `"degraded"`.
255    pub status: String,
256    /// Per-detector health map.
257    #[serde(default)]
258    pub detectors: HashMap<String, String>,
259    /// Evaluator readiness status.
260    pub evaluator: Option<String>,
261}
262
263/// Single detector entry returned by `GET /v1/detectors`.
264#[derive(Debug, Clone, Deserialize)]
265pub struct DetectorInfo {
266    pub name: String,
267    pub version: Option<String>,
268    pub tier: Option<String>,
269    pub status: Option<String>,
270}
271
272/// Response from `GET /v1/detectors`.
273#[derive(Debug, Clone, Deserialize)]
274pub struct ListDetectorsResponse {
275    pub detectors: Vec<DetectorInfo>,
276    pub count: u32,
277}