From bbf0e2406b0239766090ebc16f156a2c1ef54cd4 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Tue, 21 Jul 2026 02:46:46 +0000 Subject: [PATCH 1/3] runtime: extract intent-status behind a generic custom event The core nexum:host `event` variant dropped `intent-status(intent-status-update)` for a generic `custom(custom-event)` channel, so the venue-agnostic core no longer names venue/receipt at the WIT boundary and a second extension can add its own event kind with no core WIT change. The intent-status payload moved into videre's own WIT (`videre:types` intent-status-update) and rides the custom channel: videre-host emits `custom` with kind "intent-status" and the borsh envelope as payload, and `videre_sdk::event::intent_status_update` recovers it typed by matching the kind and decoding. nexum-status-body gained the shared kind const and the envelope codec while the status body stays the payload's inner encoding. The core `#[module]` macro swapped its `on_intent_status` handler for a generic `on_custom`; the `#[keeper]` macro keeps the typed `on_intent_status`, decoding it from the custom event. check-venue-agnostic gained a WIT-vocabulary gate that fails on venue/receipt/intent-status in nexum:host. Closes #518 --- crates/nexum-module-macros/src/lib.rs | 10 ++-- crates/nexum-status-body/src/lib.rs | 45 ++++++++++++++++ crates/videre-host/src/lib.rs | 28 +++++++--- crates/videre-host/src/registry.rs | 7 +-- crates/videre-host/tests/platform.rs | 15 ++++-- crates/videre-macros/src/keeper.rs | 33 +++++++++++- crates/videre-sdk/src/event.rs | 72 +++++++++++++++++++++++++ crates/videre-sdk/src/lib.rs | 10 ++++ modules/example/src/lib.rs | 7 ++- modules/examples/echo-client/src/lib.rs | 7 ++- modules/examples/echo-keeper/src/lib.rs | 2 +- scripts/check-venue-agnostic.sh | 19 +++++-- wit/nexum-host/types.wit | 27 +++++----- wit/videre-types/types.wit | 15 ++++++ 14 files changed, 254 insertions(+), 43 deletions(-) create mode 100644 crates/videre-sdk/src/event.rs diff --git a/crates/nexum-module-macros/src/lib.rs b/crates/nexum-module-macros/src/lib.rs index bb90eb72..ca2d73aa 100644 --- a/crates/nexum-module-macros/src/lib.rs +++ b/crates/nexum-module-macros/src/lib.rs @@ -28,14 +28,14 @@ const HANDLERS: [&str; 6] = [ "on_chain_logs", "on_tick", "on_message", - "on_intent_status", + "on_custom", ]; /// Generate the per-cdylib glue for a nexum module. /// /// Apply to an `impl` block whose associated functions are the event /// handlers (`init`, `on_block`, `on_chain_logs`, `on_tick`, -/// `on_message`, `on_intent_status`). Each handler takes the wit-bindgen +/// `on_message`, `on_custom`). Each handler takes the wit-bindgen /// payload for its event and returns `Result<(), Fault>`; `init` takes /// the config table. /// Handlers left undefined are ignored (their events become no-ops). The @@ -138,7 +138,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream { return syn::Error::new_spanned( self_ty, "#[nexum_sdk::module] found no recognised handlers on this impl; define at least one \ - of `init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, `on_intent_status`", + of `init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, `on_custom`", ) .to_compile_error() .into(); @@ -201,7 +201,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream { let logs_arm = arm("on_chain_logs", "ChainLogs"); let tick_arm = arm("on_tick", "Tick"); let message_arm = arm("on_message", "Message"); - let intent_status_arm = arm("on_intent_status", "IntentStatus"); + let custom_arm = arm("on_custom", "Custom"); quote! { // Anchor a rebuild on the manifest and the extension registry: @@ -232,7 +232,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream { #logs_arm #tick_arm #message_arm - #intent_status_arm + #custom_arm } } } diff --git a/crates/nexum-status-body/src/lib.rs b/crates/nexum-status-body/src/lib.rs index 2bd9c009..271b6f22 100644 --- a/crates/nexum-status-body/src/lib.rs +++ b/crates/nexum-status-body/src/lib.rs @@ -15,6 +15,51 @@ use borsh::{BorshDeserialize, BorshSerialize}; /// Wire tag of the v1 payload. pub const VERSION_V1: u8 = 1; +/// The extension event kind an intent-status transition rides on: the +/// `custom-event.kind` the venue platform stamps and a subscribing +/// module matches. Shared by the emit side and the decode side so the +/// one string is never spelt twice. +pub const INTENT_STATUS_KIND: &str = "intent-status"; + +/// The intent-status transition an intent-status `custom` event carries +/// in its opaque payload: borsh `{venue, receipt, status}`, where +/// `status` is a [`StatusBody`]-encoded body (the inner codec above). +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq)] +pub struct IntentStatusUpdate { + /// Venue id the receipt was issued by. + pub venue: String, + /// The venue-scoped intent identifier, opaque to the host. + pub receipt: Vec, + /// The [`StatusBody`]-encoded status body. + pub status: Vec, +} + +impl IntentStatusUpdate { + /// Borsh-encode the envelope. + pub fn encode(&self) -> Result, EncodeError> { + let mut out = Vec::new(); + borsh::to_writer(&mut out, self).map_err(|err| EncodeError { + detail: err.to_string(), + })?; + Ok(out) + } + + /// Decode a borsh envelope, failing typedly on malformed bytes. + pub fn decode(bytes: &[u8]) -> Result { + borsh::from_slice(bytes).map_err(|err| EnvelopeError { + detail: err.to_string(), + }) + } +} + +/// Why bytes failed to decode as an [`IntentStatusUpdate`] envelope. +#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] +#[error("malformed intent-status envelope: {detail}")] +pub struct EnvelopeError { + /// Borsh's decode failure detail. + pub detail: String, +} + /// Where an intent is in its life at the venue. The borsh discriminant /// is the wire form: append new states, never reorder. #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, Debug, Eq, PartialEq)] diff --git a/crates/videre-host/src/lib.rs b/crates/videre-host/src/lib.rs index d228e27a..a02ce912 100644 --- a/crates/videre-host/src/lib.rs +++ b/crates/videre-host/src/lib.rs @@ -14,7 +14,7 @@ mod registry; use std::sync::Arc; use std::time::Duration; -use nexum_runtime::bindings::nexum::host::types::Event; +use nexum_runtime::bindings::nexum::host::types::{CustomEvent, Event}; use nexum_runtime::engine_config::EngineConfig; use nexum_runtime::host::component::RuntimeTypes; use nexum_runtime::host::extension::{ @@ -23,7 +23,9 @@ use nexum_runtime::host::extension::{ }; use nexum_runtime::host::state::HostState; use nexum_runtime::manifest::{ExtensionSections, NamespaceCaps}; +use nexum_status_body::INTENT_STATUS_KIND; use tokio::sync::mpsc; +use tracing::warn; use wasmtime::component::{HasSelf, Linker}; pub use registry::{ @@ -31,9 +33,6 @@ pub use registry::{ VenueAdapterKind, VenueId, VenueInvoker, VenueRegistry, VenueRegistryBuilder, }; -/// The subscription kind the platform's status poller emits. -const INTENT_STATUS_KIND: &str = "intent-status"; - /// Buffer for the status poll channel; small because the event loop /// drains in real time. const STATUS_CHANNEL_BUF: usize = 64; @@ -155,10 +154,27 @@ async fn status_poll_task( loop { tokio::time::sleep(cadence).await; for update in registry.poll_status_transitions().await { + let attrs = vec![("venue", update.venue.clone())]; + // The transition rides the generic `custom` channel: the + // envelope is borsh, the status body its inner encoding. A + // keeper recovers it through `videre_sdk::event`. + let payload = match update.encode() { + Ok(payload) => payload, + Err(err) => { + warn!( + error = %err, + "intent-status envelope failed to encode - dropping transition", + ); + continue; + } + }; let event = ExtensionEvent { kind: INTENT_STATUS_KIND, - attrs: vec![("venue", update.venue.clone())], - event: Event::IntentStatus(update), + attrs, + event: Event::Custom(CustomEvent { + kind: INTENT_STATUS_KIND.to_owned(), + payload, + }), }; if tx.send(event).await.is_err() { // Receiver dropped -> engine shutting down. diff --git a/crates/videre-host/src/registry.rs b/crates/videre-host/src/registry.rs index 0f1057e9..4f0d9a0b 100644 --- a/crates/videre-host/src/registry.rs +++ b/crates/videre-host/src/registry.rs @@ -45,9 +45,10 @@ use tracing::{info, warn}; use wasmtime::Store; use wasmtime::component::HasSelf; -/// The registry-observed status transition delivered through the host -/// `event` variant, re-exported at the spelling the registry names. -pub use nexum_runtime::bindings::nexum::host::types::IntentStatusUpdate; +/// The registry-observed status transition, carried in the `custom` +/// event's opaque payload, re-exported at the spelling the registry +/// names. +pub use nexum_status_body::IntentStatusUpdate; use crate::bindings::{ IntentHeader, IntentStatus, Quotation, RateLimit, SubmitOutcome, VenueAdapter, VenueError, diff --git a/crates/videre-host/tests/platform.rs b/crates/videre-host/tests/platform.rs index 9c44c425..2301013f 100644 --- a/crates/videre-host/tests/platform.rs +++ b/crates/videre-host/tests/platform.rs @@ -99,12 +99,19 @@ fn block(chain_id: u64) -> nexum::host::types::Block { } } -/// Wrap a polled transition as the extension event the platform emits. +/// Wrap a polled transition as the extension event the platform emits: +/// the transition rides the generic `custom` channel, its borsh envelope +/// the opaque payload. fn status_event(update: videre_host::IntentStatusUpdate) -> ExtensionEvent { + let attrs = vec![("venue", update.venue.clone())]; + let payload = update.encode().expect("encode intent-status envelope"); ExtensionEvent { kind: INTENT_STATUS, - attrs: vec![("venue", update.venue.clone())], - event: nexum::host::types::Event::IntentStatus(update), + attrs, + event: nexum::host::types::Event::Custom(nexum::host::types::CustomEvent { + kind: INTENT_STATUS.to_owned(), + payload, + }), } } @@ -426,7 +433,7 @@ async fn e2e_intent_status_flows_through_the_event_loop() { page.records .iter() .any(|r| r.message.contains("intent status update from venue cow")), - "the module's on_intent_status handler ran; records were: {:?}", + "the module's on_custom handler decoded the transition; records were: {:?}", page.records .iter() .map(|r| r.message.as_str()) diff --git a/crates/videre-macros/src/keeper.rs b/crates/videre-macros/src/keeper.rs index 568796d8..9d21c79f 100644 --- a/crates/videre-macros/src/keeper.rs +++ b/crates/videre-macros/src/keeper.rs @@ -165,7 +165,36 @@ pub(crate) fn expand(input: &ItemImpl) -> syn::Result { let logs_arm = arm("on_chain_logs", "ChainLogs"); let tick_arm = arm("on_tick", "Tick"); let message_arm = arm("on_message", "Message"); - let intent_status_arm = arm("on_intent_status", "IntentStatus"); + // The intent-status transition rides the generic `custom` channel; + // recover it typed through `videre_sdk::event` and dispatch to the + // keeper's `on_intent_status` when the kind matches. A malformed + // payload is the caller's `invalid-input`; a foreign kind is another + // extension's event and no-ops. + let custom_arm = match handler("on_intent_status") { + Some((_, is_async)) => { + let call = quote! { <#self_ty>::on_intent_status(update) }; + let body = if is_async { drive(call) } else { call }; + quote! { + nexum::host::types::Event::Custom(payload) => { + match ::videre_sdk::event::intent_status_update( + &payload.kind, + &payload.payload, + ) { + ::core::option::Option::Some(::core::result::Result::Ok(update)) => #body, + ::core::option::Option::Some(::core::result::Result::Err(err)) => { + ::core::result::Result::Err(nexum::host::types::Fault::InvalidInput( + ::std::string::ToString::to_string(&err), + )) + } + ::core::option::Option::None => ::core::result::Result::Ok(()), + } + } + } + } + None => quote! { + nexum::host::types::Event::Custom(_) => ::core::result::Result::Ok(()), + }, + }; Ok(quote! { // Anchor a rebuild on the manifest and the extension registry: @@ -210,7 +239,7 @@ pub(crate) fn expand(input: &ItemImpl) -> syn::Result { #logs_arm #tick_arm #message_arm - #intent_status_arm + #custom_arm } } } diff --git a/crates/videre-sdk/src/event.rs b/crates/videre-sdk/src/event.rs new file mode 100644 index 00000000..3f9b69fc --- /dev/null +++ b/crates/videre-sdk/src/event.rs @@ -0,0 +1,72 @@ +//! Typed recovery of videre events from the core `custom` channel. +//! +//! A venue transition reaches a module as a `custom` event; this module +//! decodes it back into the typed [`IntentStatusUpdate`] a keeper handler +//! works with. The `#[keeper]` macro calls it for `on_intent_status`. + +use crate::IntentStatusUpdate; + +/// The `custom-event.kind` an intent-status transition rides on. +pub use nexum_sdk::status_body::INTENT_STATUS_KIND; + +/// Why an intent-status `custom` payload did not decode. +pub use nexum_sdk::status_body::EnvelopeError; + +/// Recover an [`IntentStatusUpdate`] from a `custom` event, keyed by its +/// `kind` and `payload`. `None` when the kind is another extension's; +/// `Some(Err)` when the payload is malformed. +pub fn intent_status_update( + kind: &str, + payload: &[u8], +) -> Option> { + if kind != INTENT_STATUS_KIND { + return None; + } + Some(IntentStatusUpdate::decode(payload)) +} + +#[cfg(test)] +mod tests { + use nexum_sdk::status_body::{IntentStatus, StatusBody}; + + use super::*; + + fn envelope() -> Vec { + IntentStatusUpdate { + venue: "cow".to_owned(), + receipt: b"receipt".to_vec(), + status: StatusBody { + status: IntentStatus::Fulfilled, + proof: None, + reason: None, + } + .encode() + .expect("encode body"), + } + .encode() + .expect("encode envelope") + } + + #[test] + fn recovers_a_matching_kind() { + let recovered = intent_status_update(INTENT_STATUS_KIND, &envelope()) + .expect("kind matches") + .expect("payload decodes"); + assert_eq!(recovered.venue, "cow"); + assert_eq!(recovered.receipt, b"receipt"); + } + + #[test] + fn ignores_a_foreign_kind() { + assert!(intent_status_update("other-kind", &envelope()).is_none()); + } + + #[test] + fn reports_a_malformed_payload() { + assert!( + intent_status_update(INTENT_STATUS_KIND, b"\xff") + .expect("kind matches") + .is_err() + ); + } +} diff --git a/crates/videre-sdk/src/lib.rs b/crates/videre-sdk/src/lib.rs index 149f0a3e..ddf0c252 100644 --- a/crates/videre-sdk/src/lib.rs +++ b/crates/videre-sdk/src/lib.rs @@ -45,6 +45,10 @@ //! fault, the SDK-neutral fault, and [`VenueError`]; plus //! [`VenueFault`], the owned client-side mirror. //! +//! - [`event`] - typed recovery of videre events from the core `custom` +//! escape hatch: [`event::intent_status_update`] decodes an +//! intent-status transition a keeper subscribes to. +//! //! ## Why the bindgen lives in this crate //! //! The shared interfaces generate once, in [`bindings`], from an @@ -67,6 +71,7 @@ pub mod bindings; pub mod adapter; pub mod body; pub mod client; +pub mod event; pub mod faults; pub mod keeper; pub mod rt; @@ -105,6 +110,11 @@ pub use bindings::videre::types::types::{ }; /// The value-flow vocabulary intent headers are expressed in. pub use bindings::videre::value_flow::types as value_flow; +/// The intent-status transition a keeper recovers from a `custom` event +/// through [`event::intent_status_update`]. Its wire form is the borsh +/// envelope the `videre:types` `intent-status-update` record documents; +/// the status body rides its inner codec. +pub use nexum_sdk::status_body::IntentStatusUpdate; /// The wire config table (`nexum:host/types.config`) `init` receives. pub use bindings::nexum::host::types::Config; diff --git a/modules/example/src/lib.rs b/modules/example/src/lib.rs index da1ddd26..8d874800 100644 --- a/modules/example/src/lib.rs +++ b/modules/example/src/lib.rs @@ -67,7 +67,12 @@ impl ExampleModule { Ok(()) } - fn on_intent_status(update: types::IntentStatusUpdate) -> Result<(), Fault> { + fn on_custom(event: types::CustomEvent) -> Result<(), Fault> { + if event.kind != nexum_sdk::status_body::INTENT_STATUS_KIND { + return Ok(()); + } + let update = nexum_sdk::status_body::IntentStatusUpdate::decode(&event.payload) + .map_err(|err| Fault::InvalidInput(err.to_string()))?; let body = nexum_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( diff --git a/modules/examples/echo-client/src/lib.rs b/modules/examples/echo-client/src/lib.rs index 185e22da..1b1696c8 100644 --- a/modules/examples/echo-client/src/lib.rs +++ b/modules/examples/echo-client/src/lib.rs @@ -68,7 +68,12 @@ impl EchoClient { Ok(()) } - fn on_intent_status(update: types::IntentStatusUpdate) -> Result<(), Fault> { + fn on_custom(event: types::CustomEvent) -> Result<(), Fault> { + if event.kind != nexum_sdk::status_body::INTENT_STATUS_KIND { + return Ok(()); + } + let update = nexum_sdk::status_body::IntentStatusUpdate::decode(&event.payload) + .map_err(|err| Fault::InvalidInput(err.to_string()))?; let body = nexum_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( diff --git a/modules/examples/echo-keeper/src/lib.rs b/modules/examples/echo-keeper/src/lib.rs index 05e41619..9a54d980 100644 --- a/modules/examples/echo-keeper/src/lib.rs +++ b/modules/examples/echo-keeper/src/lib.rs @@ -92,7 +92,7 @@ impl EchoKeeper { Ok(()) } - fn on_intent_status(update: types::IntentStatusUpdate) -> Result<(), Fault> { + fn on_intent_status(update: videre_sdk::IntentStatusUpdate) -> Result<(), Fault> { let body = nexum_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( diff --git a/scripts/check-venue-agnostic.sh b/scripts/check-venue-agnostic.sh index cf1df1b7..e69ef22b 100755 --- a/scripts/check-venue-agnostic.sh +++ b/scripts/check-venue-agnostic.sh @@ -5,9 +5,9 @@ # charter symbol # (videre:|videre_host|Venue[A-Z]|EgressGuard|synthesize_venue|value-flow) # and no privileged router field; and nexum:host names no foreign WIT -# package and resolves as a leaf. The opaque-status envelope -# (intent-status-update, its venue id string) is ratified host surface, -# not a leak. Blocking in CI; run locally via `just check-venue-agnostic`. +# package, carries no venue-domain vocabulary (venue|receipt|intent-status), +# and resolves as a leaf. Blocking in CI; run locally via +# `just check-venue-agnostic`. set -uo pipefail @@ -62,8 +62,8 @@ case $? in esac # 4. WIT surface: nexum:host is a leaf. No foreign package named -# anywhere in its sources, no cross-package use/import, and the -# package resolves standalone. The opaque-status envelope stays. +# anywhere in its sources, no cross-package use/import, no venue-domain +# vocabulary, and the package resolves standalone. wit_charter='nexum:intent|nexum:adapter|value-flow|videre:|shepherd:cow' rg -n --no-heading -e "$wit_charter" wit/nexum-host case $? in @@ -77,6 +77,15 @@ case $? in 1) pass "nexum:host has no cross-package reference" ;; *) fail "WIT scan errored (wit/nexum-host missing?)" ;; esac +# Venue-domain vocabulary must not appear in the core event surface: the +# intent-status envelope lives in videre's WIT now, so a term leaking +# back into nexum:host is a regression of the extraction. +rg -n --no-heading -i -e 'venue|receipt|intent-status' wit/nexum-host +case $? in + 0) fail "venue-domain vocabulary leaks into wit/nexum-host" ;; + 1) pass "nexum:host carries no venue-domain vocabulary" ;; + *) fail "WIT vocabulary scan errored (wit/nexum-host missing?)" ;; +esac if command -v wasm-tools >/dev/null; then if wasm-tools component wit wit/nexum-host >/dev/null; then pass "nexum:host resolves standalone" diff --git a/wit/nexum-host/types.wit b/wit/nexum-host/types.wit index 8e834263..0142f119 100644 --- a/wit/nexum-host/types.wit +++ b/wit/nexum-host/types.wit @@ -58,20 +58,17 @@ interface types { fired-at: u64, } - /// A host-observed status transition for a previously submitted - /// intent. Transport-blind: the subscriber sees only where the - /// intent is in its life, never how the host learnt it. - record intent-status-update { - /// Venue id the receipt was issued by. - venue: string, - /// The venue-scoped intent identifier, opaque to the host. - receipt: list, - /// Opaque versioned status body: a leading u8 version tag, - /// then that version's borsh payload (v1: lifecycle status, - /// optional settlement proof, optional failure reason). An - /// unknown tag is a decode error and the body is never empty. - /// The host never inspects it. - status: list, + /// The generic extension event: a domain extension's own event kind + /// and its opaque payload. The core routes by `kind` and never reads + /// `payload`; the subscribing module decodes it against the extension + /// that emitted it. + record custom-event { + /// Extension-scoped event kind, matched against a module's + /// `[[subscription]]` kind. + kind: string, + /// Opaque bytes the emitting extension defines and the module + /// decodes. + payload: list, } variant event { @@ -79,7 +76,7 @@ interface types { chain-logs(chain-logs), tick(tick), message(message), - intent-status(intent-status-update), + custom(custom-event), } /// Opaque config from module.toml [config] section. diff --git a/wit/videre-types/types.wit b/wit/videre-types/types.wit index e8b69c81..73c002d1 100644 --- a/wit/videre-types/types.wit +++ b/wit/videre-types/types.wit @@ -57,6 +57,21 @@ interface types { expired, } + /// A host-observed status transition for a submitted intent, delivered + /// through the core `custom` event (kind "intent-status"). Transport-blind: + /// the subscriber sees where the intent is in its life, never how the host + /// learnt it. + record intent-status-update { + /// Venue id the receipt was issued by. + venue: string, + /// The venue-scoped intent identifier, opaque to the host. + receipt: receipt, + /// Opaque versioned status body (the nexum-status-body codec): a + /// leading u8 version tag, then that version's borsh payload. The + /// host never inspects it. + status: list, + } + /// Failure of a client or adapter call. `denied` and `rate-limited` are the /// only guard/transport shapes; `denied` MUST NOT be retried. variant venue-error { From 9e277e22bd5bc6ba86de0567e0f6633ed6e74e90 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Tue, 21 Jul 2026 04:03:29 +0000 Subject: [PATCH 2/3] sdk: move the venue status codec into the videre layer The status-body codec is wholly venue-domain: the IntentStatus lifecycle at the venue, the venue-reported FailReason, the opaque StatusBody bytes, and the {venue, receipt, status} IntentStatusUpdate envelope keyed by INTENT_STATUS_KIND. Rename the crate nexum-status-body to videre-status-body so it sits in the videre (L2) layer, and stop the generic guest SDK re-exporting it. nexum-sdk no longer depends on the codec nor re-exports it as status_body, so venue vocabulary no longer leaks into the venue-agnostic guest SDK. The guest-facing surface now lives on videre-sdk, which depends on videre-status-body and re-exports it as videre_sdk::status_body; the event decode path and the keeper macro reach it there. Venue-consuming example modules gain a videre-sdk dependency and decode through videre_sdk::status_body. Extend check-venue-agnostic.sh with a nexum-sdk scan: its crate graph must reach no videre or venue crate, and its sources must name none of the intent-status codec vocabulary. The wire form and behaviour are unchanged; the intent-status E2E tests pass unaltered. --- Cargo.lock | 22 ++++++++------- Cargo.toml | 2 +- crates/nexum-sdk/Cargo.toml | 3 --- crates/nexum-sdk/src/lib.rs | 8 ------ crates/videre-host/Cargo.toml | 2 +- crates/videre-host/src/lib.rs | 2 +- crates/videre-host/src/registry.rs | 8 +++--- crates/videre-host/tests/platform.rs | 10 +++---- crates/videre-sdk/Cargo.toml | 3 +++ crates/videre-sdk/src/event.rs | 6 ++--- crates/videre-sdk/src/lib.rs | 10 ++++++- .../Cargo.toml | 2 +- .../src/lib.rs | 0 modules/example/Cargo.toml | 3 +++ modules/example/src/lib.rs | 6 ++--- modules/examples/echo-client/Cargo.toml | 3 +++ modules/examples/echo-client/src/lib.rs | 6 ++--- modules/examples/echo-keeper/src/lib.rs | 2 +- scripts/check-venue-agnostic.sh | 27 +++++++++++++++++++ wit/videre-types/types.wit | 2 +- 20 files changed, 81 insertions(+), 46 deletions(-) rename crates/{nexum-status-body => videre-status-body}/Cargo.toml (93%) rename crates/{nexum-status-body => videre-status-body}/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 242c99f1..95bb111e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2156,6 +2156,7 @@ name = "echo-client" version = "0.1.0" dependencies = [ "nexum-sdk", + "videre-sdk", "wit-bindgen 0.58.0", ] @@ -2306,6 +2307,7 @@ name = "example" version = "0.1.0" dependencies = [ "nexum-sdk", + "videre-sdk", "wit-bindgen 0.59.0", ] @@ -3667,7 +3669,6 @@ dependencies = [ "http", "nexum-module-macros", "nexum-sdk-test", - "nexum-status-body", "nexum-world", "proptest", "serde_json", @@ -3687,14 +3688,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "nexum-status-body" -version = "0.1.0" -dependencies = [ - "borsh", - "thiserror 2.0.18", -] - [[package]] name = "nexum-tasks" version = "0.1.0" @@ -6045,7 +6038,6 @@ dependencies = [ "async-trait", "futures", "nexum-runtime", - "nexum-status-body", "nexum-tasks", "serde", "tempfile", @@ -6053,6 +6045,7 @@ dependencies = [ "tokio", "toml 1.1.2+spec-1.1.0", "tracing", + "videre-status-body", "wasmtime", ] @@ -6076,9 +6069,18 @@ dependencies = [ "strum", "thiserror 2.0.18", "videre-macros", + "videre-status-body", "wit-bindgen 0.59.0", ] +[[package]] +name = "videre-status-body" +version = "0.1.0" +dependencies = [ + "borsh", + "thiserror 2.0.18", +] + [[package]] name = "videre-test" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 94377aa5..54cf5075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "crates/nexum-runtime", "crates/nexum-sdk", "crates/nexum-sdk-test", - "crates/nexum-status-body", "crates/nexum-tasks", "crates/nexum-world", "crates/no-std-probe", @@ -19,6 +18,7 @@ members = [ "crates/videre-host", "crates/videre-macros", "crates/videre-sdk", + "crates/videre-status-body", "crates/videre-test", "modules/ethflow-watcher", "modules/example", diff --git a/crates/nexum-sdk/Cargo.toml b/crates/nexum-sdk/Cargo.toml index 4118fb81..07260cfd 100644 --- a/crates/nexum-sdk/Cargo.toml +++ b/crates/nexum-sdk/Cargo.toml @@ -23,9 +23,6 @@ stderr-echo = [] # calls back into this crate (`bind_host_via_wit_bindgen!`, the host # trait seam, the tracing facade). nexum-module-macros = { path = "../nexum-module-macros" } -# Decoder for the opaque status body an `intent-status` event carries; -# re-exported as `nexum_sdk::status_body`. -nexum-status-body = { path = "../nexum-status-body" } alloy-primitives.workspace = true # Typed EIP-155 chain id; already in the guest graph via alloy-provider. alloy-chains.workspace = true diff --git a/crates/nexum-sdk/src/lib.rs b/crates/nexum-sdk/src/lib.rs index cce15425..201aa1fd 100644 --- a/crates/nexum-sdk/src/lib.rs +++ b/crates/nexum-sdk/src/lib.rs @@ -51,9 +51,6 @@ //! handle plus [`ChainLogParts`], the WIT-edge `From` input the bind //! macro fills to rebuild it from the wire record. //! -//! - [`status_body`] - decoder for the opaque versioned status body an -//! `intent-status` event carries ([`StatusBody`]). -//! //! - [`config`] - `(key, value)` config-table lookups and decimal //! scaling ([`get_required`], [`get_optional`], [`scale_decimal`]). //! @@ -110,7 +107,6 @@ //! [`read_latest_answer`]: chain::chainlink::read_latest_answer //! [`Log`]: events::Log //! [`ChainLogParts`]: events::ChainLogParts -//! [`StatusBody`]: status_body::StatusBody //! [`get_required`]: config::get_required //! [`get_optional`]: config::get_optional //! [`scale_decimal`]: config::scale_decimal @@ -141,10 +137,6 @@ pub mod prelude; pub mod tracing; pub mod wit_bindgen_macro; -/// The opaque status-body codec: decode an `intent-status` event's -/// `status` bytes into a typed [`StatusBody`](status_body::StatusBody). -pub use nexum_status_body as status_body; - /// The level vocabulary for every SDK log path: the host logging trait, /// the guest tracing facade sink, and the module mocks all speak /// `tracing_core::Level`. Its `Ord` is filter-oriented (`ERROR` is the diff --git a/crates/videre-host/Cargo.toml b/crates/videre-host/Cargo.toml index 8db90362..a8c6dd11 100644 --- a/crates/videre-host/Cargo.toml +++ b/crates/videre-host/Cargo.toml @@ -14,7 +14,7 @@ workspace = true nexum-runtime = { path = "../nexum-runtime" } # Encoder for the opaque status body the host event stream carries; the # registry lowers an adapter-reported status through it. -nexum-status-body = { path = "../nexum-status-body" } +videre-status-body = { path = "../videre-status-body" } wasmtime.workspace = true anyhow.workspace = true diff --git a/crates/videre-host/src/lib.rs b/crates/videre-host/src/lib.rs index a02ce912..34187ca9 100644 --- a/crates/videre-host/src/lib.rs +++ b/crates/videre-host/src/lib.rs @@ -23,9 +23,9 @@ use nexum_runtime::host::extension::{ }; use nexum_runtime::host::state::HostState; use nexum_runtime::manifest::{ExtensionSections, NamespaceCaps}; -use nexum_status_body::INTENT_STATUS_KIND; use tokio::sync::mpsc; use tracing::warn; +use videre_status_body::INTENT_STATUS_KIND; use wasmtime::component::{HasSelf, Linker}; pub use registry::{ diff --git a/crates/videre-host/src/registry.rs b/crates/videre-host/src/registry.rs index 4f0d9a0b..d872d804 100644 --- a/crates/videre-host/src/registry.rs +++ b/crates/videre-host/src/registry.rs @@ -39,16 +39,16 @@ use nexum_runtime::host::extension::{ HostService, Installed, ProviderInstance, ProviderKind, downcast_service, }; use nexum_runtime::host::state::HostState; -use nexum_status_body::StatusBody; use tokio::sync::Mutex as AsyncMutex; use tracing::{info, warn}; +use videre_status_body::StatusBody; use wasmtime::Store; use wasmtime::component::HasSelf; /// The registry-observed status transition, carried in the `custom` /// event's opaque payload, re-exported at the spelling the registry /// names. -pub use nexum_status_body::IntentStatusUpdate; +pub use videre_status_body::IntentStatusUpdate; use crate::bindings::{ IntentHeader, IntentStatus, Quotation, RateLimit, SubmitOutcome, VenueAdapter, VenueError, @@ -292,7 +292,7 @@ fn is_terminal(status: IntentStatus) -> bool { /// stream carries. The registry attests the lifecycle state alone; proof /// and failure reason ride the body only when the venue supplies them. fn status_body(status: IntentStatus) -> StatusBody { - use nexum_status_body::IntentStatus as Lifecycle; + use videre_status_body::IntentStatus as Lifecycle; let status = match status { IntentStatus::Pending => Lifecycle::Pending, @@ -858,7 +858,7 @@ pub struct DuplicateVenue { mod tests { use std::sync::atomic::{AtomicUsize, Ordering}; - use nexum_status_body::IntentStatus as Lifecycle; + use videre_status_body::IntentStatus as Lifecycle; use crate::bindings::value_flow::{Asset, AssetAmount}; use crate::bindings::{AuthScheme, IntentHeader, Settlement, UnsignedTx}; diff --git a/crates/videre-host/tests/platform.rs b/crates/videre-host/tests/platform.rs index 2301013f..743c4360 100644 --- a/crates/videre-host/tests/platform.rs +++ b/crates/videre-host/tests/platform.rs @@ -338,8 +338,8 @@ async fn e2e_intent_status_subscription_receives_polled_transitions() { let foreign = videre_host::IntentStatusUpdate { venue: "other".to_owned(), receipt: b"receipt".to_vec(), - status: nexum_status_body::StatusBody { - status: nexum_status_body::IntentStatus::Open, + status: videre_status_body::StatusBody { + status: videre_status_body::IntentStatus::Open, proof: None, reason: None, } @@ -543,11 +543,11 @@ async fn e2e_echo_module_registry_adapter_round_trip() { for _ in 0..2 { for update in registry.poll_status_transitions().await { assert_eq!(update.venue, "echo-venue"); - let body = - nexum_status_body::StatusBody::decode(&update.status).expect("status body decodes"); + let body = videre_status_body::StatusBody::decode(&update.status) + .expect("status body decodes"); assert_eq!( body.status, - nexum_status_body::IntentStatus::Fulfilled, + videre_status_body::IntentStatus::Fulfilled, "echo settles instantly", ); delivered += supervisor diff --git a/crates/videre-sdk/Cargo.toml b/crates/videre-sdk/Cargo.toml index 54701fd4..41755813 100644 --- a/crates/videre-sdk/Cargo.toml +++ b/crates/videre-sdk/Cargo.toml @@ -28,6 +28,9 @@ videre-macros = { path = "../videre-macros" } # chain wrapper implements, the shared `Fault` vocabulary, and the # wasi:http `fetch` surface re-exported as `transport::http`. nexum-sdk = { path = "../nexum-sdk" } +# The venue status-body codec, re-exported as `videre_sdk::status_body`; +# venue guests reach the `intent-status` decode path through here. +videre-status-body = { path = "../videre-status-body" } strum.workspace = true thiserror.workspace = true wit-bindgen.workspace = true diff --git a/crates/videre-sdk/src/event.rs b/crates/videre-sdk/src/event.rs index 3f9b69fc..fe450ba6 100644 --- a/crates/videre-sdk/src/event.rs +++ b/crates/videre-sdk/src/event.rs @@ -7,10 +7,10 @@ use crate::IntentStatusUpdate; /// The `custom-event.kind` an intent-status transition rides on. -pub use nexum_sdk::status_body::INTENT_STATUS_KIND; +pub use crate::status_body::INTENT_STATUS_KIND; /// Why an intent-status `custom` payload did not decode. -pub use nexum_sdk::status_body::EnvelopeError; +pub use crate::status_body::EnvelopeError; /// Recover an [`IntentStatusUpdate`] from a `custom` event, keyed by its /// `kind` and `payload`. `None` when the kind is another extension's; @@ -27,7 +27,7 @@ pub fn intent_status_update( #[cfg(test)] mod tests { - use nexum_sdk::status_body::{IntentStatus, StatusBody}; + use crate::status_body::{IntentStatus, StatusBody}; use super::*; diff --git a/crates/videre-sdk/src/lib.rs b/crates/videre-sdk/src/lib.rs index ddf0c252..0fe4b476 100644 --- a/crates/videre-sdk/src/lib.rs +++ b/crates/videre-sdk/src/lib.rs @@ -49,6 +49,10 @@ //! escape hatch: [`event::intent_status_update`] decodes an //! intent-status transition a keeper subscribes to. //! +//! - [`status_body`] - the venue status-body codec: decode an +//! `intent-status` event's `status` bytes into a typed +//! [`StatusBody`](status_body::StatusBody). +//! //! ## Why the bindgen lives in this crate //! //! The shared interfaces generate once, in [`bindings`], from an @@ -110,11 +114,15 @@ pub use bindings::videre::types::types::{ }; /// The value-flow vocabulary intent headers are expressed in. pub use bindings::videre::value_flow::types as value_flow; +/// The venue status-body codec: decode an `intent-status` event's +/// `status` bytes into a typed [`StatusBody`](status_body::StatusBody). +pub use videre_status_body as status_body; + /// The intent-status transition a keeper recovers from a `custom` event /// through [`event::intent_status_update`]. Its wire form is the borsh /// envelope the `videre:types` `intent-status-update` record documents; /// the status body rides its inner codec. -pub use nexum_sdk::status_body::IntentStatusUpdate; +pub use videre_status_body::IntentStatusUpdate; /// The wire config table (`nexum:host/types.config`) `init` receives. pub use bindings::nexum::host::types::Config; diff --git a/crates/nexum-status-body/Cargo.toml b/crates/videre-status-body/Cargo.toml similarity index 93% rename from crates/nexum-status-body/Cargo.toml rename to crates/videre-status-body/Cargo.toml index cd4361b3..67967b12 100644 --- a/crates/nexum-status-body/Cargo.toml +++ b/crates/videre-status-body/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nexum-status-body" +name = "videre-status-body" version = "0.1.0" edition.workspace = true license.workspace = true diff --git a/crates/nexum-status-body/src/lib.rs b/crates/videre-status-body/src/lib.rs similarity index 100% rename from crates/nexum-status-body/src/lib.rs rename to crates/videre-status-body/src/lib.rs diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index 19311814..24d9f4a0 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -13,4 +13,7 @@ crate-type = ["cdylib"] [dependencies] nexum-sdk = { path = "../../crates/nexum-sdk" } +# The venue status-body codec the `on_custom` handler decodes an +# intent-status transition through, reached via `videre_sdk::status_body`. +videre-sdk = { path = "../../crates/videre-sdk" } wit-bindgen = { version = "0.59", default-features = false, features = ["macros", "realloc"] } diff --git a/modules/example/src/lib.rs b/modules/example/src/lib.rs index 8d874800..2e4cc80b 100644 --- a/modules/example/src/lib.rs +++ b/modules/example/src/lib.rs @@ -68,12 +68,12 @@ impl ExampleModule { } fn on_custom(event: types::CustomEvent) -> Result<(), Fault> { - if event.kind != nexum_sdk::status_body::INTENT_STATUS_KIND { + if event.kind != videre_sdk::status_body::INTENT_STATUS_KIND { return Ok(()); } - let update = nexum_sdk::status_body::IntentStatusUpdate::decode(&event.payload) + let update = videre_sdk::status_body::IntentStatusUpdate::decode(&event.payload) .map_err(|err| Fault::InvalidInput(err.to_string()))?; - let body = nexum_sdk::status_body::StatusBody::decode(&update.status) + let body = videre_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( logging::Level::Info, diff --git a/modules/examples/echo-client/Cargo.toml b/modules/examples/echo-client/Cargo.toml index f4cf47a0..c5c20896 100644 --- a/modules/examples/echo-client/Cargo.toml +++ b/modules/examples/echo-client/Cargo.toml @@ -14,4 +14,7 @@ crate-type = ["cdylib"] [dependencies] nexum-sdk = { path = "../../../crates/nexum-sdk" } +# The venue status-body codec the `on_custom` handler decodes an +# intent-status transition through, reached via `videre_sdk::status_body`. +videre-sdk = { path = "../../../crates/videre-sdk" } wit-bindgen = { version = "0.58", default-features = false, features = ["macros", "realloc"] } diff --git a/modules/examples/echo-client/src/lib.rs b/modules/examples/echo-client/src/lib.rs index 1b1696c8..33025943 100644 --- a/modules/examples/echo-client/src/lib.rs +++ b/modules/examples/echo-client/src/lib.rs @@ -69,12 +69,12 @@ impl EchoClient { } fn on_custom(event: types::CustomEvent) -> Result<(), Fault> { - if event.kind != nexum_sdk::status_body::INTENT_STATUS_KIND { + if event.kind != videre_sdk::status_body::INTENT_STATUS_KIND { return Ok(()); } - let update = nexum_sdk::status_body::IntentStatusUpdate::decode(&event.payload) + let update = videre_sdk::status_body::IntentStatusUpdate::decode(&event.payload) .map_err(|err| Fault::InvalidInput(err.to_string()))?; - let body = nexum_sdk::status_body::StatusBody::decode(&update.status) + let body = videre_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( logging::Level::Info, diff --git a/modules/examples/echo-keeper/src/lib.rs b/modules/examples/echo-keeper/src/lib.rs index 9a54d980..b840f7c0 100644 --- a/modules/examples/echo-keeper/src/lib.rs +++ b/modules/examples/echo-keeper/src/lib.rs @@ -93,7 +93,7 @@ impl EchoKeeper { } fn on_intent_status(update: videre_sdk::IntentStatusUpdate) -> Result<(), Fault> { - let body = nexum_sdk::status_body::StatusBody::decode(&update.status) + let body = videre_sdk::status_body::StatusBody::decode(&update.status) .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( logging::Level::Info, diff --git a/scripts/check-venue-agnostic.sh b/scripts/check-venue-agnostic.sh index e69ef22b..81e5a8a0 100755 --- a/scripts/check-venue-agnostic.sh +++ b/scripts/check-venue-agnostic.sh @@ -38,6 +38,33 @@ for crate in nexum-runtime nexum-launch nexum-cli; do fi done +# 1b. Guest SDK: the generic guest SDK stays venue-free too. Its crate +# graph must reach no videre/venue crate (normal + build edges), so +# the venue status-codec never re-enters the domain-free SDK through a +# dependency; and its sources must name none of the intent-status +# codec's own vocabulary. The symbol scan is curated (not a bare +# `venue|receipt` sweep): the keeper stores legitimately speak of +# receipt-keyed journals and generic venue prose, so only the codec's +# distinctive names flag. +if tree="$(cargo tree -p nexum-sdk -e normal,build --all-features --prefix none --locked)"; then + reached="$(printf '%s\n' "$tree" | + awk '{print $1}' | sort -u | rg -i 'videre|intent|venue|cow' || true)" + if [[ -n $reached ]]; then + fail "nexum-sdk crate graph reaches: $(tr '\n' ' ' <<<"$reached")" + else + pass "nexum-sdk crate graph clean" + fi +else + fail "cargo tree failed for nexum-sdk" +fi +sdk_charter='intent-status|IntentStatusUpdate|INTENT_STATUS_KIND|[Ss]tatus[Bb]ody|status[-_]body' +rg -n --no-heading -e "$sdk_charter" crates/nexum-sdk/src +case $? in + 0) fail "venue status-codec vocabulary leaks into nexum-sdk" ;; + 1) pass "nexum-sdk carries no venue status-codec vocabulary" ;; + *) fail "nexum-sdk scan errored (crates/nexum-sdk/src missing?)" ;; +esac + # 2. Symbol scan: the charter set (the current venue vocabulary that would # signal a leak - videre WIT/crate refs, the Venue* types, the egress # guard). Section 1 guards dependency edges; this scan stays curated to diff --git a/wit/videre-types/types.wit b/wit/videre-types/types.wit index 73c002d1..1b6d523a 100644 --- a/wit/videre-types/types.wit +++ b/wit/videre-types/types.wit @@ -66,7 +66,7 @@ interface types { venue: string, /// The venue-scoped intent identifier, opaque to the host. receipt: receipt, - /// Opaque versioned status body (the nexum-status-body codec): a + /// Opaque versioned status body (the videre-status-body codec): a /// leading u8 version tag, then that version's borsh payload. The /// host never inspects it. status: list, From c2f5a3f9b196d2b411a8f7f83d603a0eb06ddfdb Mon Sep 17 00:00:00 2001 From: mfw78 Date: Tue, 21 Jul 2026 04:36:59 +0000 Subject: [PATCH 3/3] sdk: drop the dead intent-status wit record and generalise the example The videre:types intent-status-update record was declaration-only: wit-bindgen prunes it (no world function references it, the payload rides as opaque bytes), so it generated no binding; the wire contract lives in videre-status-body's Rust type. The example module decoded intent-status in on_custom despite never submitting an intent, pulling videre-sdk in gratuitously. It now logs the raw custom event generically and drops the videre-sdk dependency. echo-client keeps its decode: it submits through videre:venue/client. --- Cargo.lock | 1 - crates/videre-host/tests/platform.rs | 30 +++++++++++++++++++++++----- crates/videre-sdk/src/lib.rs | 6 +++--- modules/example/Cargo.toml | 3 --- modules/example/src/lib.rs | 14 +++---------- scripts/check-venue-agnostic.sh | 5 +++-- wit/videre-types/types.wit | 15 -------------- 7 files changed, 34 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95bb111e..53a199de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2307,7 +2307,6 @@ name = "example" version = "0.1.0" dependencies = [ "nexum-sdk", - "videre-sdk", "wit-bindgen 0.59.0", ] diff --git a/crates/videre-host/tests/platform.rs b/crates/videre-host/tests/platform.rs index 743c4360..c068fdca 100644 --- a/crates/videre-host/tests/platform.rs +++ b/crates/videre-host/tests/platform.rs @@ -251,6 +251,26 @@ fn scripted_registry(adapter: ScriptedAdapter) -> VenueRegistry { /// Write a manifest subscribing the example module to intent-status /// events from the `cow` venue. +fn echo_client_status_manifest(dir: &Path) -> PathBuf { + let manifest = dir.join("module.toml"); + std::fs::write( + &manifest, + r#" +[module] +name = "echo-client" + +[capabilities] +required = ["client", "logging"] + +[[subscription]] +kind = "intent-status" +venue = "cow" +"#, + ) + .expect("write manifest"); + manifest +} + fn intent_status_manifest(dir: &Path) -> PathBuf { let manifest = dir.join("module.toml"); std::fs::write( @@ -362,11 +382,11 @@ async fn e2e_intent_status_subscription_receives_polled_transitions() { async fn e2e_intent_status_flows_through_the_event_loop() { use nexum_tasks::{TaskManager, TaskSet}; - let Some(wasm) = module_wasm_or_skip("example") else { + let Some(wasm) = module_wasm_or_skip("echo-client") else { return; }; let dir = tempfile::tempdir().expect("tempdir"); - let manifest = intent_status_manifest(dir.path()); + let manifest = echo_client_status_manifest(dir.path()); let registry = scripted_registry(ScriptedAdapter::new([])); let videre = Arc::new(Videre::from_registry(registry.clone())); @@ -426,13 +446,13 @@ async fn e2e_intent_status_flows_through_the_event_loop() { .await; assert_eq!(supervisor.alive_count(), 1, "module must remain alive"); - let runs = logs.list_runs("example"); - assert_eq!(runs.len(), 1, "one run recorded for the example module"); + let runs = logs.list_runs("echo-client"); + assert_eq!(runs.len(), 1, "one run recorded for the echo-client module"); let page = logs.read(&runs[0].run, 0); assert!( page.records .iter() - .any(|r| r.message.contains("intent status update from venue cow")), + .any(|r| r.message.contains("intent status from venue cow")), "the module's on_custom handler decoded the transition; records were: {:?}", page.records .iter() diff --git a/crates/videre-sdk/src/lib.rs b/crates/videre-sdk/src/lib.rs index 0fe4b476..c4be49d6 100644 --- a/crates/videre-sdk/src/lib.rs +++ b/crates/videre-sdk/src/lib.rs @@ -119,9 +119,9 @@ pub use bindings::videre::value_flow::types as value_flow; pub use videre_status_body as status_body; /// The intent-status transition a keeper recovers from a `custom` event -/// through [`event::intent_status_update`]. Its wire form is the borsh -/// envelope the `videre:types` `intent-status-update` record documents; -/// the status body rides its inner codec. +/// through [`event::intent_status_update`]. Its wire form is a borsh +/// envelope defined by this struct, not a WIT record: it crosses the +/// `custom` event as opaque bytes. The status body rides its inner codec. pub use videre_status_body::IntentStatusUpdate; /// The wire config table (`nexum:host/types.config`) `init` receives. diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index 24d9f4a0..19311814 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -13,7 +13,4 @@ crate-type = ["cdylib"] [dependencies] nexum-sdk = { path = "../../crates/nexum-sdk" } -# The venue status-body codec the `on_custom` handler decodes an -# intent-status transition through, reached via `videre_sdk::status_body`. -videre-sdk = { path = "../../crates/videre-sdk" } wit-bindgen = { version = "0.59", default-features = false, features = ["macros", "realloc"] } diff --git a/modules/example/src/lib.rs b/modules/example/src/lib.rs index 2e4cc80b..5597f442 100644 --- a/modules/example/src/lib.rs +++ b/modules/example/src/lib.rs @@ -68,20 +68,12 @@ impl ExampleModule { } fn on_custom(event: types::CustomEvent) -> Result<(), Fault> { - if event.kind != videre_sdk::status_body::INTENT_STATUS_KIND { - return Ok(()); - } - let update = videre_sdk::status_body::IntentStatusUpdate::decode(&event.payload) - .map_err(|err| Fault::InvalidInput(err.to_string()))?; - let body = videre_sdk::status_body::StatusBody::decode(&update.status) - .map_err(|err| Fault::InvalidInput(err.to_string()))?; logging::log( logging::Level::Info, &format!( - "intent status update from venue {}: {:?} ({} receipt bytes)", - update.venue, - body.status, - update.receipt.len(), + "custom event kind {} ({} payload bytes)", + event.kind, + event.payload.len(), ), ); Ok(()) diff --git a/scripts/check-venue-agnostic.sh b/scripts/check-venue-agnostic.sh index 81e5a8a0..fe52c029 100755 --- a/scripts/check-venue-agnostic.sh +++ b/scripts/check-venue-agnostic.sh @@ -105,8 +105,9 @@ case $? in *) fail "WIT scan errored (wit/nexum-host missing?)" ;; esac # Venue-domain vocabulary must not appear in the core event surface: the -# intent-status envelope lives in videre's WIT now, so a term leaking -# back into nexum:host is a regression of the extraction. +# intent-status envelope is a videre-side borsh struct crossing `custom` +# as opaque bytes, so a term leaking back into nexum:host is a regression +# of the extraction. rg -n --no-heading -i -e 'venue|receipt|intent-status' wit/nexum-host case $? in 0) fail "venue-domain vocabulary leaks into wit/nexum-host" ;; diff --git a/wit/videre-types/types.wit b/wit/videre-types/types.wit index 1b6d523a..e8b69c81 100644 --- a/wit/videre-types/types.wit +++ b/wit/videre-types/types.wit @@ -57,21 +57,6 @@ interface types { expired, } - /// A host-observed status transition for a submitted intent, delivered - /// through the core `custom` event (kind "intent-status"). Transport-blind: - /// the subscriber sees where the intent is in its life, never how the host - /// learnt it. - record intent-status-update { - /// Venue id the receipt was issued by. - venue: string, - /// The venue-scoped intent identifier, opaque to the host. - receipt: receipt, - /// Opaque versioned status body (the videre-status-body codec): a - /// leading u8 version tag, then that version's borsh payload. The - /// host never inspects it. - status: list, - } - /// Failure of a client or adapter call. `denied` and `rate-limited` are the /// only guard/transport shapes; `denied` MUST NOT be retried. variant venue-error {