#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::sync::Arc;
use std::time::Duration;
use chio_core::crypto::{sha256_hex, Keypair};
use chio_core::receipt::{
body::chio_receipt_id, body::ChioReceiptBody, decision::Decision, decision::ToolCallAction,
kinds::TrustLevel, signing::CHIO_RECEIPT_SIGNING_NONCE_METADATA_KEY,
};
use chio_kernel::{
ChioKernel, KernelConfig, DEFAULT_CHECKPOINT_BATCH_SIZE, DEFAULT_MAX_STREAM_DURATION_SECS,
DEFAULT_MAX_STREAM_TOTAL_BYTES,
};
use serde_json::json;
const KERNEL_SEED: [u8; 32] = [
0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7E, 0x8F, 0x90,
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00,
];
fn make_keypair() -> Keypair {
Keypair::from_seed(&KERNEL_SEED)
}
fn make_config(keypair: Keypair) -> KernelConfig {
KernelConfig {
keypair,
ca_public_keys: Vec::new(),
max_delegation_depth: 5,
policy_hash: sha256_hex(b"policy:test-async-receipt").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,
allow_ephemeral_receipt_log: true,
allow_ephemeral_revocation_store: true,
checkpoint_batch_size: DEFAULT_CHECKPOINT_BATCH_SIZE,
retention_config: None,
memory_budget: chio_kernel::MemoryBudgetConfig::defaults(),
deadlines: chio_kernel::HotPathDeadlineConfig::default(),
}
}
fn make_body(n: usize, kernel_key: &Keypair) -> (ChioReceiptBody, Vec<u8>) {
let nonce = format!("t3-{n:04}");
let action = ToolCallAction::from_parameters(json!({
"n": n,
"label": nonce,
}))
.expect("payload canonicalises");
let canonical_content = action.parameter_hash.as_bytes().to_vec();
let content_hash = sha256_hex(&canonical_content);
let policy_hash = sha256_hex(format!("policy:{nonce}").as_bytes());
let mut body = ChioReceiptBody {
id: format!("rcpt-{nonce}"),
timestamp: 1_700_000_000 + (n as u64),
capability_id: format!("cap-{nonce}"),
tool_server: "tool.example".to_string(),
tool_name: "echo".to_string(),
action,
decision: Some(Decision::Allow),
receipt_kind: Default::default(),
boundary_class: Default::default(),
observation_outcome: None,
tool_origin: Default::default(),
redaction_mode: Default::default(),
actor_chain: Vec::new(),
content_hash,
policy_hash,
evidence: Vec::new(),
metadata: None,
trust_level: TrustLevel::default(),
tenant_id: None,
kernel_key: kernel_key.public_key(),
bbs_projection_version: None,
};
body.id = chio_receipt_id(&body).expect("canonical receipt id computes");
(body, canonical_content)
}
fn bind_signing_nonce(body: &mut ChioReceiptBody) {
let nonce = body.id.trim();
if nonce.is_empty() {
return;
}
let mut metadata = match body.metadata.take() {
Some(serde_json::Value::Object(map)) => map,
Some(value) => {
let mut map = serde_json::Map::new();
map.insert("original_metadata".to_string(), value);
map
}
None => serde_json::Map::new(),
};
metadata.insert(
CHIO_RECEIPT_SIGNING_NONCE_METADATA_KEY.to_string(),
serde_json::Value::String(nonce.to_string()),
);
body.metadata = Some(serde_json::Value::Object(metadata));
}
fn expected_signed_id(body: &ChioReceiptBody) -> String {
let mut bound = body.clone();
bind_signing_nonce(&mut bound);
chio_receipt_id(&bound).expect("canonical receipt id computes")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn mpsc_signing_path_signs_n_receipts_with_valid_signatures() {
let keypair = make_keypair();
let kernel = Arc::new(ChioKernel::new(make_config(keypair.clone())));
let public_key = keypair.public_key();
const N: usize = 32;
let mut handles = Vec::with_capacity(N);
for i in 0..N {
let kernel = Arc::clone(&kernel);
let (body, canonical_content) = make_body(i, &keypair);
let expected_id = expected_signed_id(&body);
let expected_timestamp = body.timestamp;
handles.push(tokio::spawn(async move {
let receipt = kernel
.sign_receipt_via_channel(body, canonical_content)
.await
.expect("mpsc signing should succeed");
(expected_id, expected_timestamp, receipt)
}));
}
let mut signed = Vec::with_capacity(N);
for handle in handles {
signed.push(handle.await.expect("signing task should not panic"));
}
for (expected_id, expected_timestamp, receipt) in &signed {
assert!(
receipt.verify_signature().expect("signature verifiable"),
"receipt {} signature failed verification",
receipt.id
);
assert_eq!(receipt.kernel_key, public_key, "kernel_key drift");
assert_eq!(
&receipt.id, expected_id,
"receipt id diverged from the canonical nonce-bound id"
);
assert_eq!(
receipt.timestamp, *expected_timestamp,
"receipt timestamp was rewritten"
);
}
let signature_set: std::collections::HashSet<String> = signed
.iter()
.map(|(_, _, r)| r.signature.to_hex())
.collect();
assert_eq!(
signature_set.len(),
signed.len(),
"duplicate signatures: channel collapsed distinct requests"
);
kernel.shutdown().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn mpsc_signing_path_applies_backpressure_at_capacity() {
let keypair = make_keypair();
let kernel = Arc::new(ChioKernel::new(make_config(keypair.clone())));
let default_capacity = chio_kernel::SIGNING_CHANNEL_DEFAULT_CAPACITY;
assert!(
default_capacity >= 16,
"default capacity {default_capacity} too small to exercise backpressure"
);
let target = chio_kernel::SIGNING_CHANNEL_DEFAULT_CAPACITY.saturating_mul(2);
let mut handles = Vec::with_capacity(target);
for i in 0..target {
let kernel = Arc::clone(&kernel);
let (body, canonical_content) = make_body(i, &keypair);
handles.push(tokio::spawn(async move {
kernel
.sign_receipt_via_channel(body, canonical_content)
.await
}));
}
let mut signed = Vec::with_capacity(target);
for handle in handles {
let result = handle.await.expect("signer task does not panic");
signed.push(result.expect("backpressured send eventually succeeds"));
}
for receipt in &signed {
assert!(
receipt.verify_signature().expect("signature verifiable"),
"post-backpressure receipt {} failed verification",
receipt.id
);
}
let mut ids: Vec<&str> = signed.iter().map(|r| r.id.as_str()).collect();
ids.sort_unstable();
let original_len = ids.len();
ids.dedup();
assert_eq!(
ids.len(),
original_len,
"post-backpressure id duplication: channel collapsed requests"
);
kernel.shutdown().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn shutdown_drains_in_flight_signing_requests() {
let keypair = make_keypair();
let kernel = Arc::new(ChioKernel::new(make_config(keypair.clone())));
const N: usize = 8;
let mut phase_a_handles = Vec::with_capacity(N);
for i in 0..N {
let kernel = Arc::clone(&kernel);
let (body, canonical_content) = make_body(i, &keypair);
phase_a_handles.push(tokio::spawn(async move {
kernel
.sign_receipt_via_channel(body, canonical_content)
.await
}));
}
let mut phase_a_signed = Vec::with_capacity(N);
for handle in phase_a_handles {
let receipt = handle
.await
.expect("phase-A producer task does not panic")
.expect("phase-A request observed signed receipt");
phase_a_signed.push(receipt);
}
for receipt in &phase_a_signed {
assert!(
receipt.verify_signature().expect("signature verifiable"),
"phase-A receipt {} failed verification",
receipt.id
);
}
let racing_kernel = Arc::clone(&kernel);
let (racing_body, racing_content) = make_body(N + 1, &keypair);
let racing_producer = tokio::spawn(async move {
racing_kernel
.sign_receipt_via_channel(racing_body, racing_content)
.await
});
tokio::time::timeout(Duration::from_secs(5), kernel.shutdown())
.await
.expect("shutdown must complete within 5 s");
let racing_outcome = tokio::time::timeout(Duration::from_secs(2), racing_producer)
.await
.expect("racing producer must resolve within 2 s of shutdown")
.expect("racing producer task does not panic");
match racing_outcome {
Ok(receipt) => {
assert!(
receipt.verify_signature().expect("signature verifiable"),
"racing receipt {} failed verification on drain path",
receipt.id
);
}
Err(err) => {
let msg = format!("{err}");
assert!(
msg.contains("signing task")
|| msg.contains("shut down")
|| msg.contains("no longer running"),
"unexpected error message on fail-closed path: {msg}"
);
}
}
tokio::time::timeout(Duration::from_secs(1), kernel.shutdown())
.await
.expect("second shutdown must be a fast no-op");
let (post_body, post_content) = make_body(9_999, &keypair);
let post = tokio::time::timeout(
Duration::from_secs(1),
kernel.sign_receipt_via_channel(post_body, post_content),
)
.await
.expect("post-shutdown sign must resolve, not hang");
assert!(
post.is_err(),
"signing after shutdown should fail closed, got Ok(_)"
);
}