Skip to main content

parallax_core/
capabilities.rs

1//! Explicit runtime capability declarations.
2//!
3//! Adapters must never hide limitations. Every capture/restore feature is
4//! reported with a clear support level.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Support level for a single capability.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum CapabilityLevel {
13    /// Fully supported with well-defined semantics.
14    Yes,
15    /// Partially supported; see adapter docs for constraints.
16    Partial,
17    /// Experimental and subject to change or failure.
18    Experimental,
19    /// Not supported. Attempts return `Unsupported`.
20    No,
21}
22
23impl CapabilityLevel {
24    /// Whether the capability may be attempted.
25    pub fn is_usable(self) -> bool {
26        matches!(self, Self::Yes | Self::Partial | Self::Experimental)
27    }
28
29    /// Compact status glyph for CLI tables.
30    pub fn glyph(self) -> &'static str {
31        match self {
32            Self::Yes => "YES",
33            Self::Partial => "PARTIAL",
34            Self::Experimental => "EXPERIMENTAL",
35            Self::No => "NO",
36        }
37    }
38}
39
40impl fmt::Display for CapabilityLevel {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "{}", self.glyph())
43    }
44}
45
46/// Declared capabilities of a runtime adapter.
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48pub struct RuntimeCapabilities {
49    /// Convert guest values to/from PIR.
50    pub values: CapabilityLevel,
51    /// Capture/restore global bindings.
52    pub globals: CapabilityLevel,
53    /// Capture/restore local bindings.
54    pub locals: CapabilityLevel,
55    /// Represent functions as references.
56    pub functions: CapabilityLevel,
57    /// Capture closures with environment.
58    pub closures: CapabilityLevel,
59    /// Capture call-stack frames (alias surface: stack_frames).
60    pub stack_capture: CapabilityLevel,
61    /// Structured stack frame export into UniversalFrame / UES.
62    #[serde(default = "default_no")]
63    pub stack_frames: CapabilityLevel,
64    /// Control / instruction position at safepoints.
65    #[serde(default = "default_no")]
66    pub control_position: CapabilityLevel,
67    /// In-flight exception state capture/restore.
68    #[serde(default = "default_no")]
69    pub exception_state: CapabilityLevel,
70    /// Capture a continuation / UES at a supported safepoint.
71    #[serde(default = "default_no")]
72    pub continuation_capture: CapabilityLevel,
73    /// Restore and continue from captured continuations.
74    pub continuation_restore: CapabilityLevel,
75    /// Resume a continuation on a different runtime.
76    #[serde(default = "default_no")]
77    pub cross_runtime_resume: CapabilityLevel,
78    /// Migrate in-flight async tasks.
79    pub async_migration: CapabilityLevel,
80    /// Execute guest programs.
81    pub execution: CapabilityLevel,
82    /// Capture stdout/stderr.
83    pub stdio_capture: CapabilityLevel,
84    /// Enforce execution timeouts.
85    pub timeouts: CapabilityLevel,
86    /// Enforce memory/output limits.
87    pub resource_limits: CapabilityLevel,
88    /// Cancel in-flight work.
89    pub cancellation: CapabilityLevel,
90}
91
92fn default_no() -> CapabilityLevel {
93    CapabilityLevel::No
94}
95
96impl RuntimeCapabilities {
97    /// Fully unsupported baseline.
98    pub fn none() -> Self {
99        Self {
100            values: CapabilityLevel::No,
101            globals: CapabilityLevel::No,
102            locals: CapabilityLevel::No,
103            functions: CapabilityLevel::No,
104            closures: CapabilityLevel::No,
105            stack_capture: CapabilityLevel::No,
106            stack_frames: CapabilityLevel::No,
107            control_position: CapabilityLevel::No,
108            exception_state: CapabilityLevel::No,
109            continuation_capture: CapabilityLevel::No,
110            continuation_restore: CapabilityLevel::No,
111            cross_runtime_resume: CapabilityLevel::No,
112            async_migration: CapabilityLevel::No,
113            execution: CapabilityLevel::No,
114            stdio_capture: CapabilityLevel::No,
115            timeouts: CapabilityLevel::No,
116            resource_limits: CapabilityLevel::No,
117            cancellation: CapabilityLevel::No,
118        }
119    }
120
121    /// Python adapter capability profile (Continuum: explicit checkpoint EXP).
122    pub fn python() -> Self {
123        Self {
124            values: CapabilityLevel::Yes,
125            globals: CapabilityLevel::Yes,
126            locals: CapabilityLevel::Partial,
127            functions: CapabilityLevel::Partial,
128            closures: CapabilityLevel::Partial,
129            stack_capture: CapabilityLevel::Experimental,
130            stack_frames: CapabilityLevel::Experimental,
131            control_position: CapabilityLevel::Experimental,
132            exception_state: CapabilityLevel::No,
133            continuation_capture: CapabilityLevel::Experimental,
134            continuation_restore: CapabilityLevel::Experimental,
135            cross_runtime_resume: CapabilityLevel::No,
136            async_migration: CapabilityLevel::No,
137            execution: CapabilityLevel::Yes,
138            stdio_capture: CapabilityLevel::Yes,
139            timeouts: CapabilityLevel::Yes,
140            resource_limits: CapabilityLevel::Partial,
141            cancellation: CapabilityLevel::Yes,
142        }
143    }
144
145    /// JavaScript adapter capability profile.
146    pub fn javascript() -> Self {
147        Self {
148            values: CapabilityLevel::Yes,
149            globals: CapabilityLevel::Yes,
150            locals: CapabilityLevel::Partial,
151            functions: CapabilityLevel::Partial,
152            closures: CapabilityLevel::Partial,
153            stack_capture: CapabilityLevel::No,
154            stack_frames: CapabilityLevel::Experimental,
155            control_position: CapabilityLevel::Experimental,
156            exception_state: CapabilityLevel::No,
157            continuation_capture: CapabilityLevel::Experimental,
158            continuation_restore: CapabilityLevel::Experimental,
159            cross_runtime_resume: CapabilityLevel::No,
160            async_migration: CapabilityLevel::No,
161            execution: CapabilityLevel::Yes,
162            stdio_capture: CapabilityLevel::Yes,
163            timeouts: CapabilityLevel::Yes,
164            resource_limits: CapabilityLevel::Partial,
165            cancellation: CapabilityLevel::Yes,
166        }
167    }
168
169    /// WASM adapter capability profile — deliberately constrained.
170    pub fn wasm() -> Self {
171        Self {
172            values: CapabilityLevel::Partial,
173            globals: CapabilityLevel::No,
174            locals: CapabilityLevel::No,
175            functions: CapabilityLevel::Partial,
176            closures: CapabilityLevel::No,
177            stack_capture: CapabilityLevel::No,
178            stack_frames: CapabilityLevel::No,
179            control_position: CapabilityLevel::No,
180            exception_state: CapabilityLevel::No,
181            continuation_capture: CapabilityLevel::No,
182            continuation_restore: CapabilityLevel::No,
183            cross_runtime_resume: CapabilityLevel::No,
184            async_migration: CapabilityLevel::No,
185            execution: CapabilityLevel::Yes,
186            stdio_capture: CapabilityLevel::No,
187            timeouts: CapabilityLevel::Yes,
188            resource_limits: CapabilityLevel::Yes,
189            cancellation: CapabilityLevel::Yes,
190        }
191    }
192
193    /// Iterate named capabilities for CLI display (value + continuum fields).
194    pub fn entries(&self) -> Vec<(&'static str, CapabilityLevel)> {
195        vec![
196            ("Values", self.values),
197            ("Globals", self.globals),
198            ("Locals", self.locals),
199            ("Functions", self.functions),
200            ("Closures", self.closures),
201            ("Stack capture", self.stack_capture),
202            ("Stack frames", self.stack_frames),
203            ("Control position", self.control_position),
204            ("Exception state", self.exception_state),
205            ("Continuation capture", self.continuation_capture),
206            ("Continuation restore", self.continuation_restore),
207            ("Cross-runtime resume", self.cross_runtime_resume),
208            ("Async migration", self.async_migration),
209            ("Execution", self.execution),
210            ("Stdio capture", self.stdio_capture),
211            ("Timeouts", self.timeouts),
212            ("Resource limits", self.resource_limits),
213            ("Cancellation", self.cancellation),
214        ]
215    }
216
217    /// Continuation-only rows for `plx capabilities --continuations`.
218    pub fn continuation_entries(&self) -> Vec<(&'static str, CapabilityLevel)> {
219        vec![
220            ("Stack frames", self.stack_frames),
221            ("Control position", self.control_position),
222            ("Exception state", self.exception_state),
223            ("Closures", self.closures),
224            ("Async migration", self.async_migration),
225            ("Continuation capture", self.continuation_capture),
226            ("Continuation restore", self.continuation_restore),
227            ("Cross-runtime resume", self.cross_runtime_resume),
228        ]
229    }
230
231    /// Look up a capability by canonical snake_case name.
232    pub fn level_of(&self, name: &str) -> Option<CapabilityLevel> {
233        match name {
234            "values" => Some(self.values),
235            "globals" => Some(self.globals),
236            "locals" => Some(self.locals),
237            "functions" => Some(self.functions),
238            "closures" => Some(self.closures),
239            "stack_capture" => Some(self.stack_capture),
240            "stack_frames" => Some(self.stack_frames),
241            "control_position" => Some(self.control_position),
242            "exception_state" => Some(self.exception_state),
243            "continuation_capture" => Some(self.continuation_capture),
244            "continuation_restore" => Some(self.continuation_restore),
245            "cross_runtime_resume" => Some(self.cross_runtime_resume),
246            "async_migration" => Some(self.async_migration),
247            "execution" => Some(self.execution),
248            "stdio_capture" => Some(self.stdio_capture),
249            "timeouts" => Some(self.timeouts),
250            "resource_limits" => Some(self.resource_limits),
251            "cancellation" => Some(self.cancellation),
252            _ => None,
253        }
254    }
255
256    /// Require a usable capability or return a structured `CapabilityViolation`.
257    pub fn require(&self, name: &str, runtime: &crate::runtime::RuntimeKind) -> crate::Result<()> {
258        let level = self.level_of(name).unwrap_or(CapabilityLevel::No);
259        if level.is_usable() {
260            return Ok(());
261        }
262        Err(crate::error::ParallaxError::new(
263            crate::error::ErrorCode::CapabilityViolation,
264            format!("capability '{name}' is unsupported on {runtime}"),
265        )
266        .with_source("parallax-core")
267        .with_operation("require_capability")
268        .with_runtime(runtime.clone())
269        .context("capability", name.to_string())
270        .context("level", level.glyph())
271        .remediate(crate::error::Remediation::new(
272            "Choose a runtime that declares this capability, or omit the unsupported operation",
273        )))
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn python_can_execute() {
283        assert!(RuntimeCapabilities::python().execution.is_usable());
284        assert!(RuntimeCapabilities::python()
285            .continuation_capture
286            .is_usable());
287        assert!(!RuntimeCapabilities::python()
288            .cross_runtime_resume
289            .is_usable());
290    }
291
292    #[test]
293    fn wasm_has_no_continuations() {
294        let w = RuntimeCapabilities::wasm();
295        assert!(!w.continuation_capture.is_usable());
296        assert!(!w.continuation_restore.is_usable());
297    }
298}