From 3ee038f2fc9ff29c00843928ef4e5fe495b4e24b Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 08:45:46 +0000 Subject: [PATCH 1/5] refactor(runtime)!: delete the RuntimeTypes Ext axis Extension state flows through HostState.services; the Ext slot, ExtState, the harness ext payload seam, and the MockTypes generic parameter had no consumer. Components, ComponentsBuilder, and the Runtime preset lose the extension-payload slot; MockTypes becomes a unit marker. AI Assistance: Fable 5 used for implementation --- crates/nexum-runtime/src/builder.rs | 56 +++++++-------- .../src/host/component/builder.rs | 46 +++---------- .../nexum-runtime/src/host/component/mod.rs | 3 - .../src/host/component/runtime_types.rs | 12 ++-- crates/nexum-runtime/src/host/state.rs | 19 ----- crates/nexum-runtime/src/preset.rs | 19 ++--- crates/nexum-runtime/src/supervisor/store.rs | 1 - .../nexum-runtime/src/supervisor/tests/mod.rs | 3 +- .../nexum-runtime/src/test_utils/harness.rs | 69 ++++++------------- crates/nexum-runtime/src/test_utils/mod.rs | 12 +--- .../nexum-runtime/src/test_utils/scenario.rs | 1 - crates/nexum-runtime/src/test_utils/types.rs | 13 ++-- 12 files changed, 71 insertions(+), 183 deletions(-) diff --git a/crates/nexum-runtime/src/builder.rs b/crates/nexum-runtime/src/builder.rs index 0319c32..f6fcdd4 100644 --- a/crates/nexum-runtime/src/builder.rs +++ b/crates/nexum-runtime/src/builder.rs @@ -347,20 +347,19 @@ impl AssembledRuntime<'_, T> { /// Opens the backends with a fresh [`TaskManager`], then drives /// [`AssembledRuntime::launch`]; the shared tail of every terminal stage. -async fn open_and_launch( +async fn open_and_launch( config: &EngineConfig, extensions: Vec>>, add_ons: &[&dyn RuntimeAddOn], wasm: Option<&Path>, manifest: Option<&Path>, clocks: Option, - components: ComponentsBuilder, + components: ComponentsBuilder, ) -> anyhow::Result where T: RuntimeTypes, C: ComponentBuilder, S: ComponentBuilder, - E: ComponentBuilder, L: ComponentBuilder, { let tasks = TaskManager::new(); @@ -466,12 +465,12 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> { /// Override the preset's component builders before launch; `map` swaps one /// seam while the preset's extensions and add-ons carry through. Mirror of /// [`TypedBuilder::with_components`]. - pub fn with_components( + pub fn with_components( self, map: impl FnOnce( - ComponentsBuilder, - ) -> ComponentsBuilder, - ) -> PresetComponentsBuilder<'a, R::Types, C, S, E, L> { + ComponentsBuilder, + ) -> ComponentsBuilder, + ) -> PresetComponentsBuilder<'a, R::Types, C, S, L> { // Gather the preset's extensions and add-ons before `components` // consumes the preset by value. let mut extensions = self.preset.extensions(self.config); @@ -521,22 +520,21 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> { /// A preset with its component builders overridden through /// [`PresetBuilder::with_components`], leaving only [`launch`](Self::launch). -pub struct PresetComponentsBuilder<'a, T: RuntimeTypes, C, S, E, L> { +pub struct PresetComponentsBuilder<'a, T: RuntimeTypes, C, S, L> { config: &'a EngineConfig, extensions: Vec>>, add_ons: AddOns, wasm: Option, manifest: Option, clocks: Option, - components: ComponentsBuilder, + components: ComponentsBuilder, } -impl PresetComponentsBuilder<'_, T, C, S, E, L> +impl PresetComponentsBuilder<'_, T, C, S, L> where T: RuntimeTypes, C: ComponentBuilder, S: ComponentBuilder, - E: ComponentBuilder, L: ComponentBuilder, { /// Open the overridden backends and launch, otherwise as @@ -595,10 +593,10 @@ impl<'a, T: RuntimeTypes> TypedBuilder<'a, T> { } /// Bind the component builders that open the backends at launch. - pub fn with_components( + pub fn with_components( self, - components: ComponentsBuilder, - ) -> ReadyBuilder<'a, T, C, S, E, L> { + components: ComponentsBuilder, + ) -> ReadyBuilder<'a, T, C, S, L> { ReadyBuilder { config: self.config, extensions: self.extensions, @@ -613,17 +611,17 @@ impl<'a, T: RuntimeTypes> TypedBuilder<'a, T> { /// The assembly is complete; [`launch`](Self::launch) opens the backends and /// runs. -pub struct ReadyBuilder<'a, T: RuntimeTypes, C, S, E, L> { +pub struct ReadyBuilder<'a, T: RuntimeTypes, C, S, L> { config: &'a EngineConfig, extensions: Vec>>, wasm: Option, manifest: Option, clocks: Option, - components: ComponentsBuilder, + components: ComponentsBuilder, add_ons: &'a [&'a dyn RuntimeAddOn], } -impl<'a, T: RuntimeTypes, C, S, E, L> ReadyBuilder<'a, T, C, S, E, L> { +impl<'a, T: RuntimeTypes, C, S, L> ReadyBuilder<'a, T, C, S, L> { /// Bind the cross-cutting add-on set installed before the engine boots; /// defaults to none. pub fn with_add_ons(mut self, add_ons: &'a [&'a dyn RuntimeAddOn]) -> Self { @@ -632,12 +630,11 @@ impl<'a, T: RuntimeTypes, C, S, E, L> ReadyBuilder<'a, T, C, S, E, L> { } } -impl ReadyBuilder<'_, T, C, S, E, L> +impl ReadyBuilder<'_, T, C, S, L> where T: RuntimeTypes, C: ComponentBuilder, S: ComponentBuilder, - E: ComponentBuilder, L: ComponentBuilder, { /// Open the backends and launch, driving [`AssembledRuntime::launch`] @@ -728,11 +725,10 @@ mod tests { type Types = CoreRuntime; type ChainBuilder = ProviderPoolBuilder; type StoreBuilder = LocalStoreBuilder; - type ExtBuilder = (); type LogsBuilder = LogPipelineBuilder; - fn components(self) -> ComponentsBuilder { - ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + fn components(self) -> ComponentsBuilder { + ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) } fn add_ons(&self) -> AddOns { @@ -827,7 +823,6 @@ mod tests { .with_components(ComponentsBuilder::new( ProviderPoolBuilder, LocalStoreBuilder, - (), )) .launch() .await @@ -881,14 +876,13 @@ mod tests { type Types = CoreRuntime; type ChainBuilder = ProviderPoolBuilder; type StoreBuilder = LocalStoreBuilder; - type ExtBuilder = (); type LogsBuilder = Prebuilt; fn components( self, - ) -> ComponentsBuilder> + ) -> ComponentsBuilder> { - ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) .with_logs(Prebuilt(self.logs)) } @@ -935,11 +929,10 @@ mod tests { type Types = CoreRuntime; type ChainBuilder = ProviderPoolBuilder; type StoreBuilder = LocalStoreBuilder; - type ExtBuilder = (); type LogsBuilder = LogPipelineBuilder; - fn components(self) -> ComponentsBuilder { - ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + fn components(self) -> ComponentsBuilder { + ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) } fn add_ons(&self) -> AddOns { @@ -1050,7 +1043,6 @@ mod tests { .with_components(ComponentsBuilder::new( ProviderPoolBuilder, LocalStoreBuilder, - (), )) .launch() .await @@ -1083,7 +1075,6 @@ mod tests { .with_components(ComponentsBuilder::new( ProviderPoolBuilder, LocalStoreBuilder, - (), )) .launch() .await @@ -1122,7 +1113,7 @@ mod tests { data_dir: &data_dir, executor: &executor, }; - let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) .build::(&build_ctx) .await .expect("build core components"); @@ -1174,7 +1165,6 @@ mod tests { .with_components(ComponentsBuilder::new( ProviderPoolBuilder, LocalStoreBuilder, - (), )) .launch() .await diff --git a/crates/nexum-runtime/src/host/component/builder.rs b/crates/nexum-runtime/src/host/component/builder.rs index 40a9e72..9df2e79 100644 --- a/crates/nexum-runtime/src/host/component/builder.rs +++ b/crates/nexum-runtime/src/host/component/builder.rs @@ -1,6 +1,6 @@ //! Per-component builders. Each core backend is a [`ComponentBuilder`]; -//! [`ComponentsBuilder`] assembles the core seams, the lattice `Ext` payload, -//! and the log pipeline into a [`Components`] bundle. +//! [`ComponentsBuilder`] assembles the core seams and the log pipeline into a +//! [`Components`] bundle. use std::future::Future; use std::path::Path; @@ -93,55 +93,38 @@ pub enum BuildError { /// The store backend builder failed. #[error("build the store backend: {0}")] Store(anyhow::Error), - /// The extension payload builder failed. - #[error("build the extension payload: {0}")] - Ext(anyhow::Error), /// The log pipeline builder failed. #[error("build the log pipeline: {0}")] Logs(anyhow::Error), } -/// The empty extension payload: a no-op builder for a core-only lattice -/// (`Ext = ()`). -impl ComponentBuilder for () { - type Output = (); - - async fn build(self, _ctx: &BuilderContext<'_>) -> anyhow::Result<()> { - Ok(()) - } -} - -/// Assembles the core, `Ext`, and log-pipeline builders into a [`Components`] +/// Assembles the core and log-pipeline builders into a [`Components`] /// bundle; the logs slot defaults to [`LogPipelineBuilder`]. -pub struct ComponentsBuilder { +pub struct ComponentsBuilder { pub chain: C, /// Builds the store backend ([`RuntimeTypes::Store`]). pub store: S, - /// Builds the extension payload ([`RuntimeTypes::Ext`]). - pub ext: E, /// Builds the shared [`LogPipeline`]. pub logs: L, } -impl ComponentsBuilder { +impl ComponentsBuilder { /// Create a new [`ComponentsBuilder`] with the default log pipeline. - pub fn new(chain: C, store: S, ext: E) -> Self { + pub fn new(chain: C, store: S) -> Self { Self { chain, store, - ext, logs: LogPipelineBuilder, } } } -impl ComponentsBuilder { +impl ComponentsBuilder { /// Replace the log pipeline builder. - pub fn with_logs(self, logs: L2) -> ComponentsBuilder { + pub fn with_logs(self, logs: L2) -> ComponentsBuilder { ComponentsBuilder { chain: self.chain, store: self.store, - ext: self.ext, logs, } } @@ -153,19 +136,12 @@ impl ComponentsBuilder { T: RuntimeTypes, C: ComponentBuilder, S: ComponentBuilder, - E: ComponentBuilder, L: ComponentBuilder, { let chain = self.chain.build(ctx).await.map_err(BuildError::Chain)?; let store = self.store.build(ctx).await.map_err(BuildError::Store)?; - let ext = self.ext.build(ctx).await.map_err(BuildError::Ext)?; let logs = self.logs.build(ctx).await.map_err(BuildError::Logs)?; - Ok(Components { - chain, - store, - ext, - logs, - }) + Ok(Components { chain, store, logs }) } } @@ -189,7 +165,7 @@ mod tests { executor: &executor, }; - let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) .build::(&ctx) .await .expect("build core components"); @@ -219,7 +195,7 @@ mod tests { }; let custom = LogPipeline::in_memory(config.limits.logs()); - let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) .with_logs(crate::test_utils::Prebuilt(custom.clone())) .build::(&ctx) .await diff --git a/crates/nexum-runtime/src/host/component/mod.rs b/crates/nexum-runtime/src/host/component/mod.rs index 5c31259..bc6a743 100644 --- a/crates/nexum-runtime/src/host/component/mod.rs +++ b/crates/nexum-runtime/src/host/component/mod.rs @@ -23,8 +23,6 @@ use crate::host::provider_pool::ProviderPool; pub struct Components { pub chain: ProviderPool, pub store: T::Store, - /// Extension backends (the lattice `Ext` payload). - pub ext: T::Ext, /// Shared log pipeline. pub logs: crate::host::logs::LogPipeline, } @@ -34,7 +32,6 @@ impl Clone for Components { Self { chain: self.chain.clone(), store: self.store.clone(), - ext: self.ext.clone(), logs: self.logs.clone(), } } diff --git a/crates/nexum-runtime/src/host/component/runtime_types.rs b/crates/nexum-runtime/src/host/component/runtime_types.rs index 015af42..ca250f3 100644 --- a/crates/nexum-runtime/src/host/component/runtime_types.rs +++ b/crates/nexum-runtime/src/host/component/runtime_types.rs @@ -1,17 +1,13 @@ -//! The RuntimeTypes lattice: one trait naming the core backend seams plus the -//! pluggable [`RuntimeTypes::Ext`] slot, so every generic signature takes one -//! parameter. +//! The RuntimeTypes lattice: one trait naming the core backend seams, so +//! every generic signature takes one parameter. use crate::host::component::StateStore; -/// Core backend seams a runtime assembly provides, plus the extension slot -/// ([`Ext`](RuntimeTypes::Ext)). The marker bound is reserved for semver -/// evolution. The chain backend is not a seam. +/// Core backend seams a runtime assembly provides. The marker bound is +/// reserved for semver evolution. The chain backend is not a seam. pub trait RuntimeTypes: crate::sealed::SealedRuntimeTypes + 'static { /// Process-wide store vending per-module handles. type Store: StateStore + Clone + Send + Sync + 'static; - /// Extension state slot; `()` for an assembly with no extensions. - type Ext: Clone + Send + Sync + 'static; } /// Per-module store handle of a lattice's Store member. diff --git a/crates/nexum-runtime/src/host/state.rs b/crates/nexum-runtime/src/host/state.rs index f582083..4396283 100644 --- a/crates/nexum-runtime/src/host/state.rs +++ b/crates/nexum-runtime/src/host/state.rs @@ -31,9 +31,6 @@ pub struct HostState { pub run: RunId, /// Shared log pipeline the `nexum:host/logging` glue routes through. pub log_router: Arc, - /// Extension backends (the lattice `Ext` payload), reached via - /// [`ExtState`]. - pub ext: T::Ext, /// `chain` backend: per-chain provider pool. pub chain: ProviderPool, /// Cap on a chain JSON-RPC response body; larger responses are rejected. @@ -55,19 +52,3 @@ impl WasiView for HostState { } } } - -/// Generic access to the extension payload of a host state, without naming -/// the concrete lattice `T`. -pub trait ExtState { - /// The extension payload type (the lattice `Ext` member). - type Ext; - /// Borrow the extension payload. - fn ext(&self) -> &Self::Ext; -} - -impl ExtState for HostState { - type Ext = T::Ext; - fn ext(&self) -> &Self::Ext { - &self.ext - } -} diff --git a/crates/nexum-runtime/src/preset.rs b/crates/nexum-runtime/src/preset.rs index bbea422..8df1809 100644 --- a/crates/nexum-runtime/src/preset.rs +++ b/crates/nexum-runtime/src/preset.rs @@ -4,7 +4,7 @@ //! pre-built backends or non-static extensions binds by value through //! [`RuntimeBuilder::with_runtime`](crate::builder::RuntimeBuilder::with_runtime). //! [`CoreRuntime`] is the domain-free default: a chain provider pool and a -//! local redb store, no extension payload, with the Prometheus add-on. +//! local redb store, with the Prometheus add-on. use std::sync::Arc; @@ -31,8 +31,6 @@ pub trait Runtime: crate::sealed::SealedRuntime { type ChainBuilder: ComponentBuilder; /// Builds the store backend ([`RuntimeTypes::Store`]). type StoreBuilder: ComponentBuilder::Store>; - /// Builds the extension payload ([`RuntimeTypes::Ext`]). - type ExtBuilder: ComponentBuilder::Ext>; /// Builds the shared [`LogPipeline`]. type LogsBuilder: ComponentBuilder; @@ -40,12 +38,7 @@ pub trait Runtime: crate::sealed::SealedRuntime { /// preset, so a value-bound preset hands over owned, pre-built backends. fn components( self, - ) -> ComponentsBuilder< - Self::ChainBuilder, - Self::StoreBuilder, - Self::ExtBuilder, - Self::LogsBuilder, - >; + ) -> ComponentsBuilder; /// The cross-cutting add-ons installed before the engine boots. fn add_ons(&self) -> AddOns; @@ -61,7 +54,7 @@ pub trait Runtime: crate::sealed::SealedRuntime { } /// The domain-free default preset: a chain provider pool and a local redb -/// store, no extension payload, with the Prometheus add-on. Doubles as its own +/// store, with the Prometheus add-on. Doubles as its own /// [`RuntimeTypes`] lattice. #[derive(Debug, Clone, Copy, Default)] pub struct CoreRuntime; @@ -71,20 +64,18 @@ impl crate::sealed::SealedRuntime for CoreRuntime {} impl RuntimeTypes for CoreRuntime { type Store = LocalStore; - type Ext = (); } impl Runtime for CoreRuntime { type Types = Self; type ChainBuilder = ProviderPoolBuilder; type StoreBuilder = LocalStoreBuilder; - type ExtBuilder = (); type LogsBuilder = LogPipelineBuilder; fn components( self, - ) -> ComponentsBuilder { - ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()) + ) -> ComponentsBuilder { + ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder) } fn add_ons(&self) -> AddOns { diff --git a/crates/nexum-runtime/src/supervisor/store.rs b/crates/nexum-runtime/src/supervisor/store.rs index 548b7f1..09e4163 100644 --- a/crates/nexum-runtime/src/supervisor/store.rs +++ b/crates/nexum-runtime/src/supervisor/store.rs @@ -167,7 +167,6 @@ fn build( messaging_topics: spec.messaging_topics.clone(), run, log_router: router, - ext: shared.components.ext.clone(), chain: shared.components.chain.clone(), chain_response_max_bytes: spec.chain_response_max_bytes, // Provider guests never reach this: `build_provider_linker` diff --git a/crates/nexum-runtime/src/supervisor/tests/mod.rs b/crates/nexum-runtime/src/supervisor/tests/mod.rs index b3147fd..fc30444 100644 --- a/crates/nexum-runtime/src/supervisor/tests/mod.rs +++ b/crates/nexum-runtime/src/supervisor/tests/mod.rs @@ -56,12 +56,11 @@ fn make_linker(engine: &wasmtime::Engine) -> Linker> { .expect("build_linker") } -/// An empty chain pool, an empty extension slot, and the given store. +/// An empty chain pool and the given store. fn test_components(store: crate::host::local_store_redb::LocalStore) -> Components { Components { chain: ProviderPool::empty(), store, - ext: (), logs: crate::test_utils::in_memory_logs(), } } diff --git a/crates/nexum-runtime/src/test_utils/harness.rs b/crates/nexum-runtime/src/test_utils/harness.rs index c35bc6a..b651964 100644 --- a/crates/nexum-runtime/src/test_utils/harness.rs +++ b/crates/nexum-runtime/src/test_utils/harness.rs @@ -9,8 +9,7 @@ //! [`store`](TestRuntime::store) and [`logs`](TestRuntime::logs). Events //! dispatch on the spawned event-loop task, so //! [`wait_for_log`](TestRuntime::wait_for_log) polls for an observable -//! effect. Bind an extension payload through -//! [`builder_with_ext`](TestRuntime::builder_with_ext). +//! effect. use std::path::PathBuf; use std::sync::Arc; @@ -31,14 +30,10 @@ use crate::host::logs::{LogPipeline, LogRecord}; /// Builder for a [`TestRuntime`]; the launched handle shares the same mock /// backends. A manifest is mandatory. -pub struct TestRuntimeBuilder -where - E: Clone + Send + Sync + 'static, -{ +pub struct TestRuntimeBuilder { wasm: PathBuf, manifest: ManifestSource, - extensions: Vec>>>, - ext: E, + extensions: Vec>>, limits: ModuleLimits, chain: FakeNode, chains: Vec, @@ -46,23 +41,13 @@ where clock: ManualClock, } -impl TestRuntime<()> { - /// Start a harness for the module at `wasm`, with an empty extension slot. - pub fn builder(wasm: impl Into) -> TestRuntimeBuilder<()> { - TestRuntime::builder_with_ext(wasm, ()) - } -} - -impl TestRuntime { - /// Start a harness binding `ext` as the extension payload; pair with - /// [`extension`](TestRuntimeBuilder::extension) to register its linker - /// hook and capability namespace. - pub fn builder_with_ext(wasm: impl Into, ext: E) -> TestRuntimeBuilder { +impl TestRuntime { + /// Start a harness for the module at `wasm`. + pub fn builder(wasm: impl Into) -> TestRuntimeBuilder { TestRuntimeBuilder { wasm: wasm.into(), manifest: ManifestSource::Beside, extensions: Vec::new(), - ext, limits: ModuleLimits::default(), chain: FakeNode::new(), chains: vec![Chain::from_id(1)], @@ -72,7 +57,7 @@ impl TestRuntime { } } -impl TestRuntimeBuilder { +impl TestRuntimeBuilder { /// Load the manifest from an existing file. pub fn manifest_path(mut self, path: impl Into) -> Self { self.manifest = ManifestSource::Path(path.into()); @@ -86,7 +71,7 @@ impl TestRuntimeBuilder { } /// Register an extension. - pub fn extension(mut self, extension: Arc>>) -> Self { + pub fn extension(mut self, extension: Arc>) -> Self { self.extensions.push(extension); self } @@ -94,7 +79,7 @@ impl TestRuntimeBuilder { /// Register several extensions at once. pub fn extensions( mut self, - extensions: impl IntoIterator>>>, + extensions: impl IntoIterator>>, ) -> Self { self.extensions.extend(extensions); self @@ -129,7 +114,7 @@ impl TestRuntimeBuilder { } /// Open the module and start the runtime through the public builder path. - pub async fn launch(self) -> anyhow::Result> { + pub async fn launch(self) -> anyhow::Result { // A temp directory roots any inline manifest and stands in as the // (unused, in-memory backends) state directory. let tmp = tempfile::tempdir()?; @@ -144,14 +129,13 @@ impl TestRuntimeBuilder { let pool = self.chain.pool(&self.chains, HARNESS_POLL_INTERVAL); let handle = RuntimeBuilder::new(&config) - .with_types::>() + .with_types::() .with_extensions(self.extensions) .with_module_source(Some(self.wasm), manifest) .with_wasi_clocks(self.clock.as_override()) .with_components(ComponentsBuilder::new( Prebuilt(pool), Prebuilt(self.store.clone()), - Prebuilt(self.ext.clone()), )) .launch() .await?; @@ -161,7 +145,6 @@ impl TestRuntimeBuilder { chain: self.chain, store: self.store, clock: self.clock, - ext: self.ext, _tmp: tmp, }) } @@ -169,18 +152,17 @@ impl TestRuntimeBuilder { /// A launched in-process runtime over the mock assembly; dropping it fires /// the shutdown trigger. -pub struct TestRuntime { +pub struct TestRuntime { handle: RuntimeHandle, chain: FakeNode, store: MockStateStore, clock: ManualClock, - ext: E, // Holds any inline manifest for the lifetime of the harness; dropped // when the `TestRuntime` is dropped (or consumed by `wait`). _tmp: tempfile::TempDir, } -impl TestRuntime { +impl TestRuntime { pub fn chain(&self) -> &FakeNode { &self.chain } @@ -195,11 +177,6 @@ impl TestRuntime { &self.clock } - /// The extension payload bound into the lattice ext slot. - pub fn ext(&self) -> &E { - &self.ext - } - /// The shared log pipeline. pub fn logs(&self) -> &LogPipeline { self.handle.logs() @@ -385,18 +362,17 @@ mod tests { rt.wait().await.expect("clean shutdown"); } - /// The extension slot threads through the harness: a trivial extension - /// and an ext payload compose, the module dispatches, and the harness - /// hands the payload back. + /// An extension threads through the harness: its linker hook runs at + /// boot and the module still dispatches. #[tokio::test] - async fn harness_threads_an_extension_and_ext_payload() { + async fn harness_threads_an_extension() { let Some(wasm) = example_wasm_or_skip() else { return; }; struct CountingExtension(Arc); - impl Extension>> for CountingExtension { + impl Extension for CountingExtension { fn namespace(&self) -> &'static str { "test" } @@ -408,9 +384,7 @@ mod tests { } fn link( &self, - _linker: &mut wasmtime::component::Linker< - crate::host::state::HostState>>, - >, + _linker: &mut wasmtime::component::Linker>, ) -> anyhow::Result<()> { self.0.fetch_add(1, Ordering::SeqCst); Ok(()) @@ -420,25 +394,22 @@ mod tests { let calls = Arc::new(AtomicUsize::new(0)); let extension = Arc::new(CountingExtension(calls.clone())); - let mut rt = TestRuntime::builder_with_ext(wasm, calls.clone()) + let mut rt = TestRuntime::builder(wasm) .extension(extension) .manifest_inline(example_block_manifest()) .launch() .await .expect("launch with a trivial extension"); - // The extension's linker hook ran during boot, and the payload the - // harness threaded is the one it hands back. assert!( calls.load(Ordering::SeqCst) >= 1, "the extension linker hook ran at boot", ); - assert!(Arc::ptr_eq(rt.ext(), &calls), "the ext payload is retained"); rt.push_block(header_numbered(21_000_000)); rt.wait_for_log("example", "block 21000000") .await - .expect("the module dispatched under the extension-bearing lattice"); + .expect("the module dispatched with the extension linked"); rt.shutdown(); rt.wait().await.expect("clean shutdown"); diff --git a/crates/nexum-runtime/src/test_utils/mod.rs b/crates/nexum-runtime/src/test_utils/mod.rs index 3a48949..7264fda 100644 --- a/crates/nexum-runtime/src/test_utils/mod.rs +++ b/crates/nexum-runtime/src/test_utils/mod.rs @@ -21,11 +21,7 @@ //! let store = MockStateStore::new(); //! let _handle = RuntimeBuilder::new(config) //! .with_types::() -//! .with_components(ComponentsBuilder::new( -//! Prebuilt(pool), -//! Prebuilt(store.clone()), -//! (), -//! )) +//! .with_components(ComponentsBuilder::new(Prebuilt(pool), Prebuilt(store.clone()))) //! .launch() //! .await?; //! # Ok(()) @@ -89,13 +85,12 @@ pub fn mock_components() -> Components { mock_components_from(&FakeNode::new(), MockStateStore::new()) } -/// A [`Components`] bundle serving chain id 1 from `node`, with an empty -/// extension slot and an in-memory log pipeline. +/// A [`Components`] bundle serving chain id 1 from `node`, with an in-memory +/// log pipeline. pub fn mock_components_from(node: &FakeNode, store: MockStateStore) -> Components { Components { chain: node.pool(&[alloy_chains::Chain::from_id(1)], HARNESS_POLL_INTERVAL), store, - ext: (), logs: in_memory_logs(), } } @@ -127,7 +122,6 @@ mod tests { .with_components(ComponentsBuilder::new( Prebuilt(pool.clone()), Prebuilt(store), - (), )) .launch() .await diff --git a/crates/nexum-runtime/src/test_utils/scenario.rs b/crates/nexum-runtime/src/test_utils/scenario.rs index 93f43ef..be8377e 100644 --- a/crates/nexum-runtime/src/test_utils/scenario.rs +++ b/crates/nexum-runtime/src/test_utils/scenario.rs @@ -113,7 +113,6 @@ impl BootScenario { Components { chain: ProviderPool::empty(), store, - ext: (), logs: in_memory_logs(), }, ) diff --git a/crates/nexum-runtime/src/test_utils/types.rs b/crates/nexum-runtime/src/test_utils/types.rs index a4c16d9..63697e7 100644 --- a/crates/nexum-runtime/src/test_utils/types.rs +++ b/crates/nexum-runtime/src/test_utils/types.rs @@ -1,18 +1,13 @@ //! The [`RuntimeTypes`] lattice over the in-process mocks. -use std::marker::PhantomData; - use crate::host::component::RuntimeTypes; use crate::test_utils::MockStateStore; -/// Lattice binding the mock backends. The extension slot is the type -/// parameter `E` (default `()`); an extension crate binds its own payload as -/// `MockTypes`. A type-level marker, only ever named. -pub struct MockTypes(PhantomData E>); +/// Lattice binding the mock backends. A type-level marker, only ever named. +pub struct MockTypes; -impl crate::sealed::SealedRuntimeTypes for MockTypes {} +impl crate::sealed::SealedRuntimeTypes for MockTypes {} -impl RuntimeTypes for MockTypes { +impl RuntimeTypes for MockTypes { type Store = MockStateStore; - type Ext = E; } From a7c26a53eaa77fd91425ca9bf0a085c099608ab4 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 08:48:12 +0000 Subject: [PATCH 2/5] refactor(runtime)!: strip IntoStaticStr from the pub error enums EngineConfigError, EnvVarError, DigestParseError, and ParseError lose the derive; no consumer exercises the static-str conversion. strum stays for LogSource, and the dependency rationale now says so. AI Assistance: Fable 5 used for implementation --- Cargo.toml | 2 +- crates/nexum-runtime/Cargo.toml | 5 ++--- crates/nexum-runtime/src/digest.rs | 4 +--- crates/nexum-runtime/src/engine_config.rs | 7 ++----- crates/nexum-runtime/src/manifest/error.rs | 4 +--- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a11a5e..13af584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ tracing = "0.1" tracing-core = { version = "0.1", default-features = false, features = ["std"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "env-filter", "ansi", "json"] } -# `strum::IntoStaticStr` on every error / event enum gives a free +# `strum::IntoStaticStr` on error / event enums gives a free # snake_case `&'static str` for every variant, which feeds directly # into `metrics::counter!(..., "error_kind" => name)` and # `tracing::warn!(error_kind = name, ...)` recordings without an diff --git a/crates/nexum-runtime/Cargo.toml b/crates/nexum-runtime/Cargo.toml index 5f26565..0f737bd 100644 --- a/crates/nexum-runtime/Cargo.toml +++ b/crates/nexum-runtime/Cargo.toml @@ -25,9 +25,8 @@ thiserror.workspace = true async-trait.workspace = true # Newtype boilerplate (`Display`, `AsRef`, `From`) for identity wrappers. derive_more.workspace = true -# `strum::IntoStaticStr` on the pub error enums: a snake_case -# `&'static str` per variant, published for consumers' metric labels; -# nothing in-crate consumes the conversion. +# `strum::IntoStaticStr` on `LogSource`: the snake_case variant name is +# the tracing `source` field. strum.workspace = true tokio.workspace = true # Task lifecycle and graceful shutdown; the sole crate that raw-spawns diff --git a/crates/nexum-runtime/src/digest.rs b/crates/nexum-runtime/src/digest.rs index bdd8548..b8940b3 100644 --- a/crates/nexum-runtime/src/digest.rs +++ b/crates/nexum-runtime/src/digest.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; use std::str::FromStr; use sha2::{Digest, Sha256}; -use strum::IntoStaticStr; use thiserror::Error; const SCHEME: &str = "sha256"; @@ -64,8 +63,7 @@ impl fmt::Display for ContentDigest { } } -#[derive(Debug, Error, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] +#[derive(Debug, Error)] #[non_exhaustive] pub enum DigestParseError { /// No `scheme:` prefix; the empty string lands here too. diff --git a/crates/nexum-runtime/src/engine_config.rs b/crates/nexum-runtime/src/engine_config.rs index 9dbf10e..4ab3115 100644 --- a/crates/nexum-runtime/src/engine_config.rs +++ b/crates/nexum-runtime/src/engine_config.rs @@ -11,7 +11,6 @@ use std::time::Duration; use alloy_chains::Chain; use serde::Deserialize; -use strum::IntoStaticStr; use thiserror::Error; use tracing::{info, warn}; @@ -110,8 +109,7 @@ impl Default for WatchLimit { } /// Errors surfaced by [`load_or_default`]. -#[derive(Debug, Error, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] +#[derive(Debug, Error)] #[non_exhaustive] pub enum EngineConfigError { /// Failed to read the config file from disk. @@ -737,8 +735,7 @@ fn is_valid_env_name(s: &str) -> bool { } /// Errors from `${VAR}` substitution in `engine.toml`. -#[derive(Debug, thiserror::Error, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum EnvVarError { #[error( diff --git a/crates/nexum-runtime/src/manifest/error.rs b/crates/nexum-runtime/src/manifest/error.rs index 9794f3c..1856729 100644 --- a/crates/nexum-runtime/src/manifest/error.rs +++ b/crates/nexum-runtime/src/manifest/error.rs @@ -1,11 +1,9 @@ //! Error types for manifest parsing and capability enforcement. -use strum::IntoStaticStr; use thiserror::Error; /// Errors from loading or validating a manifest. -#[derive(Debug, Error, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] +#[derive(Debug, Error)] #[non_exhaustive] pub enum ParseError { /// Failed to read the manifest file from disk. From dfdb794777e766952f660ae4a4b5c535a4b8eebd Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 08:50:21 +0000 Subject: [PATCH 3/5] refactor(runtime): drop unexercised test DSL conversions and ModuleId derives Entry loses From and From<&Path>, ManifestSource loses From<&TestManifest> and From<&Path>; no call site consumes them. ModuleId loses AsRef, Ord, and PartialOrd; nothing keys or sorts by it. AI Assistance: Fable 5 used for implementation --- crates/nexum-runtime/src/module_id.rs | 5 ++--- crates/nexum-runtime/src/test_utils/manifest.rs | 12 ------------ crates/nexum-runtime/src/test_utils/scenario.rs | 12 ------------ 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/crates/nexum-runtime/src/module_id.rs b/crates/nexum-runtime/src/module_id.rs index 4185310..a504ce3 100644 --- a/crates/nexum-runtime/src/module_id.rs +++ b/crates/nexum-runtime/src/module_id.rs @@ -3,12 +3,11 @@ use std::borrow::Borrow; use std::sync::Arc; -use derive_more::{AsRef, Display, From}; +use derive_more::{Display, From}; /// The manifest namespace. `Arc`-backed so dispatch-path clones are /// refcount bumps; `Display` is the bare namespace. -#[derive(AsRef, Clone, Debug, Display, Eq, From, Hash, Ord, PartialEq, PartialOrd)] -#[as_ref(str)] +#[derive(Clone, Debug, Display, Eq, From, Hash, PartialEq)] #[from(forward)] pub struct ModuleId(Arc); diff --git a/crates/nexum-runtime/src/test_utils/manifest.rs b/crates/nexum-runtime/src/test_utils/manifest.rs index f247467..c4861e0 100644 --- a/crates/nexum-runtime/src/test_utils/manifest.rs +++ b/crates/nexum-runtime/src/test_utils/manifest.rs @@ -32,12 +32,6 @@ impl From for ManifestSource { } } -impl From<&TestManifest> for ManifestSource { - fn from(manifest: &TestManifest) -> Self { - Self::Toml(manifest.to_toml()) - } -} - impl From for ManifestSource { fn from(toml: String) -> Self { Self::Toml(toml) @@ -50,12 +44,6 @@ impl From for ManifestSource { } } -impl From<&Path> for ManifestSource { - fn from(path: &Path) -> Self { - Self::Path(path.to_path_buf()) - } -} - /// Builder for positive-path manifest TOML. #[derive(Debug, Clone)] pub struct TestManifest { diff --git a/crates/nexum-runtime/src/test_utils/scenario.rs b/crates/nexum-runtime/src/test_utils/scenario.rs index be8377e..d3f3363 100644 --- a/crates/nexum-runtime/src/test_utils/scenario.rs +++ b/crates/nexum-runtime/src/test_utils/scenario.rs @@ -58,12 +58,6 @@ impl Entry { } } -impl From for Entry { - fn from(manifest: ManifestSource) -> Self { - Self::new(manifest) - } -} - impl From for Entry { fn from(manifest: TestManifest) -> Self { Self::new(manifest) @@ -82,12 +76,6 @@ impl From for Entry { } } -impl From<&Path> for Entry { - fn from(manifest: &Path) -> Self { - Self::new(manifest) - } -} - /// Every terminal boots through the real [`Supervisor::boot`] admission path. pub struct BootScenario { dir: TempDir, From 8a27edba4b0937475cffe7ed86062d349179a3a1 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 08:54:31 +0000 Subject: [PATCH 4/5] refactor(runtime)!: own add-ons and module-source paths in AssembledRuntime add_ons becomes an owned AddOns and wasm/manifest become Option, dropping the borrowed-slice threading, both add_on_refs bridge blocks, and the AssembledRuntime lifetime. AI Assistance: Fable 5 used for implementation --- crates/nexum-runtime/src/builder.rs | 60 +++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/crates/nexum-runtime/src/builder.rs b/crates/nexum-runtime/src/builder.rs index f6fcdd4..f42458e 100644 --- a/crates/nexum-runtime/src/builder.rs +++ b/crates/nexum-runtime/src/builder.rs @@ -10,7 +10,7 @@ use std::future::IntoFuture; use std::marker::PhantomData; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -18,7 +18,7 @@ use nexum_tasks::{DrainOutcome, TaskExit, TaskHandle, TaskManager, TaskSet}; use tracing::{error, info, warn}; use wasmtime::Engine; -use crate::addons::{AddOnHandle, AddOns, AddOnsContext, RuntimeAddOn}; +use crate::addons::{AddOnHandle, AddOns, AddOnsContext}; use crate::engine_config::{EngineConfig, ModuleEntry}; use crate::host::component::{ BuilderContext, ComponentBuilder, Components, ComponentsBuilder, RuntimeTypes, @@ -123,23 +123,23 @@ pub(crate) fn wasmtime_config() -> wasmtime::Config { /// A fully-assembled runtime: concrete backends, extensions, add-ons, and the /// optional module-source override. [`launch`](Self::launch) runs it. -pub struct AssembledRuntime<'a, T: RuntimeTypes> { +pub struct AssembledRuntime { /// Shared backends threaded into every module store. pub components: Components, /// Extensions: namespaces, capabilities, linker hooks, services, and /// provider kinds. pub extensions: Vec>>, /// Cross-cutting facilities installed before the engine boots. - pub add_ons: &'a [&'a dyn RuntimeAddOn], + pub add_ons: AddOns, /// Single-module source override; `None` runs `[[modules]]`. - pub wasm: Option<&'a Path>, + pub wasm: Option, /// Manifest paired with `wasm`. - pub manifest: Option<&'a Path>, + pub manifest: Option, /// Per-store WASI clock override; `None` leaves the ambient host clocks. pub clocks: Option, } -impl AssembledRuntime<'_, T> { +impl AssembledRuntime { /// Run the imperative launch sequence and return the running handle. pub async fn launch(self, ctx: LaunchContext<'_>) -> anyhow::Result { let AssembledRuntime { @@ -184,8 +184,8 @@ impl AssembledRuntime<'_, T> { ); } let entry = ModuleEntry { - path: wasm.to_path_buf(), - manifest: manifest.map(Path::to_path_buf), + path: wasm, + manifest, }; Supervisor::boot_single( &engine, @@ -350,9 +350,9 @@ impl AssembledRuntime<'_, T> { async fn open_and_launch( config: &EngineConfig, extensions: Vec>>, - add_ons: &[&dyn RuntimeAddOn], - wasm: Option<&Path>, - manifest: Option<&Path>, + add_ons: AddOns, + wasm: Option, + manifest: Option, clocks: Option, components: ComponentsBuilder, ) -> anyhow::Result @@ -501,16 +501,13 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> { } = self; let mut extensions = preset.extensions(config); extensions.extend(appended); - // `add_ons` owns the boxed add-ons; `add_on_refs` borrows into it and is - // consumed by the launch call, so both must stay in scope for that call. let add_ons = preset.add_ons(); - let add_on_refs: Vec<&dyn RuntimeAddOn> = add_ons.iter().map(|a| &**a).collect(); open_and_launch( config, extensions, - &add_on_refs, - wasm.as_deref(), - manifest.as_deref(), + add_ons, + wasm, + manifest, clocks, preset.components(), ) @@ -540,15 +537,12 @@ where /// Open the overridden backends and launch, otherwise as /// [`PresetBuilder::launch`]. pub async fn launch(self) -> anyhow::Result { - // `add_ons` owns the boxed add-ons; `add_on_refs` borrows into it and is - // consumed by the launch call, so both must stay in scope for that call. - let add_on_refs: Vec<&dyn RuntimeAddOn> = self.add_ons.iter().map(|a| &**a).collect(); open_and_launch( self.config, self.extensions, - &add_on_refs, - self.wasm.as_deref(), - self.manifest.as_deref(), + self.add_ons, + self.wasm, + self.manifest, self.clocks, self.components, ) @@ -604,7 +598,7 @@ impl<'a, T: RuntimeTypes> TypedBuilder<'a, T> { manifest: self.manifest, clocks: self.clocks, components, - add_ons: &[], + add_ons: AddOns::new(), } } } @@ -618,13 +612,13 @@ pub struct ReadyBuilder<'a, T: RuntimeTypes, C, S, L> { manifest: Option, clocks: Option, components: ComponentsBuilder, - add_ons: &'a [&'a dyn RuntimeAddOn], + add_ons: AddOns, } -impl<'a, T: RuntimeTypes, C, S, L> ReadyBuilder<'a, T, C, S, L> { +impl ReadyBuilder<'_, T, C, S, L> { /// Bind the cross-cutting add-on set installed before the engine boots; /// defaults to none. - pub fn with_add_ons(mut self, add_ons: &'a [&'a dyn RuntimeAddOn]) -> Self { + pub fn with_add_ons(mut self, add_ons: AddOns) -> Self { self.add_ons = add_ons; self } @@ -644,8 +638,8 @@ where self.config, self.extensions, self.add_ons, - self.wasm.as_deref(), - self.manifest.as_deref(), + self.wasm, + self.manifest, self.clocks, self.components, ) @@ -660,7 +654,7 @@ mod tests { use std::time::{SystemTime, UNIX_EPOCH}; use super::*; - use crate::addons::AddOns; + use crate::addons::{AddOns, RuntimeAddOn}; use crate::engine_config::EngineConfig; use crate::host::component::{LocalStoreBuilder, LogPipelineBuilder, ProviderPoolBuilder}; use crate::host::extension::HostWallClock; @@ -1119,12 +1113,10 @@ mod tests { .expect("build core components"); let calls = Arc::new(AtomicUsize::new(0)); - let add_on = CountingAddOn(calls.clone()); - let add_on_refs: Vec<&dyn RuntimeAddOn> = vec![&add_on]; let runtime = AssembledRuntime { components, extensions: Vec::new(), - add_ons: &add_on_refs, + add_ons: vec![Box::new(CountingAddOn(calls.clone()))], wasm: None, manifest: None, clocks: None, From 40541b451e278fe6e97fec80c8d72c006ff5da0f Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 09:27:17 +0000 Subject: [PATCH 5/5] docs(runtime): describe RuntimeTypes as the single store seam The lattice names one seam now that the Ext slot is gone. AI Assistance: Fable 5 used for review and the doc correction --- crates/nexum-runtime/src/host/component/runtime_types.rs | 6 +++--- crates/nexum-runtime/src/preset.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/nexum-runtime/src/host/component/runtime_types.rs b/crates/nexum-runtime/src/host/component/runtime_types.rs index ca250f3..9ae933e 100644 --- a/crates/nexum-runtime/src/host/component/runtime_types.rs +++ b/crates/nexum-runtime/src/host/component/runtime_types.rs @@ -1,9 +1,9 @@ -//! The RuntimeTypes lattice: one trait naming the core backend seams, so -//! every generic signature takes one parameter. +//! The RuntimeTypes lattice: one trait naming the store seam, so every +//! generic signature takes one parameter. use crate::host::component::StateStore; -/// Core backend seams a runtime assembly provides. The marker bound is +/// The store seam a runtime assembly provides. The marker bound is /// reserved for semver evolution. The chain backend is not a seam. pub trait RuntimeTypes: crate::sealed::SealedRuntimeTypes + 'static { /// Process-wide store vending per-module handles. diff --git a/crates/nexum-runtime/src/preset.rs b/crates/nexum-runtime/src/preset.rs index 8df1809..0bf0cac 100644 --- a/crates/nexum-runtime/src/preset.rs +++ b/crates/nexum-runtime/src/preset.rs @@ -54,8 +54,8 @@ pub trait Runtime: crate::sealed::SealedRuntime { } /// The domain-free default preset: a chain provider pool and a local redb -/// store, with the Prometheus add-on. Doubles as its own -/// [`RuntimeTypes`] lattice. +/// store, with the Prometheus add-on. Doubles as its own [`RuntimeTypes`] +/// lattice. #[derive(Debug, Clone, Copy, Default)] pub struct CoreRuntime;