lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 9e4ab3c50a4bcf5ba5f954017f5e6731a32e14fb
parent 58fa0ada8e0bb003cbc86e89a3b6ec1afebaeb38
Author: triesap <tyson@radroots.org>
Date:   Thu, 30 Jul 2026 16:50:07 +0000

transport: define capability, maturity, availability, and status

- separate compiled source and sink operations from runtime configuration
- model experimental, preview, and stable maturity independently of availability
- preserve extensible transport identities through protocol descriptor conversion
- verify status combinations, serialization, native no-std, wasm, and workspace gates

Diffstat:
Mcontracts/codegen/protocol_v1.inventory.json | 2+-
Mcontracts/codegen/protocol_v1.inventory.sha256 | 2+-
Mcrates/protocol/src/capability/v1.rs | 2++
Mcrates/transport/src/capability.rs | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/transport/src/kind.rs | 19++++---------------
Mcrates/transport/src/sink.rs | 9++-------
Mcrates/transport/src/source.rs | 12+++---------
Mcrates/transport/src/status.rs | 200++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Acrates/transport/tests/capability_status.rs | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/transport/tests/spi.rs | 44+++++++++++++++++++++-----------------------
Mdocs/api/radroots_protocol.txt | 1+
11 files changed, 554 insertions(+), 57 deletions(-)

