feat(grid-agent): enforce central action policy (#120)
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m44s
CI / required (push) Failing after 2m42s

This commit is contained in:
2026-08-17 21:18:52 +00:00
parent a46bc42a8f
commit e3b9d575f9
13 changed files with 3288 additions and 33 deletions

View File

@@ -1,6 +1,7 @@
//! Narrow injected boundaries between orchestration and grid/world I/O.
use crate::types::{GridEvent, GridEventKind, PolicyDecision, ProposedToolCall, ToolCallOutcome};
use crate::policy::AuthorizedAction;
use crate::types::{GridEvent, GridEventKind, ToolCallOutcome};
use libremetaverse_types::compat::CancellationToken;
use std::error::Error;
use std::fmt;
@@ -49,18 +50,21 @@ pub trait GridBackend: Send + Sync + 'static {
) -> BackendFuture<'_, Result<(), BackendError>>;
}
/// The sole world-mutation boundary. Later tool implementations cannot bypass
/// the policy decision passed to this trait, and fake/live implementations use
/// the same call shape.
pub trait WorldMutator: Send + Sync + 'static {
/// Backend for an already authorized tool action. `AuthorizedAction` has no
/// public constructor and binds the exact name, arguments, principal, budget,
/// and one policy authorization.
pub trait AuthorizedToolBackend: Send + Sync + 'static {
fn apply(
&self,
call: ProposedToolCall,
decision: PolicyDecision,
action: AuthorizedAction,
cancellation: CancellationToken,
) -> BackendFuture<'_, Result<ToolCallOutcome, BackendError>>;
}
/// Marker for world-mutating backends. Production mutations use the same
/// opaque authorization boundary as every other tool backend.
pub trait WorldMutator: AuthorizedToolBackend {}
/// Inert deterministic backend used by the foundational offline service.
///
/// It performs no login or network operation and is available without the

View File

@@ -7,13 +7,20 @@
pub mod backend;
pub mod config;
pub mod llm;
pub mod policy;
pub mod service;
pub mod tool_loop;
pub mod types;
#[cfg(test)]
mod policy_tests;
#[cfg(feature = "live-grid")]
pub use backend::LibremetaverseClientOwner;
pub use backend::{BackendError, BackendFuture, GridBackend, OfflineGridBackend, WorldMutator};
pub use backend::{
AuthorizedToolBackend, BackendError, BackendFuture, GridBackend, OfflineGridBackend,
WorldMutator,
};
pub use config::{
AgentConfig, BehaviorSettings, ConfigError, ConfigLoader, EndpointUrl, Environment,
GridConnection, Limits, LlmConnection, MapEnvironment, OperatingMode, SecretString,
@@ -23,6 +30,14 @@ pub use llm::{
Completion, CompletionMessage, ContentPart, ImageDetail, LlmClient, LlmError,
LlmTransportLimits, ToolDefinition, ToolSchema, Usage,
};
pub use policy::{
ActionOrigin, AllowedOrigins, ApprovalId, ApprovalRule, AuthenticatedPrincipal,
AuthorizedAction, BudgetLimits, Capability, FixedCost, Idempotency, MemoryPolicyAudit,
OriginClass, PolicyAuditError, PolicyAuditRecord, PolicyAuditSink, PolicyDisposition,
PolicyError, PolicyEvaluation, PolicyFinalOutcome, PolicyGateway, PolicyLimits,
PolicyReasonCode, PolicyRequestContext, PolicySnapshot, PolicyTool, PolicyToolExecutor,
ResourceCost, ResourceEstimator, Risk, SchedulerGrantId, UntrustedData, UntrustedSource,
};
pub use service::{AgentService, ServiceError, ServiceHandle, ServiceState};
pub use tool_loop::{
HistorySummarizer, SessionGeneration, ToolExecution, ToolExecutor, ToolFuture, ToolLoop,
@@ -30,6 +45,6 @@ pub use tool_loop::{
};
pub use types::{
BoundaryError, BoundedText, BoundedVec, ControlCommand, Conversation, ConversationMessage,
GridEvent, GridEventKind, LlmRequest, LlmResult, MessageRole, ObservableEvent, PolicyDecision,
GridEvent, GridEventKind, LlmRequest, LlmResult, MessageRole, ObservableEvent,
ProposedToolCall, ToolCallOutcome,
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,8 +21,9 @@ const MAX_SESSION_TOOL_CALLS: usize = 256;
pub type ToolFuture<'a> = Pin<Box<dyn Future<Output = ToolExecution> + Send + 'a>>;
/// Downstream execution boundary. Issue #120 can implement policy evaluation
/// here; this loop guarantees its input already passed name/schema checks.
/// Downstream execution boundary. Production wiring uses
/// [`crate::policy::PolicyToolExecutor`]; this loop guarantees its input already
/// passed name/schema checks, and the policy gateway validates it again.
pub trait ToolExecutor: Send + Sync {
fn execute<'a>(
&'a self,

View File

@@ -347,19 +347,6 @@ impl ProposedToolCall {
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PolicyDecision {
Approved {
authorization_id: u64,
},
Denied {
reason: BoundedText<MAX_OBSERVABLE_DETAIL_BYTES>,
},
NeedsOperatorApproval {
prompt: BoundedText<MAX_OBSERVABLE_DETAIL_BYTES>,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LlmResult {
pub request_id: u64,
@@ -393,10 +380,7 @@ pub enum ObservableEvent {
state: &'static str,
},
Grid(GridEvent),
Policy {
call_id: BoundedText<MAX_IDENTIFIER_BYTES>,
decision: PolicyDecision,
},
Policy(crate::policy::PolicyAuditRecord),
Tool(ToolCallOutcome),
Diagnostic {
detail: BoundedText<MAX_OBSERVABLE_DETAIL_BYTES>,