#![allow(clippy::expect_used, clippy::unwrap_used)]
use chio_core_types::capability::{CapabilityToken, ChioScope, Operation, ToolGrant};
use chio_core_types::crypto::Keypair;
use chio_core_types::receipt::{
ChioReceipt, ChioReceiptBody, Decision, ToolCallAction, TrustLevel,
};
use chio_http_core::{
EvaluateResponse, ExecutionNonceConfig, HttpMethod, HttpReceipt, HttpReceiptBody,
InMemoryExecutionNonceStore, NonceBinding, SignedExecutionNonce, Verdict,
};
use chio_kernel::{
mint_execution_nonce, verify_execution_nonce, ChioKernel, KernelConfig, KernelError,
NestedFlowBridge, ServerId, ToolCallRequest, ToolServerConnection,
DEFAULT_CHECKPOINT_BATCH_SIZE, DEFAULT_MAX_STREAM_DURATION_SECS,
DEFAULT_MAX_STREAM_TOTAL_BYTES,
};
struct EchoServer {
id: ServerId,
tool: String,
}
impl ToolServerConnection for EchoServer {
fn server_id(&self) -> &str {
&self.id
}
fn tool_names(&self) -> Vec<String> {
vec![self.tool.clone()]
}
fn invoke(
&self,
tool_name: &str,
arguments: serde_json::Value,
_bridge: Option<&mut dyn NestedFlowBridge>,
) -> Result<serde_json::Value, KernelError> {
if tool_name != self.tool {
return Err(KernelError::Internal(format!(
"unexpected tool {tool_name}"
)));
}
Ok(arguments)
}
}
fn make_kernel_with_nonce() -> ChioKernel {
let mut kernel = ChioKernel::new(KernelConfig {
keypair: Keypair::generate(),
ca_public_keys: vec![],
max_delegation_depth: 4,
policy_hash: "http-nonce-test-policy".to_string(),
allow_sampling: false,
allow_sampling_tool_use: false,
allow_elicitation: false,
max_stream_duration_secs: DEFAULT_MAX_STREAM_DURATION_SECS,
max_stream_total_bytes: DEFAULT_MAX_STREAM_TOTAL_BYTES,
require_web3_evidence: false,
checkpoint_batch_size: DEFAULT_CHECKPOINT_BATCH_SIZE,
retention_config: None,
});
kernel.register_tool_server(Box::new(EchoServer {
id: "srv-a".to_string(),
tool: "read_file".to_string(),
}));
let cfg = ExecutionNonceConfig {
nonce_ttl_secs: 30,
nonce_store_capacity: 1024,
require_nonce: false,
};
let store = Box::new(InMemoryExecutionNonceStore::from_config(&cfg));
kernel.set_execution_nonce_store(cfg, store);
kernel
}
fn issue_capability(kernel: &ChioKernel, subject: &Keypair) -> CapabilityToken {
let scope = ChioScope {
grants: vec![ToolGrant {
server_id: "srv-a".to_string(),
tool_name: "read_file".to_string(),
operations: vec![Operation::Invoke],
constraints: vec![],
max_invocations: None,
max_cost_per_invocation: None,
max_total_cost: None,
dpop_required: None,
}],
resource_grants: Vec::new(),
prompt_grants: Vec::new(),
};
kernel
.issue_capability(&subject.public_key(), scope, 300)
.expect("issue capability")
}
fn make_request(id: &str, cap: &CapabilityToken) -> ToolCallRequest {
ToolCallRequest {
request_id: id.to_string(),
capability: cap.clone(),
tool_name: "read_file".to_string(),
server_id: "srv-a".to_string(),
agent_id: cap.subject.to_hex(),
arguments: serde_json::json!({"path": "/tmp/hello"}),
dpop_proof: None,
governed_intent: None,
approval_token: None,
model_metadata: None,
federated_origin_kernel_id: None,
}
}
fn http_receipt(signer: &Keypair, id: &str) -> HttpReceipt {
let body = HttpReceiptBody {
id: id.to_string(),
request_id: "req-test".to_string(),
route_pattern: "/evaluate".to_string(),
method: HttpMethod::Post,
caller_identity_hash: "0".repeat(64),
session_id: None,
verdict: Verdict::Allow,
evidence: vec![],
response_status: 200,
timestamp: 1_000_000,
content_hash: "0".repeat(64),
policy_hash: "test-policy".to_string(),
capability_id: None,
metadata: None,
kernel_key: signer.public_key(),
};
HttpReceipt::sign(body, signer).expect("sign http receipt")
}
#[test]
fn evaluate_response_serializes_execution_nonce_field() {
let kp = Keypair::generate();
let chio_body = ChioReceiptBody {
id: "rcpt-1".to_string(),
timestamp: 1_000_000,
capability_id: "cap-1".to_string(),
tool_server: "srv-a".to_string(),
tool_name: "read_file".to_string(),
action: ToolCallAction::from_parameters(serde_json::json!({"path": "/x"})).unwrap(),
decision: Decision::Allow,
content_hash: "0".repeat(64),
policy_hash: "policy".to_string(),
evidence: vec![],
metadata: None,
trust_level: TrustLevel::default(),
kernel_key: kp.public_key(),
tenant_id: None,
};
let chio_receipt = ChioReceipt::sign(chio_body, &kp).unwrap();
let http_rcpt = http_receipt(&kp, "http-rcpt-nonce");
let binding = NonceBinding {
subject_id: "subject-1".to_string(),
capability_id: "cap-1".to_string(),
tool_server: "srv-a".to_string(),
tool_name: "read_file".to_string(),
parameter_hash: chio_receipt.action.parameter_hash.clone(),
};
let cfg = ExecutionNonceConfig::default();
let signed = mint_execution_nonce(&kp, binding, &cfg, 1_000_000).unwrap();
let response = EvaluateResponse {
verdict: Verdict::Allow,
receipt: http_rcpt,
evidence: vec![],
execution_nonce: Some(signed.clone()),
};
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("execution_nonce"));
assert!(json.contains("chio.execution_nonce.v1"));
let back: EvaluateResponse = serde_json::from_str(&json).unwrap();
let recovered = back.execution_nonce.expect("nonce round-trips");
assert_eq!(recovered.nonce.nonce_id, signed.nonce.nonce_id);
assert_eq!(recovered.nonce.expires_at, signed.nonce.expires_at);
}
#[test]
fn evaluate_response_omits_nonce_when_absent() {
let kp = Keypair::generate();
let http_rcpt = http_receipt(&kp, "http-rcpt-absent");
let response = EvaluateResponse {
verdict: Verdict::Allow,
receipt: http_rcpt,
evidence: vec![],
execution_nonce: None,
};
let json = serde_json::to_string(&response).unwrap();
assert!(
!json.contains("execution_nonce"),
"unexpected execution_nonce field: {json}"
);
}
#[test]
fn kernel_issued_nonce_verifies_and_replay_fails_end_to_end() {
let kernel = make_kernel_with_nonce();
let agent = Keypair::generate();
let cap = issue_capability(&kernel, &agent);
let req = make_request("http-e2e", &cap);
let response = kernel
.evaluate_tool_call_blocking(&req)
.expect("evaluate allow");
let signed: SignedExecutionNonce = *response
.execution_nonce
.expect("allow response carries a nonce");
let binding = NonceBinding {
subject_id: cap.subject.to_hex(),
capability_id: cap.id.clone(),
tool_server: req.server_id.clone(),
tool_name: req.tool_name.clone(),
parameter_hash: response.receipt.action.parameter_hash.clone(),
};
kernel
.verify_presented_execution_nonce(&signed, &binding)
.expect("first presentation verifies");
let err = kernel
.verify_presented_execution_nonce(&signed, &binding)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("consumed") || msg.contains("Replayed"),
"{msg}"
);
}
#[test]
fn stale_nonce_is_rejected_against_local_clock() {
let kp = Keypair::generate();
let store = InMemoryExecutionNonceStore::default();
let cfg = ExecutionNonceConfig::default();
let binding = NonceBinding {
subject_id: "s".into(),
capability_id: "c".into(),
tool_server: "t".into(),
tool_name: "n".into(),
parameter_hash: "h".into(),
};
let now = 1_000_000;
let signed = mint_execution_nonce(&kp, binding.clone(), &cfg, now).unwrap();
let err = verify_execution_nonce(
&signed,
&kp.public_key(),
&binding,
now + cfg.nonce_ttl_secs as i64 + 1,
&store,
)
.unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("expired"), "{msg}");
}