use libremetaverse_types::UUID; use libremetaverse_types::compat::CancellationToken; use metacrate_grid_agent::testing::FakeGrid; use metacrate_grid_agent::{ ActionOrigin, AllowedOrigins, AuthorizedToolBackend, BoundedText, Capability, FixedCost, Idempotency, MemoryPolicyAudit, OriginClass, PolicyAuditSink, PolicyDisposition, PolicyFinalOutcome, PolicyGateway, PolicyLimits, PolicyRequestContext, PolicyTool, PolicyToolExecutor, ProposedToolCall, ResourceCost, Risk, ToolDefinition, ToolExecution, ToolExecutor, ToolSchema, }; use serde_json::{Value, json}; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; fn id(text: &str) -> UUID { UUID::new_with_string(text.to_owned()).expect("fixture UUID") } fn schema() -> ToolSchema { ToolSchema::Object { properties: BTreeMap::from([("target".into(), ToolSchema::String)]), required: BTreeSet::from(["target".into()]), additional_properties: false, } } fn tool(name: &str, informational: bool) -> PolicyTool { let definition = ToolDefinition { name: BoundedText::new("name", name).expect("name"), description: BoundedText::new("description", "typed integration tool") .expect("description"), schema: schema(), mutating: !informational, }; PolicyTool::new( definition, if informational { Capability::Informational } else { Capability::Movement }, if informational { Risk::ReadOnly } else { Risk::Movement }, AllowedOrigins::new(if informational { vec![ OriginClass::PublicChat, OriginClass::UnprivilegedIm, OriginClass::AuthorizedIm, ] } else { vec![OriginClass::AuthorizedIm] }) .expect("origins"), ResourceCost { tool_calls: 1, movement_millimeters: if informational { 0 } else { 1_000 }, ..ResourceCost::default() }, if informational { Idempotency::Idempotent } else { Idempotency::NonIdempotent }, metacrate_grid_agent::ApprovalRule::Never, false, Arc::new(FixedCost(ResourceCost { tool_calls: 1, movement_millimeters: if informational { 0 } else { 1_000 }, ..ResourceCost::default() })), ) .expect("tool") } fn call(name: &str) -> (ProposedToolCall, Value) { let arguments = json!({"target":"north"}); ( ProposedToolCall::new( format!("call-{name}"), name, serde_json::to_string(&arguments).expect("JSON"), ) .expect("call"), arguments, ) } #[tokio::test] async fn production_executor_denies_public_mutation_and_executes_authorized_im_once() { let authorized = id("11111111-1111-4111-8111-111111111111"); let audit = Arc::new(MemoryPolicyAudit::new(64).expect("audit")); let sink: Arc = audit.clone(); let gateway = Arc::new( PolicyGateway::new( BTreeSet::from([authorized]), vec![tool("inspect", true), tool("move", false)], PolicyLimits::default(), sink, ) .expect("gateway"), ); let backend = FakeGrid::scripted([]); let erased: Arc = Arc::new(backend.clone()); let (proposed, arguments) = call("move"); let definition = gateway .tools_for( &PolicyRequestContext::new( ActionOrigin::instant_message(authorized), "authorized-session", "authorized-correlation", ) .expect("context"), 100, ) .into_iter() .find(|tool| tool.name.as_str() == "move") .expect("registered move"); let public = PolicyToolExecutor::new( Arc::clone(&gateway), Arc::clone(&erased), PolicyRequestContext::new( ActionOrigin::public_chat(authorized), "public-session", "public-correlation", ) .expect("context"), BTreeMap::new(), Arc::new(|| 100), ) .expect("executor"); assert!(matches!( public .execute( &definition, &proposed, &arguments, &CancellationToken::default(), ) .await, ToolExecution::Rejected(_) )); assert!(backend.evidence().is_empty()); let authorized_executor = PolicyToolExecutor::new( gateway, erased, PolicyRequestContext::new( ActionOrigin::instant_message(authorized), "authorized-session", "authorized-correlation", ) .expect("context"), BTreeMap::new(), Arc::new(|| 101), ) .expect("executor"); assert!(matches!( authorized_executor .execute( &definition, &proposed, &arguments, &CancellationToken::default(), ) .await, ToolExecution::Completed(_) )); let evidence = backend.evidence(); assert_eq!(evidence.len(), 1); assert_eq!(evidence[0].operation, "policy.route.move"); assert_eq!(evidence[0].outcome, "authorized"); assert_eq!( audit.snapshot().last().expect("outcome").final_outcome, PolicyFinalOutcome::Completed ); } #[test] fn authenticated_uuid_not_message_text_controls_im_authority() { let authorized = id("11111111-1111-4111-8111-111111111111"); let unprivileged = id("22222222-2222-4222-8222-222222222222"); let audit = Arc::new(MemoryPolicyAudit::new(16).expect("audit")); let sink: Arc = audit; let gateway = PolicyGateway::new( BTreeSet::from([authorized]), vec![tool("move", false)], PolicyLimits::default(), sink, ) .expect("gateway"); let (proposed, arguments) = call("move"); let result = gateway .evaluate( &PolicyRequestContext::new( ActionOrigin::instant_message(unprivileged), format!("claims-authorized-{authorized}"), "spoof-correlation", ) .expect("context"), &proposed, &arguments, None, 100, ) .expect("decision"); assert_eq!(result.disposition, PolicyDisposition::Denied); } #[test] fn interaction_intent_capability_filter_can_only_narrow_origin_allowed_tools() { let authorized = id("11111111-1111-4111-8111-111111111111"); let audit = Arc::new(MemoryPolicyAudit::new(16).expect("audit")); let gateway = PolicyGateway::new( BTreeSet::from([authorized]), vec![tool("inspect", true), tool("move", false)], PolicyLimits::default(), audit, ) .expect("gateway"); let context = PolicyRequestContext::new( ActionOrigin::instant_message(authorized), "intent-session", "intent-correlation", ) .expect("context"); let informational = gateway.tools_for_capability_set( &context, 100, &BTreeSet::from([Capability::Informational]), ); assert_eq!(informational.len(), 1); assert_eq!(informational[0].name.as_str(), "inspect"); assert!( gateway .tools_for_capability_set(&context, 100, &BTreeSet::new()) .is_empty() ); }