use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub mod canonical;
pub mod client;
pub mod reason_codes;
pub mod types_gen;
pub use types_gen::*;
#[cfg(feature = "codegen")]
pub mod generated {
pub mod kernel {
include!("generated/helm.kernel.v1.rs");
}
pub mod authority {
include!("generated/helm.authority.v1.rs");
}
pub mod effects {
include!("generated/helm.effects.v1.rs");
}
pub mod intervention {
include!("generated/helm.intervention.v1.rs");
}
pub mod truth {
include!("generated/helm.truth.v1.rs");
}
pub mod errors {
include!("generated/helm.errors.v1.rs");
}
}
const ERROR_INTERNAL: &str = "ERROR_INTERNAL";
#[derive(Debug, Default)]
pub struct HelmApiError {
pub status: u16,
pub message: String,
pub reason_code: ReasonCode,
pub code: Option<String>,
pub retryable: bool,
}
impl std::fmt::Display for HelmApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"HELM API {}: {} ({})",
self.status, self.message, self.reason_code
)
}
}
impl std::error::Error for HelmApiError {}
fn api_error(status: u16, body: serde_json::Value) -> HelmApiError {
let text = |v: &serde_json::Value| v.as_str().filter(|s| !s.is_empty()).map(str::to_owned);
if !body["code"].is_string() && !body["error"].is_object() {
return HelmApiError {
status,
message: "unknown error".into(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
};
}
let detail = body["details"]
.as_array()
.and_then(|d| d.iter().find(|d| d["type"] == "helm.errors.v1.ErrorDetail"))
.map(|d| &d["debug"]);
HelmApiError {
status,
message: text(&body["message"])
.or_else(|| text(&body["detail"]))
.or_else(|| text(&body["error"]["message"]))
.unwrap_or_else(|| "unknown error".into()),
reason_code: detail
.and_then(|d| text(&d["reason_code"]))
.or_else(|| text(&body["error"]["reason_code"]))
.unwrap_or_default(),
code: text(&body["code"]),
retryable: detail
.and_then(|d| d["retryable"].as_bool())
.unwrap_or(false),
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvidenceEnvelopeExportRequest {
pub manifest_id: String,
pub envelope: String,
pub native_evidence_hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub experimental: bool,
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvidenceEnvelopeManifest {
pub manifest_id: String,
pub envelope: String,
pub native_evidence_hash: String,
pub native_authority: bool,
pub created_at: String,
#[serde(default)]
pub subject: Option<String>,
#[serde(default)]
pub statement_hash: Option<String>,
#[serde(default)]
pub payload_type: Option<String>,
#[serde(default)]
pub payload_hash: Option<String>,
#[serde(default)]
pub experimental: bool,
#[serde(default)]
pub manifest_hash: Option<String>,
}
pub type EvidenceEnvelopePayload = serde_json::Value;
pub type ApprovalWebAuthnChallenge = serde_json::Value;
pub type ApprovalWebAuthnAssertion = serde_json::Value;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NegativeBoundaryVector {
pub id: String,
pub category: String,
pub trigger: String,
pub expected_verdict: String,
pub expected_reason_code: String,
pub must_emit_receipt: bool,
pub must_not_dispatch: bool,
#[serde(default)]
pub must_bind_evidence: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct McpRegistryDiscoverRequest {
pub server_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transport: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_names: Vec<String>,
#[serde(default = "default_mcp_risk")]
pub risk: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
fn default_mcp_risk() -> String {
"unknown".to_string()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct McpRegistryApprovalRequest {
pub server_id: String,
pub approver_id: String,
pub approval_receipt_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct McpQuarantineRecord {
pub server_id: String,
pub risk: String,
pub state: String,
pub discovered_at: String,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub transport: Option<String>,
#[serde(default)]
pub endpoint: Option<String>,
#[serde(default)]
pub tool_names: Vec<String>,
#[serde(default)]
pub approved_at: Option<String>,
#[serde(default)]
pub approved_by: Option<String>,
#[serde(default)]
pub approval_receipt_id: Option<String>,
#[serde(default)]
pub revoked_at: Option<String>,
#[serde(default)]
pub expires_at: Option<String>,
#[serde(default)]
pub reason: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SandboxBackendProfile {
pub name: String,
pub kind: String,
pub runtime: String,
pub hosted: bool,
pub deny_network_by_default: bool,
pub native_isolation: bool,
#[serde(default)]
pub experimental: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SandboxGrant {
pub grant_id: String,
pub runtime: String,
pub profile: String,
pub env: serde_json::Value,
pub network: serde_json::Value,
pub declared_at: String,
#[serde(default)]
pub runtime_version: Option<String>,
#[serde(default)]
pub image_digest: Option<String>,
#[serde(default)]
pub template_digest: Option<String>,
#[serde(default)]
pub filesystem_preopens: Vec<serde_json::Value>,
#[serde(default)]
pub limits: Option<serde_json::Value>,
#[serde(default)]
pub policy_epoch: Option<String>,
#[serde(default)]
pub grant_hash: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SandboxGrantInspection {
Profiles(Vec<SandboxBackendProfile>),
Grant(SandboxGrant),
}
pub struct HelmClient {
base_url: String,
client: Client,
}
impl HelmClient {
pub fn new(base_url: &str) -> Self {
Self::with_auth(base_url, None, None, None)
}
pub fn with_auth(
base_url: &str,
api_key: Option<&str>,
tenant_id: Option<&str>,
principal_id: Option<&str>,
) -> Self {
let mut headers = HeaderMap::new();
if let Some(api_key) = api_key.filter(|value| !value.trim().is_empty()) {
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {api_key}"))
.expect("HELM API key must be a valid HTTP header value"),
);
}
if let Some(tenant_id) = tenant_id.filter(|value| !value.trim().is_empty()) {
headers.insert(
"X-Helm-Tenant-ID",
HeaderValue::from_str(tenant_id)
.expect("HELM tenant ID must be a valid HTTP header value"),
);
}
if let Some(principal_id) = principal_id.filter(|value| !value.trim().is_empty()) {
headers.insert(
"X-Helm-Principal-ID",
HeaderValue::from_str(principal_id)
.expect("HELM principal ID must be a valid HTTP header value"),
);
}
Self {
base_url: base_url.trim_end_matches('/').to_string(),
client: Client::builder()
.timeout(Duration::from_secs(30))
.default_headers(headers)
.build()
.expect("failed to build HTTP client"),
}
}
fn url(&self, path: &str) -> String {
format!("{}{}", self.base_url, path)
}
fn check(
&self,
resp: reqwest::blocking::Response,
) -> Result<reqwest::blocking::Response, HelmApiError> {
if resp.status().is_success() {
return Ok(resp);
}
let status = resp.status().as_u16();
Err(api_error(status, resp.json().unwrap_or_default()))
}
fn get_value(&self, path: &str) -> Result<serde_json::Value, HelmApiError> {
let resp = self
.client
.get(self.url(path))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
fn post_value<T: Serialize>(
&self,
path: &str,
body: &T,
) -> Result<serde_json::Value, HelmApiError> {
let resp = self
.client
.post(self.url(path))
.json(body)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
fn put_value<T: Serialize>(
&self,
path: &str,
body: &T,
) -> Result<serde_json::Value, HelmApiError> {
let resp = self
.client
.put(self.url(path))
.json(body)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn get_boundary_status(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/boundary/status")
}
pub fn list_boundary_capabilities(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/boundary/capabilities")
}
pub fn list_boundary_records(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/boundary/records")
}
pub fn get_boundary_record(&self, record_id: &str) -> Result<serde_json::Value, HelmApiError> {
self.get_value(&format!(
"/api/v1/boundary/records/{}",
encode_query(record_id)
))
}
pub fn verify_boundary_record(
&self,
record_id: &str,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!(
"/api/v1/boundary/records/{}/verify",
encode_query(record_id)
),
&serde_json::json!({}),
)
}
pub fn list_boundary_checkpoints(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/boundary/checkpoints")
}
pub fn create_boundary_checkpoint(&self) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/boundary/checkpoints", &serde_json::json!({}))
}
pub fn verify_boundary_checkpoint(
&self,
checkpoint_id: &str,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!(
"/api/v1/boundary/checkpoints/{}/verify",
encode_query(checkpoint_id)
),
&serde_json::json!({}),
)
}
pub fn chat_completions(
&self,
req: &ChatCompletionRequest,
) -> Result<ChatCompletionResponse, HelmApiError> {
let resp = self
.client
.post(self.url("/v1/chat/completions"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
#[deprecated(note = "use evaluate_decision_v5 for the typed V5 contract")]
pub fn evaluate_decision<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/evaluate", req)
}
pub fn evaluate_decision_v5(
&self,
req: &EvaluateRequest,
) -> Result<EvaluateResponse, HelmApiError> {
for (field, value) in [
("tool", req.tool.as_deref()),
("effect_level", req.effect_level.as_deref()),
("session_id", req.session_id.as_deref()),
] {
if value.map(str::trim).is_none_or(str::is_empty) {
return Err(HelmApiError {
status: 0,
message: format!("evaluate_decision_v5 requires a non-blank {field}"),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
});
}
}
let resp = self
.client
.post(self.url("/api/v1/evaluate"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn approve_intent(&self, req: &ApprovalRequest) -> Result<Receipt, HelmApiError> {
let resp = self
.client
.post(self.url("/api/v1/kernel/approve"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn list_sessions(&self) -> Result<Vec<Session>, HelmApiError> {
let resp = self
.client
.get(self.url("/api/v1/proofgraph/sessions"))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn get_receipts(&self, session_id: &str) -> Result<Vec<Receipt>, HelmApiError> {
let resp = self
.client
.get(self.url(&format!(
"/api/v1/proofgraph/sessions/{}/receipts",
session_id
)))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn export_evidence(&self, session_id: Option<&str>) -> Result<Vec<u8>, HelmApiError> {
let body = serde_json::json!({
"session_id": session_id,
"format": "tar.gz"
});
let resp = self
.client
.post(self.url("/api/v1/evidence/export"))
.json(&body)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.bytes().map(|b| b.to_vec()).map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn verify_evidence(&self, bundle: &[u8]) -> Result<VerificationResult, HelmApiError> {
let form = reqwest::blocking::multipart::Form::new().part(
"bundle",
reqwest::blocking::multipart::Part::bytes(bundle.to_vec())
.file_name("pack.tar.gz")
.mime_str("application/octet-stream")
.unwrap(),
);
let resp = self
.client
.post(self.url("/api/v1/evidence/verify"))
.multipart(form)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn replay_verify(&self, bundle: &[u8]) -> Result<VerificationResult, HelmApiError> {
let form = reqwest::blocking::multipart::Form::new().part(
"bundle",
reqwest::blocking::multipart::Part::bytes(bundle.to_vec())
.file_name("pack.tar.gz")
.mime_str("application/octet-stream")
.unwrap(),
);
let resp = self
.client
.post(self.url("/api/v1/replay/verify"))
.multipart(form)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn create_evidence_envelope_manifest(
&self,
req: &EvidenceEnvelopeExportRequest,
) -> Result<EvidenceEnvelopeManifest, HelmApiError> {
let resp = self
.client
.post(self.url("/api/v1/evidence/envelopes"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn list_evidence_envelope_manifests(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/evidence/envelopes")
}
pub fn get_evidence_envelope_manifest(
&self,
manifest_id: &str,
) -> Result<serde_json::Value, HelmApiError> {
self.get_value(&format!(
"/api/v1/evidence/envelopes/{}",
encode_query(manifest_id)
))
}
pub fn get_evidence_envelope_payload(
&self,
manifest_id: &str,
) -> Result<EvidenceEnvelopePayload, HelmApiError> {
self.get_value(&format!(
"/api/v1/evidence/envelopes/{}/payload",
encode_query(manifest_id)
))
}
pub fn verify_evidence_envelope_manifest(
&self,
manifest_id: &str,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!(
"/api/v1/evidence/envelopes/{}/verify",
encode_query(manifest_id)
),
&serde_json::json!({}),
)
}
pub fn get_receipt(&self, receipt_hash: &str) -> Result<Receipt, HelmApiError> {
let resp = self
.client
.get(self.url(&format!("/api/v1/proofgraph/receipts/{}", receipt_hash)))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn list_negative_conformance_vectors(
&self,
) -> Result<Vec<NegativeBoundaryVector>, HelmApiError> {
let resp = self
.client
.get(self.url("/api/v1/conformance/negative"))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn list_conformance_vectors(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/conformance/vectors")
}
pub fn list_mcp_registry(&self) -> Result<Vec<McpQuarantineRecord>, HelmApiError> {
let resp = self
.client
.get(self.url("/api/v1/mcp/registry"))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn discover_mcp_server(
&self,
req: &McpRegistryDiscoverRequest,
) -> Result<McpQuarantineRecord, HelmApiError> {
let resp = self
.client
.post(self.url("/api/v1/mcp/registry"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn approve_mcp_server(
&self,
req: &McpRegistryApprovalRequest,
) -> Result<McpQuarantineRecord, HelmApiError> {
let resp = self
.client
.post(self.url("/api/v1/mcp/registry/approve"))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn get_mcp_registry_record(
&self,
server_id: &str,
) -> Result<McpQuarantineRecord, HelmApiError> {
let resp = self
.client
.get(self.url(&format!("/api/v1/mcp/registry/{}", encode_query(server_id))))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn approve_mcp_registry_record(
&self,
server_id: &str,
req: &McpRegistryApprovalRequest,
) -> Result<McpQuarantineRecord, HelmApiError> {
let resp = self
.client
.post(self.url(&format!(
"/api/v1/mcp/registry/{}/approve",
encode_query(server_id)
)))
.json(req)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn revoke_mcp_registry_record(
&self,
server_id: &str,
reason: Option<&str>,
) -> Result<McpQuarantineRecord, HelmApiError> {
let body = serde_json::json!({ "reason": reason.unwrap_or("") });
let resp = self
.client
.post(self.url(&format!(
"/api/v1/mcp/registry/{}/revoke",
encode_query(server_id)
)))
.json(&body)
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn scan_mcp_server<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/mcp/scan", req)
}
pub fn list_mcp_auth_profiles(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/mcp/auth-profiles")
}
pub fn put_mcp_auth_profile<T: Serialize>(
&self,
profile_id: &str,
profile: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.put_value(
&format!("/api/v1/mcp/auth-profiles/{}", encode_query(profile_id)),
profile,
)
}
pub fn authorize_mcp_call<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/mcp/authorize-call", req)
}
pub fn inspect_sandbox_grants(
&self,
runtime: Option<&str>,
profile: Option<&str>,
policy_epoch: Option<&str>,
) -> Result<SandboxGrantInspection, HelmApiError> {
let mut path = "/api/v1/sandbox/grants/inspect".to_string();
let mut params = Vec::new();
if let Some(runtime) = runtime {
params.push(format!("runtime={}", encode_query(runtime)));
}
if let Some(profile) = profile {
params.push(format!("profile={}", encode_query(profile)));
}
if let Some(policy_epoch) = policy_epoch {
params.push(format!("policy_epoch={}", encode_query(policy_epoch)));
}
if !params.is_empty() {
path.push('?');
path.push_str(¶ms.join("&"));
}
let resp = self
.client
.get(self.url(&path))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn list_sandbox_profiles(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/sandbox/profiles")
}
pub fn list_sandbox_grants(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/sandbox/grants")
}
pub fn create_sandbox_grant<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/sandbox/grants", req)
}
pub fn get_sandbox_grant(&self, grant_id: &str) -> Result<serde_json::Value, HelmApiError> {
self.get_value(&format!(
"/api/v1/sandbox/grants/{}",
encode_query(grant_id)
))
}
pub fn verify_sandbox_grant(&self, grant_id: &str) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!("/api/v1/sandbox/grants/{}/verify", encode_query(grant_id)),
&serde_json::json!({}),
)
}
pub fn preflight_sandbox_grant<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/sandbox/preflight", req)
}
pub fn list_agent_identities(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/identity/agents")
}
pub fn get_authz_health(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/authz/health")
}
pub fn check_authz<T: Serialize>(&self, req: &T) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/authz/check", req)
}
pub fn list_authz_snapshots(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/authz/snapshots")
}
pub fn get_authz_snapshot(&self, snapshot_id: &str) -> Result<serde_json::Value, HelmApiError> {
self.get_value(&format!(
"/api/v1/authz/snapshots/{}",
encode_query(snapshot_id)
))
}
pub fn list_approval_ceremonies(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/approvals")
}
pub fn create_approval_ceremony<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/approvals", req)
}
pub fn transition_approval_ceremony<T: Serialize>(
&self,
approval_id: &str,
action: &str,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!(
"/api/v1/approvals/{}/{}",
encode_query(approval_id),
encode_query(action)
),
req,
)
}
pub fn create_approval_webauthn_challenge<T: Serialize>(
&self,
approval_id: &str,
req: &T,
) -> Result<ApprovalWebAuthnChallenge, HelmApiError> {
self.post_value(
&format!(
"/api/v1/approvals/{}/webauthn/challenge",
encode_query(approval_id)
),
req,
)
}
pub fn assert_approval_webauthn_challenge<T: Serialize>(
&self,
approval_id: &str,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value(
&format!(
"/api/v1/approvals/{}/webauthn/assert",
encode_query(approval_id)
),
req,
)
}
pub fn list_budget_ceilings(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/budgets")
}
pub fn put_budget_ceiling<T: Serialize>(
&self,
budget_id: &str,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.put_value(&format!("/api/v1/budgets/{}", encode_query(budget_id)), req)
}
pub fn get_coexistence_capabilities(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/coexistence/capabilities")
}
pub fn get_telemetry_otel_config(&self) -> Result<serde_json::Value, HelmApiError> {
self.get_value("/api/v1/telemetry/otel/config")
}
pub fn export_telemetry<T: Serialize>(
&self,
req: &T,
) -> Result<serde_json::Value, HelmApiError> {
self.post_value("/api/v1/telemetry/export", req)
}
pub fn health(&self) -> Result<serde_json::Value, HelmApiError> {
let resp = self
.client
.get(self.url("/healthz"))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
pub fn version(&self) -> Result<VersionInfo, HelmApiError> {
let resp = self
.client
.get(self.url("/version"))
.send()
.map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})?;
let resp = self.check(resp)?;
resp.json().map_err(|e| HelmApiError {
status: 0,
message: e.to_string(),
reason_code: ERROR_INTERNAL.into(),
..Default::default()
})
}
}
fn encode_query(value: &str) -> String {
value
.bytes()
.flat_map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
vec![b as char]
}
_ => format!("%{b:02X}").chars().collect(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::TcpListener;
#[test]
fn test_reason_codes_are_registry_strings() {
assert_eq!(reason_codes::ALL.len(), 122);
assert_eq!(reason_codes::EMERGENCY_STOP_FENCED, "EMERGENCY_STOP_FENCED");
assert!(reason_codes::is_registered(
reason_codes::EMERGENCY_STOP_FENCED
));
assert!(!reason_codes::is_registered("NOT_A_REGISTERED_CODE"));
}
#[test]
fn test_client_creation() {
let _client = HelmClient::new("http://localhost:8080");
}
#[test]
fn test_authenticated_client_sends_context_headers() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let server = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request = Vec::new();
let mut buffer = [0; 1024];
while !request.windows(4).any(|bytes| bytes == b"\r\n\r\n") {
let count = stream.read(&mut buffer).unwrap();
assert!(count > 0, "connection closed before HTTP headers arrived");
request.extend_from_slice(&buffer[..count]);
}
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}")
.unwrap();
String::from_utf8(request).unwrap()
});
let client = HelmClient::with_auth(
&format!("http://{address}"),
Some("test-api-key"),
Some("tenant-a"),
Some("principal-a"),
);
assert_eq!(client.health().unwrap(), serde_json::json!({}));
let request = server.join().unwrap().to_ascii_lowercase();
assert!(request.contains("authorization: bearer test-api-key"));
assert!(request.contains("x-helm-tenant-id: tenant-a"));
assert!(request.contains("x-helm-principal-id: principal-a"));
}
#[test]
fn test_api_error_reads_the_error_model() {
let body: serde_json::Value = serde_json::from_str(include_str!(
"../../../protocols/specs/errors/error-model-503.json"
))
.unwrap();
let err = api_error(503, body);
assert_eq!(err.message, "emergency-stop fence active");
assert_eq!(err.reason_code, "EMERGENCY_STOP_FENCED");
assert_eq!(err.code.as_deref(), Some("unavailable"));
assert!(err.retryable);
assert_eq!(
api_error(500, serde_json::Value::Null).reason_code,
ERROR_INTERNAL
);
}
#[test]
fn test_evaluate_decision_v5_requires_canonical_request() {
let request = EvaluateRequest {
tool: Some("read_file".to_string()),
effect_level: Some("read".to_string()),
session_id: Some("session-test".to_string()),
..EvaluateRequest::new()
};
let encoded = serde_json::to_value(&request).unwrap();
assert_eq!(encoded["tool"], "read_file");
assert_eq!(encoded["effect_level"], "read");
assert_eq!(encoded["session_id"], "session-test");
let client = HelmClient::new("http://127.0.0.1:1");
let blank = EvaluateRequest {
tool: Some("read_file".to_string()),
effect_level: Some("read".to_string()),
session_id: Some(" ".to_string()),
..EvaluateRequest::new()
};
let err = client.evaluate_decision_v5(&blank).unwrap_err();
assert_eq!(err.status, 0);
assert!(err.message.contains("non-blank session_id"));
}
#[test]
#[allow(deprecated)]
fn test_legacy_evaluate_decision_accepts_dynamic_request() {
let client = HelmClient::new("http://127.0.0.1:1");
let err = client
.evaluate_decision(&serde_json::json!({
"action": "read_file",
"resource": "read",
"context": {"session_id": "legacy-session"},
}))
.unwrap_err();
assert_eq!(err.status, 0);
}
#[test]
fn test_execution_boundary_types_serde() {
let req = EvidenceEnvelopeExportRequest {
manifest_id: "env1".to_string(),
envelope: "dsse".to_string(),
native_evidence_hash: "sha256:native".to_string(),
subject: None,
experimental: false,
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("native_evidence_hash"));
let manifest: EvidenceEnvelopeManifest = serde_json::from_str(
r#"{"manifest_id":"env1","envelope":"dsse","native_evidence_hash":"sha256:native","native_authority":false,"created_at":"2026-05-05T00:00:00Z","payload_type":"application/vnd.dsse+json","payload_hash":"sha256:payload","manifest_hash":"sha256:manifest"}"#,
)
.unwrap();
assert_eq!(manifest.payload_hash.as_deref(), Some("sha256:payload"));
let record: McpQuarantineRecord = serde_json::from_str(
r#"{"server_id":"mcp1","risk":"high","state":"quarantined","discovered_at":"2026-05-05T00:00:00Z"}"#,
)
.unwrap();
assert_eq!(record.server_id, "mcp1");
let grant: SandboxGrant = serde_json::from_str(
r#"{"grant_id":"grant1","runtime":"wazero","profile":"deny-default","env":{"mode":"deny-all"},"network":{"mode":"deny-all"},"declared_at":"2026-05-05T00:00:00Z"}"#,
)
.unwrap();
assert_eq!(grant.grant_id, "grant1");
}
#[test]
fn test_boundary_status_default_is_fail_closed() {
let status = BoundaryStatus::default();
assert_eq!(status.status, BoundaryStatusStatus::Degraded);
assert_eq!(
status.receipt_signer,
BoundaryStatusReceiptSigner::Unavailable
);
assert_eq!(
status.receipt_store,
BoundaryStatusReceiptStore::Unavailable
);
let json = serde_json::to_value(status).unwrap();
assert_eq!(json["status"], "degraded");
assert_eq!(json["receipt_signer"], "unavailable");
assert_eq!(json["receipt_store"], "unavailable");
}
}