diff --git a/contracts/codegen/protocol_v1.inventory.json b/contracts/codegen/protocol_v1.inventory.json @@ -7,7 +7,7 @@ { "module": "capability::v1", "path": "crates/protocol/src/capability/v1.rs", - "sha256": "416f3ae2a5f97c46b56b0c24095f6f6813d2851b7856c84cfbdc251bdd30a846", + "sha256": "ec6ee0f70283ca6ac9ef2954ae557181810b685e0b09eb7af27ce58e63e3627a", "types": [ { "rust_path": "radroots_protocol::capability::v1::Availability", diff --git a/contracts/codegen/protocol_v1.inventory.sha256 b/contracts/codegen/protocol_v1.inventory.sha256 @@ -1 +1 @@ -6f9eab5185f426fed8a9e2fab514dbf19a4eecd9fdff6756db60634396023b29 +7b9f51e93a819f2d345006dc7ea6f4d9be3be3ab322d0eb791886cdee084d019 diff --git a/crates/protocol/src/capability/v1.rs b/crates/protocol/src/capability/v1.rs @@ -112,6 +112,8 @@ impl<'de> serde::Deserialize<'de> for TransportKind { #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Maturity { + /// Supported only as an experimental contract. + Experimental, /// Supported as a preview contract. Preview, /// Supported as a stable contract. diff --git a/crates/transport/src/capability.rs b/crates/transport/src/capability.rs @@ -1 +1,181 @@ //! Transport-neutral capability vocabulary. + +use radroots_protocol::capability::v1 as protocol; + +/// Product maturity of a transport capability. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Maturity { + /// Contract may change without compatibility guarantees. + Experimental, + /// Contract is available for preview use. + Preview, + /// Contract is supported as stable. + Stable, +} + +/// Current runtime availability of a transport capability. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Availability { + /// Fully available. + Available, + /// Available with reduced functionality. + Degraded, + /// Not currently available. + Unavailable, +} + +impl From<protocol::Maturity> for Maturity { + fn from(value: protocol::Maturity) -> Self { + match value { + protocol::Maturity::Experimental => Self::Experimental, + protocol::Maturity::Preview => Self::Preview, + protocol::Maturity::Stable => Self::Stable, + } + } +} + +impl From<Maturity> for protocol::Maturity { + fn from(value: Maturity) -> Self { + match value { + Maturity::Experimental => Self::Experimental, + Maturity::Preview => Self::Preview, + Maturity::Stable => Self::Stable, + } + } +} + +impl From<protocol::Availability> for Availability { + fn from(value: protocol::Availability) -> Self { + match value { + protocol::Availability::Available => Self::Available, + protocol::Availability::Degraded => Self::Degraded, + protocol::Availability::Unavailable => Self::Unavailable, + } + } +} + +impl From<Availability> for protocol::Availability { + fn from(value: Availability) -> Self { + match value { + Availability::Available => Self::Available, + Availability::Degraded => Self::Degraded, + Availability::Unavailable => Self::Unavailable, + } + } +} + +/// Operations supported by an event source. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct SourceCapabilities { + fetch: bool, + discovery: bool, +} + +impl SourceCapabilities { + /// No source operations are supported. + pub const NONE: Self = Self { + fetch: false, + discovery: false, + }; + + /// Bounded fetch is supported. + pub const FETCH: Self = Self { + fetch: true, + discovery: false, + }; + + /// Sets discovery support. + #[must_use] + pub const fn with_discovery(mut self, discovery: bool) -> Self { + self.discovery = discovery; + self + } + + /// Whether bounded fetch is supported. + pub const fn can_fetch(self) -> bool { + self.fetch + } + + /// Whether target discovery is supported. + pub const fn can_discover(self) -> bool { + self.discovery + } +} + +impl From<&protocol::TransportDescriptor> for SourceCapabilities { + fn from(value: &protocol::TransportDescriptor) -> Self { + Self { + fetch: value.can_fetch, + discovery: value.can_discover, + } + } +} + +/// Operations supported by an event sink. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct SinkCapabilities { + deliver: bool, + gateway_forwarding: bool, + receipt_observation: bool, +} + +impl SinkCapabilities { + /// No sink operations are supported. + pub const NONE: Self = Self { + deliver: false, + gateway_forwarding: false, + receipt_observation: false, + }; + + /// Delivery is supported. + pub const DELIVER: Self = Self { + deliver: true, + gateway_forwarding: false, + receipt_observation: false, + }; + + /// Sets gateway-forwarding support. + #[must_use] + pub const fn with_gateway_forwarding(mut self, supported: bool) -> Self { + self.gateway_forwarding = supported; + self + } + + /// Sets receipt-observation support. + #[must_use] + pub const fn with_receipt_observation(mut self, supported: bool) -> Self { + self.receipt_observation = supported; + self + } + + /// Whether delivery is supported. + pub const fn can_deliver(self) -> bool { + self.deliver + } + + /// Whether gateway forwarding is supported. + pub const fn can_gateway_forward(self) -> bool { + self.gateway_forwarding + } + + /// Whether delivery receipt observation is supported. + pub const fn can_observe_receipts(self) -> bool { + self.receipt_observation + } +} + +impl From<&protocol::TransportDescriptor> for SinkCapabilities { + fn from(value: &protocol::TransportDescriptor) -> Self { + Self { + deliver: value.can_deliver, + gateway_forwarding: value.can_gateway_forward, + receipt_observation: value.can_observe_receipts, + } + } +} diff --git a/crates/transport/src/kind.rs b/crates/transport/src/kind.rs @@ -6,19 +6,8 @@ pub enum RadrootsTransportImplementationState { Mock, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum RadrootsTransportCapabilityMaturity { - Preview, - Stable, -} +#[doc(hidden)] +pub type RadrootsTransportCapabilityMaturity = crate::capability::Maturity; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum RadrootsTransportCapabilityAvailability { - Available, - Degraded, - Unavailable, -} +#[doc(hidden)] +pub type RadrootsTransportCapabilityAvailability = crate::capability::Availability; diff --git a/crates/transport/src/sink.rs b/crates/transport/src/sink.rs @@ -1,15 +1,10 @@ //! Outbound event delivery SPI and request models. use crate::{ - Error, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest, - RadrootsTransportStatus, source::BoxFuture, + Error, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest, source::BoxFuture, }; -/// Current sink status. -/// -/// This compatibility alias is replaced by the dedicated sink status model in -/// the ordered capability/status checkpoint. -pub type SinkStatus = RadrootsTransportStatus; +pub use crate::status::SinkStatus; /// Bounded delivery request. /// diff --git a/crates/transport/src/source.rs b/crates/transport/src/source.rs @@ -1,20 +1,14 @@ //! Inbound event source SPI and page models. -use crate::{ - Error, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest, RadrootsTransportStatus, -}; +use crate::{Error, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest}; use alloc::boxed::Box; use core::{future::Future, pin::Pin}; +pub use crate::status::SourceStatus; + /// Heap-backed future returned by transport SPIs. pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; -/// Current source status. -/// -/// This compatibility alias is replaced by the dedicated source status model -/// in the ordered capability/status checkpoint. -pub type SourceStatus = RadrootsTransportStatus; - /// Bounded source request. /// /// The dedicated bounded-page checkpoint replaces this compatibility alias diff --git a/crates/transport/src/status.rs b/crates/transport/src/status.rs @@ -1,9 +1,207 @@ use crate::{ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity, - RadrootsTransportImplementationState, RadrootsTransportKind, + RadrootsTransportImplementationState, RadrootsTransportKind, TransportId, + capability::{Availability, Maturity, SinkCapabilities, SourceCapabilities}, delivery::RadrootsTransportSatisfactionClass, }; use alloc::string::String; +use radroots_protocol::capability::v1::TransportDescriptor; + +/// Runtime state shared by source and sink capability reports. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, Eq, PartialEq)] +struct RuntimeStatus { + transport_id: TransportId, + configured: bool, + maturity: Maturity, + availability: Availability, + message: String, +} + +impl RuntimeStatus { + fn new( + transport_id: TransportId, + configured: bool, + maturity: Maturity, + availability: Availability, + message: impl Into<String>, + ) -> Self { + Self { + transport_id, + configured, + maturity, + availability, + message: message.into(), + } + } + + const fn transport_id(&self) -> TransportId { + self.transport_id + } + + const fn is_configured(&self) -> bool { + self.configured + } + + const fn maturity(&self) -> Maturity { + self.maturity + } + + const fn availability(&self) -> Availability { + self.availability + } + + fn message(&self) -> &str { + self.message.as_str() + } +} + +/// Current state and operations supported by an event source. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SourceStatus { + #[cfg_attr(feature = "serde", serde(flatten))] + status: RuntimeStatus, + capabilities: SourceCapabilities, +} + +impl SourceStatus { + /// Creates a source-specific status report. + pub fn new( + transport_id: TransportId, + configured: bool, + maturity: Maturity, + availability: Availability, + capabilities: SourceCapabilities, + message: impl Into<String>, + ) -> Self { + Self { + status: RuntimeStatus::new(transport_id, configured, maturity, availability, message), + capabilities, + } + } + + /// Builds source status from the versioned passive capability catalog. + pub fn from_descriptor( + descriptor: &TransportDescriptor, + configured: bool, + message: impl Into<String>, + ) -> Self { + Self::new( + descriptor.kind.into(), + configured, + descriptor.maturity.into(), + descriptor.availability.into(), + SourceCapabilities::from(descriptor), + message, + ) + } + + /// Returns the extensible transport identity. + pub const fn transport_id(&self) -> TransportId { + self.status.transport_id() + } + + /// Whether the host has configured this source. + pub const fn is_configured(&self) -> bool { + self.status.is_configured() + } + + /// Returns the source capability's product maturity. + pub const fn maturity(&self) -> Maturity { + self.status.maturity() + } + + /// Returns current source availability. + pub const fn availability(&self) -> Availability { + self.status.availability() + } + + /// Returns the operator-facing status detail. + pub fn message(&self) -> &str { + self.status.message() + } + + /// Returns supported source operations. + pub const fn capabilities(&self) -> SourceCapabilities { + self.capabilities + } +} + +/// Current state and operations supported by an event sink. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SinkStatus { + #[cfg_attr(feature = "serde", serde(flatten))] + status: RuntimeStatus, + capabilities: SinkCapabilities, +} + +impl SinkStatus { + /// Creates a sink-specific status report. + pub fn new( + transport_id: TransportId, + configured: bool, + maturity: Maturity, + availability: Availability, + capabilities: SinkCapabilities, + message: impl Into<String>, + ) -> Self { + Self { + status: RuntimeStatus::new(transport_id, configured, maturity, availability, message), + capabilities, + } + } + + /// Builds sink status from the versioned passive capability catalog. + pub fn from_descriptor( + descriptor: &TransportDescriptor, + configured: bool, + message: impl Into<String>, + ) -> Self { + Self::new( + descriptor.kind.into(), + configured, + descriptor.maturity.into(), + descriptor.availability.into(), + SinkCapabilities::from(descriptor), + message, + ) + } + + /// Returns the extensible transport identity. + pub const fn transport_id(&self) -> TransportId { + self.status.transport_id() + } + + /// Whether the host has configured this sink. + pub const fn is_configured(&self) -> bool { + self.status.is_configured() + } + + /// Returns the sink capability's product maturity. + pub const fn maturity(&self) -> Maturity { + self.status.maturity() + } + + /// Returns current sink availability. + pub const fn availability(&self) -> Availability { + self.status.availability() + } + + /// Returns the operator-facing status detail. + pub fn message(&self) -> &str { + self.status.message() + } + + /// Returns supported sink operations. + pub const fn capabilities(&self) -> SinkCapabilities { + self.capabilities + } +} #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/crates/transport/tests/capability_status.rs b/crates/transport/tests/capability_status.rs @@ -0,0 +1,140 @@ +use radroots_transport::{ + SinkStatus, SourceStatus, TransportId, + capability::{Availability, Maturity, SinkCapabilities, SourceCapabilities}, +}; + +#[test] +fn maturity_and_availability_round_trip_through_protocol_dtos() { + for maturity in [Maturity::Experimental, Maturity::Preview, Maturity::Stable] { + let protocol = radroots_protocol::capability::v1::Maturity::from(maturity); + assert_eq!(Maturity::from(protocol), maturity); + } + + for availability in [ + Availability::Available, + Availability::Degraded, + Availability::Unavailable, + ] { + let protocol = radroots_protocol::capability::v1::Availability::from(availability); + assert_eq!(Availability::from(protocol), availability); + } +} + +#[test] +fn runtime_state_dimensions_are_independent() { + let transport_id = TransportId::parse("future-mesh").expect("custom transport"); + let states = [ + (false, Maturity::Experimental, Availability::Available), + (true, Maturity::Preview, Availability::Degraded), + (true, Maturity::Stable, Availability::Unavailable), + ]; + + for (configured, maturity, availability) in states { + let status = SourceStatus::new( + transport_id, + configured, + maturity, + availability, + SourceCapabilities::NONE, + "explicit state", + ); + assert_eq!(status.transport_id(), transport_id); + assert_eq!(status.is_configured(), configured); + assert_eq!(status.maturity(), maturity); + assert_eq!(status.availability(), availability); + assert_eq!(status.message(), "explicit state"); + } +} + +#[test] +fn source_and_sink_capabilities_are_separate_contracts() { + let source = SourceStatus::new( + TransportId::NOSTR, + true, + Maturity::Stable, + Availability::Available, + SourceCapabilities::FETCH.with_discovery(true), + "source", + ); + assert!(source.capabilities().can_fetch()); + assert!(source.capabilities().can_discover()); + assert_eq!(source.transport_id(), TransportId::NOSTR); + + let sink = SinkStatus::new( + TransportId::NOSTR, + true, + Maturity::Stable, + Availability::Available, + SinkCapabilities::DELIVER + .with_gateway_forwarding(true) + .with_receipt_observation(true), + "sink", + ); + assert!(sink.capabilities().can_deliver()); + assert!(sink.capabilities().can_gateway_forward()); + assert!(sink.capabilities().can_observe_receipts()); + assert_eq!(sink.transport_id(), TransportId::NOSTR); +} + +#[test] +fn protocol_descriptors_preserve_unknown_transports_and_split_capabilities() { + use radroots_protocol::capability::v1::{ + Availability as ProtocolAvailability, Maturity as ProtocolMaturity, TransportDescriptor, + TransportKind, + }; + + let descriptor = TransportDescriptor { + kind: TransportKind::parse("future-mesh").expect("custom protocol transport"), + maturity: ProtocolMaturity::Experimental, + availability: ProtocolAvailability::Degraded, + can_deliver: true, + can_fetch: false, + can_discover: true, + can_gateway_forward: true, + can_observe_receipts: false, + required_for_v1: false, + }; + + let source = SourceStatus::from_descriptor(&descriptor, false, "source not configured"); + assert_eq!(source.transport_id().as_str(), "future-mesh"); + assert!(!source.is_configured()); + assert_eq!(source.maturity(), Maturity::Experimental); + assert_eq!(source.availability(), Availability::Degraded); + assert!(!source.capabilities().can_fetch()); + assert!(source.capabilities().can_discover()); + + let sink = SinkStatus::from_descriptor(&descriptor, true, "sink configured"); + assert_eq!(sink.transport_id().as_str(), "future-mesh"); + assert!(sink.is_configured()); + assert!(sink.capabilities().can_deliver()); + assert!(sink.capabilities().can_gateway_forward()); + assert!(!sink.capabilities().can_observe_receipts()); +} + +#[cfg(feature = "serde")] +#[test] +fn experimental_maturity_has_a_stable_wire_spelling() { + assert_eq!( + serde_json::to_string(&Maturity::Experimental).expect("serialize maturity"), + "\"experimental\"" + ); + assert_eq!( + serde_json::from_str::<Maturity>("\"experimental\"").expect("deserialize maturity"), + Maturity::Experimental + ); + + let status = SourceStatus::new( + TransportId::parse("future-mesh").expect("custom transport"), + true, + Maturity::Experimental, + Availability::Degraded, + SourceCapabilities::FETCH, + "degraded source", + ); + let encoded = serde_json::to_string(&status).expect("serialize source status"); + assert!(encoded.contains("\"transport_id\":\"future-mesh\"")); + assert_eq!( + serde_json::from_str::<SourceStatus>(&encoded).expect("deserialize source status"), + status + ); +} diff --git a/crates/transport/tests/spi.rs b/crates/transport/tests/spi.rs @@ -1,10 +1,10 @@ use futures::executor::block_on; use radroots_transport::{ BoxFuture, DeliveryReceipt, DeliveryRequest, EventSink, EventSource, FetchPage, FetchRequest, - RadrootsTransportCapabilities, RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportOutcome, RadrootsTransportOutcomeKind, RadrootsTransportPayload, - RadrootsTransportSatisfactionPolicy, RadrootsTransportStatus, RadrootsTransportTarget, - RadrootsTransportTargetReceipt, RadrootsTransportTargetSet, SinkStatus, SourceStatus, + RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetReceipt, + RadrootsTransportTargetSet, SinkStatus, SourceStatus, TransportId, + capability::{Availability, Maturity, SinkCapabilities, SourceCapabilities}, }; struct SourceOnly; @@ -82,26 +82,26 @@ impl EventSink for Bidirectional { } } -fn source_status() -> RadrootsTransportStatus { - RadrootsTransportStatus::new( - RadrootsTransportKind::Local, +fn source_status() -> SourceStatus { + SourceStatus::new( + TransportId::LOCAL, true, - RadrootsTransportImplementationState::Real, - false, + Maturity::Stable, + Availability::Available, + SourceCapabilities::FETCH, "source ready", ) - .with_capabilities(RadrootsTransportCapabilities::fetch_only()) } -fn sink_status() -> RadrootsTransportStatus { - RadrootsTransportStatus::new( - RadrootsTransportKind::Local, - true, - RadrootsTransportImplementationState::Real, +fn sink_status() -> SinkStatus { + SinkStatus::new( + TransportId::LOCAL, true, + Maturity::Stable, + Availability::Available, + SinkCapabilities::DELIVER, "sink ready", ) - .with_capabilities(RadrootsTransportCapabilities::deliver_only()) } fn target_set() -> RadrootsTransportTargetSet { @@ -122,15 +122,13 @@ fn source_only_and_sink_only_implementations_are_independently_dispatchable() { assert_sink_dyn_compatible(&sink); let source_status = block_on(EventSource::status(&source)).expect("source status"); - assert!(source_status.capabilities.fetch); - assert!(!source_status.capabilities.deliver); + assert!(source_status.capabilities().can_fetch()); let page = block_on(source.fetch(FetchRequest::new("fetch-1", target_set()))).expect("fetch page"); assert_eq!(page.request_id, "fetch-1"); let sink_status = block_on(EventSink::status(&sink)).expect("sink status"); - assert!(sink_status.capabilities.deliver); - assert!(!sink_status.capabilities.fetch); + assert!(sink_status.capabilities().can_deliver()); let receipt = block_on( sink.deliver( DeliveryRequest::new( @@ -158,13 +156,13 @@ fn a_bidirectional_adapter_exposes_both_dyn_contracts() { assert!( block_on(EventSource::status(&adapter)) .expect("source status") - .capabilities - .fetch + .capabilities() + .can_fetch() ); assert!( block_on(EventSink::status(&adapter)) .expect("sink status") - .capabilities - .deliver + .capabilities() + .can_deliver() ); } diff --git a/docs/api/radroots_protocol.txt b/docs/api/radroots_protocol.txt @@ -19,6 +19,7 @@ impl core::error::Error for radroots_protocol::capability::v1::Error impl core::fmt::Display for radroots_protocol::capability::v1::Error pub fn radroots_protocol::capability::v1::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub enum radroots_protocol::capability::v1::Maturity +pub radroots_protocol::capability::v1::Maturity::Experimental pub radroots_protocol::capability::v1::Maturity::Preview pub radroots_protocol::capability::v1::Maturity::Stable pub struct radroots_protocol::capability::v1::MeshScopeId