Files
MetaCrate/crates/metacrate-grid-agent/src/policy.rs
Chili Palmer 962d17257d
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m44s
CI / required (push) Failing after 2m43s
feat(grid-agent): add portable control plane (#126)
2026-08-18 04:29:12 +00:00

1982 lines
64 KiB
Rust

//! Central deny-by-default authorization, approval, budget, and audit gateway.
#![allow(clippy::missing_errors_doc)] // Public fallible APIs share PolicyError/PolicyReasonCode.
use crate::backend::AuthorizedToolBackend;
use crate::llm::ToolDefinition;
use crate::tool_loop::{ToolExecution, ToolExecutor, ToolFuture};
use crate::types::{
BoundedText, MAX_IDENTIFIER_BYTES, MAX_MESSAGE_BYTES, ProposedToolCall, ToolCallOutcome,
};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::CancellationToken;
use serde_json::{Map, Value};
use sha2::{Digest as _, Sha256};
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::error::Error;
use std::fmt;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
const MAX_POLICY_TOOLS: usize = 64;
const MAX_AUTHORIZED_AVATARS: usize = 1_024;
const MAX_AUDIT_RECORDS: usize = 4_096;
const MAX_APPROVALS: usize = 4_096;
const MAX_SCHEDULER_GRANTS: usize = 1_024;
const MAX_BUDGET_TOOL_CALLS: u64 = 1_000_000;
const MAX_BUDGET_UPLOAD_BYTES: u64 = 1024 * 1024 * 1024;
const MAX_BUDGET_INVENTORY_OPERATIONS: u64 = 1_000_000;
const MAX_BUDGET_MOVEMENT_MILLIMETERS: u64 = 1_000_000_000_000;
const MAX_BUDGET_BUILD_PRIMS: u64 = 1_000_000;
const MAX_OPERATOR_PRINCIPAL_BYTES: usize = 96;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum OriginClass {
PublicChat,
UnprivilegedIm,
AuthorizedIm,
LocalOperator,
InternalScheduler,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum OriginKind {
PublicChat(UUID),
InstantMessage(UUID),
LocalOperator(AuthenticatedPrincipal),
InternalScheduler(SchedulerGrantId),
}
/// Request origin whose privileged variants have no public constructor.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ActionOrigin(OriginKind);
impl ActionOrigin {
#[must_use]
pub const fn public_chat(avatar_id: UUID) -> Self {
Self(OriginKind::PublicChat(avatar_id))
}
#[must_use]
pub const fn instant_message(avatar_id: UUID) -> Self {
Self(OriginKind::InstantMessage(avatar_id))
}
}
/// Principal produced only by the authenticated control-plane implementation.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct AuthenticatedPrincipal(BoundedText<MAX_OPERATOR_PRINCIPAL_BYTES>);
impl AuthenticatedPrincipal {
pub(crate) fn from_authenticated_control(
principal: impl Into<String>,
) -> Result<Self, PolicyError> {
let principal = BoundedText::new("policy.operator_principal", principal)?;
if !principal.chars().all(|character| {
character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-' | ':')
}) {
return Err(PolicyError::InvalidPrincipal);
}
Ok(Self(principal))
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicyRequestContext {
origin: ActionOrigin,
session_id: BoundedText<MAX_IDENTIFIER_BYTES>,
correlation_id: BoundedText<MAX_IDENTIFIER_BYTES>,
}
impl PolicyRequestContext {
pub fn new(
origin: ActionOrigin,
session_id: impl Into<String>,
correlation_id: impl Into<String>,
) -> Result<Self, PolicyError> {
Ok(Self {
origin,
session_id: policy_identifier("policy.session_id", session_id)?,
correlation_id: policy_identifier("policy.correlation_id", correlation_id)?,
})
}
pub fn authenticated_operator(
principal: AuthenticatedPrincipal,
session_id: impl Into<String>,
correlation_id: impl Into<String>,
) -> Result<Self, PolicyError> {
Self::new(
ActionOrigin(OriginKind::LocalOperator(principal)),
session_id,
correlation_id,
)
}
pub fn scheduler(
grant: SchedulerGrantId,
session_id: impl Into<String>,
correlation_id: impl Into<String>,
) -> Result<Self, PolicyError> {
Self::new(
ActionOrigin(OriginKind::InternalScheduler(grant)),
session_id,
correlation_id,
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum PrincipalKey {
Avatar(UUID),
Operator(AuthenticatedPrincipal),
}
impl Ord for PrincipalKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.audit_label().cmp(&other.audit_label())
}
}
impl PartialOrd for PrincipalKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PrincipalKey {
fn audit_label(&self) -> String {
match self {
Self::Avatar(id) => format!("avatar:{id}"),
Self::Operator(id) => format!("operator:{}", id.as_str()),
}
}
const fn avatar_id(&self) -> Option<UUID> {
match self {
Self::Avatar(id) => Some(*id),
Self::Operator(_) => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Capability {
Informational,
PublicLslRequest,
InventoryMutation,
Movement,
Build,
ObjectMutation,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Risk {
ReadOnly,
InventoryMutation,
Movement,
Build,
ObjectMutation,
CurrencySpend,
EstateOrParcelChange,
PermanentDelete,
ArbitraryInventoryAcceptance,
GeneratedCodeExecution,
}
impl Risk {
const fn forbidden_in_milestone(self) -> bool {
matches!(
self,
Self::CurrencySpend
| Self::EstateOrParcelChange
| Self::PermanentDelete
| Self::ArbitraryInventoryAcceptance
| Self::GeneratedCodeExecution
)
}
const fn is_mutating(self) -> bool {
!matches!(self, Self::ReadOnly)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Idempotency {
Idempotent,
NonIdempotent,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AllowedOrigins(BTreeSet<OriginClass>);
impl AllowedOrigins {
pub fn new(origins: impl IntoIterator<Item = OriginClass>) -> Result<Self, PolicyError> {
let origins = origins.into_iter().collect::<BTreeSet<_>>();
if origins.is_empty() || origins.len() > 5 {
return Err(PolicyError::InvalidRegistration);
}
Ok(Self(origins))
}
fn contains(&self, origin: OriginClass) -> bool {
self.0.contains(&origin)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ResourceCost {
pub tool_calls: u64,
pub linden_dollars: u64,
pub upload_bytes: u64,
pub inventory_operations: u64,
pub movement_millimeters: u64,
pub build_prims: u64,
}
/// Secret-free operator projection of one pending approval.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PendingApprovalMetadata {
pub id: ApprovalId,
pub principal: String,
pub tool: String,
pub expires_at: u64,
pub cost: ResourceCost,
}
impl ResourceCost {
#[must_use]
pub const fn one_call() -> Self {
Self {
tool_calls: 1,
linden_dollars: 0,
upload_bytes: 0,
inventory_operations: 0,
movement_millimeters: 0,
build_prims: 0,
}
}
fn normalized(mut self) -> Self {
self.tool_calls = self.tool_calls.max(1);
self
}
fn checked_add(self, other: Self) -> Option<Self> {
Some(Self {
tool_calls: self.tool_calls.checked_add(other.tool_calls)?,
linden_dollars: self.linden_dollars.checked_add(other.linden_dollars)?,
upload_bytes: self.upload_bytes.checked_add(other.upload_bytes)?,
inventory_operations: self
.inventory_operations
.checked_add(other.inventory_operations)?,
movement_millimeters: self
.movement_millimeters
.checked_add(other.movement_millimeters)?,
build_prims: self.build_prims.checked_add(other.build_prims)?,
})
}
fn within(self, maximum: Self) -> bool {
self.tool_calls <= maximum.tool_calls
&& self.linden_dollars <= maximum.linden_dollars
&& self.upload_bytes <= maximum.upload_bytes
&& self.inventory_operations <= maximum.inventory_operations
&& self.movement_millimeters <= maximum.movement_millimeters
&& self.build_prims <= maximum.build_prims
}
fn exceeds(self, threshold: Self) -> bool {
!self.within(threshold)
}
}
pub trait ResourceEstimator: Send + Sync {
fn estimate(&self, arguments: &Value) -> Result<ResourceCost, PolicyReasonCode>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FixedCost(pub ResourceCost);
impl ResourceEstimator for FixedCost {
fn estimate(&self, _arguments: &Value) -> Result<ResourceCost, PolicyReasonCode> {
Ok(self.0)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ApprovalRule {
Never,
Always,
WhenExceeds(ResourceCost),
}
pub struct PolicyTool {
pub definition: ToolDefinition,
pub capability: Capability,
pub risk: Risk,
pub allowed_origins: AllowedOrigins,
pub maximum_cost: ResourceCost,
pub idempotency: Idempotency,
pub approval: ApprovalRule,
pub scheduler_allowed: bool,
estimator: Arc<dyn ResourceEstimator>,
}
impl fmt::Debug for PolicyTool {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PolicyTool")
.field("definition", &self.definition)
.field("capability", &self.capability)
.field("risk", &self.risk)
.field("allowed_origins", &self.allowed_origins)
.field("maximum_cost", &self.maximum_cost)
.field("idempotency", &self.idempotency)
.field("approval", &self.approval)
.field("scheduler_allowed", &self.scheduler_allowed)
.finish_non_exhaustive()
}
}
impl PolicyTool {
#[allow(clippy::too_many_arguments)]
pub fn new(
definition: ToolDefinition,
capability: Capability,
risk: Risk,
allowed_origins: AllowedOrigins,
maximum_cost: ResourceCost,
idempotency: Idempotency,
approval: ApprovalRule,
scheduler_allowed: bool,
estimator: Arc<dyn ResourceEstimator>,
) -> Result<Self, PolicyError> {
definition.validate().map_err(PolicyError::Llm)?;
let capability_matches_risk = matches!(
(capability, risk),
(Capability::Informational, Risk::ReadOnly)
| (
Capability::PublicLslRequest | Capability::InventoryMutation,
Risk::InventoryMutation
)
| (Capability::Movement, Risk::Movement)
| (Capability::Build, Risk::Build)
| (Capability::ObjectMutation, Risk::ObjectMutation)
);
let approval_threshold_valid = match approval {
ApprovalRule::WhenExceeds(threshold) => threshold.within(maximum_cost),
ApprovalRule::Never | ApprovalRule::Always => true,
};
if risk.forbidden_in_milestone()
|| definition.mutating != risk.is_mutating()
|| !capability_matches_risk
|| !approval_threshold_valid
|| maximum_cost.tool_calls == 0
|| maximum_cost.linden_dollars != 0
|| (capability == Capability::PublicLslRequest && scheduler_allowed)
{
return Err(PolicyError::InvalidRegistration);
}
Ok(Self {
definition,
capability,
risk,
allowed_origins,
maximum_cost,
idempotency,
approval,
scheduler_allowed,
estimator,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BudgetLimits {
pub per_principal: ResourceCost,
pub global: ResourceCost,
pub window_seconds: u64,
}
impl Default for BudgetLimits {
fn default() -> Self {
Self {
per_principal: ResourceCost {
tool_calls: 32,
linden_dollars: 0,
upload_bytes: 4 * 1024 * 1024,
inventory_operations: 32,
movement_millimeters: 100_000,
build_prims: 32,
},
global: ResourceCost {
tool_calls: 256,
linden_dollars: 0,
upload_bytes: 16 * 1024 * 1024,
inventory_operations: 128,
movement_millimeters: 500_000,
build_prims: 128,
},
window_seconds: 60,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyLimits {
pub budgets: BudgetLimits,
pub approval_ttl_seconds: u64,
pub max_pending_approvals: usize,
pub max_scheduler_grants: usize,
pub max_scheduler_runs_per_grant: u64,
pub max_scheduler_ttl_seconds: u64,
}
impl Default for PolicyLimits {
fn default() -> Self {
Self {
budgets: BudgetLimits::default(),
approval_ttl_seconds: 300,
max_pending_approvals: 128,
max_scheduler_grants: 128,
max_scheduler_runs_per_grant: 1_024,
max_scheduler_ttl_seconds: 86_400,
}
}
}
impl PolicyLimits {
fn validate(self) -> Result<Self, PolicyError> {
if self.budgets.window_seconds == 0
|| self.budgets.window_seconds > 86_400
|| self.budgets.per_principal.tool_calls == 0
|| self.budgets.global.tool_calls == 0
|| self.budgets.per_principal.linden_dollars != 0
|| self.budgets.global.linden_dollars != 0
|| self.approval_ttl_seconds == 0
|| self.approval_ttl_seconds > 86_400
|| self.max_pending_approvals == 0
|| self.max_pending_approvals > MAX_APPROVALS
|| self.max_scheduler_grants == 0
|| self.max_scheduler_grants > MAX_SCHEDULER_GRANTS
|| self.max_scheduler_runs_per_grant == 0
|| self.max_scheduler_runs_per_grant > 10_000
|| self.max_scheduler_ttl_seconds == 0
|| self.max_scheduler_ttl_seconds > 7 * 86_400
|| !safe_budget(self.budgets.per_principal)
|| !safe_budget(self.budgets.global)
|| !self.budgets.per_principal.within(self.budgets.global)
{
return Err(PolicyError::UnsafeLimits);
}
Ok(self)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyDisposition {
Allowed,
Denied,
ApprovalRequired,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyReasonCode {
Allowed,
UnknownTool,
InvalidArguments,
OriginDenied,
CapabilityDenied,
ForbiddenMilestoneOperation,
ToolCostExceeded,
PrincipalBudgetExceeded,
GlobalBudgetExceeded,
ApprovalRequired,
ApprovalUnknown,
ApprovalNotGranted,
ApprovalExpired,
ApprovalReplayed,
ApprovalMismatch,
ApprovalUnexpected,
SchedulerGrantUnknown,
SchedulerGrantExpired,
SchedulerGrantExhausted,
SchedulerPrivilegeEscalation,
AuditUnavailable,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyFinalOutcome {
Denied,
ApprovalRequired,
ApprovalGranted,
Authorized,
Completed,
Rejected,
Failed,
AmbiguousMutation,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PolicyAuditRecord {
pub recorded_unix_millis: u64,
pub authorization_id: Option<u64>,
pub approval_id: Option<ApprovalId>,
pub origin: OriginClass,
pub origin_avatar_id: Option<UUID>,
pub principal: BoundedText<MAX_IDENTIFIER_BYTES>,
pub session_id: BoundedText<MAX_IDENTIFIER_BYTES>,
pub correlation_id: BoundedText<MAX_IDENTIFIER_BYTES>,
pub tool: BoundedText<MAX_IDENTIFIER_BYTES>,
pub disposition: PolicyDisposition,
pub reason: PolicyReasonCode,
pub applied_budget: ResourceCost,
pub arguments_hash: BoundedText<64>,
pub final_outcome: PolicyFinalOutcome,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyAuditError;
impl fmt::Display for PolicyAuditError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("policy audit sink unavailable or full")
}
}
impl Error for PolicyAuditError {}
pub trait PolicyAuditSink: Send + Sync {
fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyAuditError>;
}
#[derive(Debug)]
pub struct MemoryPolicyAudit {
capacity: usize,
records: Mutex<VecDeque<PolicyAuditRecord>>,
}
impl MemoryPolicyAudit {
pub fn new(capacity: usize) -> Result<Self, PolicyError> {
if capacity == 0 || capacity > MAX_AUDIT_RECORDS {
return Err(PolicyError::UnsafeLimits);
}
Ok(Self {
capacity,
records: Mutex::new(VecDeque::with_capacity(capacity)),
})
}
#[must_use]
pub fn snapshot(&self) -> Vec<PolicyAuditRecord> {
lock(&self.records).iter().cloned().collect()
}
}
impl PolicyAuditSink for MemoryPolicyAudit {
fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyAuditError> {
let mut records = lock(&self.records);
if records.len() == self.capacity {
return Err(PolicyAuditError);
}
records.push_back(record);
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ApprovalId(u64);
impl ApprovalId {
#[must_use]
pub const fn from_raw(value: u64) -> Option<Self> {
if value == 0 { None } else { Some(Self(value)) }
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct SchedulerGrantId(u64);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ApprovalStatus {
Pending,
Granted,
Denied,
Consumed,
}
#[derive(Clone, Debug)]
struct ApprovalRecord {
id: ApprovalId,
principal: PrincipalKey,
context: PolicyRequestContext,
origin: OriginClass,
tool: String,
arguments_hash: [u8; 32],
cost: ResourceCost,
expires_at: u64,
status: ApprovalStatus,
}
#[derive(Clone, Debug)]
struct SchedulerGrant {
id: SchedulerGrantId,
principal: PrincipalKey,
tool: String,
arguments_hash: [u8; 32],
expires_at: u64,
remaining_runs: u64,
}
#[derive(Clone, Debug)]
struct PolicyState {
next_id: u64,
window_started_at: u64,
global_used: ResourceCost,
principal_used: BTreeMap<PrincipalKey, ResourceCost>,
approvals: BTreeMap<ApprovalId, ApprovalRecord>,
scheduler_grants: BTreeMap<SchedulerGrantId, SchedulerGrant>,
}
impl Default for PolicyState {
fn default() -> Self {
Self {
next_id: 1,
window_started_at: 0,
global_used: ResourceCost::default(),
principal_used: BTreeMap::new(),
approvals: BTreeMap::new(),
scheduler_grants: BTreeMap::new(),
}
}
}
/// Opaque persistence value. A later storage adapter can durably protect and
/// restore it without gaining a way to mint an `AuthorizedAction`.
#[derive(Clone, Debug)]
pub struct PolicySnapshot(PolicyState);
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PolicyError {
Boundary(crate::types::BoundaryError),
Llm(crate::llm::LlmError),
UnsafeLimits,
InvalidPrincipal,
InvalidContext,
InvalidRegistration,
DuplicateTool,
TooManyTools,
AuditUnavailable,
}
impl fmt::Display for PolicyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Boundary(error) => write!(formatter, "policy boundary rejected input: {error}"),
Self::Llm(error) => write!(formatter, "policy tool schema rejected: {error}"),
Self::UnsafeLimits => formatter.write_str("unsafe policy limits"),
Self::InvalidPrincipal => formatter.write_str("invalid authenticated principal"),
Self::InvalidContext => formatter.write_str("invalid policy request context"),
Self::InvalidRegistration => formatter.write_str("invalid policy tool registration"),
Self::DuplicateTool => formatter.write_str("duplicate policy tool registration"),
Self::TooManyTools => formatter.write_str("too many policy tool registrations"),
Self::AuditUnavailable => formatter.write_str("policy audit emission failed closed"),
}
}
}
impl Error for PolicyError {}
impl From<crate::types::BoundaryError> for PolicyError {
fn from(value: crate::types::BoundaryError) -> Self {
Self::Boundary(value)
}
}
#[derive(Debug)]
pub struct PolicyEvaluation {
pub disposition: PolicyDisposition,
pub reason: PolicyReasonCode,
pub approval_id: Option<ApprovalId>,
authorization: Option<AuthorizedAction>,
}
impl PolicyEvaluation {
#[must_use]
pub fn into_authorization(self) -> Option<AuthorizedAction> {
self.authorization
}
}
#[derive(Clone, Debug)]
struct ActionReceipt {
authorization_id: u64,
context: PolicyRequestContext,
origin: OriginClass,
principal: PrincipalKey,
tool: BoundedText<MAX_IDENTIFIER_BYTES>,
call_id: BoundedText<MAX_IDENTIFIER_BYTES>,
cost: ResourceCost,
arguments_hash: [u8; 32],
idempotency: Idempotency,
mutating: bool,
}
/// Single-use exact authorization. Its private fields prevent callers from
/// forging a policy decision or swapping arguments after evaluation.
///
/// ```compile_fail
/// use metacrate_grid_agent::AuthorizedAction;
/// let forged = AuthorizedAction { /* private authorization fields */ };
/// ```
#[derive(Debug)]
pub struct AuthorizedAction {
call: ProposedToolCall,
receipt: ActionReceipt,
}
impl AuthorizedAction {
#[must_use]
pub const fn call(&self) -> &ProposedToolCall {
&self.call
}
#[must_use]
pub const fn authorization_id(&self) -> u64 {
self.receipt.authorization_id
}
#[must_use]
pub const fn applied_budget(&self) -> ResourceCost {
self.receipt.cost
}
}
pub struct PolicyGateway {
authorized_avatars: BTreeSet<UUID>,
tools: BTreeMap<String, PolicyTool>,
limits: PolicyLimits,
audit: Arc<dyn PolicyAuditSink>,
state: Mutex<PolicyState>,
}
impl fmt::Debug for PolicyGateway {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PolicyGateway")
.field("authorized_avatar_count", &self.authorized_avatars.len())
.field("tool_count", &self.tools.len())
.field("limits", &self.limits)
.finish_non_exhaustive()
}
}
impl PolicyGateway {
pub fn new(
authorized_avatars: BTreeSet<UUID>,
tools: Vec<PolicyTool>,
limits: PolicyLimits,
audit: Arc<dyn PolicyAuditSink>,
) -> Result<Self, PolicyError> {
Self::restore(
authorized_avatars,
tools,
limits,
audit,
PolicySnapshot(PolicyState::default()),
)
}
pub fn restore(
authorized_avatars: BTreeSet<UUID>,
tools: Vec<PolicyTool>,
limits: PolicyLimits,
audit: Arc<dyn PolicyAuditSink>,
snapshot: PolicySnapshot,
) -> Result<Self, PolicyError> {
let limits = limits.validate()?;
if authorized_avatars.len() > MAX_AUTHORIZED_AVATARS
|| authorized_avatars.contains(&UUID::zero())
{
return Err(PolicyError::InvalidPrincipal);
}
if tools.len() > MAX_POLICY_TOOLS {
return Err(PolicyError::TooManyTools);
}
let mut registry = BTreeMap::new();
for tool in tools {
if !tool.maximum_cost.within(limits.budgets.per_principal)
|| !tool.maximum_cost.within(limits.budgets.global)
{
return Err(PolicyError::InvalidRegistration);
}
let name = tool.definition.name.as_str().to_owned();
if registry.insert(name, tool).is_some() {
return Err(PolicyError::DuplicateTool);
}
}
if snapshot.0.approvals.len() > MAX_APPROVALS
|| snapshot.0.scheduler_grants.len() > limits.max_scheduler_grants
{
return Err(PolicyError::UnsafeLimits);
}
Ok(Self {
authorized_avatars,
tools: registry,
limits,
audit,
state: Mutex::new(snapshot.0),
})
}
#[must_use]
pub fn snapshot(&self) -> PolicySnapshot {
PolicySnapshot(lock(&self.state).clone())
}
/// Returns bounded, content-free approval metadata for operator UIs.
#[must_use]
pub fn pending_approvals(&self, now: u64) -> Vec<PendingApprovalMetadata> {
lock(&self.state)
.approvals
.values()
.filter(|approval| {
approval.status == ApprovalStatus::Pending && approval.expires_at > now
})
.map(|approval| PendingApprovalMetadata {
id: approval.id,
principal: approval.principal.audit_label(),
tool: approval.tool.clone(),
expires_at: approval.expires_at,
cost: approval.cost,
})
.collect()
}
/// Current global budget use for a redacted control-plane projection.
#[must_use]
pub fn global_budget_usage(&self) -> ResourceCost {
lock(&self.state).global_used
}
#[cfg(test)]
pub(crate) fn pending_approval_count(&self) -> usize {
lock(&self.state)
.approvals
.values()
.filter(|approval| approval.status == ApprovalStatus::Pending)
.count()
}
#[must_use]
pub fn tools_for(&self, context: &PolicyRequestContext, now: u64) -> Vec<ToolDefinition> {
self.tools_for_capabilities(context, now, None)
}
/// Returns only origin-allowed tools whose capability is in `capabilities`.
/// An empty set exposes no tools; filtering can never broaden policy access.
#[must_use]
pub fn tools_for_capability_set(
&self,
context: &PolicyRequestContext,
now: u64,
capabilities: &BTreeSet<Capability>,
) -> Vec<ToolDefinition> {
self.tools_for_capabilities(context, now, Some(capabilities))
}
fn tools_for_capabilities(
&self,
context: &PolicyRequestContext,
now: u64,
capabilities: Option<&BTreeSet<Capability>>,
) -> Vec<ToolDefinition> {
if let OriginKind::InternalScheduler(id) = &context.origin.0 {
let state = lock(&self.state);
let Some(grant) = state.scheduler_grants.get(id) else {
return Vec::new();
};
if grant.expires_at <= now
|| grant.remaining_runs == 0
|| matches!(&grant.principal, PrincipalKey::Avatar(avatar) if !self.authorized_avatars.contains(avatar))
{
return Vec::new();
}
return self
.tools
.get(&grant.tool)
.filter(|tool| {
origin_allows(tool, OriginClass::InternalScheduler)
&& capabilities.is_none_or(|allowed| allowed.contains(&tool.capability))
})
.map(|tool| vec![tool.definition.clone()])
.unwrap_or_default();
}
let Ok(classified) = self.classify(context, now, None, None) else {
return Vec::new();
};
self.tools
.values()
.filter(|tool| {
origin_allows(tool, classified.origin)
&& capabilities.is_none_or(|allowed| allowed.contains(&tool.capability))
})
.map(|tool| tool.definition.clone())
.collect()
}
#[allow(clippy::too_many_lines)] // Ordered fail-closed checks mirror the policy audit sequence.
pub fn evaluate(
&self,
context: &PolicyRequestContext,
call: &ProposedToolCall,
arguments: &Value,
approval_id: Option<ApprovalId>,
now: u64,
) -> Result<PolicyEvaluation, PolicyError> {
let encoded_arguments = serde_json::from_str::<Value>(call.arguments_json.as_str());
let arguments_hash = match &encoded_arguments {
Ok(encoded) => hash_arguments(encoded)?,
Err(_) => Sha256::digest(call.arguments_json.as_bytes()).into(),
};
let classified = match self.classify(
context,
now,
Some(call.name.as_str()),
Some(&arguments_hash),
) {
Ok(classified) => classified,
Err(reason) => {
return self.denied(
context,
call,
fallback_principal(context),
OriginClass::InternalScheduler,
arguments_hash,
reason,
);
}
};
let Some(tool) = self.tools.get(call.name.as_str()) else {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::UnknownTool,
);
};
if encoded_arguments.map_or(true, |encoded| encoded != *arguments) {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::InvalidArguments,
);
}
if tool.definition.schema.validate_value(arguments).is_err() {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::InvalidArguments,
);
}
if !tool.allowed_origins.contains(classified.origin) {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::OriginDenied,
);
}
if !origin_allows(tool, classified.origin) {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::CapabilityDenied,
);
}
if tool.risk.forbidden_in_milestone() {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
PolicyReasonCode::ForbiddenMilestoneOperation,
);
}
let cost = match tool.estimator.estimate(arguments) {
Ok(cost) => cost.normalized(),
Err(reason) => {
return self.denied(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
reason,
);
}
};
if cost.linden_dollars != 0 || !cost.within(tool.maximum_cost) {
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
if cost.linden_dollars != 0 {
PolicyReasonCode::ForbiddenMilestoneOperation
} else {
PolicyReasonCode::ToolCostExceeded
},
);
}
self.authorize(
context,
call,
classified,
arguments_hash,
cost,
tool,
approval_id,
now,
)
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)] // Atomic approval/budget commit.
fn authorize(
&self,
context: &PolicyRequestContext,
call: &ProposedToolCall,
classified: ClassifiedOrigin,
arguments_hash: [u8; 32],
cost: ResourceCost,
tool: &PolicyTool,
approval_id: Option<ApprovalId>,
now: u64,
) -> Result<PolicyEvaluation, PolicyError> {
let requires_approval = match tool.approval {
ApprovalRule::Never => false,
ApprovalRule::Always => true,
ApprovalRule::WhenExceeds(threshold) => cost.exceeds(threshold),
};
let mut state = lock(&self.state);
rotate_budgets(&mut state, self.limits.budgets, now);
let mut approval_to_consume = None;
if requires_approval {
if let Some(id) = approval_id {
let Some(approval) = state.approvals.get(&id) else {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::ApprovalUnknown,
);
};
let reason = if approval.expires_at <= now {
Some(PolicyReasonCode::ApprovalExpired)
} else if approval.principal != classified.principal
|| approval.tool != call.name.as_str()
|| approval.arguments_hash != arguments_hash
|| approval.cost != cost
{
Some(PolicyReasonCode::ApprovalMismatch)
} else {
match approval.status {
ApprovalStatus::Pending => Some(PolicyReasonCode::ApprovalNotGranted),
ApprovalStatus::Granted => None,
ApprovalStatus::Denied | ApprovalStatus::Consumed => {
Some(PolicyReasonCode::ApprovalReplayed)
}
}
};
if let Some(reason) = reason {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
reason,
);
}
approval_to_consume = Some(id);
} else {
if let Some(existing) = state.approvals.values().find(|approval| {
approval.principal == classified.principal
&& approval.tool == call.name.as_str()
&& approval.arguments_hash == arguments_hash
&& approval.status == ApprovalStatus::Pending
&& approval.expires_at > now
}) {
let id = existing.id;
drop(state);
return self.approval_required(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
id,
);
}
let pending = state
.approvals
.values()
.filter(|approval| {
approval.status == ApprovalStatus::Pending && approval.expires_at > now
})
.count();
if pending >= self.limits.max_pending_approvals {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::PrincipalBudgetExceeded,
);
}
if state.approvals.len() == MAX_APPROVALS {
let removable = state.approvals.iter().find_map(|(id, approval)| {
(approval.expires_at <= now
|| matches!(
approval.status,
ApprovalStatus::Denied | ApprovalStatus::Consumed
))
.then_some(*id)
});
let Some(removable) = removable else {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::GlobalBudgetExceeded,
);
};
state.approvals.remove(&removable);
}
let id = ApprovalId(next_id(&mut state));
let record = ApprovalRecord {
id,
principal: classified.principal.clone(),
context: context.clone(),
origin: classified.origin,
tool: call.name.as_str().to_owned(),
arguments_hash,
cost,
expires_at: now.saturating_add(self.limits.approval_ttl_seconds),
status: ApprovalStatus::Pending,
};
let audit = audit_record(
context,
classified.origin,
&classified.principal,
call.name.clone(),
None,
Some(id),
PolicyDisposition::ApprovalRequired,
PolicyReasonCode::ApprovalRequired,
cost,
arguments_hash,
PolicyFinalOutcome::ApprovalRequired,
)?;
self.emit(audit)?;
state.approvals.insert(id, record);
return Ok(PolicyEvaluation {
disposition: PolicyDisposition::ApprovalRequired,
reason: PolicyReasonCode::ApprovalRequired,
approval_id: Some(id),
authorization: None,
});
}
} else if approval_id.is_some() {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::ApprovalUnexpected,
);
}
let principal_used = state
.principal_used
.get(&classified.principal)
.copied()
.unwrap_or_default();
let Some(next_principal) = principal_used.checked_add(cost) else {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::PrincipalBudgetExceeded,
);
};
if !next_principal.within(self.limits.budgets.per_principal) {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::PrincipalBudgetExceeded,
);
}
let Some(next_global) = state.global_used.checked_add(cost) else {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::GlobalBudgetExceeded,
);
};
if !next_global.within(self.limits.budgets.global) {
drop(state);
return self.denied_with_cost(
context,
call,
classified.principal,
classified.origin,
arguments_hash,
cost,
PolicyReasonCode::GlobalBudgetExceeded,
);
}
let authorization_id = next_id(&mut state);
let record = audit_record(
context,
classified.origin,
&classified.principal,
call.name.clone(),
Some(authorization_id),
approval_to_consume,
PolicyDisposition::Allowed,
PolicyReasonCode::Allowed,
cost,
arguments_hash,
PolicyFinalOutcome::Authorized,
)?;
self.emit(record)?;
state.global_used = next_global;
state
.principal_used
.insert(classified.principal.clone(), next_principal);
if let Some(id) = approval_to_consume
&& let Some(approval) = state.approvals.get_mut(&id)
{
approval.status = ApprovalStatus::Consumed;
}
if let Some(grant_id) = classified.scheduler_grant
&& let Some(grant) = state.scheduler_grants.get_mut(&grant_id)
{
grant.remaining_runs = grant.remaining_runs.saturating_sub(1);
}
let receipt = ActionReceipt {
authorization_id,
context: context.clone(),
origin: classified.origin,
principal: classified.principal,
tool: call.name.clone(),
call_id: call.call_id.clone(),
cost,
arguments_hash,
idempotency: tool.idempotency,
mutating: tool.risk.is_mutating(),
};
Ok(PolicyEvaluation {
disposition: PolicyDisposition::Allowed,
reason: PolicyReasonCode::Allowed,
approval_id: approval_to_consume,
authorization: Some(AuthorizedAction {
call: call.clone(),
receipt,
}),
})
}
pub fn grant_approval(
&self,
id: ApprovalId,
_operator: &AuthenticatedPrincipal,
now: u64,
) -> Result<PolicyReasonCode, PolicyError> {
let mut state = lock(&self.state);
let Some(approval) = state.approvals.get_mut(&id) else {
return Ok(PolicyReasonCode::ApprovalUnknown);
};
if approval.expires_at <= now {
return Ok(PolicyReasonCode::ApprovalExpired);
}
if approval.status != ApprovalStatus::Pending {
return Ok(PolicyReasonCode::ApprovalReplayed);
}
let audit = audit_record(
&approval.context,
approval.origin,
&approval.principal,
BoundedText::new("policy.tool", approval.tool.clone())?,
None,
Some(id),
PolicyDisposition::Allowed,
PolicyReasonCode::Allowed,
approval.cost,
approval.arguments_hash,
PolicyFinalOutcome::ApprovalGranted,
)?;
self.emit(audit)?;
approval.status = ApprovalStatus::Granted;
Ok(PolicyReasonCode::Allowed)
}
pub fn deny_approval(
&self,
id: ApprovalId,
_operator: &AuthenticatedPrincipal,
now: u64,
) -> Result<PolicyReasonCode, PolicyError> {
let mut state = lock(&self.state);
let Some(approval) = state.approvals.get_mut(&id) else {
return Ok(PolicyReasonCode::ApprovalUnknown);
};
if approval.expires_at <= now {
return Ok(PolicyReasonCode::ApprovalExpired);
}
if approval.status != ApprovalStatus::Pending {
return Ok(PolicyReasonCode::ApprovalReplayed);
}
let audit = audit_record(
&approval.context,
approval.origin,
&approval.principal,
BoundedText::new("policy.tool", approval.tool.clone())?,
None,
Some(id),
PolicyDisposition::Denied,
PolicyReasonCode::ApprovalNotGranted,
approval.cost,
approval.arguments_hash,
PolicyFinalOutcome::Denied,
)?;
self.emit(audit)?;
approval.status = ApprovalStatus::Denied;
Ok(PolicyReasonCode::ApprovalNotGranted)
}
/// Consumes the originating authorization so it cannot be replayed to
/// create more than one scheduler grant.
#[allow(clippy::needless_pass_by_value)]
pub fn issue_scheduler_grant(
&self,
action: AuthorizedAction,
remaining_runs: u64,
expires_at: u64,
now: u64,
) -> Result<SchedulerGrantId, PolicyReasonCode> {
if remaining_runs == 0
|| remaining_runs > self.limits.max_scheduler_runs_per_grant
|| expires_at <= now
|| expires_at > now.saturating_add(self.limits.max_scheduler_ttl_seconds)
|| !matches!(
action.receipt.origin,
OriginClass::AuthorizedIm | OriginClass::LocalOperator
)
|| !self
.tools
.get(action.receipt.tool.as_str())
.is_some_and(|tool| tool.scheduler_allowed)
{
return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
}
let mut state = lock(&self.state);
if state.scheduler_grants.len() >= self.limits.max_scheduler_grants {
return Err(PolicyReasonCode::GlobalBudgetExceeded);
}
let id = SchedulerGrantId(next_id(&mut state));
state.scheduler_grants.insert(
id,
SchedulerGrant {
id,
principal: action.receipt.principal.clone(),
tool: action.receipt.tool.as_str().to_owned(),
arguments_hash: action.receipt.arguments_hash,
expires_at,
remaining_runs,
},
);
Ok(id)
}
fn classify(
&self,
context: &PolicyRequestContext,
now: u64,
tool: Option<&str>,
arguments_hash: Option<&[u8; 32]>,
) -> Result<ClassifiedOrigin, PolicyReasonCode> {
match &context.origin.0 {
OriginKind::PublicChat(id) if *id == UUID::zero() => {
Err(PolicyReasonCode::OriginDenied)
}
OriginKind::PublicChat(id) => Ok(ClassifiedOrigin {
origin: OriginClass::PublicChat,
principal: PrincipalKey::Avatar(*id),
scheduler_grant: None,
}),
OriginKind::InstantMessage(id) if *id == UUID::zero() => {
Err(PolicyReasonCode::OriginDenied)
}
OriginKind::InstantMessage(id) => Ok(ClassifiedOrigin {
origin: if self.authorized_avatars.contains(id) {
OriginClass::AuthorizedIm
} else {
OriginClass::UnprivilegedIm
},
principal: PrincipalKey::Avatar(*id),
scheduler_grant: None,
}),
OriginKind::LocalOperator(principal) => Ok(ClassifiedOrigin {
origin: OriginClass::LocalOperator,
principal: PrincipalKey::Operator(principal.clone()),
scheduler_grant: None,
}),
OriginKind::InternalScheduler(id) => {
let state = lock(&self.state);
let grant = state
.scheduler_grants
.get(id)
.ok_or(PolicyReasonCode::SchedulerGrantUnknown)?;
if grant.id != *id || grant.expires_at <= now {
return Err(PolicyReasonCode::SchedulerGrantExpired);
}
if grant.remaining_runs == 0 {
return Err(PolicyReasonCode::SchedulerGrantExhausted);
}
if grant.remaining_runs > self.limits.max_scheduler_runs_per_grant {
return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
}
if tool != Some(grant.tool.as_str())
|| arguments_hash != Some(&grant.arguments_hash)
|| matches!(&grant.principal, PrincipalKey::Avatar(avatar) if !self.authorized_avatars.contains(avatar))
{
return Err(PolicyReasonCode::SchedulerPrivilegeEscalation);
}
Ok(ClassifiedOrigin {
origin: OriginClass::InternalScheduler,
principal: grant.principal.clone(),
scheduler_grant: Some(*id),
})
}
}
}
#[allow(clippy::needless_pass_by_value)] // Denial branches transfer the classified principal.
fn denied(
&self,
context: &PolicyRequestContext,
call: &ProposedToolCall,
principal: PrincipalKey,
origin: OriginClass,
arguments_hash: [u8; 32],
reason: PolicyReasonCode,
) -> Result<PolicyEvaluation, PolicyError> {
self.denied_with_cost(
context,
call,
principal,
origin,
arguments_hash,
ResourceCost::default(),
reason,
)
}
#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
fn denied_with_cost(
&self,
context: &PolicyRequestContext,
call: &ProposedToolCall,
principal: PrincipalKey,
origin: OriginClass,
arguments_hash: [u8; 32],
cost: ResourceCost,
reason: PolicyReasonCode,
) -> Result<PolicyEvaluation, PolicyError> {
self.emit(audit_record(
context,
origin,
&principal,
call.name.clone(),
None,
None,
PolicyDisposition::Denied,
reason,
cost,
arguments_hash,
PolicyFinalOutcome::Denied,
)?)?;
Ok(PolicyEvaluation {
disposition: PolicyDisposition::Denied,
reason,
approval_id: None,
authorization: None,
})
}
#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
fn approval_required(
&self,
context: &PolicyRequestContext,
call: &ProposedToolCall,
principal: PrincipalKey,
origin: OriginClass,
arguments_hash: [u8; 32],
cost: ResourceCost,
id: ApprovalId,
) -> Result<PolicyEvaluation, PolicyError> {
self.emit(audit_record(
context,
origin,
&principal,
call.name.clone(),
None,
Some(id),
PolicyDisposition::ApprovalRequired,
PolicyReasonCode::ApprovalRequired,
cost,
arguments_hash,
PolicyFinalOutcome::ApprovalRequired,
)?)?;
Ok(PolicyEvaluation {
disposition: PolicyDisposition::ApprovalRequired,
reason: PolicyReasonCode::ApprovalRequired,
approval_id: Some(id),
authorization: None,
})
}
fn emit(&self, record: PolicyAuditRecord) -> Result<(), PolicyError> {
self.audit
.emit(record)
.map_err(|_| PolicyError::AuditUnavailable)
}
fn emit_outcome(
&self,
receipt: &ActionReceipt,
outcome: PolicyFinalOutcome,
) -> Result<(), PolicyError> {
self.emit(audit_record(
&receipt.context,
receipt.origin,
&receipt.principal,
receipt.tool.clone(),
Some(receipt.authorization_id),
None,
PolicyDisposition::Allowed,
PolicyReasonCode::Allowed,
receipt.cost,
receipt.arguments_hash,
outcome,
)?)
}
}
#[derive(Clone, Debug)]
struct ClassifiedOrigin {
origin: OriginClass,
principal: PrincipalKey,
scheduler_grant: Option<SchedulerGrantId>,
}
fn origin_allows(tool: &PolicyTool, origin: OriginClass) -> bool {
if !tool.allowed_origins.contains(origin) {
return false;
}
match origin {
OriginClass::PublicChat => {
(tool.risk == Risk::ReadOnly && tool.capability == Capability::Informational)
|| tool.capability == Capability::PublicLslRequest
}
OriginClass::UnprivilegedIm => {
tool.risk == Risk::ReadOnly && tool.capability == Capability::Informational
}
OriginClass::AuthorizedIm | OriginClass::LocalOperator => true,
OriginClass::InternalScheduler => tool.scheduler_allowed,
}
}
fn rotate_budgets(state: &mut PolicyState, limits: BudgetLimits, now: u64) {
if state.window_started_at == 0
|| now
>= state
.window_started_at
.saturating_add(limits.window_seconds)
{
state.window_started_at = now;
state.global_used = ResourceCost::default();
state.principal_used.clear();
}
}
fn safe_budget(value: ResourceCost) -> bool {
value.tool_calls > 0
&& value.tool_calls <= MAX_BUDGET_TOOL_CALLS
&& value.linden_dollars == 0
&& value.upload_bytes <= MAX_BUDGET_UPLOAD_BYTES
&& value.inventory_operations <= MAX_BUDGET_INVENTORY_OPERATIONS
&& value.movement_millimeters <= MAX_BUDGET_MOVEMENT_MILLIMETERS
&& value.build_prims <= MAX_BUDGET_BUILD_PRIMS
}
fn next_id(state: &mut PolicyState) -> u64 {
let id = state.next_id;
state.next_id = state.next_id.saturating_add(1);
id
}
fn fallback_principal(context: &PolicyRequestContext) -> PrincipalKey {
match &context.origin.0 {
OriginKind::PublicChat(id) | OriginKind::InstantMessage(id) => PrincipalKey::Avatar(*id),
OriginKind::LocalOperator(principal) => PrincipalKey::Operator(principal.clone()),
OriginKind::InternalScheduler(_) => PrincipalKey::Operator(
AuthenticatedPrincipal::from_authenticated_control("invalid-scheduler")
.expect("static principal is valid"),
),
}
}
#[allow(clippy::too_many_arguments)]
fn audit_record(
context: &PolicyRequestContext,
origin: OriginClass,
principal: &PrincipalKey,
tool: BoundedText<MAX_IDENTIFIER_BYTES>,
authorization_id: Option<u64>,
approval_id: Option<ApprovalId>,
disposition: PolicyDisposition,
reason: PolicyReasonCode,
applied_budget: ResourceCost,
arguments_hash: [u8; 32],
final_outcome: PolicyFinalOutcome,
) -> Result<PolicyAuditRecord, PolicyError> {
Ok(PolicyAuditRecord {
recorded_unix_millis: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}),
authorization_id,
approval_id,
origin,
origin_avatar_id: principal.avatar_id(),
principal: BoundedText::new("policy.audit.principal", principal.audit_label())?,
session_id: context.session_id.clone(),
correlation_id: context.correlation_id.clone(),
tool,
disposition,
reason,
applied_budget,
arguments_hash: BoundedText::new("policy.audit.arguments_hash", hex_hash(arguments_hash))?,
final_outcome,
})
}
fn hash_arguments(arguments: &Value) -> Result<[u8; 32], PolicyError> {
let canonical = canonical_value(arguments);
let bytes = serde_json::to_vec(&canonical).map_err(|_| PolicyError::InvalidRegistration)?;
Ok(Sha256::digest(bytes).into())
}
fn canonical_value(value: &Value) -> Value {
match value {
Value::Object(object) => {
let sorted = object
.iter()
.map(|(key, value)| (key.clone(), canonical_value(value)))
.collect::<BTreeMap<_, _>>();
Value::Object(sorted.into_iter().collect::<Map<_, _>>())
}
Value::Array(values) => Value::Array(values.iter().map(canonical_value).collect()),
_ => value.clone(),
}
}
fn hex_hash(hash: [u8; 32]) -> String {
use std::fmt::Write as _;
let mut output = String::with_capacity(64);
for byte in hash {
write!(output, "{byte:02x}").expect("writing to String cannot fail");
}
output
}
fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn policy_identifier(
field: &'static str,
value: impl Into<String>,
) -> Result<BoundedText<MAX_IDENTIFIER_BYTES>, PolicyError> {
let value = BoundedText::new(field, value)?;
if !value.chars().all(|character| {
character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-' | ':')
}) {
return Err(PolicyError::InvalidContext);
}
Ok(value)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UntrustedSource {
PublicChat,
InstantMessage,
InventoryMetadata,
ObjectText,
ParcelData,
WebResponse,
LlmOutput,
GeneratedScript,
}
/// Bounded untrusted text that can only be rendered as a labelled JSON data
/// record. It never contributes system instructions or tool registrations.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UntrustedData {
source: UntrustedSource,
body: BoundedText<MAX_MESSAGE_BYTES>,
}
impl UntrustedData {
pub fn new(source: UntrustedSource, body: impl Into<String>) -> Result<Self, PolicyError> {
Ok(Self {
source,
body: BoundedText::new("policy.untrusted_data", body)?,
})
}
pub fn render_json_record(&self) -> Result<BoundedText<MAX_MESSAGE_BYTES>, PolicyError> {
let source = match self.source {
UntrustedSource::PublicChat => "public_chat",
UntrustedSource::InstantMessage => "instant_message",
UntrustedSource::InventoryMetadata => "inventory_metadata",
UntrustedSource::ObjectText => "object_text",
UntrustedSource::ParcelData => "parcel_data",
UntrustedSource::WebResponse => "web_response",
UntrustedSource::LlmOutput => "llm_output",
UntrustedSource::GeneratedScript => "generated_script",
};
let render = |text: &str, truncated: bool| {
serde_json::to_string(&serde_json::json!({
"trust":"untrusted_data_only",
"source":source,
"truncated":truncated,
"text":text
}))
.map_err(|_| PolicyError::InvalidRegistration)
};
let complete = render(self.body.as_str(), false)?;
if complete.len() <= MAX_MESSAGE_BYTES {
return Ok(BoundedText::new("policy.untrusted_record", complete)?);
}
let boundaries = self
.body
.char_indices()
.map(|(index, _)| index)
.chain(std::iter::once(self.body.len()))
.collect::<Vec<_>>();
let mut low = 0;
let mut high = boundaries.len() - 1;
while low < high {
let middle = low + (high - low).div_ceil(2);
if render(&self.body[..boundaries[middle]], true)?.len() <= MAX_MESSAGE_BYTES {
low = middle;
} else {
high = middle - 1;
}
}
let value = render(&self.body[..boundaries[low]], true)?;
Ok(BoundedText::new("policy.untrusted_record", value)?)
}
}
/// The only production bridge from the generic tool loop to an action backend.
pub struct PolicyToolExecutor {
gateway: Arc<PolicyGateway>,
backend: Arc<dyn AuthorizedToolBackend>,
context: PolicyRequestContext,
approvals: BTreeMap<String, ApprovalId>,
now: Arc<dyn Fn() -> u64 + Send + Sync>,
}
impl fmt::Debug for PolicyToolExecutor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PolicyToolExecutor")
.field("gateway", &self.gateway)
.field("context", &self.context)
.field("approval_count", &self.approvals.len())
.finish_non_exhaustive()
}
}
impl PolicyToolExecutor {
pub fn new(
gateway: Arc<PolicyGateway>,
backend: Arc<dyn AuthorizedToolBackend>,
context: PolicyRequestContext,
approvals: BTreeMap<String, ApprovalId>,
now: Arc<dyn Fn() -> u64 + Send + Sync>,
) -> Result<Self, PolicyError> {
if approvals.len() > crate::types::MAX_TOOL_CALLS
|| approvals
.keys()
.any(|call_id| call_id.is_empty() || call_id.len() > MAX_IDENTIFIER_BYTES)
{
return Err(PolicyError::UnsafeLimits);
}
Ok(Self {
gateway,
backend,
context,
approvals,
now,
})
}
}
impl ToolExecutor for PolicyToolExecutor {
fn execute<'a>(
&'a self,
_definition: &'a ToolDefinition,
call: &'a ProposedToolCall,
arguments: &'a Value,
cancellation: &'a CancellationToken,
) -> ToolFuture<'a> {
Box::pin(async move {
let approval = self.approvals.get(call.call_id.as_str()).copied();
let Ok(evaluation) =
self.gateway
.evaluate(&self.context, call, arguments, approval, (self.now)())
else {
return fixed_failure("policy gateway failed closed");
};
let Some(action) = evaluation.into_authorization() else {
return fixed_rejection("policy denied or requires approval");
};
let receipt = action.receipt.clone();
let result = self.backend.apply(action, cancellation.clone()).await;
match result {
Ok(ToolCallOutcome::Completed { call_id, result })
if call_id == receipt.call_id =>
{
if self
.gateway
.emit_outcome(&receipt, PolicyFinalOutcome::Completed)
.is_err()
{
fixed_failure("policy outcome audit failed")
} else {
ToolExecution::Completed(result)
}
}
Ok(ToolCallOutcome::Rejected { call_id, reason }) if call_id == receipt.call_id => {
if self
.gateway
.emit_outcome(&receipt, PolicyFinalOutcome::Rejected)
.is_err()
{
fixed_failure("policy outcome audit failed")
} else {
ToolExecution::Rejected(
BoundedText::new("policy.backend_rejection", reason.as_str())
.expect("observable detail fits message bound"),
)
}
}
Ok(_) | Err(_)
if receipt.mutating && receipt.idempotency == Idempotency::NonIdempotent =>
{
let _ = self
.gateway
.emit_outcome(&receipt, PolicyFinalOutcome::AmbiguousMutation);
ToolExecution::AmbiguousMutation
}
Ok(_) | Err(_) => {
let _ = self
.gateway
.emit_outcome(&receipt, PolicyFinalOutcome::Failed);
fixed_failure("authorized tool backend failed")
}
}
})
}
}
fn fixed_rejection(message: &'static str) -> ToolExecution {
ToolExecution::Rejected(
BoundedText::<MAX_MESSAGE_BYTES>::new("policy.rejection", message)
.expect("fixed rejection is bounded"),
)
}
fn fixed_failure(message: &'static str) -> ToolExecution {
ToolExecution::Failed(
BoundedText::<MAX_MESSAGE_BYTES>::new("policy.failure", message)
.expect("fixed failure is bounded"),
)
}