Skip to main content

chio_http_core/
evaluation.rs

1//! Shared HTTP substrate response types.
2
3use chio_kernel::SignedExecutionNonce;
4use serde::{Deserialize, Serialize};
5
6use crate::{GuardEvidence, HttpReceipt, Verdict};
7
8/// Response body for sidecar HTTP request evaluation.
9///
10/// On an `Allow` verdict from a kernel configured with
11/// `ExecutionNonceConfig`, the response carries a short-lived signed nonce
12/// that the client MUST re-present as `ToolCallRequest::execution_nonce`
13/// before executing the tool call. In strict mode, nonce preflight responses
14/// carry `Verdict::Incomplete` plus this field; callers must retry with the
15/// nonce before any side effect is authorized. The field is `None` on
16/// `Deny`/`Cancel`, on deployments without a nonce config, and on advisory
17/// sidecar aliases that do not perform kernel dispatch.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EvaluateResponse {
20    pub verdict: Verdict,
21    pub receipt: HttpReceipt,
22    #[serde(default)]
23    pub evidence: Vec<GuardEvidence>,
24    /// Optional signed execution nonce. Present only when the kernel
25    /// issues one (allow verdict + strict/opt-in nonce mode). See
26    /// `docs/protocols/STRUCTURAL-SECURITY-FIXES.md` section 1.
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub execution_nonce: Option<SignedExecutionNonce>,
29}
30
31/// Response body for receipt verification.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct VerifyReceiptResponse {
34    pub signature_valid: bool,
35    pub signer_trusted: bool,
36    pub receipt_id_valid: bool,
37    pub parameter_hash_valid: bool,
38    pub receipt_kind: String,
39    pub boundary_class: String,
40    pub trust_level: String,
41    pub result: String,
42    pub authorized: bool,
43    pub signer_key_hex: String,
44    pub ok: bool,
45}
46
47impl VerifyReceiptResponse {
48    #[must_use]
49    pub fn from_http_receipt(receipt: &HttpReceipt, signer_trusted: bool) -> Self {
50        let signature_valid = receipt.verify_signature().unwrap_or(false);
51        let receipt_id_valid = receipt.receipt_id_valid().unwrap_or(false);
52        let parameter_hash_valid = is_lower_hex_64(&receipt.content_hash);
53        let semantic_valid = receipt.receipt_kind
54            == chio_core_types::receipt::kinds::ReceiptKind::MediatedDecision
55            && receipt.boundary_class == chio_core_types::receipt::kinds::BoundaryClass::Prevent
56            && receipt.observation_outcome.is_none()
57            && receipt.trust_level == chio_core_types::receipt::kinds::TrustLevel::Mediated;
58        let ok = signature_valid
59            && signer_trusted
60            && receipt_id_valid
61            && parameter_hash_valid
62            && semantic_valid;
63        let authorized = ok && receipt.verdict.is_allowed();
64
65        Self {
66            signature_valid,
67            signer_trusted,
68            receipt_id_valid,
69            parameter_hash_valid,
70            receipt_kind: receipt.receipt_kind.as_str().to_string(),
71            boundary_class: receipt.boundary_class.as_str().to_string(),
72            trust_level: receipt.trust_level.as_str().to_string(),
73            result: verdict_result(&receipt.verdict).to_string(),
74            authorized,
75            signer_key_hex: receipt.kernel_key.to_hex(),
76            ok,
77        }
78    }
79}
80
81fn is_lower_hex_64(value: &str) -> bool {
82    value.len() == 64
83        && value
84            .bytes()
85            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
86}
87
88fn verdict_result(verdict: &Verdict) -> &'static str {
89    match verdict {
90        Verdict::Allow => "allow",
91        Verdict::Deny { .. } => "deny",
92        Verdict::Cancel { .. } => "cancelled",
93        Verdict::Incomplete { .. } => "incomplete",
94    }
95}
96
97/// Sidecar health states.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum SidecarStatus {
101    Healthy,
102    Degraded,
103    Unhealthy,
104}
105
106/// Response body for sidecar health checks.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct HealthResponse {
109    pub status: SidecarStatus,
110    pub version: String,
111    /// Backend of the embedded kernel's receipt log ("durable" or "ephemeral").
112    #[serde(default)]
113    pub receipt_backend: String,
114    /// Backend of the embedded kernel's revocation state ("durable", "remote",
115    /// or "ephemeral").
116    #[serde(default)]
117    pub revocation_backend: String,
118}