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 crate::events::types::TraverseEvent;
28use serde_json::Value;
29use traverse_contracts::{EventReference, ServiceType};
30
31/// The artifact type recorded in a capability registration, used to route to the correct executor.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub enum ArtifactType {
34    /// Native Rust implementation — executed via [`NativeExecutor`].
35    Native,
36    /// WASM binary — executed via [`WasmExecutor`].
37    Wasm,
38}
39
40/// A resolved capability ready for execution.
41#[derive(Debug, Clone)]
42pub struct ExecutorCapability {
43    /// Unique capability identifier.
44    pub capability_id: String,
45    /// How the binary is packaged.
46    pub artifact_type: ArtifactType,
47    /// File-system path to the `.wasm` binary (only relevant for `ArtifactType::Wasm`).
48    pub wasm_binary_path: Option<String>,
49    /// Expected SHA-256 hex digest of the WASM binary (only relevant for `ArtifactType::Wasm`).
50    pub wasm_checksum: Option<String>,
51    /// Traverse Host ABI version declared by the module manifest.
52    pub host_abi_version: Option<String>,
53    /// Event types this capability's contract declares under `emits`, used to
54    /// validate `traverse_host::emit_event` calls synchronously at call time
55    /// (spec 098-capability-event-host-abi FR-002).
56    pub emits: Vec<EventReference>,
57    /// The capability contract's `service_type`; only `Subscribable` may call
58    /// `traverse_host::emit_event` (spec 098-capability-event-host-abi FR-003).
59    pub service_type: ServiceType,
60}
61
62/// Output of a [`CapabilityExecutor::execute`] call.
63#[derive(Debug, Clone, PartialEq)]
64pub struct ExecutorOutput {
65    /// The capability's JSON output value.
66    pub value: Value,
67    /// Events accepted via `traverse_host::emit_event` during this execution,
68    /// already validated against the capability's contract (spec
69    /// 098-capability-event-host-abi FR-002/FR-003). Always empty for
70    /// non-WASM executors, since the host ABI is WASM-only.
71    pub emitted_events: Vec<TraverseEvent>,
72}
73
74/// Error returned by a [`CapabilityExecutor`].
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum ExecutorError {
77    /// The WASM binary could not be loaded from the given path.
78    BinaryLoadFailed(String),
79    /// The SHA-256 checksum of the loaded binary did not match the expected value.
80    ChecksumMismatch { expected: String, actual: String },
81    /// The Wasmtime engine or linker could not be configured.
82    RuntimeSetupFailed(String),
83    /// The WASM artifact is malformed and cannot be parsed as a module.
84    MalformedWasmArtifact { error_code: String, detail: String },
85    /// The WASM artifact declares an unsupported Traverse Host ABI version.
86    UnsupportedAbiVersion {
87        error_code: String,
88        requested: String,
89        supported: String,
90    },
91    /// The WASM artifact imports a host function outside the declared ABI whitelist.
92    UnauthorizedHostImport {
93        error_code: String,
94        abi_version: String,
95        module: String,
96        name: String,
97    },
98    /// The WASM module trapped or returned a non-zero exit code.
99    ExecutionFailed(String),
100    /// WASM execution exhausted its configured CPU budget.
101    Timeout(String),
102    /// WASM execution exceeded its configured memory, table, or instance budget.
103    ResourceExhausted(String),
104    /// The executor produced output that could not be parsed as JSON.
105    OutputDeserializationFailed(String),
106    /// The executor type does not support the requested capability.
107    UnsupportedArtifactType,
108}
109
110impl std::fmt::Display for ExecutorError {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            Self::BinaryLoadFailed(msg) => write!(f, "binary load failed: {msg}"),
114            Self::ChecksumMismatch { expected, actual } => {
115                write!(f, "checksum mismatch: expected {expected}, got {actual}")
116            }
117            Self::RuntimeSetupFailed(msg) => write!(f, "runtime setup failed: {msg}"),
118            Self::MalformedWasmArtifact { error_code, detail } => {
119                write!(f, "{error_code}: {detail}")
120            }
121            Self::UnsupportedAbiVersion {
122                error_code,
123                requested,
124                supported,
125            } => write!(
126                f,
127                "{error_code}: requested Traverse Host ABI {requested}, supported {supported}"
128            ),
129            Self::UnauthorizedHostImport {
130                error_code,
131                abi_version,
132                module,
133                name,
134            } => write!(
135                f,
136                "{error_code}: ABI {abi_version} does not allow import {module}::{name}"
137            ),
138            Self::ExecutionFailed(msg) => write!(f, "execution failed: {msg}"),
139            Self::Timeout(msg) => write!(f, "execution timed out: {msg}"),
140            Self::ResourceExhausted(msg) => write!(f, "resource exhausted: {msg}"),
141            Self::OutputDeserializationFailed(msg) => {
142                write!(f, "output deserialization failed: {msg}")
143            }
144            Self::UnsupportedArtifactType => {
145                write!(f, "unsupported artifact type for this executor")
146            }
147        }
148    }
149}
150
151impl std::error::Error for ExecutorError {}
152
153/// Trait implemented by all capability executors.
154///
155/// Executors are stateless; all context is passed per call.
156pub trait CapabilityExecutor: Send + Sync {
157    /// Execute `capability` with `input`, returning the output or an error.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`ExecutorError`] when execution cannot be completed.
162    fn execute(
163        &self,
164        capability: &ExecutorCapability,
165        input: &Value,
166    ) -> Result<ExecutorOutput, ExecutorError>;
167}