lib

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

commit 2ecdd53b3271fbf506c3acc7c47767a64297593b
parent f1fa0799ec46285ebd1419773b28309a285b9b0c
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 00:24:56 +0000

contract: add protocol contract v1 root

- add the protocol contract crate with V1 transport and event catalogs
- model Reticulum as a V1 transport with preview maturity metadata
- wire protocol contract validation into xtask and contract inventories
- validate protocol all-features, focused tests, clippy, and contract checks

Diffstat:
MCargo.lock | 9+++++++++
MCargo.toml | 2++
Mcontracts/coverage.toml | 1+
Mcontracts/manifest.toml | 2++
Mcontracts/operations.toml | 1+
Acrates/protocol_contract_v1/Cargo.toml | 26++++++++++++++++++++++++++
Acrates/protocol_contract_v1/README | 13+++++++++++++
Acrates/protocol_contract_v1/src/lib.rs | 508+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/Cargo.toml | 1+
Mtools/xtask/src/main.rs | 2++
10 files changed, 565 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4557,6 +4557,14 @@ dependencies = [ ] [[package]] +name = "radroots_protocol_contract_v1" +version = "0.1.0-alpha.2" +dependencies = [ + "dto_bindgen", + "serde", +] + +[[package]] name = "radroots_replica_schema" version = "0.1.0-alpha.2" dependencies = [ @@ -8649,6 +8657,7 @@ version = "0.1.0-alpha.2" dependencies = [ "hex", "radroots_event_codec", + "radroots_protocol_contract_v1", "serde", "serde_json", "sha2", diff --git a/Cargo.toml b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/nostrdb", "crates/nostr_runtime", "crates/outbox", + "crates/protocol_contract_v1", "crates/transport_publish_protocol", "crates/transport_nostr", "crates/transport_reticulum", @@ -86,6 +87,7 @@ radroots_log = { path = "crates/log", version = "0.1.0-alpha.2", default-feature radroots_net = { path = "crates/net", version = "0.1.0-alpha.2", default-features = false } radroots_nostr_runtime = { path = "crates/nostr_runtime", version = "0.1.0-alpha.2", default-features = false } radroots_outbox = { path = "crates/outbox", version = "0.1.0-alpha.2", default-features = false } +radroots_protocol_contract_v1 = { path = "crates/protocol_contract_v1", version = "0.1.0-alpha.2", default-features = false } radroots_transport_publish_protocol = { path = "crates/transport_publish_protocol", version = "0.1.0-alpha.2", default-features = false } radroots_transport_nostr = { path = "crates/transport_nostr", version = "0.1.0-alpha.2", default-features = false } radroots_transport_reticulum = { path = "crates/transport_reticulum", version = "0.1.0-alpha.2", default-features = false } diff --git a/contracts/coverage.toml b/contracts/coverage.toml @@ -52,6 +52,7 @@ crates = [ "radroots_nostr_runtime", "radroots_nostr_signer", "radroots_outbox", + "radroots_protocol_contract_v1", "radroots_protected_store", "radroots_transport_publish_protocol", "radroots_transport_nostr", diff --git a/contracts/manifest.toml b/contracts/manifest.toml @@ -5,6 +5,7 @@ source = "rust" [surface] model_crates = [ + "radroots_protocol_contract_v1", "radroots_core", "radroots_event", "radroots_trade", @@ -32,6 +33,7 @@ published_support = [ "radroots_runtime", "radroots_runtime_distribution", "radroots_runtime_manager", + "radroots_protocol_contract_v1", "radroots_geocoder", "radroots_event_index", "radroots_sql_core", diff --git a/contracts/operations.toml b/contracts/operations.toml @@ -74,6 +74,7 @@ classes = [ [implementation_provenance] model_crates = [ + "radroots_protocol_contract_v1", "radroots_core", "radroots_event", "radroots_trade", diff --git a/crates/protocol_contract_v1/Cargo.toml b/crates/protocol_contract_v1/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "radroots_protocol_contract_v1" +publish = ["crates-io"] +version = "0.1.0-alpha.2" +edition.workspace = true +authors = ["Tyson Lupul <tyson@radroots.org>"] +rust-version.workspace = true +license.workspace = true +description = "Protocol Contract V1 descriptors for Radroots" +repository.workspace = true +homepage.workspace = true +documentation = "https://docs.rs/radroots_protocol_contract_v1" +readme = "README" + +[features] +default = ["std", "serde"] +dto-bindgen = ["std", "serde", "dep:dto_bindgen"] +serde = ["dep:serde"] +std = [] + +[dependencies] +dto_bindgen = { workspace = true, optional = true } +serde = { workspace = true, default-features = false, features = [ + "alloc", + "derive", +], optional = true } diff --git a/crates/protocol_contract_v1/README b/crates/protocol_contract_v1/README @@ -0,0 +1,13 @@ +# radroots_protocol_contract_v1 + +This is the README for `radroots_protocol_contract_v1`, which contains the source-owned Protocol Contract V1 descriptors for public Radroots event catalogs and transport capability vocabulary. + +## Copyright + +Except as otherwise noted, all files in the `radroots_protocol_contract_v1` distribution are + + Copyright (c) 2026 Tyson Lupul + +## License + +This repository is licensed under either MIT or Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE. diff --git a/crates/protocol_contract_v1/src/lib.rs b/crates/protocol_contract_v1/src/lib.rs @@ -0,0 +1,508 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(unsafe_code)] + +extern crate alloc; + +use alloc::collections::BTreeSet; +use alloc::string::{String, ToString}; + +pub const PROTOCOL_CONTRACT_NAME_V1: &str = "radroots.protocol"; +pub const PROTOCOL_CONTRACT_VERSION_V1: u16 = 1; + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] +#[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 TransportKindV1 { + #[cfg_attr(feature = "dto-bindgen", dto(rename = "local"))] + Local, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "nostr"))] + Nostr, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "reticulum"))] + Reticulum, +} + +impl TransportKindV1 { + pub fn as_str(self) -> &'static str { + match self { + Self::Local => "local", + Self::Nostr => "nostr", + Self::Reticulum => "reticulum", + } + } + + pub fn parse(value: &str) -> Result<Self, ProtocolContractErrorV1> { + match value { + "local" => Ok(Self::Local), + "nostr" => Ok(Self::Nostr), + "reticulum" => Ok(Self::Reticulum), + "reticulum_preview" | "mesh" | "proxy" | "radrootsd_proxy" | "hybrid" => { + Err(ProtocolContractErrorV1::RetiredTransportIdentity { + identity: value.to_string(), + }) + } + _ => Err(ProtocolContractErrorV1::UnknownTransportKind { + value: value.to_string(), + }), + } + } +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] +#[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 CapabilityMaturityV1 { + #[cfg_attr(feature = "dto-bindgen", dto(rename = "preview"))] + Preview, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "stable"))] + Stable, +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] +#[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 CapabilityAvailabilityV1 { + #[cfg_attr(feature = "dto-bindgen", dto(rename = "available"))] + Available, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "degraded"))] + Degraded, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "unavailable"))] + Unavailable, +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MeshScopeIdV1 { + value: String, +} + +impl MeshScopeIdV1 { + pub fn parse(value: impl Into<String>) -> Result<Self, ProtocolContractErrorV1> { + let value = value.into(); + if value.is_empty() + || value != value.trim() + || value + .chars() + .any(|ch| !(ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.'))) + { + return Err(ProtocolContractErrorV1::InvalidMeshScopeId); + } + Ok(Self { value }) + } + + pub fn as_str(&self) -> &str { + self.value.as_str() + } +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ReticulumDestinationV1 { + canonical: String, +} + +impl ReticulumDestinationV1 { + pub fn parse(value: impl Into<String>) -> Result<Self, ProtocolContractErrorV1> { + let value = value.into(); + if value.is_empty() + || value != value.trim() + || value + .chars() + .any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace()) + { + return Err(ProtocolContractErrorV1::InvalidReticulumDestination); + } + Ok(Self { canonical: value }) + } + + pub fn as_str(&self) -> &str { + self.canonical.as_str() + } +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ReticulumTargetV1 { + pub destination: ReticulumDestinationV1, + pub mesh_scope: Option<MeshScopeIdV1>, +} + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TransportCapabilityDescriptorV1 { + pub kind: TransportKindV1, + pub maturity: CapabilityMaturityV1, + pub availability: CapabilityAvailabilityV1, + pub can_deliver: bool, + pub can_fetch: bool, + pub can_discover: bool, + pub can_gateway_forward: bool, + pub can_observe_receipts: bool, + pub required_for_v1: bool, +} + +pub const TRANSPORT_CAPABILITY_CATALOG_V1: &[TransportCapabilityDescriptorV1] = &[ + TransportCapabilityDescriptorV1 { + kind: TransportKindV1::Local, + maturity: CapabilityMaturityV1::Stable, + availability: CapabilityAvailabilityV1::Available, + can_deliver: true, + can_fetch: true, + can_discover: false, + can_gateway_forward: false, + can_observe_receipts: true, + required_for_v1: true, + }, + TransportCapabilityDescriptorV1 { + kind: TransportKindV1::Nostr, + maturity: CapabilityMaturityV1::Stable, + availability: CapabilityAvailabilityV1::Available, + can_deliver: true, + can_fetch: true, + can_discover: true, + can_gateway_forward: false, + can_observe_receipts: true, + required_for_v1: true, + }, + TransportCapabilityDescriptorV1 { + kind: TransportKindV1::Reticulum, + maturity: CapabilityMaturityV1::Preview, + availability: CapabilityAvailabilityV1::Unavailable, + can_deliver: true, + can_fetch: false, + can_discover: true, + can_gateway_forward: true, + can_observe_receipts: true, + required_for_v1: true, + }, +]; + +#[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] +#[cfg_attr(feature = "dto-bindgen", dto(export))] +#[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] +#[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 ProtocolEventClassV1 { + #[cfg_attr(feature = "dto-bindgen", dto(rename = "regular"))] + Regular, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "replaceable"))] + Replaceable, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "addressable"))] + Addressable, + #[cfg_attr(feature = "dto-bindgen", dto(rename = "unsigned_rumor"))] + UnsignedRumor, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProtocolEventDescriptorV1 { + pub name: &'static str, + pub kind: u32, + pub event_class: ProtocolEventClassV1, + pub purpose: &'static str, +} + +pub const PROTOCOL_EVENT_CATALOG_V1: &[ProtocolEventDescriptorV1] = &[ + ProtocolEventDescriptorV1 { + name: "profile", + kind: 0, + event_class: ProtocolEventClassV1::Replaceable, + purpose: "actor public profile/supporting discovery", + }, + ProtocolEventDescriptorV1 { + name: "deletion_request", + kind: 5, + event_class: ProtocolEventClassV1::Regular, + purpose: "best-effort NIP-09 request; no global erasure guarantee", + }, + ProtocolEventDescriptorV1 { + name: "gift_wrap", + kind: 1059, + event_class: ProtocolEventClassV1::Regular, + purpose: "NIP-59 encrypted private delivery wrapper", + }, + ProtocolEventDescriptorV1 { + name: "trade_private_coordination_rumor", + kind: 3421, + event_class: ProtocolEventClassV1::UnsignedRumor, + purpose: "NIP-44 encrypted buyer/seller private coordination; never relay-published directly", + }, + ProtocolEventDescriptorV1 { + name: "trade_order_request", + kind: 3422, + event_class: ProtocolEventClassV1::Regular, + purpose: "buyer request against exact listing/quote/validator set", + }, + ProtocolEventDescriptorV1 { + name: "trade_order_decision", + kind: 3423, + event_class: ProtocolEventClassV1::Regular, + purpose: "seller accept or decline", + }, + ProtocolEventDescriptorV1 { + name: "trade_order_cancellation", + kind: 3432, + event_class: ProtocolEventClassV1::Regular, + purpose: "authorized predecision cancellation", + }, + ProtocolEventDescriptorV1 { + name: "trade_validation_receipt", + kind: 3440, + event_class: ProtocolEventClassV1::Regular, + purpose: "RHI validation result bound to root/target/listing/validator set", + }, + ProtocolEventDescriptorV1 { + name: "dm_relay_list", + kind: 10050, + event_class: ProtocolEventClassV1::Replaceable, + purpose: "recipient private-message relay advertisement", + }, + ProtocolEventDescriptorV1 { + name: "relay_auth", + kind: 22242, + event_class: ProtocolEventClassV1::Regular, + purpose: "NIP-42 relay authentication", + }, + ProtocolEventDescriptorV1 { + name: "farm", + kind: 30340, + event_class: ProtocolEventClassV1::Addressable, + purpose: "public farm aggregate", + }, + ProtocolEventDescriptorV1 { + name: "validator_set", + kind: 30381, + event_class: ProtocolEventClassV1::Addressable, + purpose: "immutable one-validator set artifact signed by network authority", + }, + ProtocolEventDescriptorV1 { + name: "listing", + kind: 30402, + event_class: ProtocolEventClassV1::Addressable, + purpose: "public listing aggregate and revision", + }, +]; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProtocolSchemaMetadataV1 { + pub type_name: &'static str, + pub schema_id: &'static str, + pub schema_version: u16, +} + +pub const PROTOCOL_SCHEMA_METADATA_V1: &[ProtocolSchemaMetadataV1] = &[ + ProtocolSchemaMetadataV1 { + type_name: "TransportKindV1", + schema_id: "radroots.protocol.transport_kind.v1", + schema_version: 1, + }, + ProtocolSchemaMetadataV1 { + type_name: "TransportCapabilityDescriptorV1", + schema_id: "radroots.protocol.transport_capability_descriptor.v1", + schema_version: 1, + }, + ProtocolSchemaMetadataV1 { + type_name: "ProtocolEventDescriptorV1", + schema_id: "radroots.protocol.event_descriptor.v1", + schema_version: 1, + }, + ProtocolSchemaMetadataV1 { + type_name: "ReticulumTargetV1", + schema_id: "radroots.protocol.reticulum_target.v1", + schema_version: 1, + }, +]; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ProtocolContractErrorV1 { + DuplicateTransportKind { kind: TransportKindV1 }, + DuplicateEventName { name: String }, + DuplicateEventKind { kind: u32 }, + DuplicateSchemaId { schema_id: String }, + MissingRequiredTransport { kind: TransportKindV1 }, + RetiredTransportIdentity { identity: String }, + UnknownTransportKind { value: String }, + InvalidMeshScopeId, + InvalidReticulumDestination, +} + +impl core::fmt::Display for ProtocolContractErrorV1 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::DuplicateTransportKind { kind } => { + write!(f, "duplicate transport kind {}", kind.as_str()) + } + Self::DuplicateEventName { name } => write!(f, "duplicate event name {name}"), + Self::DuplicateEventKind { kind } => write!(f, "duplicate event kind {kind}"), + Self::DuplicateSchemaId { schema_id } => write!(f, "duplicate schema id {schema_id}"), + Self::MissingRequiredTransport { kind } => { + write!(f, "missing required transport {}", kind.as_str()) + } + Self::RetiredTransportIdentity { identity } => { + write!(f, "retired transport identity {identity}") + } + Self::UnknownTransportKind { value } => write!(f, "unknown transport kind {value}"), + Self::InvalidMeshScopeId => f.write_str("invalid mesh scope id"), + Self::InvalidReticulumDestination => f.write_str("invalid Reticulum destination"), + } + } +} + +pub fn validate_protocol_contract_v1() -> Result<(), ProtocolContractErrorV1> { + validate_transport_capability_catalog(TRANSPORT_CAPABILITY_CATALOG_V1)?; + validate_event_catalog(PROTOCOL_EVENT_CATALOG_V1)?; + validate_schema_metadata(PROTOCOL_SCHEMA_METADATA_V1) +} + +fn validate_transport_capability_catalog( + descriptors: &[TransportCapabilityDescriptorV1], +) -> Result<(), ProtocolContractErrorV1> { + let mut kinds = BTreeSet::new(); + for descriptor in descriptors { + if !kinds.insert(descriptor.kind) { + return Err(ProtocolContractErrorV1::DuplicateTransportKind { + kind: descriptor.kind, + }); + } + } + for required in [ + TransportKindV1::Local, + TransportKindV1::Nostr, + TransportKindV1::Reticulum, + ] { + if !kinds.contains(&required) { + return Err(ProtocolContractErrorV1::MissingRequiredTransport { kind: required }); + } + } + Ok(()) +} + +fn validate_event_catalog( + descriptors: &[ProtocolEventDescriptorV1], +) -> Result<(), ProtocolContractErrorV1> { + let mut names = BTreeSet::new(); + let mut kinds = BTreeSet::new(); + for descriptor in descriptors { + if !names.insert(descriptor.name) { + return Err(ProtocolContractErrorV1::DuplicateEventName { + name: descriptor.name.to_string(), + }); + } + if !kinds.insert(descriptor.kind) { + return Err(ProtocolContractErrorV1::DuplicateEventKind { + kind: descriptor.kind, + }); + } + } + Ok(()) +} + +fn validate_schema_metadata( + descriptors: &[ProtocolSchemaMetadataV1], +) -> Result<(), ProtocolContractErrorV1> { + let mut schema_ids = BTreeSet::new(); + for descriptor in descriptors { + if !schema_ids.insert(descriptor.schema_id) { + return Err(ProtocolContractErrorV1::DuplicateSchemaId { + schema_id: descriptor.schema_id.to_string(), + }); + } + } + Ok(()) +} + +#[cfg(feature = "dto-bindgen")] +pub fn dto_roots() -> alloc::vec::Vec<dto_bindgen::export::RootDescriptor> { + use dto_bindgen::export::RootDescriptor; + alloc::vec![ + RootDescriptor::new::<TransportKindV1>(), + RootDescriptor::new::<CapabilityMaturityV1>(), + RootDescriptor::new::<CapabilityAvailabilityV1>(), + RootDescriptor::new::<MeshScopeIdV1>(), + RootDescriptor::new::<ReticulumDestinationV1>(), + RootDescriptor::new::<ReticulumTargetV1>(), + RootDescriptor::new::<TransportCapabilityDescriptorV1>(), + RootDescriptor::new::<ProtocolEventClassV1>(), + ] +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn protocol_contract_catalogs_validate() { + validate_protocol_contract_v1().expect("protocol contract validates"); + } + + #[test] + fn transport_kind_v1_rejects_retired_identities() { + for identity in [ + "reticulum_preview", + "mesh", + "proxy", + "radrootsd_proxy", + "hybrid", + ] { + assert!(matches!( + TransportKindV1::parse(identity), + Err(ProtocolContractErrorV1::RetiredTransportIdentity { .. }) + )); + } + } + + #[test] + fn reticulum_is_required_preview_v1_transport() { + let reticulum = TRANSPORT_CAPABILITY_CATALOG_V1 + .iter() + .find(|descriptor| descriptor.kind == TransportKindV1::Reticulum) + .expect("reticulum descriptor"); + + assert!(reticulum.required_for_v1); + assert_eq!(reticulum.maturity, CapabilityMaturityV1::Preview); + assert_eq!( + reticulum.availability, + CapabilityAvailabilityV1::Unavailable + ); + assert!(reticulum.can_deliver); + assert!(!reticulum.can_fetch); + } + + #[test] + fn reticulum_target_newtypes_validate() { + let target = ReticulumTargetV1 { + destination: ReticulumDestinationV1::parse("reticulum:local").expect("destination"), + mesh_scope: Some(MeshScopeIdV1::parse("local_preview").expect("scope")), + }; + + assert_eq!(target.destination.as_str(), "reticulum:local"); + assert_eq!( + target.mesh_scope.as_ref().expect("scope").as_str(), + "local_preview" + ); + } +} diff --git a/tools/xtask/Cargo.toml b/tools/xtask/Cargo.toml @@ -13,6 +13,7 @@ authors = ["Tyson Lupul <tyson@radroots.org>"] radroots_event_codec = { workspace = true, features = [ "contract-manifest", ], default-features = false } +radroots_protocol_contract_v1 = { workspace = true, features = ["std"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -50,6 +50,8 @@ fn workspace_root() -> PathBuf { } fn validate_contract() -> Result<(), String> { + radroots_protocol_contract_v1::validate_protocol_contract_v1() + .map_err(|error| error.to_string())?; let root = workspace_root(); contract::load_contract_bundle(&root) .and_then(|bundle| contract::validate_contract_bundle(&bundle))