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}