use picloud_shared::ScriptSandbox; /// Resource and capability limits applied to every script execution. /// /// Defaults are conservative and safe to expose to untrusted Rhai sources. /// Per-script overrides (via `Limits::with_overrides`) replace individual /// fields; the manager clamps every override against the admin ceiling at /// write time, so the executor trusts what arrives in `ExecRequest`. #[derive(Debug, Clone, Copy)] pub struct Limits { /// Hard cap on Rhai operations executed per invocation. /// Doubles as a CPU-time proxy without needing real timers. pub max_operations: u64, /// Max length of any single string the script constructs. pub max_string_size: usize, /// Max number of elements in any array. pub max_array_size: usize, /// Max number of properties in any object/map. pub max_map_size: usize, /// Max call/expression nesting depth. pub max_call_levels: usize, pub max_expr_depth: usize, /// v1.1.3: hard ceiling on `import` chain depth (A→B→C→…). Independent /// of cycle detection — guards against deep but acyclic graphs. /// Not script-overridable (this is a platform-level guard, not a /// per-script knob). pub module_import_depth_max: u32, } impl Default for Limits { fn default() -> Self { Self { max_operations: 1_000_000, max_string_size: 64 * 1024, max_array_size: 10_000, max_map_size: 10_000, max_call_levels: 64, max_expr_depth: 64, module_import_depth_max: 8, } } } impl Limits { /// Returns a new `Limits` with each field replaced by the matching /// override if present, otherwise the existing field. Overrides /// arrive as `u64` for JSONB round-tripping cleanliness; they're /// narrowed to `usize` here, saturating on the unlikely overflow /// (these caps come from admin-clamped writes, so the values are /// always small). #[must_use] pub fn with_overrides(&self, overrides: &ScriptSandbox) -> Self { Self { max_operations: overrides.max_operations.unwrap_or(self.max_operations), max_string_size: overrides .max_string_size .map_or(self.max_string_size, narrow_usize), max_array_size: overrides .max_array_size .map_or(self.max_array_size, narrow_usize), max_map_size: overrides .max_map_size .map_or(self.max_map_size, narrow_usize), max_call_levels: overrides .max_call_levels .map_or(self.max_call_levels, narrow_usize), max_expr_depth: overrides .max_expr_depth .map_or(self.max_expr_depth, narrow_usize), // module_import_depth_max is platform-level — overrides // never touch it. Carry through unchanged. module_import_depth_max: self.module_import_depth_max, } } } fn narrow_usize(v: u64) -> usize { usize::try_from(v).unwrap_or(usize::MAX) }