From 0abc29798cf7450cc5faa0e9659eb6bcc98fd84f Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 10:51:02 +0000 Subject: [PATCH 1/2] refactor(world): derive the capability and fault vocabulary end to end AI Assistance: Claude Fable 5 used for implementation and tests --- crates/nexum-world/src/lib.rs | 77 +++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/crates/nexum-world/src/lib.rs b/crates/nexum-world/src/lib.rs index 5ee94c7..8e92363 100644 --- a/crates/nexum-world/src/lib.rs +++ b/crates/nexum-world/src/lib.rs @@ -10,11 +10,13 @@ //! ([`manifest_extensions`]) and are passed to [`synthesize`]. use std::path::{Path, PathBuf}; -use strum::{EnumString, IntoStaticStr, VariantNames}; +use strum::{Display, EnumString, IntoStaticStr, VariantNames}; /// A core capability name; the single source [`CORE`] and the runtime's /// capability registry emit from. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, EnumString, VariantNames)] +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Hash, Display, EnumString, IntoStaticStr, VariantNames, +)] #[strum(serialize_all = "kebab-case")] #[non_exhaustive] pub enum Cap { @@ -35,24 +37,18 @@ pub enum Cap { } impl Cap { - /// The declared name, as a manifest spells it. Const so [`CORE`] and - /// [`CORE_IFACES`] can evaluate it. + /// The declared name; const (via the derived [`VariantNames`], which + /// is in declaration order) so const consumers can evaluate it. pub const fn as_str(self) -> &'static str { - match self { - Self::Chain => "chain", - Self::Identity => "identity", - Self::LocalStore => "local-store", - Self::RemoteStore => "remote-store", - Self::Messaging => "messaging", - Self::Logging => "logging", - Self::Http => "http", - } + Self::VARIANTS[self as usize] } } /// A `nexum:host/types.fault` case as a stable snake_case label, in WIT /// declaration order; the single source every label mirror emits from. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, EnumString, IntoStaticStr, VariantNames)] +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Hash, Display, EnumString, IntoStaticStr, VariantNames, +)] #[strum(serialize_all = "snake_case")] #[non_exhaustive] pub enum FaultLabel { @@ -397,7 +393,7 @@ pub fn find_extensions_manifest(start: &Path) -> Option { /// or another registration is an error. pub fn synthesize(declared: &[String], extensions: &[ExtensionRow]) -> Result { for (idx, ext) in extensions.iter().enumerate() { - if CORE.iter().any(|c| c.name.as_str() == ext.name) + if ext.name.parse::().is_ok() || extensions[..idx].iter().any(|prior| prior.name == ext.name) { return Err(format!( @@ -408,18 +404,23 @@ pub fn synthesize(declared: &[String], extensions: &[ExtensionRow]) -> Result>().join(", "); - return Err(format!( - "unknown capability `{name}` in module.toml [capabilities]; expected one of: \ - {names}" - )); + match name.parse::() { + Ok(cap) => caps.push(cap), + Err(_) if extensions.iter().any(|e| &e.name == name) => {} + Err(_) => { + let names = Cap::VARIANTS + .iter() + .copied() + .chain(extensions.iter().map(|e| e.name.as_str())) + .collect::>() + .join(", "); + return Err(format!( + "unknown capability `{name}` in module.toml [capabilities]; expected one of: \ + {names}" + )); + } } } @@ -432,7 +433,7 @@ pub fn synthesize(declared: &[String], extensions: &[ExtensionRow]) -> Result = CORE.iter().map(|c| c.name.as_str()).collect(); assert_eq!(names, Cap::VARIANTS); for name in Cap::VARIANTS { - assert_eq!(name.parse::().unwrap().as_str(), *name); + let cap = name.parse::().unwrap(); + assert_eq!(cap.as_str(), *name); + assert_eq!(<&'static str>::from(cap), *name); + assert_eq!(cap.to_string(), *name); } } @@ -652,6 +656,7 @@ mod tests { for label in FaultLabel::VARIANTS { let parsed: FaultLabel = label.parse().unwrap(); assert_eq!(<&'static str>::from(parsed), *label); + assert_eq!(parsed.to_string(), *label); } assert!("nonesuch".parse::().is_err()); } @@ -698,14 +703,14 @@ mod tests { #[test] fn http_declares_no_world_import() { - let world = synthesize(&["logging".to_string(), "http".to_string()], &[]).unwrap(); + let world = synthesize(&[Cap::Logging.to_string(), Cap::Http.to_string()], &[]).unwrap(); assert!(!world.wit.contains("wasi:http")); assert_eq!(world.packages, MODULE_PACKAGES); } #[test] fn duplicate_declarations_emit_one_import() { - let world = synthesize(&["chain".to_string(), "chain".to_string()], &[]).unwrap(); + let world = synthesize(&[Cap::Chain.to_string(), Cap::Chain.to_string()], &[]).unwrap(); assert_eq!(world.wit.matches("import nexum:host/chain").count(), 1); assert_eq!(world.adapters, vec!["chain"]); } From 84aa22845bdacd0af90e7788fe9bc1abd7b4e45e Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 6 Aug 2026 11:03:57 +0000 Subject: [PATCH 2/2] test(world): pin the operator-facing unknown-capability wording AI Assistance: Claude Opus 5 used for red-team review and fixes --- crates/nexum-runtime/src/manifest/load.rs | 7 +++++++ crates/nexum-world/src/lib.rs | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/nexum-runtime/src/manifest/load.rs b/crates/nexum-runtime/src/manifest/load.rs index a43aa3b..5350317 100644 --- a/crates/nexum-runtime/src/manifest/load.rs +++ b/crates/nexum-runtime/src/manifest/load.rs @@ -286,6 +286,13 @@ required = ["chain", "not-a-real-cap"] assert!( matches!(err, ParseError::UnknownCapability { ref name, .. } if name == "not-a-real-cap") ); + // Operator-facing wording and order, pinned verbatim. + assert_eq!( + err.to_string(), + "manifest: unknown capability \"not-a-real-cap\" in [capabilities] (known: chain, \ + identity, local-store, remote-store, messaging, logging, http, wasi-sockets, \ + wasi-filesystem)" + ); } #[test] diff --git a/crates/nexum-world/src/lib.rs b/crates/nexum-world/src/lib.rs index 8e92363..78d543b 100644 --- a/crates/nexum-world/src/lib.rs +++ b/crates/nexum-world/src/lib.rs @@ -37,8 +37,7 @@ pub enum Cap { } impl Cap { - /// The declared name; const (via the derived [`VariantNames`], which - /// is in declaration order) so const consumers can evaluate it. + /// The declared name; the discriminant indexes `VARIANTS`, so this is const. pub const fn as_str(self) -> &'static str { Self::VARIANTS[self as usize] } @@ -718,9 +717,12 @@ mod tests { #[test] fn unknown_capability_is_rejected_with_the_known_list() { let err = synthesize(&["telepathy".to_string()], &ext()).unwrap_err(); - assert!(err.contains("unknown capability `telepathy`")); - assert!(err.contains("logging")); - assert!(err.contains("acme")); + // Operator-facing wording and order, pinned verbatim. + assert_eq!( + err, + "unknown capability `telepathy` in module.toml [capabilities]; expected one of: \ + chain, identity, local-store, remote-store, messaging, logging, http, acme" + ); } #[test] @@ -817,7 +819,7 @@ allow = [] fn world_is_valid_wit_shape() { // Not a full WIT parse (that is the module build's job); pin the // structural pieces the runtime contract depends on. - let world = synthesize(&["logging".to_string()], &[]).unwrap(); + let world = synthesize(&[Cap::Logging.to_string()], &[]).unwrap(); assert!(world.wit.starts_with("package nexum:module-world;")); assert!(world.wit.contains("world module {")); assert!(