From c273e5034cdde9901b9b4bf5f0afff7817e3d2a4 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:39:18 +0000 Subject: [PATCH 1/9] chore(runtime): strip inert IntoStaticStr derives from private enums LifecycleState, Role, and FilterError never convert to &'static str; the crate manifest rationale now states the pub-enum surface only. AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/Cargo.toml | 11 +++-------- crates/nexum-runtime/src/supervisor/lifecycle.rs | 2 +- crates/nexum-runtime/src/supervisor/role.rs | 2 +- crates/nexum-runtime/src/supervisor/subscriptions.rs | 3 +-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/nexum-runtime/Cargo.toml b/crates/nexum-runtime/Cargo.toml index 19be69b..5f26565 100644 --- a/crates/nexum-runtime/Cargo.toml +++ b/crates/nexum-runtime/Cargo.toml @@ -25,14 +25,9 @@ thiserror.workspace = true async-trait.workspace = true # Newtype boilerplate (`Display`, `AsRef`, `From`) for identity wrappers. derive_more.workspace = true -# `strum::IntoStaticStr` on error enums gives metric labels (`error_kind`) -# free via a snake_case `&'static str` for every variant. Used at -# `tracing::warn!(error_kind = .into(), ...)` sites and -# any `metrics::counter!(... "error_kind" => kind)` recordings, so the -# Prometheus labels stay in lock-step with the Rust enum source of -# truth instead of needing a `match err { ... => "connect" ... }` -# ladder per call site. Pinned via the workspace so every consumer -# moves in lockstep. +# `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.workspace = true tokio.workspace = true # Task lifecycle and graceful shutdown; the sole crate that raw-spawns diff --git a/crates/nexum-runtime/src/supervisor/lifecycle.rs b/crates/nexum-runtime/src/supervisor/lifecycle.rs index 8fbcfd9..43f2841 100644 --- a/crates/nexum-runtime/src/supervisor/lifecycle.rs +++ b/crates/nexum-runtime/src/supervisor/lifecycle.rs @@ -17,7 +17,7 @@ use crate::module_id::ModuleId; use crate::runtime::poison_policy::{PoisonPolicy, should_poison}; use crate::runtime::restart_policy::backoff_for; -#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::IntoStaticStr)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum LifecycleState { /// Callable; the failure count beside it may still be nonzero. Alive, diff --git a/crates/nexum-runtime/src/supervisor/role.rs b/crates/nexum-runtime/src/supervisor/role.rs index 60e7f22..07b376f 100644 --- a/crates/nexum-runtime/src/supervisor/role.rs +++ b/crates/nexum-runtime/src/supervisor/role.rs @@ -12,7 +12,7 @@ use crate::module_id::ModuleId; use crate::runtime::poison_policy::PoisonPolicy; /// Keys the per-role metric names and the compile-time tracing field keys. -#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::IntoStaticStr)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(super) enum Role { Module, Adapter, diff --git a/crates/nexum-runtime/src/supervisor/subscriptions.rs b/crates/nexum-runtime/src/supervisor/subscriptions.rs index c50bcb7..2fa8cbf 100644 --- a/crates/nexum-runtime/src/supervisor/subscriptions.rs +++ b/crates/nexum-runtime/src/supervisor/subscriptions.rs @@ -120,8 +120,7 @@ impl From<&alloy_rpc_types_eth::Log> for nexum::host::types::ChainLog { } } -#[derive(Debug, thiserror::Error, strum::IntoStaticStr)] -#[strum(serialize_all = "snake_case")] +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub(super) enum FilterError { /// `[[subscriptions]].address` did not parse as an EVM address. From c51c091574695a837c98c421a5a56e206a173da3 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:41:38 +0000 Subject: [PATCH 2/9] chore(supervisor): drop an inert allow and unused DispatchOutcome derives poisoned_count is pub, so dead_code cannot fire; DispatchOutcome is consumed only through matches!, never compared. AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/src/supervisor/dispatch.rs | 2 +- crates/nexum-runtime/src/supervisor/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/nexum-runtime/src/supervisor/dispatch.rs b/crates/nexum-runtime/src/supervisor/dispatch.rs index 3419e06..c5ba4e3 100644 --- a/crates/nexum-runtime/src/supervisor/dispatch.rs +++ b/crates/nexum-runtime/src/supervisor/dispatch.rs @@ -323,7 +323,7 @@ pub(super) async fn with_dispatch_deadline( .map_err(|_elapsed| DeadlineExceeded(deadline)) } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] pub(super) enum DispatchOutcome { Ok, /// Guest returned a typed `fault` via WIT, not a trap; the module stays alive. diff --git a/crates/nexum-runtime/src/supervisor/mod.rs b/crates/nexum-runtime/src/supervisor/mod.rs index 7e7fdf3..922b4b3 100644 --- a/crates/nexum-runtime/src/supervisor/mod.rs +++ b/crates/nexum-runtime/src/supervisor/mod.rs @@ -203,7 +203,6 @@ impl Supervisor { .any(|m| !m.health.dispatchable() && !m.subscriptions.is_empty()) } - #[cfg_attr(not(test), allow(dead_code))] pub fn poisoned_count(&self) -> usize { self.modules .iter() From 828128942db1de94e34142ac76f2213976e7e41c Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:42:11 +0000 Subject: [PATCH 3/9] refactor(supervisor): tighten Health and LifecycleState visibility Neither is referenced outside supervisor/; LifecycleState never leaves lifecycle.rs. AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/src/supervisor/lifecycle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nexum-runtime/src/supervisor/lifecycle.rs b/crates/nexum-runtime/src/supervisor/lifecycle.rs index 43f2841..f562856 100644 --- a/crates/nexum-runtime/src/supervisor/lifecycle.rs +++ b/crates/nexum-runtime/src/supervisor/lifecycle.rs @@ -18,7 +18,7 @@ use crate::runtime::poison_policy::{PoisonPolicy, should_poison}; use crate::runtime::restart_policy::backoff_for; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum LifecycleState { +enum LifecycleState { /// Callable; the failure count beside it may still be nonzero. Alive, /// Dead pending a restart once `until` passes. @@ -32,7 +32,7 @@ pub(crate) enum LifecycleState { /// The failure count survives a restart unless the caller resets it; only a /// successful dispatch clears it. Methods take instants; nothing samples the clock. -pub(crate) struct Health { +pub(super) struct Health { state: LifecycleState, failure_count: u32, window: VecDeque, From 95ac0fdf6457f3db5efd5589c1ea061ebef46860 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:44:59 +0000 Subject: [PATCH 4/9] refactor(supervisor): move the test import ladder beside the tests The cfg(test) use ladder and TestTypes lattice lived in supervisor/mod.rs only to feed the tests glob; preset::CoreRuntime already provides the identical core-only lattice, so the bespoke test types collapse into it. AI Assistance: Fable 5 used for cruft analysis and implementation --- .../nexum-runtime/src/host/component/mod.rs | 14 +---- crates/nexum-runtime/src/host/extension.rs | 10 ++-- crates/nexum-runtime/src/supervisor/mod.rs | 51 ------------------- .../src/supervisor/tests/chain_gate.rs | 3 +- .../src/supervisor/tests/ledger.rs | 12 ++--- .../src/supervisor/tests/lifecycle.rs | 16 +++--- .../nexum-runtime/src/supervisor/tests/mod.rs | 32 ++++++++++-- 7 files changed, 49 insertions(+), 89 deletions(-) diff --git a/crates/nexum-runtime/src/host/component/mod.rs b/crates/nexum-runtime/src/host/component/mod.rs index a2cba91..5c31259 100644 --- a/crates/nexum-runtime/src/host/component/mod.rs +++ b/crates/nexum-runtime/src/host/component/mod.rs @@ -44,17 +44,7 @@ impl Clone for Components { mod tests { use super::*; use crate::host::local_store_redb::{LocalStore, ModuleStore}; - - /// Core-only lattice (no extension payload). - #[derive(Clone, Copy, Default)] - struct CoreTypes; - - impl crate::sealed::SealedRuntimeTypes for CoreTypes {} - - impl RuntimeTypes for CoreTypes { - type Store = LocalStore; - type Ext = (); - } + use crate::preset::CoreRuntime; fn store() {} fn handle() {} @@ -64,6 +54,6 @@ mod tests { fn concrete_backends_satisfy_the_traits() { store::(); handle::(); - lattice::(); + lattice::(); } } diff --git a/crates/nexum-runtime/src/host/extension.rs b/crates/nexum-runtime/src/host/extension.rs index 78f2ee1..fce70db 100644 --- a/crates/nexum-runtime/src/host/extension.rs +++ b/crates/nexum-runtime/src/host/extension.rs @@ -282,7 +282,7 @@ impl HostServices { #[cfg(test)] mod tests { use super::*; - use crate::supervisor::TestTypes; + use crate::preset::CoreRuntime; struct Registry(u64); impl HostService for Registry {} @@ -295,7 +295,7 @@ mod tests { service: Option>, } - impl Extension for ServiceExt { + impl Extension for ServiceExt { fn namespace(&self) -> &'static str { self.namespace } @@ -305,7 +305,7 @@ mod tests { ifaces: &[], } } - fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { + fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { Ok(()) } fn service(&self) -> Option> { @@ -316,7 +316,7 @@ mod tests { fn ext( namespace: &'static str, service: Arc, - ) -> Arc> { + ) -> Arc> { Arc::new(ServiceExt { namespace, service: Some(service), @@ -340,7 +340,7 @@ mod tests { /// A serviceless extension contributes nothing to the map. #[test] fn serviceless_extension_is_absent() { - let serviceless: Arc> = Arc::new(ServiceExt { + let serviceless: Arc> = Arc::new(ServiceExt { namespace: "quiet", service: None, }); diff --git a/crates/nexum-runtime/src/supervisor/mod.rs b/crates/nexum-runtime/src/supervisor/mod.rs index 922b4b3..708d845 100644 --- a/crates/nexum-runtime/src/supervisor/mod.rs +++ b/crates/nexum-runtime/src/supervisor/mod.rs @@ -297,56 +297,5 @@ fn assemble( } } -/// Core-only lattice for the runtime's own tests (`Ext = ()`). -#[cfg(test)] -#[derive(Clone, Copy, Default)] -pub(crate) struct TestTypes; - -#[cfg(test)] -impl crate::sealed::SealedRuntimeTypes for TestTypes {} - -#[cfg(test)] -impl RuntimeTypes for TestTypes { - type Store = crate::host::local_store_redb::LocalStore; - type Ext = (); -} - -#[cfg(test)] -pub(crate) type DefaultSupervisor = Supervisor; - -#[cfg(test)] -use admission::enforce_extension_sections; -#[cfg(test)] -use artifact::read_verified_component; -#[cfg(test)] -use cursors::{chainlog_cursor_key, commit_chain_log_cursor, progress_key, read_chain_log_cursor}; -#[cfg(test)] -use dispatch::with_dispatch_deadline; -#[cfg(test)] -use prepass::{NamespaceLedger, claim_namespace, unconfigured_chain}; -#[cfg(test)] -use store::resolve_module_limits; -#[cfg(test)] -use subscriptions::build_alloy_filter; - -#[cfg(test)] -use crate::bindings::nexum; -#[cfg(test)] -use crate::digest::{ContentDigest, DigestMismatch}; -#[cfg(test)] -use crate::host::extension::{HostService, Installed, ProviderInstance, ProviderKind}; -#[cfg(test)] -use crate::host::logs::LogSource; -#[cfg(test)] -use crate::host::provider_pool::ProviderPool; -#[cfg(test)] -use crate::manifest::{self, CapabilityRegistry}; -#[cfg(test)] -use alloy_chains::Chain; -#[cfg(test)] -use std::time::Duration; -#[cfg(test)] -use tracing_core::Level; - #[cfg(test)] pub(crate) mod tests; diff --git a/crates/nexum-runtime/src/supervisor/tests/chain_gate.rs b/crates/nexum-runtime/src/supervisor/tests/chain_gate.rs index 4e8a5cb..a868a71 100644 --- a/crates/nexum-runtime/src/supervisor/tests/chain_gate.rs +++ b/crates/nexum-runtime/src/supervisor/tests/chain_gate.rs @@ -209,8 +209,7 @@ fn configured_chains_normalise_named_and_numeric_spellings() { #[test] fn unconfigured_chain_message_says_none_when_engine_toml_declares_no_chains() { let chains = ConfiguredChains::from_config(&EngineConfig::default()); - let msg = crate::supervisor::unconfigured_chain(Role::Module, "example", 424_242, &chains) - .to_string(); + let msg = unconfigured_chain(Role::Module, "example", 424_242, &chains).to_string(); assert!(msg.contains("configured chains: none"), "{msg}"); assert!(!msg.contains("no engine.toml was found"), "{msg}"); } diff --git a/crates/nexum-runtime/src/supervisor/tests/ledger.rs b/crates/nexum-runtime/src/supervisor/tests/ledger.rs index 158e9c1..f9ade48 100644 --- a/crates/nexum-runtime/src/supervisor/tests/ledger.rs +++ b/crates/nexum-runtime/src/supervisor/tests/ledger.rs @@ -7,7 +7,7 @@ use super::*; #[test] fn extension_sections_must_be_claimed() { struct Claiming; - impl Extension for Claiming { + impl Extension for Claiming { fn namespace(&self) -> &'static str { "acme" } @@ -17,14 +17,14 @@ fn extension_sections_must_be_claimed() { ifaces: &[], } } - fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { + fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { Ok(()) } fn manifest_sections(&self) -> &'static [&'static str] { &["venue"] } } - let extensions: Vec>> = vec![Arc::new(Claiming)]; + let extensions: Vec>> = vec![Arc::new(Claiming)]; let mut sections = manifest::ExtensionSections::new(); sections.insert("venue".into(), toml::Value::Boolean(true)); @@ -46,7 +46,7 @@ fn extension_claims_must_be_unique() { subscriptions: &'static [&'static str], sections: &'static [&'static str], } - impl Extension for Claiming { + impl Extension for Claiming { fn namespace(&self) -> &'static str { self.namespace } @@ -56,7 +56,7 @@ fn extension_claims_must_be_unique() { ifaces: &[], } } - fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { + fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { Ok(()) } fn subscriptions(&self) -> &'static [&'static str] { @@ -70,7 +70,7 @@ fn extension_claims_must_be_unique() { namespace: &'static str, subscriptions: &'static [&'static str], sections: &'static [&'static str], - ) -> Arc> { + ) -> Arc> { Arc::new(Claiming { namespace, subscriptions, diff --git a/crates/nexum-runtime/src/supervisor/tests/lifecycle.rs b/crates/nexum-runtime/src/supervisor/tests/lifecycle.rs index 2c9e28b..c3ede10 100644 --- a/crates/nexum-runtime/src/supervisor/tests/lifecycle.rs +++ b/crates/nexum-runtime/src/supervisor/tests/lifecycle.rs @@ -383,18 +383,18 @@ async fn poison_pill_quarantines_module_after_threshold() { struct ScriptedKind(Arc); #[async_trait::async_trait] -impl ProviderKind for ScriptedKind { +impl ProviderKind for ScriptedKind { fn kind(&self) -> &'static str { "scripted-adapter" } - fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { + fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { Ok(()) } async fn install( &self, - _instance: ProviderInstance<'_, TestTypes>, + _instance: ProviderInstance<'_, CoreRuntime>, _service: &Arc, ) -> anyhow::Result { Ok(if self.0.load(Ordering::SeqCst) { @@ -410,7 +410,7 @@ impl HostService for ScriptedService {} struct ScriptedExtension(Arc); -impl Extension for ScriptedExtension { +impl Extension for ScriptedExtension { fn namespace(&self) -> &'static str { "scripted" } @@ -422,7 +422,7 @@ impl Extension for ScriptedExtension { } } - fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { + fn link(&self, _linker: &mut Linker>) -> anyhow::Result<()> { Ok(()) } @@ -430,16 +430,16 @@ impl Extension for ScriptedExtension { Some(Arc::new(ScriptedService)) } - fn provider(&self) -> Option>> { + fn provider(&self) -> Option>> { Some(Box::new(ScriptedKind(self.0.clone()))) } } /// The shared wiring the sweep reinstalls through, with the scripted kind /// registered. The `TempDir` keeps the local store alive. -fn scripted_shared(live: &Arc) -> (tempfile::TempDir, Shared) { +fn scripted_shared(live: &Arc) -> (tempfile::TempDir, Shared) { let (dir, store) = temp_local_store(); - let extensions: Vec>> = + let extensions: Vec>> = vec![Arc::new(ScriptedExtension(live.clone()))]; let services = HostServices::from_extensions(&extensions).expect("services"); let kinds = diff --git a/crates/nexum-runtime/src/supervisor/tests/mod.rs b/crates/nexum-runtime/src/supervisor/tests/mod.rs index fe36f76..b3147fd 100644 --- a/crates/nexum-runtime/src/supervisor/tests/mod.rs +++ b/crates/nexum-runtime/src/supervisor/tests/mod.rs @@ -10,15 +10,36 @@ mod ledger; mod lifecycle; use std::path::{Path, PathBuf}; +use std::time::Duration; +use alloy_chains::Chain; +use tracing_core::Level; + +use super::admission::enforce_extension_sections; +use super::artifact::read_verified_component; +use super::cursors::{ + chainlog_cursor_key, commit_chain_log_cursor, progress_key, read_chain_log_cursor, +}; +use super::dispatch::with_dispatch_deadline; +use super::prepass::{NamespaceLedger, claim_namespace, unconfigured_chain}; +use super::store::resolve_module_limits; +use super::subscriptions::build_alloy_filter; use super::*; +use crate::bindings::nexum; +use crate::digest::{ContentDigest, DigestMismatch}; use crate::engine_config::ModuleLimits; -use crate::manifest::ResourceSection; +use crate::host::extension::{HostService, Installed, ProviderInstance, ProviderKind}; +use crate::host::logs::LogSource; +use crate::host::provider_pool::ProviderPool; +use crate::manifest::{self, CapabilityRegistry, ResourceSection}; +use crate::preset::CoreRuntime; use crate::test_utils::{ BootScenario, Entry, ManifestSource, Refusal, TestManifest, example_wasm_or_skip, mock_components, module_wasm_or_skip, test_wasmtime_engine, }; +type DefaultSupervisor = Supervisor; + const SEPOLIA: u64 = 11_155_111; /// Path to a manifest checked into the workspace tree. @@ -26,16 +47,17 @@ fn workspace_manifest(relative: &str) -> PathBuf { crate::test_utils::wasm::workspace_root().join(relative) } -fn core_extensions() -> Vec>> { +fn core_extensions() -> Vec>> { Vec::new() } -fn make_linker(engine: &wasmtime::Engine) -> Linker> { - crate::supervisor::build_linker::(engine, &core_extensions()).expect("build_linker") +fn make_linker(engine: &wasmtime::Engine) -> Linker> { + crate::supervisor::build_linker::(engine, &core_extensions()) + .expect("build_linker") } /// An empty chain pool, an empty extension slot, and the given store. -fn test_components(store: crate::host::local_store_redb::LocalStore) -> Components { +fn test_components(store: crate::host::local_store_redb::LocalStore) -> Components { Components { chain: ProviderPool::empty(), store, From 4976b2076355cb028f77612d25840223880f10fd Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:46:36 +0000 Subject: [PATCH 5/9] chore(test-utils): delete From impls without a call site Entry keeps TestManifest/String/PathBuf conversions and ManifestSource keeps TestManifest/String/PathBuf; the by-reference and pass-through variants convert nowhere in the tree. AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/src/test_utils/manifest.rs | 12 ------------ crates/nexum-runtime/src/test_utils/scenario.rs | 12 ------------ 2 files changed, 24 deletions(-) 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 93f43ef..258c7e2 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 28b72da165caf3024cd588265c7da940296aa9b2 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:47:20 +0000 Subject: [PATCH 6/9] chore(runtime): drop the unconsumed ModuleId AsRef derive as_str and Borrow cover every string read; nothing calls as_ref. AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/Cargo.toml | 2 +- crates/nexum-runtime/src/module_id.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/nexum-runtime/Cargo.toml b/crates/nexum-runtime/Cargo.toml index 5f26565..0d51d17 100644 --- a/crates/nexum-runtime/Cargo.toml +++ b/crates/nexum-runtime/Cargo.toml @@ -23,7 +23,7 @@ wasmtime-wasi-http.workspace = true anyhow.workspace = true thiserror.workspace = true async-trait.workspace = true -# Newtype boilerplate (`Display`, `AsRef`, `From`) for identity wrappers. +# Newtype boilerplate (`Display`, `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; diff --git a/crates/nexum-runtime/src/module_id.rs b/crates/nexum-runtime/src/module_id.rs index 4185310..c5893bf 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, Ord, PartialEq, PartialOrd)] #[from(forward)] pub struct ModuleId(Arc); From d8b32e5039c9fc5762cea510f4e3af39be66c213 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:47:52 +0000 Subject: [PATCH 7/9] docs(runtime): describe the sealing markers as semver-evolution reserves AI Assistance: Fable 5 used for cruft analysis and implementation --- crates/nexum-runtime/src/host/component/runtime_types.rs | 3 ++- crates/nexum-runtime/src/lib.rs | 2 +- crates/nexum-runtime/src/preset.rs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/nexum-runtime/src/host/component/runtime_types.rs b/crates/nexum-runtime/src/host/component/runtime_types.rs index ef7b97a..015af42 100644 --- a/crates/nexum-runtime/src/host/component/runtime_types.rs +++ b/crates/nexum-runtime/src/host/component/runtime_types.rs @@ -5,7 +5,8 @@ use crate::host::component::StateStore; /// Core backend seams a runtime assembly provides, plus the extension slot -/// ([`Ext`](RuntimeTypes::Ext)). Sealed. The chain backend is not a seam. +/// ([`Ext`](RuntimeTypes::Ext)). 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; diff --git a/crates/nexum-runtime/src/lib.rs b/crates/nexum-runtime/src/lib.rs index 3f57075..11bbab2 100644 --- a/crates/nexum-runtime/src/lib.rs +++ b/crates/nexum-runtime/src/lib.rs @@ -15,7 +15,7 @@ use alloy_rpc_client as _; use alloy_transport as _; use alloy_transport_ws as _; -/// Sealing markers for [`preset::Runtime`] and +/// Markers reserved for semver evolution of [`preset::Runtime`] and /// [`host::component::RuntimeTypes`]: implement alongside the trait. #[doc(hidden)] pub mod sealed { diff --git a/crates/nexum-runtime/src/preset.rs b/crates/nexum-runtime/src/preset.rs index 289e14a..bbea422 100644 --- a/crates/nexum-runtime/src/preset.rs +++ b/crates/nexum-runtime/src/preset.rs @@ -22,7 +22,8 @@ use crate::host::provider_pool::ProviderPool; /// A bundled runtime assembly: the [`RuntimeTypes`] lattice plus the component /// builders, extensions, and add-ons the launcher needs. /// -/// Sealed: a preset opts in by also implementing the sealing marker. +/// The marker bound is reserved for semver evolution: a preset opts in by +/// also implementing it. pub trait Runtime: crate::sealed::SealedRuntime { /// The lattice the preset assembles. type Types: RuntimeTypes; From 24c09a56f6bb32cf42e1edbffe66dde70e5e2df9 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 05:49:08 +0000 Subject: [PATCH 8/9] refactor(runtime): share the retry bump and sleep behind backoff_pause The five event_loop retry sites keep their own log lines and fields; only the attempt bump, backoff computation, and sleep converge. AI Assistance: Fable 5 used for cruft analysis and implementation --- .../nexum-runtime/src/runtime/event_loop.rs | 113 +++++++++--------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/crates/nexum-runtime/src/runtime/event_loop.rs b/crates/nexum-runtime/src/runtime/event_loop.rs index 3f231f5..0649122 100644 --- a/crates/nexum-runtime/src/runtime/event_loop.rs +++ b/crates/nexum-runtime/src/runtime/event_loop.rs @@ -104,6 +104,15 @@ fn receiver_stream( }) } +/// Bumps `attempt`, hands `(attempt, backoff_ms)` to the site's log line, and +/// sleeps the backoff. +async fn backoff_pause(attempt: &mut u32, log: impl FnOnce(u32, u64)) { + *attempt = attempt.saturating_add(1); + let backoff = backoff_for(*attempt); + log(*attempt, backoff.as_millis() as u64); + tokio::time::sleep(backoff).await; +} + /// Reconnect-aware loop for one chain's block subscription: re-opens the /// stream with exponential backoff after every drop or error. async fn reconnecting_block_task( @@ -167,21 +176,18 @@ async fn reconnecting_block_task( } } warn!(chain_id, "block stream ended (WebSocket dropped?)"); - attempt = attempt.saturating_add(1); } Err(err) => { warn!(chain_id, error = %err, "block subscription failed"); - attempt = attempt.saturating_add(1); } } - let backoff = backoff_for(attempt); - warn!( - chain_id, - attempt, - backoff_ms = backoff.as_millis() as u64, - "reconnecting block subscription after backoff", - ); - tokio::time::sleep(backoff).await; + backoff_pause(&mut attempt, |attempt, backoff_ms| { + warn!( + chain_id, + attempt, backoff_ms, "reconnecting block subscription after backoff", + ); + }) + .await; } } @@ -230,34 +236,34 @@ async fn reconnecting_chain_log_task( let provider = match pool.provider(chain) { Ok(provider) => provider, Err(err) => { - attempt = attempt.saturating_add(1); - let backoff = backoff_for(attempt); - warn!( - module = %module, - chain_id, - error = %err, - attempt, - backoff_ms = backoff.as_millis() as u64, - "chain-log provider lookup failed - retrying after backoff", - ); - tokio::time::sleep(backoff).await; + backoff_pause(&mut attempt, |attempt, backoff_ms| { + warn!( + module = %module, + chain_id, + error = %err, + attempt, + backoff_ms, + "chain-log provider lookup failed - retrying after backoff", + ); + }) + .await; continue; } }; let head = match provider.get_block_number().await { Ok(head) => head, Err(err) => { - attempt = attempt.saturating_add(1); - let backoff = backoff_for(attempt); - warn!( - module = %module, - chain_id, - error = %err, - attempt, - backoff_ms = backoff.as_millis() as u64, - "chain-log head fetch failed - retrying after backoff", - ); - tokio::time::sleep(backoff).await; + backoff_pause(&mut attempt, |attempt, backoff_ms| { + warn!( + module = %module, + chain_id, + error = %err, + attempt, + backoff_ms, + "chain-log head fetch failed - retrying after backoff", + ); + }) + .await; continue; } }; @@ -268,17 +274,17 @@ async fn reconnecting_chain_log_task( Ok(Some(block)) if block.header.hash == t.hash => {} Ok(Some(_)) => invalidated_tail = Some(t.number), Ok(None) | Err(_) => { - attempt = attempt.saturating_add(1); - let backoff = backoff_for(attempt); - warn!( - module = %module, - chain_id, - tail_block = t.number, - attempt, - backoff_ms = backoff.as_millis() as u64, - "chain-log tail hash unconfirmed - retrying after backoff", - ); - tokio::time::sleep(backoff).await; + backoff_pause(&mut attempt, |attempt, backoff_ms| { + warn!( + module = %module, + chain_id, + tail_block = t.number, + attempt, + backoff_ms, + "chain-log tail hash unconfirmed - retrying after backoff", + ); + }) + .await; continue; } } @@ -413,7 +419,6 @@ async fn reconnecting_chain_log_task( } } warn!(module = %module, chain_id, "chain-log poller stream ended - reopening"); - attempt = attempt.saturating_add(1); } Err(err) => { warn!( @@ -422,18 +427,18 @@ async fn reconnecting_chain_log_task( error = %err, "chain-log poller open failed" ); - attempt = attempt.saturating_add(1); } } - let backoff = backoff_for(attempt); - warn!( - module = %module, - chain_id, - attempt, - backoff_ms = backoff.as_millis() as u64, - "reconnecting chain-log poller after backoff", - ); - tokio::time::sleep(backoff).await; + backoff_pause(&mut attempt, |attempt, backoff_ms| { + warn!( + module = %module, + chain_id, + attempt, + backoff_ms, + "reconnecting chain-log poller after backoff", + ); + }) + .await; } } From b6a38ca1115ce22f1715c3648b68bfb1b0ff0c07 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 06:07:58 +0000 Subject: [PATCH 9/9] revert: restore the downstream-visible ModuleId and test-utils conversions AsRef on ModuleId and the by-reference and pass-through From impls on Entry and ManifestSource are published API, so they stay until the downstream consumer audit clears them. AI Assistance: Opus 5 used for red-team review and the restore --- crates/nexum-runtime/Cargo.toml | 2 +- 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 ++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/nexum-runtime/Cargo.toml b/crates/nexum-runtime/Cargo.toml index 0d51d17..5f26565 100644 --- a/crates/nexum-runtime/Cargo.toml +++ b/crates/nexum-runtime/Cargo.toml @@ -23,7 +23,7 @@ wasmtime-wasi-http.workspace = true anyhow.workspace = true thiserror.workspace = true async-trait.workspace = true -# Newtype boilerplate (`Display`, `From`) for identity wrappers. +# 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; diff --git a/crates/nexum-runtime/src/module_id.rs b/crates/nexum-runtime/src/module_id.rs index c5893bf..4185310 100644 --- a/crates/nexum-runtime/src/module_id.rs +++ b/crates/nexum-runtime/src/module_id.rs @@ -3,11 +3,12 @@ use std::borrow::Borrow; use std::sync::Arc; -use derive_more::{Display, From}; +use derive_more::{AsRef, Display, From}; /// The manifest namespace. `Arc`-backed so dispatch-path clones are /// refcount bumps; `Display` is the bare namespace. -#[derive(Clone, Debug, Display, Eq, From, Hash, Ord, PartialEq, PartialOrd)] +#[derive(AsRef, Clone, Debug, Display, Eq, From, Hash, Ord, PartialEq, PartialOrd)] +#[as_ref(str)] #[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 c4861e0..f247467 100644 --- a/crates/nexum-runtime/src/test_utils/manifest.rs +++ b/crates/nexum-runtime/src/test_utils/manifest.rs @@ -32,6 +32,12 @@ 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) @@ -44,6 +50,12 @@ 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 258c7e2..93f43ef 100644 --- a/crates/nexum-runtime/src/test_utils/scenario.rs +++ b/crates/nexum-runtime/src/test_utils/scenario.rs @@ -58,6 +58,12 @@ 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) @@ -76,6 +82,12 @@ 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,