chio-kernel 0.1.0

Chio runtime kernel: capability validation, guard evaluation, receipt signing
Documentation
#![allow(clippy::expect_used, clippy::unwrap_used)]

use std::io::Cursor;

use chio_core::capability::{
    CapabilityToken, CapabilityTokenBody, ChioScope, Operation, ToolGrant,
};
use chio_core::crypto::Keypair;
use chio_kernel::transport::{read_frame, ChioTransport, TransportError};

fn encode_frame(body: &[u8]) -> Vec<u8> {
    let mut frame = Vec::with_capacity(4 + body.len());
    frame.extend_from_slice(&(body.len() as u32).to_be_bytes());
    frame.extend_from_slice(body);
    frame
}

fn make_capability_token() -> CapabilityToken {
    let kp = Keypair::generate();
    CapabilityToken::sign(
        CapabilityTokenBody {
            id: "cap-adversarial-001".to_string(),
            issuer: kp.public_key(),
            subject: kp.public_key(),
            scope: ChioScope {
                grants: vec![ToolGrant {
                    server_id: "srv".to_string(),
                    tool_name: "echo".to_string(),
                    operations: vec![Operation::Invoke],
                    constraints: vec![],
                    max_invocations: None,
                    max_cost_per_invocation: None,
                    max_total_cost: None,
                    dpop_required: None,
                }],
                ..ChioScope::default()
            },
            issued_at: 1_000,
            expires_at: 2_000,
            delegation_chain: vec![],
        },
        &kp,
    )
    .expect("capability token should sign")
}

fn valid_tool_call_json() -> serde_json::Value {
    serde_json::json!({
        "type": "tool_call_request",
        "id": "req-adversarial-001",
        "capability_token": serde_json::to_value(make_capability_token()).expect("token should serialize"),
        "server_id": "srv",
        "tool": "echo",
        "params": {
            "text": "hello"
        }
    })
}

#[test]
fn malformed_json_body_returns_deserialize_error() {
    let frame = encode_frame(br#"{"type":"tool_call_request","id":"req-1""#);
    let mut transport = ChioTransport::new(Cursor::new(frame), Vec::new());

    let err = transport.recv().unwrap_err();
    assert!(matches!(err, TransportError::Deserialize(_)));
}

#[test]
fn zero_length_body_returns_deserialize_error() {
    let mut transport = ChioTransport::new(Cursor::new(0_u32.to_be_bytes().to_vec()), Vec::new());

    let err = transport.recv().unwrap_err();
    assert!(matches!(err, TransportError::Deserialize(_)));
}

#[test]
fn truncated_frame_body_returns_connection_closed() {
    let full_body = serde_json::to_vec(&valid_tool_call_json()).expect("body should serialize");
    let truncated_len = full_body.len() + 8;
    let mut frame = Vec::with_capacity(4 + full_body.len());
    frame.extend_from_slice(&(truncated_len as u32).to_be_bytes());
    frame.extend_from_slice(&full_body[..full_body.len() / 2]);

    let err = read_frame(&mut Cursor::new(frame)).unwrap_err();
    assert!(matches!(err, TransportError::ConnectionClosed));
}

#[test]
fn missing_required_id_field_returns_deserialize_error() {
    let mut payload = valid_tool_call_json();
    payload
        .as_object_mut()
        .expect("payload should be an object")
        .remove("id");

    let body = serde_json::to_vec(&payload).expect("payload should serialize");
    let mut transport = ChioTransport::new(Cursor::new(encode_frame(&body)), Vec::new());

    let err = transport.recv().unwrap_err();
    assert!(matches!(err, TransportError::Deserialize(_)));
}

#[test]
fn wrong_type_fields_return_deserialize_error() {
    let payload = serde_json::json!({
        "type": "tool_call_request",
        "id": 123,
        "capability_token": serde_json::to_value(make_capability_token()).expect("token should serialize"),
        "server_id": ["srv"],
        "tool": false,
        "params": null
    });
    let body = serde_json::to_vec(&payload).expect("payload should serialize");
    let mut transport = ChioTransport::new(Cursor::new(encode_frame(&body)), Vec::new());

    let err = transport.recv().unwrap_err();
    assert!(matches!(err, TransportError::Deserialize(_)));
}