use chio_core_types::session::{
EnterpriseFederationMethod, EnterpriseIdentityContext, OAuthBearerFederatedClaims,
OAuthBearerSessionAuthInput,
};
use std::collections::BTreeMap;
fn oauth_auth_with_enterprise_tenant(tenant: &str) -> SessionAuthContext {
SessionAuthContext::streamable_http_oauth_bearer_with_claims(OAuthBearerSessionAuthInput {
principal: Some(format!("oidc:https://issuer.example#sub:user-{tenant}")),
issuer: Some("https://issuer.example".to_string()),
subject: Some(format!("user-{tenant}")),
audience: Some("chio-mcp".to_string()),
scopes: vec!["mcp:invoke".to_string()],
federated_claims: OAuthBearerFederatedClaims::default(),
enterprise_identity: Some(EnterpriseIdentityContext {
provider_id: "provider-tenant-test".to_string(),
provider_record_id: None,
provider_kind: "oidc_jwks".to_string(),
federation_method: EnterpriseFederationMethod::Jwt,
principal: format!("oidc:https://issuer.example#sub:user-{tenant}"),
subject_key: format!("subject-key-{tenant}"),
client_id: Some("client-abc".to_string()),
object_id: None,
tenant_id: Some(tenant.to_string()),
organization_id: None,
groups: Vec::new(),
roles: Vec::new(),
source_subject: None,
attribute_sources: BTreeMap::new(),
trust_material_ref: None,
}),
token_fingerprint: Some(format!("fp-{tenant}")),
origin: Some("https://app.example".to_string()),
})
}
#[test]
fn session_tenant_id_is_stamped_on_tool_call_receipt() {
let mut kernel = ChioKernel::new(make_config());
kernel.register_tool_server(Box::new(EchoServer::new("srv-a", vec!["read_file"])));
let agent_kp = make_keypair();
let scope = make_scope(vec![make_grant("srv-a", "read_file")]);
let cap = make_capability(&kernel, &agent_kp, scope, 300);
let session_id = kernel.open_session(agent_kp.public_key().to_hex(), vec![cap.clone()]);
kernel
.set_session_auth_context(&session_id, oauth_auth_with_enterprise_tenant("tenant-A"))
.unwrap();
kernel.activate_session(&session_id).unwrap();
let context = make_operation_context(&session_id, "req-tenant", &agent_kp.public_key().to_hex());
let operation = SessionOperation::ToolCall(ToolCallOperation {
capability: cap,
server_id: "srv-a".to_string(),
tool_name: "read_file".to_string(),
arguments: serde_json::json!({"path": "/app/src/main.rs"}),
model_metadata: None,
});
let response = session_tool_call(
kernel
.evaluate_session_operation(&context, &operation)
.unwrap(),
)
.expect("expected tool call response");
assert_eq!(response.verdict, Verdict::Allow);
assert_eq!(
response.receipt.tenant_id.as_deref(),
Some("tenant-A"),
"receipt must carry the session-derived tenant tag"
);
assert!(response.receipt.verify_signature().unwrap());
}
#[test]
fn session_without_tenant_id_produces_untagged_receipt() {
let mut kernel = ChioKernel::new(make_config());
kernel.register_tool_server(Box::new(EchoServer::new("srv-a", vec!["read_file"])));
let agent_kp = make_keypair();
let scope = make_scope(vec![make_grant("srv-a", "read_file")]);
let cap = make_capability(&kernel, &agent_kp, scope, 300);
let session_id = kernel.open_session(agent_kp.public_key().to_hex(), vec![cap.clone()]);
kernel.activate_session(&session_id).unwrap();
let context = make_operation_context(&session_id, "req-notenant", &agent_kp.public_key().to_hex());
let operation = SessionOperation::ToolCall(ToolCallOperation {
capability: cap,
server_id: "srv-a".to_string(),
tool_name: "read_file".to_string(),
arguments: serde_json::json!({"path": "/app/src/main.rs"}),
model_metadata: None,
});
let response = session_tool_call(
kernel
.evaluate_session_operation(&context, &operation)
.unwrap(),
)
.expect("expected tool call response");
assert_eq!(response.verdict, Verdict::Allow);
assert!(
response.receipt.tenant_id.is_none(),
"single-tenant session must produce receipts without a tenant tag"
);
}
#[test]
fn blocking_evaluate_without_session_leaves_tenant_id_none() {
let mut kernel = ChioKernel::new(make_config());
kernel.register_tool_server(Box::new(EchoServer::new("srv-a", vec!["read_file"])));
let agent_kp = make_keypair();
let scope = make_scope(vec![make_grant("srv-a", "read_file")]);
let cap = make_capability(&kernel, &agent_kp, scope, 300);
let request = make_request("req-blocking", &cap, "read_file", "srv-a");
let response = kernel.evaluate_tool_call_blocking(&request).unwrap();
assert_eq!(response.verdict, Verdict::Allow);
assert!(
response.receipt.tenant_id.is_none(),
"sessionless blocking evaluate must produce receipts without a tenant tag"
);
}
#[test]
fn tenant_id_falls_back_to_oauth_federated_claims() {
let mut kernel = ChioKernel::new(make_config());
kernel.register_tool_server(Box::new(EchoServer::new("srv-a", vec!["read_file"])));
let agent_kp = make_keypair();
let scope = make_scope(vec![make_grant("srv-a", "read_file")]);
let cap = make_capability(&kernel, &agent_kp, scope, 300);
let auth = SessionAuthContext::streamable_http_oauth_bearer_with_claims(
OAuthBearerSessionAuthInput {
principal: Some("oidc:https://issuer.example#sub:user-Z".to_string()),
issuer: Some("https://issuer.example".to_string()),
subject: Some("user-Z".to_string()),
audience: Some("chio-mcp".to_string()),
scopes: vec!["mcp:invoke".to_string()],
federated_claims: OAuthBearerFederatedClaims {
tenant_id: Some("tenant-fed".to_string()),
..OAuthBearerFederatedClaims::default()
},
enterprise_identity: None,
token_fingerprint: Some("fp-Z".to_string()),
origin: Some("https://app.example".to_string()),
},
);
let session_id = kernel.open_session(agent_kp.public_key().to_hex(), vec![cap.clone()]);
kernel.set_session_auth_context(&session_id, auth).unwrap();
kernel.activate_session(&session_id).unwrap();
let context = make_operation_context(&session_id, "req-fed", &agent_kp.public_key().to_hex());
let operation = SessionOperation::ToolCall(ToolCallOperation {
capability: cap,
server_id: "srv-a".to_string(),
tool_name: "read_file".to_string(),
arguments: serde_json::json!({"path": "/app/src/main.rs"}),
model_metadata: None,
});
let response = session_tool_call(
kernel
.evaluate_session_operation(&context, &operation)
.unwrap(),
)
.expect("expected tool call response");
assert_eq!(response.verdict, Verdict::Allow);
assert_eq!(response.receipt.tenant_id.as_deref(), Some("tenant-fed"));
}