use std::path::PathBuf; use std::time::Duration; use crate::bootstrap::ironclaw_base_dir; use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env}; use crate::error::ConfigError; /// WASM sandbox configuration. #[derive(Debug, Clone)] pub struct WasmConfig { /// Whether WASM tool execution is enabled. pub enabled: bool, /// Directory containing installed WASM tools (default: ~/.ironclaw/tools/). pub tools_dir: PathBuf, /// Default memory limit in bytes (default: 10 MB). pub default_memory_limit: u64, /// Default execution timeout in seconds (default: 60). pub default_timeout_secs: u64, /// Default fuel limit for CPU metering (default: 10M). pub default_fuel_limit: u64, /// Whether to cache compiled modules. pub cache_compiled: bool, /// Directory for compiled module cache. pub cache_dir: Option, } impl Default for WasmConfig { fn default() -> Self { Self { enabled: true, tools_dir: default_tools_dir(), default_memory_limit: 10 * 1024 * 1024, // 10 MB default_timeout_secs: 60, default_fuel_limit: 10_000_000, cache_compiled: true, cache_dir: None, } } } /// Get the default tools directory (~/.ironclaw/tools/). fn default_tools_dir() -> PathBuf { ironclaw_base_dir().join("tools") } impl WasmConfig { pub(crate) fn resolve(settings: &crate::settings::Settings) -> Result { let ws = &settings.wasm; Ok(Self { enabled: parse_bool_env("WASM_ENABLED", ws.enabled)?, tools_dir: optional_env("WASM_TOOLS_DIR")? .map(PathBuf::from) .or_else(|| ws.tools_dir.clone()) .unwrap_or_else(default_tools_dir), default_memory_limit: parse_optional_env( "WASM_DEFAULT_MEMORY_LIMIT", ws.default_memory_limit, )?, default_timeout_secs: parse_optional_env( "WASM_DEFAULT_TIMEOUT_SECS", ws.default_timeout_secs, )?, default_fuel_limit: parse_optional_env( "WASM_DEFAULT_FUEL_LIMIT", ws.default_fuel_limit, )?, cache_compiled: parse_bool_env("WASM_CACHE_COMPILED", ws.cache_compiled)?, cache_dir: optional_env("WASM_CACHE_DIR")? .map(PathBuf::from) .or_else(|| ws.cache_dir.clone()), }) } /// Convert to WasmRuntimeConfig. pub fn to_runtime_config(&self) -> crate::tools::wasm::WasmRuntimeConfig { use crate::tools::wasm::{FuelConfig, ResourceLimits, WasmRuntimeConfig}; WasmRuntimeConfig { default_limits: ResourceLimits { memory_bytes: self.default_memory_limit, fuel: self.default_fuel_limit, timeout: Duration::from_secs(self.default_timeout_secs), }, fuel_config: FuelConfig { initial_fuel: self.default_fuel_limit, enabled: true, }, cache_compiled: self.cache_compiled, cache_dir: self.cache_dir.clone(), optimization_level: wasmtime::OptLevel::Speed, } } } #[cfg(test)] mod tests { use super::*; use crate::config::helpers::lock_env; use crate::settings::Settings; #[test] fn resolve_falls_back_to_settings() { let _guard = lock_env(); let mut settings = Settings::default(); settings.wasm.default_memory_limit = 42; settings.wasm.cache_compiled = false; let cfg = WasmConfig::resolve(&settings).expect("resolve"); assert_eq!(cfg.default_memory_limit, 42); assert!(!cfg.cache_compiled); } #[test] fn env_overrides_settings() { let _guard = lock_env(); let mut settings = Settings::default(); settings.wasm.default_fuel_limit = 42; // SAFETY: Under ENV_MUTEX, no concurrent env access. unsafe { std::env::set_var("WASM_DEFAULT_FUEL_LIMIT", "7") }; let cfg = WasmConfig::resolve(&settings).expect("resolve"); unsafe { std::env::remove_var("WASM_DEFAULT_FUEL_LIMIT") }; assert_eq!(cfg.default_fuel_limit, 7); } }