Skip to main content

traverse_runtime/executor/
mod.rs

1//! Capability executor abstraction for Traverse.
2//!
3//! Governed by spec `025-wasm-executor-adapter`.
4//!
5//! Two concrete implementations:
6//! - [`NativeExecutor`] — executes capabilities implemented as native Rust closures.
7//! - `WasmExecutor` — executes capabilities compiled to `wasm32-wasi` binaries
8//!   via Wasmtime when the `wasmtime-executor` feature is enabled.
9//! - `ThreadPoolExecutor` — dispatches native capability execution onto a bounded
10//!   worker pool when the `native-executors` feature is enabled.
11pub mod native;
12#[cfg(feature = "native-executors")]
13pub mod thread_pool;
14#[cfg(feature = "wasmtime-executor")]
15pub mod wasm;
16
17pub use native::NativeExecutor;
18#[cfg(feature = "native-executors")]
19pub use thread_pool::{ConfigError, ThreadPoolExecutor, ThreadPoolExecutorConfig};
20#[cfg(feature = "wasmtime-executor")]
21pub use wasm::{
22    HostAbiImport, HostAbiValidation, SUPPORTED_HOST_ABI_VERSION, WasmExecutionLimits,
23    WasmExecutor, WasmModuleCacheConfig, WasmModuleCacheStats, supported_host_abi_versions,
24    verify_wasm_host_abi_bytes,
25};
26
27use serde_json::Value;
28
29/// The artifact type recorded in a capability registration, used to route to the correct executor.
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum ArtifactType {
32    /// Native Rust implementation — executed via [`NativeExecutor`].
33    Native,
34    /// WASM binary — executed via [`WasmExecutor`].
35    Wasm,
36}
37
38/// A resolved capability ready for execution.
39#[derive(Debug, Clone)]
40pub struct ExecutorCapability {
41    /// Unique capability identifier.
42    pub capability_id: String,
43    /// How the binary is packaged.
44    pub artifact_type: ArtifactType,
45    /// File-system path to the `.wasm` binary (only relevant for `ArtifactType::Wasm`).
46    pub wasm_binary_path: Option<String>,
47    /// Expected SHA-256 hex digest of the WASM binary (only relevant for `ArtifactType::Wasm`).
48    pub wasm_checksum: Option<String>,
49    /// Traverse Host ABI version declared by the module manifest.
50    pub host_abi_version: Option<String>,
51}
52
53/// Error returned by a [`CapabilityExecutor`].
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum ExecutorError {
56    /// The WASM binary could not be loaded from the given path.
57    BinaryLoadFailed(String),
58    /// The SHA-256 checksum of the loaded binary did not match the expected value.
59    ChecksumMismatch { expected: String, actual: String },
60    /// The Wasmtime engine or linker could not be configured.
61    RuntimeSetupFailed(String),
62    /// The WASM artifact is malformed and cannot be parsed as a module.
63    MalformedWasmArtifact { error_code: String, detail: String },
64    /// The WASM artifact declares an unsupported Traverse Host ABI version.
65    UnsupportedAbiVersion {
66        error_code: String,
67        requested: String,
68        supported: String,
69    },
70    /// The WASM artifact imports a host function outside the declared ABI whitelist.
71    UnauthorizedHostImport {
72        error_code: String,
73        abi_version: String,
74        module: String,
75        name: String,
76    },
77    /// The WASM module trapped or returned a non-zero exit code.
78    ExecutionFailed(String),
79    /// WASM execution exhausted its configured CPU budget.
80    Timeout(String),
81    /// WASM execution exceeded its configured memory, table, or instance budget.
82    ResourceExhausted(String),
83    /// The executor produced output that could not be parsed as JSON.
84    OutputDeserializationFailed(String),
85    /// The executor type does not support the requested capability.
86    UnsupportedArtifactType,
87}
88
89impl std::fmt::Display for ExecutorError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::BinaryLoadFailed(msg) => write!(f, "binary load failed: {msg}"),
93            Self::ChecksumMismatch { expected, actual } => {
94                write!(f, "checksum mismatch: expected {expected}, got {actual}")
95            }
96            Self::RuntimeSetupFailed(msg) => write!(f, "runtime setup failed: {msg}"),
97            Self::MalformedWasmArtifact { error_code, detail } => {
98                write!(f, "{error_code}: {detail}")
99            }
100            Self::UnsupportedAbiVersion {
101                error_code,
102                requested,
103                supported,
104            } => write!(
105                f,
106                "{error_code}: requested Traverse Host ABI {requested}, supported {supported}"
107            ),
108            Self::UnauthorizedHostImport {
109                error_code,
110                abi_version,
111                module,
112                name,
113            } => write!(
114                f,
115                "{error_code}: ABI {abi_version} does not allow import {module}::{name}"
116            ),
117            Self::ExecutionFailed(msg) => write!(f, "execution failed: {msg}"),
118            Self::Timeout(msg) => write!(f, "execution timed out: {msg}"),
119            Self::ResourceExhausted(msg) => write!(f, "resource exhausted: {msg}"),
120            Self::OutputDeserializationFailed(msg) => {
121                write!(f, "output deserialization failed: {msg}")
122            }
123            Self::UnsupportedArtifactType => {
124                write!(f, "unsupported artifact type for this executor")
125            }
126        }
127    }
128}
129
130impl std::error::Error for ExecutorError {}
131
132/// Trait implemented by all capability executors.
133///
134/// Executors are stateless; all context is passed per call.
135pub trait CapabilityExecutor: Send + Sync {
136    /// Execute `capability` with `input`, returning the output or an error.
137    ///
138    /// # Errors
139    ///
140    /// Returns [`ExecutorError`] when execution cannot be completed.
141    fn execute(
142        &self,
143        capability: &ExecutorCapability,
144        input: &Value,
145    ) -> Result<Value, ExecutorError>;
146}