#![allow(clippy::expect_used, clippy::unwrap_used)]
use chio_core::capability::{
CapabilityToken, CapabilityTokenBody, ChioScope, Constraint, Operation, ToolGrant,
};
use chio_core::crypto::Keypair;
use chio_guards::{MemoryGovernanceConfig, MemoryGovernanceGuard};
use chio_kernel::{Guard, GuardContext, ToolCallRequest, Verdict};
fn signed_cap(kp: &Keypair, scope: &ChioScope) -> CapabilityToken {
let body = CapabilityTokenBody {
id: "cap-mem-governance".to_string(),
issuer: kp.public_key(),
subject: kp.public_key(),
scope: scope.clone(),
issued_at: 0,
expires_at: u64::MAX,
delegation_chain: vec![],
};
CapabilityToken::sign(body, kp).expect("sign cap")
}
fn make_request_in_scope(
kp: &Keypair,
scope: &ChioScope,
tool: &str,
args: serde_json::Value,
) -> (ToolCallRequest, String, String) {
let agent_id = kp.public_key().to_hex();
let server_id = "srv-mem".to_string();
let req = ToolCallRequest {
request_id: "req-mem".to_string(),
capability: signed_cap(kp, scope),
tool_name: tool.to_string(),
server_id: server_id.clone(),
agent_id: agent_id.clone(),
arguments: args,
dpop_proof: None,
governed_intent: None,
approval_token: None,
model_metadata: None,
federated_origin_kernel_id: None,
};
(req, agent_id, server_id)
}
fn eval_at<G: Guard>(
guard: &G,
kp: &Keypair,
scope: &ChioScope,
tool: &str,
args: serde_json::Value,
matched_grant_index: Option<usize>,
) -> Verdict {
let (request, agent_id, server_id) = make_request_in_scope(kp, scope, tool, args);
let ctx = GuardContext {
request: &request,
scope,
agent_id: &agent_id,
server_id: &server_id,
session_filesystem_roots: None,
matched_grant_index,
};
guard.evaluate(&ctx).expect("guard evaluate")
}
fn scope_with_constraints(constraints: Vec<Constraint>) -> ChioScope {
ChioScope {
grants: vec![ToolGrant {
server_id: "srv-mem".to_string(),
tool_name: "*".to_string(),
operations: vec![Operation::Invoke],
constraints,
max_invocations: None,
max_cost_per_invocation: None,
max_total_cost: None,
dpop_required: None,
}],
..ChioScope::default()
}
}
#[test]
fn write_outside_memory_store_allowlist_denied() {
let guard = MemoryGovernanceGuard::new();
let scope = scope_with_constraints(vec![Constraint::MemoryStoreAllowlist(vec![
"agent-notes".to_string()
])]);
let kp = Keypair::generate();
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "secrets", "id": "x1"}),
Some(0),
);
assert!(matches!(v, Verdict::Deny), "expected Deny, got {v:?}");
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": "x1"}),
Some(0),
);
assert!(matches!(v, Verdict::Allow), "expected Allow, got {v:?}");
}
#[test]
fn read_outside_memory_store_allowlist_denied() {
let guard = MemoryGovernanceGuard::new();
let scope = scope_with_constraints(vec![Constraint::MemoryStoreAllowlist(vec![
"agent-notes".to_string()
])]);
let kp = Keypair::generate();
let v = eval_at(
&guard,
&kp,
&scope,
"vector_query",
serde_json::json!({"collection": "secrets"}),
Some(0),
);
assert!(matches!(v, Verdict::Deny));
}
#[test]
fn writes_exceeding_max_memory_entries_denied() {
let guard = MemoryGovernanceGuard::with_config(MemoryGovernanceConfig {
max_memory_entries: Some(2),
..MemoryGovernanceConfig::default()
})
.expect("build guard");
let scope = ChioScope::default();
let kp = Keypair::generate();
for i in 0..2 {
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": format!("id-{i}")}),
None,
);
assert!(
matches!(v, Verdict::Allow),
"write {i} must Allow, got {v:?}"
);
}
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": "id-3"}),
None,
);
assert!(matches!(v, Verdict::Deny), "3rd write must Deny, got {v:?}");
}
#[test]
fn max_retention_ttl_honored() {
let guard = MemoryGovernanceGuard::with_config(MemoryGovernanceConfig {
max_retention_ttl_secs: Some(3_600),
..MemoryGovernanceConfig::default()
})
.expect("build guard");
let scope = ChioScope::default();
let kp = Keypair::generate();
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": "a", "ttl": 1_800}),
None,
);
assert!(
matches!(v, Verdict::Allow),
"small TTL must Allow, got {v:?}"
);
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": "b", "ttl": 7_200}),
None,
);
assert!(matches!(v, Verdict::Deny), "over TTL must Deny, got {v:?}");
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "agent-notes", "id": "c"}),
None,
);
assert!(
matches!(v, Verdict::Deny),
"missing TTL must Deny, got {v:?}"
);
}
#[test]
fn config_store_allowlist_composes_with_grant_allowlist() {
let guard = MemoryGovernanceGuard::with_config(MemoryGovernanceConfig {
store_allowlist: vec!["deployment-wide".to_string()],
..MemoryGovernanceConfig::default()
})
.expect("build guard");
let scope = scope_with_constraints(vec![Constraint::MemoryStoreAllowlist(vec![
"grant-scoped".to_string(),
])]);
let kp = Keypair::generate();
for store in ["deployment-wide", "grant-scoped"] {
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": store, "id": "x"}),
Some(0),
);
assert!(
matches!(v, Verdict::Allow),
"store {store} should allow, got {v:?}"
);
}
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({"collection": "forbidden", "id": "x"}),
Some(0),
);
assert!(matches!(v, Verdict::Deny));
}
#[test]
fn non_memory_actions_pass_through() {
let guard = MemoryGovernanceGuard::new();
let scope = ChioScope::default();
let kp = Keypair::generate();
let v = eval_at(
&guard,
&kp,
&scope,
"read_file",
serde_json::json!({"path": "/tmp/x"}),
None,
);
assert!(matches!(v, Verdict::Allow));
}
#[test]
fn deny_patterns_block_matching_content() {
let guard = MemoryGovernanceGuard::with_config(MemoryGovernanceConfig {
deny_patterns: vec![r"(?i)password".to_string()],
..MemoryGovernanceConfig::default()
})
.expect("build guard");
let scope = ChioScope::default();
let kp = Keypair::generate();
let v = eval_at(
&guard,
&kp,
&scope,
"vector_upsert",
serde_json::json!({
"collection": "agent-notes",
"id": "x",
"content": "user password = hunter2"
}),
None,
);
assert!(matches!(v, Verdict::Deny));
}
#[test]
fn invalid_regex_fails_initialization() {
let cfg = MemoryGovernanceConfig {
deny_patterns: vec!["(unclosed".to_string()],
..MemoryGovernanceConfig::default()
};
assert!(MemoryGovernanceGuard::with_config(cfg).is_err());
}