feat(console): expose component capabilities

- project structured capability package, name, version, and interface fields
- derive capabilities during registration and artifact restoration
- keep Component imports as the source of truth without changing .wasmpkg
- verify empty and Clock capability responses through the management API
This commit is contained in:
Maofeng
2026-07-29 19:13:41 +08:00
parent 5b32886efe
commit 72a264e0d1
2 changed files with 83 additions and 3 deletions
+44 -3
View File
@@ -39,7 +39,8 @@ use wasmeld_package::{
wit_package::{WitPackageError, WitPackageMetadata},
};
use wasmeld_runtime::{
ResourceLimits, Runtime, RuntimeConfig, RuntimeError, ServiceKey, ServiceManifest,
CapabilityDescriptor, ResourceLimits, Runtime, RuntimeConfig, RuntimeError, ServiceKey,
ServiceManifest,
};
use persistence::{Persistence, StoredDeployment, StoredEvent, StoredService};
@@ -113,6 +114,7 @@ struct ConsoleState {
#[derive(Clone)]
struct ServiceRecord {
manifest: ServiceManifest,
capabilities: Vec<CapabilityView>,
status: ServiceStatus,
updated_at_ms: u64,
calls: u64,
@@ -144,6 +146,26 @@ pub struct DeploymentView {
pub updated_at_ms: u64,
}
/// Exact versioned host interface imported by a registered Component.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CapabilityView {
pub interface: String,
pub package: String,
pub name: String,
pub version: String,
}
impl From<CapabilityDescriptor> for CapabilityView {
fn from(capability: CapabilityDescriptor) -> Self {
Self {
interface: capability.interface().to_owned(),
package: capability.package().to_owned(),
name: capability.name().to_owned(),
version: capability.version().to_owned(),
}
}
}
/// API projection of a registered service and its current metrics.
#[derive(Clone, Debug, Serialize)]
pub struct ServiceView {
@@ -151,6 +173,7 @@ pub struct ServiceView {
pub revision: String,
pub artifact: String,
pub world: String,
pub capabilities: Vec<CapabilityView>,
pub status: ServiceStatus,
pub updated_at_ms: u64,
pub limits: ResourceLimits,
@@ -166,6 +189,7 @@ impl ServiceRecord {
revision: self.manifest.revision.clone(),
artifact: self.manifest.component.clone(),
world: self.manifest.world.clone(),
capabilities: self.capabilities.clone(),
status: self.status,
updated_at_ms: self.updated_at_ms,
limits: self.manifest.limits.clone(),
@@ -743,8 +767,9 @@ impl Console {
let _ = fs::remove_file(&manifest_path);
return Err(error.into());
}
let capabilities = runtime_capabilities(runtime, &key)?;
let view = self.insert_service(manifest, ServiceStatus::Stopped)?;
let view = self.insert_service(manifest, capabilities, ServiceStatus::Stopped)?;
self.push_event(
EventKind::Registered,
Some(&key),
@@ -910,10 +935,12 @@ impl Console {
)));
}
runtime.register_from_file(manifest.clone())?;
let capabilities = runtime_capabilities(runtime, &key)?;
self.state()?.services.insert(
key,
ServiceRecord {
manifest,
capabilities,
status: ServiceStatus::Stopped,
updated_at_ms: stored.updated_at_ms,
calls: stored.calls,
@@ -956,7 +983,8 @@ impl Console {
continue;
}
runtime.register_from_file(manifest.clone())?;
self.insert_service(manifest, ServiceStatus::Stopped)?;
let capabilities = runtime_capabilities(runtime, &key)?;
self.insert_service(manifest, capabilities, ServiceStatus::Stopped)?;
}
Ok(())
@@ -1070,12 +1098,14 @@ impl Console {
fn insert_service(
&self,
manifest: ServiceManifest,
capabilities: Vec<CapabilityView>,
status: ServiceStatus,
) -> Result<ServiceView, ConsoleError> {
let key = manifest.key()?;
let mut state = self.state()?;
let record = ServiceRecord {
manifest,
capabilities,
status,
updated_at_ms: unix_time_ms(),
calls: 0,
@@ -1140,6 +1170,17 @@ impl Console {
}
}
fn runtime_capabilities(
runtime: &Runtime,
key: &ServiceKey,
) -> Result<Vec<CapabilityView>, ConsoleError> {
Ok(runtime
.capabilities(key)?
.into_iter()
.map(Into::into)
.collect())
}
fn deployment_view(
state: &ConsoleState,
service_id: &str,