From 73d7c76446b56333d8438a8c57100a398ca01bf5 Mon Sep 17 00:00:00 2001 From: Maofeng Date: Thu, 30 Jul 2026 08:13:24 +0800 Subject: [PATCH] docs(wit): document capability and component contracts --- components/clock-probe/src/lib.rs | 8 +++ components/clock-probe/wit.lock | 4 +- components/clock-probe/wit/world.wit | 2 + components/counter/src/lib.rs | 8 +++ components/counter/wit.lock | 2 +- components/counter/wit/world.wit | 1 + components/echo/src/lib.rs | 4 ++ components/echo/wit.lock | 2 +- components/echo/wit/world.wit | 1 + components/fault/src/lib.rs | 8 +++ components/fault/wit.lock | 2 +- components/fault/wit/world.wit | 1 + components/kv-probe/src/lib.rs | 8 +++ components/kv-probe/wit.lock | 4 +- components/kv-probe/wit/world.wit | 2 + components/resident-probe/src/lib.rs | 10 ++++ components/resident-probe/wit.lock | 4 +- components/resident-probe/wit/world.wit | 2 + components/spin/src/lib.rs | 7 +++ components/spin/wit.lock | 2 +- components/spin/wit/world.wit | 1 + components/wasi-clock-probe/src/lib.rs | 8 +++ components/wasi-clock-probe/wit.lock | 2 +- components/wasi-clock-probe/wit/world.wit | 2 + wit/clock/package.wit | 7 +++ wit/kv/package.wit | 20 ++++++++ wit/resident/package.wit | 60 +++++++++++++++++++++++ wit/service/package.wit | 23 +++++++++ 28 files changed, 194 insertions(+), 11 deletions(-) diff --git a/components/clock-probe/src/lib.rs b/components/clock-probe/src/lib.rs index 33d7bda..ae03d63 100644 --- a/components/clock-probe/src/lib.rs +++ b/components/clock-probe/src/lib.rs @@ -1,4 +1,12 @@ +//! Example service that imports the versioned Wasmeld monotonic-clock capability. +//! +//! Each response is eight little-endian timestamp bytes followed by the input. +//! The timestamp is Actor-local monotonic time, not a Unix timestamp. + mod bindings { + // This macro generates Rust ABI adapters at compile time from the + // materialized local WIT graph. It does not make a remote protocol call or + // embed the WIT source files; the Component carries canonical ABI types. wit_bindgen::generate!({ path: "wit", world: "clock-probe-component", diff --git a/components/clock-probe/wit.lock b/components/clock-probe/wit.lock index e7753ec..4e481a5 100644 --- a/components/clock-probe/wit.lock +++ b/components/clock-probe/wit.lock @@ -4,12 +4,12 @@ schema_version = 1 name = "wasmeld:clock" version = "0.1.0" source = "path+../../wit/clock" -sha256 = "93079c69d1b9cb3afc28da6fe4a7e37f724d0a09e780c847f8524d3920a90960" +sha256 = "43ece485a550eca94cf06cb711f0b42c9f4977d275e9a042c3ac84eeb1f941e1" replaced = true [[package]] name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/clock-probe/wit/world.wit b/components/clock-probe/wit/world.wit index 06ec11f..e0e5be3 100644 --- a/components/clock-probe/wit/world.wit +++ b/components/clock-probe/wit/world.wit @@ -1,5 +1,7 @@ package component:clock-probe@0.1.0; +/// Base service exports plus the exact monotonic-clock Host capability used by +/// this Component. Other Wasmeld capabilities are not linked implicitly. world clock-probe-component { include wasmeld:service/service-component@0.1.0; import wasmeld:clock/monotonic-clock@0.1.0; diff --git a/components/counter/src/lib.rs b/components/counter/src/lib.rs index a9bac5b..712c1c2 100644 --- a/components/counter/src/lib.rs +++ b/components/counter/src/lib.rs @@ -1,6 +1,14 @@ +//! Minimal stateful Component used to demonstrate a warm Actor Store. +//! +//! Invoke returns an eight-byte little-endian counter. The value survives +//! calls to the same Actor but resets on `init`, restart, or process recovery. + use core::sync::atomic::{AtomicU64, Ordering}; mod bindings { + // Bindings are generated from `wit/world.wit` plus materialized + // `wit/deps`; WIT is a compile-time ABI contract, not downloaded at run + // time and not executed as code. wit_bindgen::generate!({ path: "wit", world: "counter-component", diff --git a/components/counter/wit.lock b/components/counter/wit.lock index 128b39a..11bc1b3 100644 --- a/components/counter/wit.lock +++ b/components/counter/wit.lock @@ -4,5 +4,5 @@ schema_version = 1 name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/counter/wit/world.wit b/components/counter/wit/world.wit index 0392217..f27ea6e 100644 --- a/components/counter/wit/world.wit +++ b/components/counter/wit/world.wit @@ -1,5 +1,6 @@ package component:counter@0.1.0; +/// Counter only needs the base service lifecycle and imports no Host capability. world counter-component { include wasmeld:service/service-component@0.1.0; } diff --git a/components/echo/src/lib.rs b/components/echo/src/lib.rs index 134ccae..f1fa01e 100644 --- a/components/echo/src/lib.rs +++ b/components/echo/src/lib.rs @@ -1,4 +1,8 @@ +//! Smallest request/response Component example: it returns input bytes unchanged. + mod bindings { + // Generate the guest trait and export adapter from the selected WIT world. + // Dependency sync must populate `wit/deps` before this macro is compiled. wit_bindgen::generate!({ path: "wit", world: "echo-component", diff --git a/components/echo/wit.lock b/components/echo/wit.lock index 128b39a..11bc1b3 100644 --- a/components/echo/wit.lock +++ b/components/echo/wit.lock @@ -4,5 +4,5 @@ schema_version = 1 name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/echo/wit/world.wit b/components/echo/wit/world.wit index 72ab42f..8820aeb 100644 --- a/components/echo/wit/world.wit +++ b/components/echo/wit/world.wit @@ -1,5 +1,6 @@ package component:echo@0.1.0; +/// Echo only needs the base service lifecycle and imports no Host capability. world echo-component { include wasmeld:service/service-component@0.1.0; } diff --git a/components/fault/src/lib.rs b/components/fault/src/lib.rs index de2ddf1..3ab9eb4 100644 --- a/components/fault/src/lib.rs +++ b/components/fault/src/lib.rs @@ -1,4 +1,10 @@ +//! Deliberately trapping Component used to verify Actor fault isolation. +//! +//! Sending the exact bytes `fault` triggers a panic. This crate is a test +//! fixture and must not be deployed as an application service. + mod bindings { + // Compile the versioned WIT world into canonical ABI Rust bindings. wit_bindgen::generate!({ path: "wit", world: "fault-component", @@ -13,6 +19,8 @@ impl bindings::Guest for Fault { } fn invoke(input: Vec) -> Result, bindings::ServiceError> { + // A panic becomes a Wasm trap so Runtime tests can assert that one + // faulted Actor does not corrupt other service instances. assert_ne!(input, b"fault", "intentional test fault"); Ok(input) } diff --git a/components/fault/wit.lock b/components/fault/wit.lock index 128b39a..11bc1b3 100644 --- a/components/fault/wit.lock +++ b/components/fault/wit.lock @@ -4,5 +4,5 @@ schema_version = 1 name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/fault/wit/world.wit b/components/fault/wit/world.wit index eb38cb3..a402561 100644 --- a/components/fault/wit/world.wit +++ b/components/fault/wit/world.wit @@ -1,5 +1,6 @@ package component:fault@0.1.0; +/// Fault fixture exports only the base service lifecycle. world fault-component { include wasmeld:service/service-component@0.1.0; } diff --git a/components/kv-probe/src/lib.rs b/components/kv-probe/src/lib.rs index a5023db..a927028 100644 --- a/components/kv-probe/src/lib.rs +++ b/components/kv-probe/src/lib.rs @@ -1,4 +1,12 @@ +//! Example service that exercises persistent, service-scoped Host KV. +//! +//! Input uses the test protocol `get:`, `set::`, or +//! `delete:`. Production Components should define an application-specific +//! request format instead of copying this colon-delimited probe protocol. + mod bindings { + // `generate_all` emits both the service export and imported KV module from + // the exact versions selected in `wit/world.wit`. wit_bindgen::generate!({ path: "wit", world: "kv-probe-component", diff --git a/components/kv-probe/wit.lock b/components/kv-probe/wit.lock index d0f2c2f..ffe6416 100644 --- a/components/kv-probe/wit.lock +++ b/components/kv-probe/wit.lock @@ -4,12 +4,12 @@ schema_version = 1 name = "wasmeld:kv" version = "0.1.0" source = "path+../../wit/kv" -sha256 = "6dc3a84dc03c7104aa8b305518466c95e4440e1031c4bef3b59843f40f24d0fb" +sha256 = "10dc81b1f04bd9c8f7b324a96e0c1d24d5af0c2dabfc723d003b8729cfd933d9" replaced = true [[package]] name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/kv-probe/wit/world.wit b/components/kv-probe/wit/world.wit index d7f9b72..ccd8821 100644 --- a/components/kv-probe/wit/world.wit +++ b/components/kv-probe/wit/world.wit @@ -1,5 +1,7 @@ package component:kv-probe@0.1.0; +/// Base service exports plus the exact persistent KV Host capability used by +/// this Component. world kv-probe-component { include wasmeld:service/service-component@0.1.0; import wasmeld:kv/store@0.1.0; diff --git a/components/resident-probe/src/lib.rs b/components/resident-probe/src/lib.rs index 80f6bfb..f63f891 100644 --- a/components/resident-probe/src/lib.rs +++ b/components/resident-probe/src/lib.rs @@ -1,6 +1,14 @@ +//! Resident event/effect fixture covering every current Host-driven I/O family. +//! +//! The callback echoes stream chunks and datagrams, rearms fired timers, +//! acknowledges messages, and reflects extension commands. It owns no socket, +//! timer, or subscription handles; those resources remain in the Host driver. + use std::sync::atomic::{AtomicU64, Ordering}; mod bindings { + // One generated module contains both the base service export and resident + // actor export because this component world composes both WIT packages. wit_bindgen::generate!({ path: "wit", world: "resident-probe-component", @@ -35,6 +43,8 @@ impl ResidentGuest for ResidentProbe { fn handle_event( input: Event, ) -> Result, bindings::exports::wasmeld::resident::actor::ResidentError> { + // Event callbacks are serialized with normal invocations by the same + // Actor mailbox, so this in-memory count is deterministic per Actor. EVENTS_HANDLED.fetch_add(1, Ordering::Relaxed); Ok(match input { Event::StreamData(chunk) => vec![Effect::WriteStream(StreamWrite { diff --git a/components/resident-probe/wit.lock b/components/resident-probe/wit.lock index ba2b6f5..0b5f1a7 100644 --- a/components/resident-probe/wit.lock +++ b/components/resident-probe/wit.lock @@ -4,12 +4,12 @@ schema_version = 1 name = "wasmeld:resident" version = "0.1.0" source = "path+../../wit/resident" -sha256 = "683a3c7196a6dd77b3f619227aa093430ff6995c097d39bb82d501b7c59a196c" +sha256 = "a13728b0e6f000c03eb0f88e2e54bad1c6e5160ac3ca4b9e87619247c0043d1b" replaced = true [[package]] name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/resident-probe/wit/world.wit b/components/resident-probe/wit/world.wit index e44e076..4f70154 100644 --- a/components/resident-probe/wit/world.wit +++ b/components/resident-probe/wit/world.wit @@ -1,5 +1,7 @@ package component:resident-probe@0.1.0; +/// Composes ordinary request/response exports with the optional resident actor +/// export. Network and timer drivers remain Host-owned. world resident-probe-component { include wasmeld:service/service-component@0.1.0; export wasmeld:resident/actor@0.1.0; diff --git a/components/spin/src/lib.rs b/components/spin/src/lib.rs index a328b3c..0f015a0 100644 --- a/components/spin/src/lib.rs +++ b/components/spin/src/lib.rs @@ -1,4 +1,11 @@ +//! CPU-bound fixture used to verify fuel and epoch deadline interruption. +//! +//! The input's first eight bytes select a little-endian iteration count. +//! Initializing with `spin` intentionally runs without a finite loop bound. +//! This crate is a Runtime test fixture, not an application service. + mod bindings { + // Compile the base service WIT contract into guest and export adapters. wit_bindgen::generate!({ path: "wit", world: "spin-component", diff --git a/components/spin/wit.lock b/components/spin/wit.lock index 128b39a..11bc1b3 100644 --- a/components/spin/wit.lock +++ b/components/spin/wit.lock @@ -4,5 +4,5 @@ schema_version = 1 name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/spin/wit/world.wit b/components/spin/wit/world.wit index de498fc..080bd3b 100644 --- a/components/spin/wit/world.wit +++ b/components/spin/wit/world.wit @@ -1,5 +1,6 @@ package component:spin@0.1.0; +/// Spin fixture exports only the base service lifecycle. world spin-component { include wasmeld:service/service-component@0.1.0; } diff --git a/components/wasi-clock-probe/src/lib.rs b/components/wasi-clock-probe/src/lib.rs index c7e1cd5..25f0f40 100644 --- a/components/wasi-clock-probe/src/lib.rs +++ b/components/wasi-clock-probe/src/lib.rs @@ -1,4 +1,12 @@ +//! Fixture proving that ambient WASI clock imports are denied by the sandbox. +//! +//! Calling `std::time::Instant::now` causes this Component to import WASI +//! monotonic-clock directly. Wasmeld only links explicitly approved capability +//! packages, so registration or startup must reject this fixture. + mod bindings { + // The declared service WIT contains no WASI clock capability; the direct + // standard-library use below deliberately creates an undeclared import. wit_bindgen::generate!({ path: "wit", world: "wasi-clock-probe-component", diff --git a/components/wasi-clock-probe/wit.lock b/components/wasi-clock-probe/wit.lock index 128b39a..11bc1b3 100644 --- a/components/wasi-clock-probe/wit.lock +++ b/components/wasi-clock-probe/wit.lock @@ -4,5 +4,5 @@ schema_version = 1 name = "wasmeld:service" version = "0.1.0" source = "path+../../wit/service" -sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305" +sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e" replaced = true diff --git a/components/wasi-clock-probe/wit/world.wit b/components/wasi-clock-probe/wit/world.wit index 3f2475b..b6633ef 100644 --- a/components/wasi-clock-probe/wit/world.wit +++ b/components/wasi-clock-probe/wit/world.wit @@ -1,5 +1,7 @@ package component:wasi-clock-probe@0.1.0; +/// The declared world grants only the base service contract. Any direct WASI +/// clock import introduced by implementation code remains undeclared. world wasi-clock-probe-component { include wasmeld:service/service-component@0.1.0; } diff --git a/wit/clock/package.wit b/wit/clock/package.wit index 5fd4ef8..e684bff 100644 --- a/wit/clock/package.wit +++ b/wit/clock/package.wit @@ -1,9 +1,16 @@ package wasmeld:clock@0.1.0; +/// Actor-local monotonic time for measuring elapsed durations. interface monotonic-clock { + /// Returns nanoseconds elapsed since this Actor Store was created. + /// + /// The value is monotonic and saturates at `u64::MAX`. It is not Unix time, + /// cannot be compared across Actor restarts, and must not be persisted as a + /// wall-clock timestamp. now: func() -> u64; } +/// Host-side world used by Wasmeld to link the clock implementation. world clock-host { import monotonic-clock; } diff --git a/wit/kv/package.wit b/wit/kv/package.wit index e088e7d..03aff1d 100644 --- a/wit/kv/package.wit +++ b/wit/kv/package.wit @@ -1,17 +1,37 @@ package wasmeld:kv@0.1.0; +/// Small persistent byte-value store scoped to the stable service ID. +/// +/// Revisions of the same service share a namespace, so a deployment change +/// preserves values. Keys are non-empty UTF-8 strings of at most 256 bytes and +/// values are at most 64 KiB in this version of the Host implementation. +/// Operations are not multi-key transactions. interface store { + /// Expected validation or backend failures. variant kv-error { + /// The key is empty or exceeds the Host byte limit. invalid-key(string), + /// The attempted or stored value exceeds the Host byte limit. value-too-large(u64), + /// Persistence failed, timed out, or was temporarily unavailable. + /// + /// A timeout is not cancellation: a mutation may finish after the caller + /// receives this error. Retried `set` and `delete` operations should be + /// idempotent. operation-failed(string), } + /// Returns the current value, or `none` when the key does not exist. get: func(key: string) -> result>, kv-error>; + + /// Creates or replaces one value. set: func(key: string, value: list) -> result<_, kv-error>; + + /// Removes one value; deleting a missing key succeeds. delete: func(key: string) -> result<_, kv-error>; } +/// Host-side world used by Wasmeld to link persistent KV. world kv-host { import store; } diff --git a/wit/resident/package.wit b/wit/resident/package.wit index 0e3d35d..6c823e1 100644 --- a/wit/resident/package.wit +++ b/wit/resident/package.wit @@ -5,43 +5,61 @@ package wasmeld:resident@0.1.0; /// and all operating-system handles. A Component handles one bounded event and /// returns effects before control goes back to the Runtime. interface actor { + /// Opaque, nonzero identity allocated by the Host for one resident session. + /// + /// IDs are scoped to one service revision and session. They are never OS + /// handles, must not be guessed, and become invalid after Host release. type resource-id = u64; + /// One occurrence of a previously registered and armed timer. record timer-fired { timer-id: resource-id, + /// Driver monotonic deadline in nanoseconds, not wall-clock time. scheduled-at-ns: u64, } + /// A stream accepted by a previously registered TCP or Unix endpoint. record stream-opened { endpoint-id: resource-id, stream-id: resource-id, + /// Driver-formatted peer address when the transport provides one. peer: option, } + /// One bounded read chunk. Chunk boundaries are not message boundaries. record stream-chunk { stream-id: resource-id, bytes: list, } + /// Terminal reason reported by the Host driver. enum close-reason { + /// The peer closed the transport normally. peer-closed, + /// The platform or Component requested closure. host-closed, + /// The configured inactivity deadline elapsed. idle-timeout, + /// Protocol validation failed above the transport layer. protocol-error, + /// The operating-system transport returned an error. transport-error, } + /// Final event for a stream identity. record stream-closed { stream-id: resource-id, reason: close-reason, } + /// One UDP datagram. A datagram is never split across events. record datagram { endpoint-id: resource-id, peer: string, bytes: list, } + /// One delivery from a Host-owned broker or internal queue. record message { subscription-id: resource-id, message-id: u64, @@ -59,65 +77,107 @@ interface actor { } variant event { + /// A registered timer reached its monotonic deadline. timer(timer-fired), + /// A Host driver accepted a new stream. stream-opened(stream-opened), + /// A stream produced one bounded read chunk. stream-data(stream-chunk), + /// The driver can accept writes after previous backpressure. stream-writable(resource-id), + /// The peer closed its write half; local writes may still be valid. stream-half-closed(resource-id), + /// The stream is terminal and its identity will be released. stream-closed(stream-closed), + /// A UDP endpoint received one datagram. datagram(datagram), + /// A subscription delivered one message. message(message), + /// A separately versioned extension source produced an event. source(source-event), + /// The session is stopping. No later events will be delivered. shutdown, } + /// Request to enqueue bytes for a stream driver. record stream-write { stream-id: resource-id, bytes: list, } + /// Request to send one datagram through a registered UDP endpoint. record datagram-send { endpoint-id: resource-id, peer: string, bytes: list, } + /// Request to arm or replace a registered timer schedule. record timer-arm { timer-id: resource-id, + /// Delay from operation application, in milliseconds. delay-ms: u64, + /// Optional repeating interval in milliseconds. interval-ms: option, } + /// Request to acknowledge a previously delivered message. record message-ack { subscription-id: resource-id, message-id: u64, } + /// Driver-specific command for a separately versioned extension source. record source-command { source-id: resource-id, command: string, payload: list, } + /// Host operations requested after handling one event. + /// + /// Effects are declarations, not completed I/O. The Host validates the + /// entire returned list before applying any item, then the external driver + /// performs the operations in order. variant effect { + /// Enqueue bytes for an open stream. write-stream(stream-write), + /// Begin closing an open stream. close-stream(resource-id), + /// Stop delivering new read data until resumed. pause-stream(resource-id), + /// Resume delivery after a matching pause. resume-stream(resource-id), + /// Send one UDP datagram. send-datagram(datagram-send), + /// Arm or replace one timer. arm-timer(timer-arm), + /// Disarm one timer; its identity remains registered. cancel-timer(resource-id), + /// Acknowledge one broker or queue message. acknowledge-message(message-ack), + /// Forward an extension-specific command. source-command(source-command), } + /// Expected Component failure while processing a resident event. variant resident-error { event-failed(string), } + /// Handles exactly one serialized, bounded event. + /// + /// This callback must not run an internal infinite loop or block on network + /// I/O. Update Component state, return requested effects, and let the Host + /// wait for the next external event. A trap faults the Actor; `event-failed` + /// reports an application error without changing the ABI. handle-event: func(input: event) -> result, resident-error>; } +/// Optional export world for Components that need long-lived Host-driven I/O. +/// +/// A Component may compose this package with `wasmeld:service` and only the +/// capability packages it actually imports. world resident-component { export actor; } diff --git a/wit/service/package.wit b/wit/service/package.wit index 93da538..a6a94f4 100644 --- a/wit/service/package.wit +++ b/wit/service/package.wit @@ -1,11 +1,34 @@ package wasmeld:service@0.1.0; +/// Minimum request/response lifecycle implemented by every Wasmeld service. +/// +/// The Runtime creates one Component Store per Actor, calls `init` once, then +/// serializes `invoke` calls through that Actor's bounded mailbox. Component +/// memory can therefore remain warm between calls, but it is not durable and +/// is lost when the Actor or process restarts. world service-component { + /// Application-level failures returned without trapping the Component. + /// + /// Traps, fuel exhaustion, deadline interruption, and invalid ABI data are + /// Runtime failures and do not use this variant. variant service-error { + /// The Actor cannot accept calls because its initial configuration failed. init-failed(string), + /// This input could not be processed; a later call may still succeed. call-failed(string), } + /// Initializes a newly created Actor Store. + /// + /// `config` is opaque to the platform. Return `init-failed` for expected + /// configuration errors; do not retain borrowed views into this byte list. export init: func(config: list) -> result<_, service-error>; + + /// Processes one opaque request and returns one opaque response. + /// + /// The management API represents these bytes as base64, while the public + /// gateway transports them as `application/octet-stream`. The Component + /// should return promptly and use the resident actor contract for long-lived + /// I/O instead of blocking this call in an internal loop. export invoke: func(input: list) -> result, service-error>; }