use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;
use std::time::Duration;
use super::intent::Command;
use super::sandbox::SandboxId;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BackendCapabilities {
pub name: String,
pub filesystem_isolation: bool,
pub network_isolation: bool,
pub process_isolation: bool,
pub resource_limits: bool,
pub syscall_filtering: bool,
pub persistent_sandboxes: bool,
pub snapshots: bool,
pub max_concurrent_sandboxes: Option<u32>,
pub available_profiles: Vec<String>,
pub platform_features: Vec<String>,
}
impl BackendCapabilities {
pub fn is_fully_isolated(&self) -> bool {
self.filesystem_isolation
&& self.network_isolation
&& self.process_isolation
&& self.syscall_filtering
}
pub fn is_soft_isolation(&self) -> bool {
!self.filesystem_isolation && !self.syscall_filtering
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BindMount {
pub source: PathBuf,
pub target: PathBuf,
pub readonly: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SandboxSpec {
pub profile: String,
pub workdir: PathBuf,
pub allowed_paths_ro: Vec<PathBuf>,
pub allowed_paths_rw: Vec<PathBuf>,
pub bind_mounts: Vec<BindMount>,
pub allowed_network: Vec<String>,
pub environment: Vec<(String, String)>,
pub limits: ResourceLimits,
pub network_enabled: bool,
pub seccomp_profile: Option<String>,
pub creation_timeout: Duration,
pub labels: Vec<(String, String)>,
}
impl Default for SandboxSpec {
fn default() -> Self {
Self {
profile: "default".to_string(),
workdir: PathBuf::from("/workspace"),
allowed_paths_ro: vec![],
allowed_paths_rw: vec![],
bind_mounts: vec![],
allowed_network: vec![],
environment: vec![],
limits: ResourceLimits::default(),
network_enabled: false,
seccomp_profile: None,
creation_timeout: Duration::from_secs(30),
labels: vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimits {
pub max_memory_bytes: Option<u64>,
pub max_cpu_time_ms: Option<u64>,
pub max_wall_time_ms: Option<u64>,
pub max_processes: Option<u32>,
pub max_open_files: Option<u32>,
pub max_output_bytes: Option<u64>,
pub max_write_bytes: Option<u64>,
pub cpu_weight: Option<u32>,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_bytes: Some(512 * 1024 * 1024), max_cpu_time_ms: Some(60_000), max_wall_time_ms: Some(120_000), max_processes: Some(64),
max_open_files: Some(256),
max_output_bytes: Some(10 * 1024 * 1024), max_write_bytes: Some(100 * 1024 * 1024), cpu_weight: Some(100),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SandboxCapabilities {
pub sandbox_id: String,
pub backend: String,
pub profile: String,
pub can_write_filesystem: bool,
pub readable_paths: Vec<PathBuf>,
pub writable_paths: Vec<PathBuf>,
pub has_network: bool,
pub allowed_destinations: Vec<String>,
pub limits: ResourceLimits,
pub syscall_filter_active: bool,
pub blocked_syscall_categories: Vec<String>,
pub is_persistent: bool,
pub created_at: chrono::DateTime<chrono::Utc>,
pub time_remaining_ms: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct ExecOutput {
pub exit_code: i32,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub duration: Duration,
pub timed_out: bool,
pub resource_limited: bool,
pub resource_usage: Option<ResourceUsage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceUsage {
pub peak_memory_bytes: u64,
pub cpu_time_ms: u64,
pub wall_time_ms: u64,
pub bytes_written: u64,
pub bytes_read: u64,
}
#[derive(Debug, Clone)]
pub enum StreamOutput {
Stdout(Vec<u8>),
Stderr(Vec<u8>),
Exit { code: i32 },
}
#[derive(Debug, Clone)]
pub struct ExecContext {
pub trace_id: String,
pub request_id: String,
pub workdir: Option<PathBuf>,
pub extra_env: Vec<(String, String)>,
pub timeout: Option<Duration>,
pub capture_stdout: bool,
pub capture_stderr: bool,
pub stream_output: bool,
}
impl Default for ExecContext {
fn default() -> Self {
Self {
trace_id: String::new(),
request_id: String::new(),
workdir: None,
extra_env: vec![],
timeout: None,
capture_stdout: true,
capture_stderr: true,
stream_output: false,
}
}
}
#[async_trait]
pub trait IsolationBackend: Send + Sync {
fn name(&self) -> &str;
async fn probe(&self) -> Result<BackendCapabilities>;
async fn create_sandbox(&self, spec: &SandboxSpec) -> Result<Box<dyn Sandbox>>;
async fn list_sandboxes(&self) -> Result<Vec<SandboxId>>;
async fn get_sandbox(&self, id: &SandboxId) -> Result<Option<Box<dyn Sandbox>>>;
async fn destroy_all(&self) -> Result<()>;
async fn health_check(&self) -> Result<BackendHealth>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendHealth {
pub healthy: bool,
pub active_sandboxes: u32,
pub resource_utilization: f32,
pub warnings: Vec<String>,
pub last_sandbox_created: Option<chrono::DateTime<chrono::Utc>>,
}
#[async_trait]
pub trait Sandbox: Send + Sync {
fn id(&self) -> &SandboxId;
fn capabilities(&self) -> &SandboxCapabilities;
async fn exec(&self, cmd: &Command, ctx: &ExecContext) -> Result<ExecOutput>;
async fn exec_streaming(
&self,
cmd: &Command,
ctx: &ExecContext,
output_tx: tokio::sync::mpsc::Sender<StreamOutput>,
) -> Result<ExecOutput>;
async fn is_alive(&self) -> bool;
async fn suspend(&self) -> Result<()>;
async fn resume(&self) -> Result<()>;
async fn snapshot(&self, name: &str) -> Result<String>;
async fn restore(&self, snapshot_id: &str) -> Result<()>;
async fn destroy(&self) -> Result<()>;
async fn resource_usage(&self) -> Result<ResourceUsage>;
}