use crate::mm::PageInRequest;
use crate::mm::memory::{MemoryQuery, MemoryWriteRequest};
use crate::scheduler::tcb::WaitReason;
use crate::types::agent::IsolationManifest;
use crate::types::message::ToolCall;
use crate::types::policy::GovernanceVerdict;
#[derive(Debug, Clone)]
pub enum Syscall {
Invoke(ToolCall),
Spawn(IsolationManifest),
PageIn(PageInRequest),
WriteMemory(MemoryWriteRequest),
QueryMemory(MemoryQuery),
SubmitNodes { count: usize },
LoadWorkflow { node_count: usize },
}
impl Syscall {
pub fn opcode(&self) -> &'static str {
match self {
Self::Invoke(_) => "invoke",
Self::Spawn(_) => "spawn",
Self::PageIn(_) => "page_in",
Self::WriteMemory(_) => "write_memory",
Self::QueryMemory(_) => "query_memory",
Self::SubmitNodes { .. } => "submit_nodes",
Self::LoadWorkflow { .. } => "load_workflow",
}
}
}
#[derive(Debug, Clone)]
pub enum Disposition {
Allow,
Deny { stage: &'static str, reason: String },
Gate { wait: WaitReason, reason: String },
Defer { slot: u32 },
RateLimited { retry_after_ms: u64 },
}
impl Disposition {
pub fn label(&self) -> &'static str {
match self {
Self::Allow => "allow",
Self::Deny { .. } => "deny",
Self::Gate { .. } => "gate",
Self::Defer { .. } => "defer",
Self::RateLimited { .. } => "rate_limited",
}
}
pub fn is_allowed(&self) -> bool {
matches!(self, Self::Allow)
}
}
impl From<GovernanceVerdict> for Disposition {
fn from(verdict: GovernanceVerdict) -> Self {
match verdict {
GovernanceVerdict::Allow => Disposition::Allow,
GovernanceVerdict::Deny { stage, reason } => Disposition::Deny { stage, reason },
GovernanceVerdict::RateLimited { retry_after_ms } => {
Disposition::RateLimited { retry_after_ms }
}
GovernanceVerdict::AskUser { reason } => Disposition::Gate {
wait: WaitReason::Approval,
reason,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verdict_allow_maps_to_allow() {
let d: Disposition = GovernanceVerdict::Allow.into();
assert!(d.is_allowed());
assert_eq!(d.label(), "allow");
}
#[test]
fn verdict_deny_preserves_stage_and_reason() {
let d: Disposition = GovernanceVerdict::Deny {
stage: "veto",
reason: "blocked".into(),
}
.into();
match d {
Disposition::Deny { stage, reason } => {
assert_eq!(stage, "veto");
assert_eq!(reason, "blocked");
}
other => panic!("expected Deny, got {other:?}"),
}
assert!(!Disposition::Deny { stage: "veto", reason: String::new() }.is_allowed());
}
#[test]
fn verdict_ask_user_maps_to_gate_approval() {
let d: Disposition = GovernanceVerdict::AskUser {
reason: "confirm".into(),
}
.into();
assert!(matches!(
&d,
Disposition::Gate { wait: WaitReason::Approval, reason } if reason == "confirm"
));
assert!(!d.is_allowed());
}
#[test]
fn verdict_rate_limited_preserves_delay() {
let d: Disposition = GovernanceVerdict::RateLimited { retry_after_ms: 500 }.into();
assert!(matches!(d, Disposition::RateLimited { retry_after_ms: 500 }));
}
#[test]
fn syscall_opcode_labels() {
use crate::types::message::ToolCall;
let call = ToolCall {
id: "c1".into(),
name: "read".into(),
arguments: serde_json::json!({}),
};
assert_eq!(Syscall::Invoke(call).opcode(), "invoke");
}
}