lib

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

commit 8bc581e6809e7065beda32cb31fbb91e8d6bf68c
parent 965979b2252ee3090dd6d46e05bc1021ef4676f5
Author: triesap <tyson@radroots.org>
Date:   Tue,  4 Aug 2026 15:20:20 +0000

refactor: retire predecessor sdk surfaces

Diffstat:
Mcrates/radroots/README.md | 2+-
Mcrates/radroots/src/listing.rs | 8+++-----
Mcrates/sdk/README.md | 6+++---
Mcrates/sdk/src/client.rs | 4++--
Mcrates/sdk/src/listing.rs | 21++++++++++++++++-----
Acrates/sdk/src/listing/operational_listing/draft.rs | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/sdk/src/listing/operational_listing/mod.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/sdk/src/listing/operational_listing/model.rs | 15+++++++++++++++
Acrates/sdk/src/listing/operational_listing/mutation.rs | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/sdk/src/listing/operational_listing/price_ext.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/sdk/src/listing/operational_listing/validation.rs | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/tests/package_boundary.rs | 16++++++++++++++--
Mtools/sdk_xtask_import/Cargo.toml | 1-
Mtools/sdk_xtask_import/src/check.rs | 2--
Dtools/sdk_xtask_import/src/cli_host.rs | 676-------------------------------------------------------------------------------
Mtools/sdk_xtask_import/src/contracts.rs | 8--------
Mtools/sdk_xtask_import/src/dto_roots.rs | 90++++---------------------------------------------------------------------------
Mtools/sdk_xtask_import/src/generate.rs | 5++---
Mtools/sdk_xtask_import/src/main.rs | 1-
Mtools/sdk_xtask_import/src/output.rs | 8--------
Mtools/sdk_xtask_import/src/package_matrix.rs | 11++---------
21 files changed, 765 insertions(+), 812 deletions(-)

diff --git a/crates/radroots/README.md b/crates/radroots/README.md @@ -5,7 +5,7 @@ safe local construction, deliberate domain paths, and the common client boundary without exposing every lower crate or duplicating the advanced SDK engine. -The package remains `publish = false` during release qualification. After the +The package is enabled for validation-only release qualification. After the separately approved publication gate, the onboarding command is: ```sh diff --git a/crates/radroots/src/listing.rs b/crates/radroots/src/listing.rs @@ -6,9 +6,7 @@ pub use radroots_event::listing::operational::{ OperationalListingPublicLocation, OperationalListingStatus, }; pub use radroots_sdk::listing::{ - Action, Plan, PrepareError, PrepareErrorKind, PrepareRequest, prepare, -}; -pub use radroots_trade::operational_listing::{ - RadrootsOperationalListingLifecycleState as Lifecycle, - draft::RadrootsOperationalListingEditDocumentV1 as EditV1, + Action, Plan, PrepareError, PrepareErrorKind, PrepareRequest, + RadrootsOperationalListingEditDocumentV1 as EditV1, + RadrootsOperationalListingLifecycleState as Lifecycle, prepare, }; diff --git a/crates/sdk/README.md b/crates/sdk/README.md @@ -163,9 +163,9 @@ surfaced, and the response must match the signed event and requested policies. ## Release posture -This package remains `publish = false` until the complete package-realistic -release qualification and separately authorized publication step. The crate is -licensed under `MIT OR Apache-2.0`. +This package is enabled for package-realistic validation only. Actual crates.io +publication remains blocked pending the approval packet and a separately +authorized operator step. The crate is licensed under `MIT OR Apache-2.0`. The reviewed all-features public API baseline is recorded at [`docs/api/radroots_sdk-0.1.0-alpha.txt`](../../docs/api/radroots_sdk-0.1.0-alpha.txt). diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -930,10 +930,10 @@ fn profile_event( event: &radroots_event::SignedEvent, ) -> std::result::Result< ProfileEvent, - radroots_event_codec::profile::inbound::RadrootsProfileMetadataParseError, + radroots_event_codec::decode::profile::RadrootsProfileMetadataParseError, > { let profile = - radroots_event_codec::profile::inbound::parse_inbound_profile_metadata(event.content())?; + radroots_event_codec::decode::profile::parse_inbound_profile_metadata(event.content())?; Ok(ProfileEvent { event_id: event.id_hex(), author: event.pubkey().to_hex(), diff --git a/crates/sdk/src/listing.rs b/crates/sdk/src/listing.rs @@ -4,11 +4,22 @@ use std::{error, fmt}; use radroots_event::{EventDraft, contract::AuthorRole, id::ClassifiedListingAddress}; use radroots_signing::Actor; -use radroots_trade::operational_listing::{ - RadrootsOperationalListingEditDocumentV1, RadrootsOperationalListingEditError, - RadrootsOperationalListingLifecycleState, RadrootsOperationalListingMutation, - RadrootsOperationalListingMutationError, build_operational_listing_mutation_draft, - canonicalize_operational_listing_edit, + +mod operational_listing; + +pub use operational_listing::{ + BinPricingTryExt, RadrootsClassifiedListingAddressParts, + RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingEditDocumentV1, + RadrootsOperationalListingEditError, RadrootsOperationalListingLifecycleState, + RadrootsOperationalListingMutation, RadrootsOperationalListingMutationError, + RadrootsOperationalListingSubtotal, RadrootsOperationalListingTotal, + RadrootsOperationalListingTradeProjection, RadrootsPublicClassifiedListingAddress, + parse_classified_listing_address, parse_operational_listing_event, + parse_public_classified_listing_address, validate_operational_listing_event, + validate_operational_listing_model, +}; +use operational_listing::{ + build_operational_listing_mutation_draft, canonicalize_operational_listing_edit, }; /// Supported public listing mutation intent. diff --git a/crates/sdk/src/listing/operational_listing/draft.rs b/crates/sdk/src/listing/operational_listing/draft.rs @@ -0,0 +1,169 @@ +//! Canonicalization for Radroots Listing v1 drafts. + +#![forbid(unsafe_code)] + +use core::fmt; + +use std::{format, vec::Vec}; + +use radroots_event::{ + envelope::kind::KIND_CLASSIFIED_LISTING, + id::{ClassifiedListingAddress, InventoryBinId, ParseError}, + listing::operational::OperationalListing, + trade::validation::OperationalListingValidationError, +}; +use radroots_identity::{Error as PublicKeyError, PublicKey}; + +use super::validation::validate_operational_listing_model; + +#[derive(Clone, Debug)] +pub struct RadrootsOperationalListingEditDocumentV1 { + pub listing: OperationalListing, +} + +impl RadrootsOperationalListingEditDocumentV1 { + pub fn new(listing: OperationalListing) -> Self { + Self { listing } + } +} + +#[derive(Clone, Debug)] +pub struct RadrootsOperationalListingCanonicalEdit { + listing: OperationalListing, + seller_pubkey: PublicKey, + public_listing_addr: ClassifiedListingAddress, +} + +impl RadrootsOperationalListingCanonicalEdit { + pub fn new( + mut listing: OperationalListing, + seller_pubkey: PublicKey, + ) -> Result<Self, RadrootsOperationalListingEditError> { + let farm_pubkey = PublicKey::from_hex(listing.farm.pubkey.as_str()) + .map_err(RadrootsOperationalListingEditError::InvalidFarmPubkey)?; + if farm_pubkey != seller_pubkey { + return Err(RadrootsOperationalListingEditError::FarmPubkeyMismatch { + expected_pubkey: seller_pubkey, + actual_pubkey: farm_pubkey, + }); + } + listing.farm.pubkey = farm_pubkey.to_hex(); + validate_listing_bins(&listing)?; + let listing = validate_operational_listing_model(listing, &seller_pubkey) + .map_err(RadrootsOperationalListingEditError::InvalidModel)? + .listing; + + let public_listing_addr = listing_addr( + KIND_CLASSIFIED_LISTING, + &seller_pubkey, + listing.d_tag.as_str(), + ); + + Ok(Self { + listing, + seller_pubkey, + public_listing_addr, + }) + } + + pub fn listing(&self) -> &OperationalListing { + &self.listing + } + + pub fn seller_pubkey(&self) -> &PublicKey { + &self.seller_pubkey + } + + pub fn public_listing_addr(&self) -> &ClassifiedListingAddress { + &self.public_listing_addr + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsOperationalListingEditError { + InvalidFarmPubkey(PublicKeyError), + InvalidClassifiedListingAddress(ParseError), + InvalidModel(OperationalListingValidationError), + FarmPubkeyMismatch { + expected_pubkey: PublicKey, + actual_pubkey: PublicKey, + }, + MissingPrimaryBin { + primary_bin_id: InventoryBinId, + }, + DuplicateBinId { + bin_id: InventoryBinId, + }, +} + +impl fmt::Display for RadrootsOperationalListingEditError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFarmPubkey(error) => { + write!(f, "invalid listing edit farm pubkey: {error}") + } + Self::InvalidClassifiedListingAddress(error) => { + write!(f, "invalid listing edit address: {error}") + } + Self::InvalidModel(error) => { + write!(f, "invalid listing edit model: {error}") + } + Self::FarmPubkeyMismatch { .. } => { + f.write_str("listing edit farm pubkey does not match seller") + } + Self::MissingPrimaryBin { .. } => f.write_str("listing edit primary bin is missing"), + Self::DuplicateBinId { .. } => f.write_str("listing edit contains duplicate bin ID"), + } + } +} + +impl core::error::Error for RadrootsOperationalListingEditError {} + +fn validate_listing_bins( + listing: &OperationalListing, +) -> Result<(), RadrootsOperationalListingEditError> { + let primary_bin_id = listing.primary_bin_id.clone(); + let mut seen_bin_ids = Vec::new(); + let mut primary_bin_found = false; + for bin in &listing.bins { + if seen_bin_ids + .iter() + .any(|seen_bin_id| seen_bin_id == &bin.bin_id) + { + return Err(RadrootsOperationalListingEditError::DuplicateBinId { + bin_id: bin.bin_id.clone(), + }); + } + if bin.bin_id == primary_bin_id { + primary_bin_found = true; + } + seen_bin_ids.push(bin.bin_id.clone()); + } + + if !primary_bin_found { + return Err(RadrootsOperationalListingEditError::MissingPrimaryBin { primary_bin_id }); + } + Ok(()) +} + +fn listing_addr(kind: u32, seller_pubkey: &PublicKey, d_tag: &str) -> ClassifiedListingAddress { + ClassifiedListingAddress::parse(format!("{kind}:{seller_pubkey}:{d_tag}")) + .expect("typed listing identity must form a listing address") +} + +/// Canonicalizes an untrusted listing edit for an already-authorized seller. +/// +/// Actor provenance and role authorization belong to the signing/workflow +/// boundary. This deterministic function validates only the supplied public +/// identity and listing data and performs no signing or authorization. +pub fn canonicalize_operational_listing_edit( + seller_pubkey: PublicKey, + mut document: RadrootsOperationalListingEditDocumentV1, +) -> Result<RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingEditError> { + let farm_pubkey = document.listing.farm.pubkey.as_str(); + if farm_pubkey.is_empty() { + document.listing.farm.pubkey = seller_pubkey.to_hex(); + } + + RadrootsOperationalListingCanonicalEdit::new(document.listing, seller_pubkey) +} diff --git a/crates/sdk/src/listing/operational_listing/mod.rs b/crates/sdk/src/listing/operational_listing/mod.rs @@ -0,0 +1,89 @@ +pub mod draft; +pub mod model; +pub mod mutation; +pub mod price_ext; +pub mod validation; + +use radroots_event::{ + envelope::EventEnvelope, + id::{AddressableCoordinateParts, ClassifiedListingAddress, DTag, ParseError}, + listing::operational::{OperationalListing, OperationalListingParseError}, +}; +use radroots_event_codec::decode::operational_listing::operational_listing_from_nostr_event; +use radroots_identity::PublicKey; + +pub use self::draft::{ + RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingEditDocumentV1, + RadrootsOperationalListingEditError, canonicalize_operational_listing_edit, +}; +pub use self::model::{RadrootsOperationalListingSubtotal, RadrootsOperationalListingTotal}; +pub use self::mutation::build_operational_listing_mutation_draft; +pub use self::mutation::{ + RadrootsOperationalListingLifecycleState, RadrootsOperationalListingMutation, + RadrootsOperationalListingMutationError, +}; +pub use self::price_ext::BinPricingTryExt; +pub use self::validation::{ + RadrootsOperationalListingTradeProjection, validate_operational_listing_event, + validate_operational_listing_model, +}; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsClassifiedListingAddressParts { + pub address: ClassifiedListingAddress, + pub kind: u32, + pub seller_pubkey: PublicKey, + pub listing_id: DTag, +} + +impl RadrootsClassifiedListingAddressParts { + pub fn parse(value: impl AsRef<str>) -> Result<Self, ParseError> { + parse_classified_listing_address(value) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsPublicClassifiedListingAddress { + pub address: ClassifiedListingAddress, + pub kind: u32, + pub seller_pubkey: PublicKey, + pub listing_id: DTag, +} + +impl RadrootsPublicClassifiedListingAddress { + pub fn parse(value: impl AsRef<str>) -> Result<Self, ParseError> { + parse_public_classified_listing_address(value) + } +} + +pub fn parse_classified_listing_address( + value: impl AsRef<str>, +) -> Result<RadrootsClassifiedListingAddressParts, ParseError> { + let value = value.as_ref(); + let address = ClassifiedListingAddress::parse(value)?; + let parts = AddressableCoordinateParts::parse(address.as_str()) + .expect("typed listing address must contain valid coordinate parts"); + Ok(RadrootsClassifiedListingAddressParts { + address, + kind: parts.kind, + seller_pubkey: parts.pubkey, + listing_id: parts.d_tag, + }) +} + +pub fn parse_public_classified_listing_address( + value: impl AsRef<str>, +) -> Result<RadrootsPublicClassifiedListingAddress, ParseError> { + let parts = parse_classified_listing_address(value)?; + Ok(RadrootsPublicClassifiedListingAddress { + address: parts.address, + kind: parts.kind, + seller_pubkey: parts.seller_pubkey, + listing_id: parts.listing_id, + }) +} + +pub fn parse_operational_listing_event( + event: &EventEnvelope, +) -> Result<OperationalListing, OperationalListingParseError> { + operational_listing_from_nostr_event(event) +} diff --git a/crates/sdk/src/listing/operational_listing/model.rs b/crates/sdk/src/listing/operational_listing/model.rs @@ -0,0 +1,15 @@ +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsOperationalListingSubtotal { + pub price_amount: radroots_core::Money, + pub price_currency: radroots_core::Currency, + pub quantity_amount: radroots_core::Decimal, + pub quantity_unit: radroots_core::Unit, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsOperationalListingTotal { + pub price_amount: radroots_core::Money, + pub price_currency: radroots_core::Currency, + pub quantity_amount: radroots_core::Decimal, + pub quantity_unit: radroots_core::Unit, +} diff --git a/crates/sdk/src/listing/operational_listing/mutation.rs b/crates/sdk/src/listing/operational_listing/mutation.rs @@ -0,0 +1,156 @@ +//! Mutation draft preparation for Radroots Listing v1. + +#![forbid(unsafe_code)] + +use core::fmt; + +use std::string::{String, ToString}; + +use radroots_event::id::ClassifiedListingAddress; +use radroots_event::{ + draft::{DraftError, EventDraft}, + envelope::kind::KIND_CLASSIFIED_LISTING, +}; +use radroots_event_codec::encode::operational_listing::to_wire_parts_with_kind; + +use crate::listing::operational_listing::draft::RadrootsOperationalListingCanonicalEdit; + +/// Listing v1 mutation intent for draft preparation only. +/// +/// Publish and update target the public listing event, while local-only draft +/// persistence and archive are intentionally unsupported as wire events. +#[derive(Clone, Debug)] +pub enum RadrootsOperationalListingMutation { + Publish { + draft: RadrootsOperationalListingCanonicalEdit, + }, + Update { + draft: RadrootsOperationalListingCanonicalEdit, + }, + SaveDraft { + draft: RadrootsOperationalListingCanonicalEdit, + }, + Archive { + listing_addr: ClassifiedListingAddress, + }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RadrootsOperationalListingLifecycleState { + Draft, + Published, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsOperationalListingMutationError { + UnsupportedMutation, + EncodeListing(String), + FrozenDraft(DraftError), +} + +impl fmt::Display for RadrootsOperationalListingMutationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnsupportedMutation => f.write_str("listing mutation is not supported"), + Self::EncodeListing(error) => { + write!(f, "failed to encode listing mutation: {error}") + } + Self::FrozenDraft(error) => { + write!(f, "failed to build listing mutation draft: {error}") + } + } + } +} + +impl core::error::Error for RadrootsOperationalListingMutationError {} + +const OPERATIONAL_LISTING_PUBLISHED_CONTRACT_ID: &str = "radroots.operational_listing.published.v1"; + +impl RadrootsOperationalListingMutation { + pub fn publish(draft: RadrootsOperationalListingCanonicalEdit) -> Self { + Self::Publish { draft } + } + + pub fn update(draft: RadrootsOperationalListingCanonicalEdit) -> Self { + Self::Update { draft } + } + + pub fn save_draft(draft: RadrootsOperationalListingCanonicalEdit) -> Self { + Self::SaveDraft { draft } + } + + pub fn archive(listing_addr: ClassifiedListingAddress) -> Self { + Self::Archive { listing_addr } + } + + pub fn lifecycle_state( + &self, + ) -> Result<RadrootsOperationalListingLifecycleState, RadrootsOperationalListingMutationError> + { + match self { + Self::Publish { .. } | Self::Update { .. } => { + Ok(RadrootsOperationalListingLifecycleState::Published) + } + Self::SaveDraft { .. } => Ok(RadrootsOperationalListingLifecycleState::Draft), + Self::Archive { .. } => { + Err(RadrootsOperationalListingMutationError::UnsupportedMutation) + } + } + } + + pub fn canonical_draft( + &self, + ) -> Result<&RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingMutationError> + { + match self { + Self::Publish { draft } | Self::Update { draft } | Self::SaveDraft { draft } => { + Ok(draft) + } + Self::Archive { .. } => { + Err(RadrootsOperationalListingMutationError::UnsupportedMutation) + } + } + } + + pub fn listing_addr( + &self, + ) -> Result<&ClassifiedListingAddress, RadrootsOperationalListingMutationError> { + match self { + Self::Publish { draft } | Self::Update { draft } => Ok(draft.public_listing_addr()), + Self::SaveDraft { draft } => Ok(draft.public_listing_addr()), + Self::Archive { .. } => { + Err(RadrootsOperationalListingMutationError::UnsupportedMutation) + } + } + } +} + +pub fn build_operational_listing_mutation_draft( + mutation: &RadrootsOperationalListingMutation, + created_at: u64, +) -> Result<EventDraft, RadrootsOperationalListingMutationError> { + let (draft, kind, contract_id) = match mutation { + RadrootsOperationalListingMutation::Publish { draft } + | RadrootsOperationalListingMutation::Update { draft } => ( + draft, + KIND_CLASSIFIED_LISTING, + OPERATIONAL_LISTING_PUBLISHED_CONTRACT_ID, + ), + RadrootsOperationalListingMutation::SaveDraft { .. } + | RadrootsOperationalListingMutation::Archive { .. } => { + return Err(RadrootsOperationalListingMutationError::UnsupportedMutation); + } + }; + let parts = to_wire_parts_with_kind(draft.listing(), kind).map_err(|error| { + RadrootsOperationalListingMutationError::EncodeListing(error.to_string()) + })?; + EventDraft::new( + contract_id, + parts.kind, + created_at, + parts.tags, + parts.content, + draft.seller_pubkey().to_hex(), + ) + .map_err(RadrootsOperationalListingMutationError::FrozenDraft) +} diff --git a/crates/sdk/src/listing/operational_listing/price_ext.rs b/crates/sdk/src/listing/operational_listing/price_ext.rs @@ -0,0 +1,63 @@ +use crate::listing::operational_listing::model::{ + RadrootsOperationalListingSubtotal, RadrootsOperationalListingTotal, +}; +use radroots_core::pricing::{Error as PricingError, QuantityPriceOps}; +use radroots_core::{Decimal, Quantity}; +use radroots_event::listing::operational::OperationalListingBin; + +pub trait BinPricingTryExt { + fn try_subtotal_for_count( + &self, + bin_count: u32, + ) -> Result<RadrootsOperationalListingSubtotal, PricingError>; + fn try_total_for_count( + &self, + bin_count: u32, + ) -> Result<RadrootsOperationalListingTotal, PricingError>; +} + +#[inline] +fn effective_quantity( + bin: &OperationalListingBin, + bin_count: u32, +) -> Result<Quantity, PricingError> { + let amount = bin + .quantity + .amount() + .checked_mul(Decimal::from(bin_count)) + .map_err(|_| PricingError::ArithmeticOverflow)?; + Quantity::try_new(amount, bin.quantity.unit()).map_err(|_| PricingError::ArithmeticOverflow) +} + +impl BinPricingTryExt for OperationalListingBin { + fn try_subtotal_for_count( + &self, + bin_count: u32, + ) -> Result<RadrootsOperationalListingSubtotal, PricingError> { + let effective_qty = effective_quantity(self, bin_count)?; + let money = self + .price_per_canonical_unit + .try_cost_for_rounded(&effective_qty)?; + let currency = money.currency(); + + Ok(RadrootsOperationalListingSubtotal { + price_amount: money, + price_currency: currency, + quantity_amount: effective_qty.amount(), + quantity_unit: effective_qty.unit(), + }) + } + + fn try_total_for_count( + &self, + bin_count: u32, + ) -> Result<RadrootsOperationalListingTotal, PricingError> { + let sub = self.try_subtotal_for_count(bin_count)?; + Ok(RadrootsOperationalListingTotal { + price_amount: sub.price_amount, + price_currency: sub.price_currency, + quantity_amount: sub.quantity_amount, + quantity_unit: sub.quantity_unit, + }) + } +} diff --git a/crates/sdk/src/listing/operational_listing/validation.rs b/crates/sdk/src/listing/operational_listing/validation.rs @@ -0,0 +1,226 @@ +#![forbid(unsafe_code)] + +use std::{ + format, + string::{String, ToString}, +}; + +use radroots_core::{Decimal, Money, Quantity, Unit}; +use radroots_event::{ + envelope::kind::{KIND_CLASSIFIED_LISTING, is_classified_listing_kind}, + farm::location::{has_textual_locality, is_public_geohash5}, + id::ClassifiedListingAddress, + listing::classified::{ClassifiedListingPartition, classify_classified_listing_tags}, + listing::operational::{ + OperationalListing, OperationalListingAvailability, OperationalListingBin, + OperationalListingDeliveryMethod, OperationalListingPublicLocation, + }, + trade::validation::OperationalListingValidationError, +}; +use radroots_identity::PublicKey; + +use radroots_event_codec::{ + decode::operational_listing::operational_listing_from_nostr_event, + verify::RadrootsSignatureVerifiedEvent, +}; + +#[derive(Clone, Debug)] +pub struct RadrootsOperationalListingTradeProjection { + pub listing_id: String, + pub listing_addr: ClassifiedListingAddress, + pub seller_pubkey: String, + pub title: String, + pub description: String, + pub product_type: String, + pub primary_bin_id: String, + pub bin_quantity: Quantity, + pub unit: Unit, + pub unit_price: Money, + pub inventory_available: Decimal, + pub availability: OperationalListingAvailability, + pub location: OperationalListingPublicLocation, + pub delivery_method: OperationalListingDeliveryMethod, + pub listing: OperationalListing, +} + +/// Validates a signature-verified Operational Listing event. +/// +/// A plain envelope cannot cross this boundary: +/// +/// ```compile_fail +/// use radroots_event::envelope::EventEnvelope; +/// use radroots_sdk::listing::validate_operational_listing_event; +/// +/// fn validate_unverified(event: &EventEnvelope) { +/// let _ = validate_operational_listing_event(event); +/// } +/// ``` +pub fn validate_operational_listing_event( + verified_event: &RadrootsSignatureVerifiedEvent, +) -> Result<RadrootsOperationalListingTradeProjection, OperationalListingValidationError> { + let event = verified_event.event(); + if !is_classified_listing_kind(event.kind_u32()) { + return Err(OperationalListingValidationError::InvalidKind { + kind: event.kind_u32(), + }); + } + if classify_classified_listing_tags(event.tags()) + != ClassifiedListingPartition::OperationalListing + { + return Err(OperationalListingValidationError::InvalidProfile); + } + + let listing = operational_listing_from_nostr_event(event) + .map_err(|error| OperationalListingValidationError::ParseError { error })?; + validate_operational_listing_model(listing, event.author()) +} + +/// Validates the trade semantics of an unsigned Operational Listing model. +/// +/// The seller is typed independently from the model because it is the +/// authority against which the listing's farm identity is checked. This +/// function does not perform event-kind, profile, decoding, or signature +/// checks; callers handling Nostr events must use +/// [`validate_operational_listing_event`] instead. +pub fn validate_operational_listing_model( + listing: OperationalListing, + seller_pubkey: &PublicKey, +) -> Result<RadrootsOperationalListingTradeProjection, OperationalListingValidationError> { + let listing_id = listing.d_tag.as_str().trim().to_string(); + + if listing.farm.pubkey != seller_pubkey.to_hex() { + return Err(OperationalListingValidationError::InvalidSeller); + } + let listing_addr_raw = format!("{KIND_CLASSIFIED_LISTING}:{}:{listing_id}", seller_pubkey); + let listing_addr = ClassifiedListingAddress::parse(&listing_addr_raw) + .expect("validated listing identity must form a listing address"); + + let title = listing.product.title.trim().to_string(); + if title.is_empty() { + return Err(OperationalListingValidationError::MissingTitle); + } + + let description = listing + .product + .summary + .as_ref() + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + if description.is_empty() { + return Err(OperationalListingValidationError::MissingDescription); + } + + let product_type = if !listing.product.category.trim().is_empty() { + listing.product.category.trim().to_string() + } else { + listing.product.key.trim().to_string() + }; + if product_type.is_empty() { + return Err(OperationalListingValidationError::MissingProductType); + } + + if listing.bins.is_empty() { + return Err(OperationalListingValidationError::MissingBins); + } + let primary_bin_id = listing.primary_bin_id.as_str().trim().to_string(); + let primary_bin_index = listing + .bins + .iter() + .position(|bin| bin.bin_id.as_str() == primary_bin_id) + .ok_or(OperationalListingValidationError::MissingPrimaryBin)?; + for (index, bin) in listing.bins.iter().enumerate() { + if listing.bins[..index] + .iter() + .any(|seen| seen.bin_id == bin.bin_id) + { + return Err(OperationalListingValidationError::InvalidBin); + } + } + let primary_bin = &listing.bins[primary_bin_index]; + + validate_listing_bin(primary_bin)?; + for (index, bin) in listing.bins.iter().enumerate() { + if index != primary_bin_index { + validate_listing_bin(bin)?; + } + } + + let inventory_available = listing + .inventory_available + .ok_or(OperationalListingValidationError::MissingInventory)?; + if inventory_available.is_sign_negative() { + return Err(OperationalListingValidationError::InvalidInventory); + } + + let availability = listing + .availability + .clone() + .ok_or(OperationalListingValidationError::MissingAvailability)?; + let location = listing + .location + .clone() + .ok_or(OperationalListingValidationError::MissingLocation)?; + if !has_textual_locality( + &location.primary, + location.city.as_deref(), + location.region.as_deref(), + location.country.as_deref(), + ) { + return Err(OperationalListingValidationError::MissingLocationLocality); + } + validate_listing_location_geohash(&location.geohash)?; + let delivery_method = listing + .delivery_method + .clone() + .ok_or(OperationalListingValidationError::MissingDeliveryMethod)?; + + Ok(RadrootsOperationalListingTradeProjection { + listing_id, + listing_addr, + seller_pubkey: seller_pubkey.to_hex(), + title, + description, + product_type, + primary_bin_id: primary_bin_id.clone(), + bin_quantity: primary_bin.quantity.clone(), + unit: primary_bin.quantity.unit(), + unit_price: primary_bin.price_per_canonical_unit.amount().clone(), + inventory_available, + availability, + location, + delivery_method, + listing, + }) +} + +fn validate_listing_bin( + bin: &OperationalListingBin, +) -> Result<(), OperationalListingValidationError> { + if bin.quantity.amount().is_sign_negative() || !bin.quantity.is_canonical() { + return Err(OperationalListingValidationError::InvalidBin); + } + if !bin.price_per_canonical_unit.is_price_per_canonical_unit() + || bin + .price_per_canonical_unit + .amount() + .amount() + .is_sign_negative() + || bin.price_per_canonical_unit.quantity().unit() != bin.quantity.unit() + { + return Err(OperationalListingValidationError::InvalidPrice); + } + Ok(()) +} + +#[cfg_attr(coverage_nightly, coverage(off))] +fn validate_listing_location_geohash( + geohash: &str, +) -> Result<(), OperationalListingValidationError> { + if geohash.trim().is_empty() { + return Err(OperationalListingValidationError::MissingLocationGeohash); + } + if !is_public_geohash5(geohash) { + return Err(OperationalListingValidationError::InvalidLocationGeohash); + } + Ok(()) +} diff --git a/crates/sdk/tests/package_boundary.rs b/crates/sdk/tests/package_boundary.rs @@ -158,6 +158,12 @@ fn package_contains_only_reachable_sources_and_registered_targets() { "farm.rs".to_owned(), "lib.rs".to_owned(), "listing.rs".to_owned(), + "listing/operational_listing/draft.rs".to_owned(), + "listing/operational_listing/mod.rs".to_owned(), + "listing/operational_listing/model.rs".to_owned(), + "listing/operational_listing/mutation.rs".to_owned(), + "listing/operational_listing/price_ext.rs".to_owned(), + "listing/operational_listing/validation.rs".to_owned(), "signing.rs".to_owned(), "storage.rs".to_owned(), "sync.rs".to_owned(), @@ -184,8 +190,8 @@ fn package_contains_only_reachable_sources_and_registered_targets() { } #[test] -fn remaining_compatibility_package_is_retired_and_not_publishable() { - assert!(PUBLICATION.contains("retired = [\"radroots_runtime_contract_v1\"]")); +fn compatibility_packages_are_removed() { + assert!(PUBLICATION.contains("retired = []")); let approved = PUBLICATION .split_once("approved_packages = [") .expect("approved package list") @@ -194,6 +200,12 @@ fn remaining_compatibility_package_is_retired_and_not_publishable() { .expect("approved package list terminator") .0; assert!(!approved.contains("radroots_runtime_contract_v1")); + let workspace = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(std::path::Path::parent) + .expect("workspace root"); + assert!(!workspace.join("crates/runtime_contract_v1").exists()); + assert!(!workspace.join("crates/event_index_bindings").exists()); } #[test] diff --git a/tools/sdk_xtask_import/Cargo.toml b/tools/sdk_xtask_import/Cargo.toml @@ -18,7 +18,6 @@ dto_bindgen_backend_ts = { workspace = true } dto_bindgen_core = { workspace = true } radroots_core_bindings = { path = "../../crates/core_bindings", version = "=0.1.0-alpha" } radroots_event_bindings = { path = "../../crates/event_bindings", version = "=0.1.0-alpha" } -radroots_event_index = { workspace = true, features = ["dto-bindgen"] } radroots_identity = { workspace = true } radroots_identity_bindings = { path = "../../crates/identity_bindings", version = "=0.1.0-alpha" } radroots_protocol = { workspace = true } diff --git a/tools/sdk_xtask_import/src/check.rs b/tools/sdk_xtask_import/src/check.rs @@ -11,7 +11,6 @@ use serde::Deserialize; use tar::Archive; use crate::{ - cli_host, contracts::validate_sdk_contracts, fs::workspace_root, output::{PackageOutput, package_outputs}, @@ -159,7 +158,6 @@ pub fn check() -> Result<(), String> { check_binding_crate_sources(&root)?; check_package_source_metadata(&root)?; check_generated_outputs(&root)?; - cli_host::check_cli_host()?; check_package_build_artifacts(&root)?; check_npm_pack_payloads(&root)?; crate::bindings::check(&root)?; diff --git a/tools/sdk_xtask_import/src/cli_host.rs b/tools/sdk_xtask_import/src/cli_host.rs @@ -1,676 +0,0 @@ -use std::path::{Path, PathBuf}; - -use radroots_protocol::runtime::v1::{CATALOG, OperationId}; - -use crate::fs::{workspace_root, write_if_changed}; - -struct CliOperationMetadata { - operation_id: &'static str, - variant: &'static str, - cli_path: &'static str, - description: &'static str, - role: &'static str, - supports_ndjson: bool, -} - -const CLI_OPERATION_METADATA: &[CliOperationMetadata] = &[ - meta( - "profile.inspect", - "ProfileInspect", - "radroots profile inspect", - "Inspect the active runtime profile.", - "Any", - false, - ), - meta( - "profile.reset", - "ProfileReset", - "radroots profile reset", - "Reset the active runtime profile stores.", - "Any", - false, - ), - meta( - "account.create", - "AccountCreate", - "radroots account create", - "Create a local account identity.", - "Any", - false, - ), - meta( - "account.import", - "AccountImport", - "radroots account import", - "Import an existing account identity.", - "Any", - false, - ), - meta( - "account.select", - "AccountSelect", - "radroots account select <selector>", - "Select the active account context.", - "Any", - false, - ), - meta( - "account.list", - "AccountList", - "radroots account list", - "List known local accounts.", - "Any", - true, - ), - meta( - "account.remove", - "AccountRemove", - "radroots account remove <selector>", - "Remove an account from local configuration and storage.", - "Any", - false, - ), - meta( - "signer.status", - "SignerStatus", - "radroots signer status", - "Inspect signer readiness.", - "Any", - false, - ), - meta( - "store.inspect", - "StoreInspect", - "radroots store inspect", - "Inspect local runtime stores.", - "Any", - false, - ), - meta( - "store.backup", - "StoreBackup", - "radroots store backup", - "Create a local runtime-store backup.", - "Any", - false, - ), - meta( - "store.restore", - "StoreRestore", - "radroots store restore <source>", - "Restore local runtime stores from a backup.", - "Any", - false, - ), - meta( - "farm.create", - "FarmCreate", - "radroots farm create", - "Create farm profile data.", - "Seller", - false, - ), - meta( - "farm.update", - "FarmUpdate", - "radroots farm update", - "Update farm profile data.", - "Seller", - false, - ), - meta( - "farm.publish", - "FarmPublish", - "radroots farm publish", - "Publish farm profile data.", - "Seller", - false, - ), - meta( - "farm.get", - "FarmGet", - "radroots farm get", - "Get farm profile data.", - "Any", - false, - ), - meta( - "farm.list", - "FarmList", - "radroots farm list", - "List farm profile data.", - "Any", - true, - ), - meta( - "listing.create", - "ListingCreate", - "radroots listing create", - "Create a listing draft.", - "Seller", - false, - ), - meta( - "listing.update", - "ListingUpdate", - "radroots listing update <file>", - "Update a listing.", - "Seller", - false, - ), - meta( - "listing.publish", - "ListingPublish", - "radroots listing publish <file>", - "Publish a listing.", - "Seller", - false, - ), - meta( - "listing.pause", - "ListingPause", - "radroots listing pause <file>", - "Pause a listing.", - "Seller", - false, - ), - meta( - "listing.withdraw", - "ListingWithdraw", - "radroots listing withdraw <file>", - "Withdraw a listing.", - "Seller", - false, - ), - meta( - "listing.get", - "ListingGet", - "radroots listing get <key>", - "Get a listing.", - "Any", - false, - ), - meta( - "listing.list", - "ListingList", - "radroots listing list", - "List listings.", - "Any", - true, - ), - meta( - "market.pull", - "MarketPull", - "radroots market pull", - "Pull market data into the local projection.", - "Any", - true, - ), - meta( - "market.search", - "MarketSearch", - "radroots market search <query>", - "Search local market projections.", - "Any", - true, - ), - meta( - "market.get", - "MarketGet", - "radroots market get <key>", - "Get market listing details.", - "Any", - false, - ), - meta( - "basket.create", - "BasketCreate", - "radroots basket create", - "Create a basket.", - "Buyer", - false, - ), - meta( - "basket.get", - "BasketGet", - "radroots basket get <basket-id>", - "Get a basket.", - "Buyer", - false, - ), - meta( - "basket.list", - "BasketList", - "radroots basket list", - "List baskets.", - "Buyer", - true, - ), - meta( - "basket.item.add", - "BasketItemAdd", - "radroots basket item add <basket-id>", - "Add an item to a basket.", - "Buyer", - false, - ), - meta( - "basket.item.update", - "BasketItemUpdate", - "radroots basket item update <basket-id>", - "Update a basket item.", - "Buyer", - false, - ), - meta( - "basket.item.remove", - "BasketItemRemove", - "radroots basket item remove <basket-id>", - "Remove a basket item.", - "Buyer", - false, - ), - meta( - "basket.quote", - "BasketQuote", - "radroots basket quote <basket-id>", - "Quote a basket.", - "Buyer", - false, - ), - meta( - "trade.proposal.submit", - "TradeProposalSubmit", - "radroots trade proposal submit <file>", - "Submit a trade proposal from a complete candidate draft.", - "Buyer", - false, - ), - meta( - "trade.revision.propose", - "TradeRevisionPropose", - "radroots trade revision propose <file>", - "Propose a complete replacement candidate for a trade.", - "Any", - false, - ), - meta( - "trade.candidate.decide", - "TradeCandidateDecide", - "radroots trade candidate decide <file>", - "Decide a candidate proposal.", - "Any", - false, - ), - meta( - "trade.cancellation.submit", - "TradeCancellationSubmit", - "radroots trade cancellation submit <file>", - "Submit a trade cancellation mutation.", - "Any", - false, - ), - meta( - "trade.operation.resume", - "TradeOperationResume", - "radroots trade operation resume <file>", - "Resume a prepared trade operation envelope.", - "Any", - false, - ), - meta( - "trade.get", - "TradeGet", - "radroots trade get <key>", - "Get SDK trade projection details.", - "Any", - false, - ), - meta( - "trade.list", - "TradeList", - "radroots trade list", - "List SDK trade projections.", - "Any", - true, - ), - meta( - "trade.evidence.refresh", - "TradeEvidenceRefresh", - "radroots trade evidence refresh <trade-id>", - "Refresh SDK trade evidence projections.", - "Any", - false, - ), - meta( - "trade.evidence.inspect", - "TradeEvidenceInspect", - "radroots trade evidence inspect <trade-id>", - "Inspect protected trade evidence metadata.", - "Any", - true, - ), - meta( - "trade.private_artifact.seal", - "TradePrivateArtifactSeal", - "radroots trade private-artifact seal <trade-id>", - "Seal a protected private trade artifact.", - "Any", - false, - ), - meta( - "trade.private_artifact.open", - "TradePrivateArtifactOpen", - "radroots trade private-artifact open <artifact-id>", - "Open a protected private trade artifact.", - "Any", - false, - ), - meta( - "trade.private_artifact.delete", - "TradePrivateArtifactDelete", - "radroots trade private-artifact delete <artifact-id>", - "Delete a protected private trade artifact.", - "Any", - false, - ), - meta( - "validation.status", - "ValidationStatus", - "radroots validation status", - "Inspect validation status.", - "Any", - false, - ), - meta( - "sync.status", - "SyncStatus", - "radroots sync status", - "Inspect synchronization status.", - "Any", - false, - ), - meta( - "sync.pull", - "SyncPull", - "radroots sync pull", - "Pull synchronization events.", - "Any", - true, - ), - meta( - "sync.push", - "SyncPush", - "radroots sync push", - "Push queued synchronization events.", - "Any", - true, - ), - meta( - "health.inspect", - "HealthInspect", - "radroots health inspect", - "Inspect runtime health.", - "Any", - false, - ), - meta( - "transport.capability.list", - "TransportCapabilityList", - "radroots transport capability list", - "List transport capabilities.", - "Any", - false, - ), - meta( - "transport.config.inspect", - "TransportConfigInspect", - "radroots transport config inspect", - "Inspect transport configuration.", - "Any", - false, - ), - meta( - "transport.config.update", - "TransportConfigUpdate", - "radroots transport config update --kind local-only", - "Update transport configuration.", - "Any", - false, - ), - meta( - "transport.status.inspect", - "TransportStatusInspect", - "radroots transport status inspect", - "Inspect transport status.", - "Any", - false, - ), - meta( - "transport.delivery.inspect", - "TransportDeliveryInspect", - "radroots transport delivery inspect", - "Inspect transport delivery.", - "Any", - false, - ), - meta( - "transport.delivery.retry", - "TransportDeliveryRetry", - "radroots transport delivery retry", - "Retry transport delivery.", - "Any", - true, - ), - meta( - "diagnostics.inspect", - "DiagnosticsInspect", - "radroots diagnostics inspect", - "Inspect runtime diagnostics.", - "Any", - false, - ), -]; - -const fn meta( - operation_id: &'static str, - variant: &'static str, - cli_path: &'static str, - description: &'static str, - role: &'static str, - supports_ndjson: bool, -) -> CliOperationMetadata { - CliOperationMetadata { - operation_id, - variant, - cli_path, - description, - role, - supports_ndjson, - } -} - -pub fn generate_cli_host() -> Result<(), String> { - let root = workspace_root()?; - let Some(cli_root) = cli_root(&root) else { - println!("CLI capsule is absent; skipping external host binding generation"); - return Ok(()); - }; - let registry = render_registry()?; - let target = render_target()?; - write_if_changed( - &cli_root.join("src/generated/runtime_contract_registry.rs"), - &registry, - )?; - write_if_changed( - &cli_root.join("src/generated/runtime_contract_target.rs"), - &target, - )?; - println!("generated CLI Runtime Contract V1 host bindings"); - Ok(()) -} - -pub fn check_cli_host() -> Result<(), String> { - let root = workspace_root()?; - let Some(cli_root) = cli_root(&root) else { - println!("CLI capsule is absent; skipping external host binding freshness check"); - return Ok(()); - }; - check_file( - &cli_root.join("src/generated/runtime_contract_registry.rs"), - &render_registry()?, - )?; - check_file( - &cli_root.join("src/generated/runtime_contract_target.rs"), - &render_target()?, - ) -} - -fn cli_root(sdk_root: &Path) -> Option<PathBuf> { - let candidate = sdk_root.join("../cli"); - if !candidate.join("Cargo.toml").is_file() { - return None; - } - candidate.canonicalize().ok() -} - -fn check_file(path: &Path, expected: &str) -> Result<(), String> { - let actual = std::fs::read_to_string(path) - .map_err(|error| format!("failed to read {}: {error}", path.display()))?; - if actual != expected { - return Err(format!( - "{} is stale; run `cargo xtask generate` from the SDK workspace", - path.display() - )); - } - Ok(()) -} - -fn render_registry() -> Result<String, String> { - validate_metadata()?; - let mut out = generated_header(); - out.push_str("pub const OPERATION_REGISTRY: &[OperationSpec] = &[\n"); - for (index, descriptor) in CATALOG.iter().enumerate() { - let metadata = metadata_for(descriptor.operation_id)?; - out.push_str(" OperationSpec {\n"); - out.push_str(&format!( - " runtime_operation_id: RuntimeOperationIdV1::{},\n", - metadata.variant - )); - out.push_str(&format!( - " descriptor: radroots_runtime_contract_v1::RUNTIME_OPERATION_DESCRIPTORS_V1[{index}],\n" - )); - out.push_str(&format!( - " operation_id: {:?},\n", - metadata.operation_id - )); - out.push_str(&format!(" cli_path: {:?},\n", metadata.cli_path)); - out.push_str(&format!( - " namespace: {:?},\n", - namespace(metadata.operation_id) - )); - out.push_str(&format!( - " mcp_tool: {:?},\n", - mcp_tool(metadata.operation_id) - )); - out.push_str(&format!( - " rust_request: {:?},\n", - format!("{}Request", metadata.variant) - )); - out.push_str(&format!( - " rust_result: {:?},\n", - format!("{}Result", metadata.variant) - )); - out.push_str(&format!( - " json_kind: {:?},\n", - metadata.operation_id - )); - out.push_str(&format!( - " description: {:?},\n", - metadata.description - )); - out.push_str(&format!( - " role: OperationRole::{},\n", - metadata.role - )); - out.push_str(" supports_json: true,\n"); - out.push_str(&format!( - " supports_ndjson: {},\n", - metadata.supports_ndjson - )); - out.push_str(" },\n"); - } - out.push_str("];\n"); - Ok(out) -} - -fn render_target() -> Result<String, String> { - validate_metadata()?; - let mut out = generated_header(); - out.push_str("target_operation_contracts! {\n"); - for descriptor in CATALOG { - let metadata = metadata_for(descriptor.operation_id)?; - let type_name = metadata.variant; - out.push_str(&format!( - " {} => ({}Request, {}Result, {:?}),\n", - type_name, type_name, type_name, metadata.operation_id - )); - } - out.push_str("}\n"); - Ok(out) -} - -fn generated_header() -> String { - "// @generated by radroots_sdk_xtask. Do not edit by hand.\n\n".to_owned() -} - -fn validate_metadata() -> Result<(), String> { - if CLI_OPERATION_METADATA.len() != CATALOG.len() { - return Err(format!( - "CLI metadata count {} does not match runtime descriptor count {}", - CLI_OPERATION_METADATA.len(), - CATALOG.len() - )); - } - for descriptor in CATALOG { - metadata_for(descriptor.operation_id)?; - } - for metadata in CLI_OPERATION_METADATA { - OperationId::parse(metadata.operation_id) - .map_err(|error| format!("invalid CLI metadata operation id: {error}"))?; - } - Ok(()) -} - -fn metadata_for(operation_id: OperationId) -> Result<&'static CliOperationMetadata, String> { - let operation_id = operation_id.as_str(); - CLI_OPERATION_METADATA - .iter() - .find(|metadata| metadata.operation_id == operation_id) - .ok_or_else(|| format!("missing CLI metadata for runtime operation {operation_id}")) -} - -fn namespace(operation_id: &str) -> &str { - operation_id.split('.').next().unwrap_or(operation_id) -} - -fn mcp_tool(operation_id: &str) -> String { - operation_id.replace('.', "_") -} - -#[cfg(test)] -mod tests { - #[test] - fn absent_external_cli_capsule_is_not_a_standalone_sdk_requirement() { - let root = tempfile::tempdir().expect("temp root"); - let sdk = root.path().join("sdk"); - std::fs::create_dir(&sdk).expect("sdk directory"); - assert!(super::cli_root(&sdk).is_none()); - - let cli = root.path().join("cli"); - std::fs::create_dir(&cli).expect("cli directory"); - std::fs::write(cli.join("Cargo.toml"), "[package]\nname = \"consumer\"\n") - .expect("consumer manifest"); - assert_eq!(super::cli_root(&sdk).as_deref(), Some(cli.as_path())); - } -} diff --git a/tools/sdk_xtask_import/src/contracts.rs b/tools/sdk_xtask_import/src/contracts.rs @@ -578,7 +578,6 @@ repository = "sdk-typescript" [packages] "radroots_core" = "@radroots/core-bindings" "radroots_event" = "@radroots/event-bindings" -"radroots_event_index" = "@radroots/event-index-bindings" "radroots_identity" = "@radroots/identity-bindings" "radroots_replica_schema" = "@radroots/replica-schema-bindings" "radroots_trade" = "@radroots/trade-bindings" @@ -623,13 +622,6 @@ crate_dir = "crates/event_bindings" package = "@radroots/event-bindings" package_dir = "packages/event-bindings" -[npm_packages.event_index] -kind = "bindings" -crate_name = "radroots_event_index_bindings" -crate_dir = "crates/event_index_bindings" -package = "@radroots/event-index-bindings" -package_dir = "packages/event-index-bindings" - [npm_packages.identity] kind = "bindings" crate_name = "radroots_identity_bindings" diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs @@ -49,16 +49,10 @@ const CORE_BINDINGS_PACKAGE_NAME: &str = "@radroots/core-bindings"; const EVENT_BINDINGS_PACKAGE_KEY: &str = "event"; const EVENT_BINDINGS_PACKAGE_NAME: &str = "@radroots/event-bindings"; -pub const DTO_PACKAGE_ROOTS: &[DtoPackageRootSet] = &[ - DtoPackageRootSet { - package_key: "core", - roots: core_roots, - }, - DtoPackageRootSet { - package_key: "event_index", - roots: event_index_roots, - }, -]; +pub const DTO_PACKAGE_ROOTS: &[DtoPackageRootSet] = &[DtoPackageRootSet { + package_key: "core", + roots: core_roots, +}]; pub const MANUAL_DESCRIPTOR_FAMILIES: &[ManualDescriptorFamily] = &[ ManualDescriptorFamily { @@ -77,11 +71,6 @@ pub const MANUAL_DESCRIPTOR_FAMILIES: &[ManualDescriptorFamily] = &[ reason: "fixed-size Rust arrays must preserve tuple semantics in TypeScript", }, ManualDescriptorFamily { - package_key: "event_index", - source_family: "checkpoint and index cursor fields", - reason: "custom deserialization and large integers require manual descriptor policy", - }, - ManualDescriptorFamily { package_key: "trade", source_family: "trade listing roots and package projection count fields", reason: "core aliases, source-owned event imports, and count-family numeric policy require explicit descriptors", @@ -105,11 +94,6 @@ pub const SDK_LOCAL_WRAPPER_ALLOWANCES: &[SdkLocalWrapperAllowance] = &[ reason: "schema operation inputs are generated package shapes rather than source-owned public DTO structs", }, SdkLocalWrapperAllowance { - package_key: "event_index", - shape_family: "RadrootsEventIndexShardId package alias", - reason: "source descriptors correctly describe the shard id newtype as a string, while package roots still need a stable named TypeScript alias", - }, - SdkLocalWrapperAllowance { package_key: "trade", shape_family: "marketplace, query, projection, sort, review, and backoffice DTO shapes", reason: "these are SDK package contract shapes layered over source-owned trade, events, and core DTOs", @@ -178,14 +162,6 @@ pub fn core_types_module() -> Result<DtoTypesModule, String> { )) } -pub fn event_index_types_module() -> Result<DtoTypesModule, String> { - let root_set = package_root_set("event_index") - .ok_or_else(|| "missing event-index DTO roots".to_owned())?; - let rendered = - render_registry_types(&root_set.registry(), &DtoRegistryRenderOptions::default())?; - Ok(with_event_index_sdk_wrappers(rendered)) -} - pub fn replica_schema_types_module() -> Result<DtoTypesModule, String> { render_registry_types( &radroots_replica_schema_bindings::dto_registry(), @@ -197,10 +173,6 @@ fn core_roots() -> Vec<RootDescriptor> { radroots_core_bindings::dto_roots() } -fn event_index_roots() -> Vec<RootDescriptor> { - radroots_event_index::dto::dto_roots().into_iter().collect() -} - fn core_import_options( mut options: DtoRegistryRenderOptions, ) -> Result<DtoRegistryRenderOptions, String> { @@ -442,37 +414,6 @@ fn declaration_name(declaration: &str) -> &str { .unwrap_or(declaration) } -fn with_event_index_sdk_wrappers(module: DtoTypesModule) -> DtoTypesModule { - let mut declarations = module - .body_ts() - .split("\n\n") - .filter(|declaration| !declaration.trim().is_empty()) - .map(str::to_owned) - .collect::<Vec<_>>(); - declarations.push(type_alias( - "RadrootsEventIndexShardId", - TypeScriptType::String, - )); - let order = [ - "RadrootsEventIndexShardId", - "RadrootsEventIndexIdRange", - "RadrootsEventIndexShardMetadata", - "RadrootsEventIndexManifest", - "RadrootsEventIndexShardCheckpoint", - "RadrootsEventIndexCheckpoint", - ]; - declarations.sort_by_key(|declaration| { - order - .iter() - .position(|name| *name == declaration_name(declaration)) - .unwrap_or(order.len()) - }); - DtoTypesModule::new( - module.imports_ts().unwrap_or_default(), - declarations.join("\n\n"), - ) -} - fn with_type_aliases_sorted( module: DtoTypesModule, aliases: impl IntoIterator<Item = String>, @@ -518,8 +459,6 @@ mod tests { include_str!("../../../packages/core-bindings/src/generated/types.ts"); const EVENT_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/event-bindings/src/generated/types.ts"); - const EVENT_INDEX_BINDINGS_TYPES_TS: &str = - include_str!("../../../packages/event-index-bindings/src/generated/types.ts"); const REPLICA_SCHEMA_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/replica-schema-bindings/src/generated/types.ts"); const TRADE_BINDINGS_TYPES_TS: &str = @@ -657,14 +596,6 @@ mod tests { "RadrootsWikiMergeRequest", "RadrootsWikiRedirect", ]; - const EVENT_INDEX_TYPE_INVENTORY: &[&str] = &[ - "RadrootsEventIndexShardId", - "RadrootsEventIndexIdRange", - "RadrootsEventIndexShardMetadata", - "RadrootsEventIndexManifest", - "RadrootsEventIndexShardCheckpoint", - "RadrootsEventIndexCheckpoint", - ]; const TRADE_TYPE_INVENTORY: &[&str] = &[ "RadrootsOperationalListingSubtotal", "RadrootsOperationalListingTotal", @@ -720,7 +651,6 @@ mod tests { #[test] fn package_roots_are_explicit_not_discovered() { assert!(package_root_set("core").is_some()); - assert!(package_root_set("event_index").is_some()); assert!(package_root_set("event").is_none()); assert!(package_root_set("trade").is_none()); } @@ -732,11 +662,6 @@ mod tests { .iter() .any(|family| family.source_family.contains("GeoJSON")) ); - assert!( - SDK_LOCAL_WRAPPER_ALLOWANCES - .iter() - .any(|allowance| { allowance.shape_family.contains("RadrootsEventIndexShardId") }) - ); assert!(SDK_LOCAL_WRAPPER_ALLOWANCES.iter().any(|allowance| { allowance .shape_family @@ -873,13 +798,6 @@ mod tests { } #[test] - fn event_index_type_inventory_matches_current_package_surface() { - let actual = type_inventory(EVENT_INDEX_BINDINGS_TYPES_TS); - - assert_eq!(actual, EVENT_INDEX_TYPE_INVENTORY); - } - - #[test] fn trade_type_inventory_matches_current_package_surface() { let actual = type_inventory(TRADE_BINDINGS_TYPES_TS); diff --git a/tools/sdk_xtask_import/src/generate.rs b/tools/sdk_xtask_import/src/generate.rs @@ -1,12 +1,11 @@ use crate::{ - cli_host, fs::workspace_root, output::package_outputs, package_matrix::validate_package_matrix, + fs::workspace_root, output::package_outputs, package_matrix::validate_package_matrix, package_metadata, }; pub fn generate_all() -> Result<(), String> { generate_ts()?; - generate_package_metadata()?; - cli_host::generate_cli_host() + generate_package_metadata() } pub fn generate_ts() -> Result<(), String> { diff --git a/tools/sdk_xtask_import/src/main.rs b/tools/sdk_xtask_import/src/main.rs @@ -2,7 +2,6 @@ mod api_qualification; mod architecture; mod bindings; mod check; -mod cli_host; mod contracts; mod coverage; mod coverage_policy; diff --git a/tools/sdk_xtask_import/src/output.rs b/tools/sdk_xtask_import/src/output.rs @@ -128,13 +128,6 @@ pub fn package_outputs() -> Result<Vec<PackageOutput>, String> { kinds_ts: Some(TsSource::Module(radroots_event_bindings::kinds_module())), }, PackageOutput { - spec: spec_by_key("event_index"), - types_ts: Some(TsSource::DtoRegistry(dto_roots::event_index_types_module()?)), - types_imports: Vec::new(), - constants_ts: None, - kinds_ts: None, - }, - PackageOutput { spec: spec_by_key("identity"), types_ts: None, types_imports: Vec::new(), @@ -321,7 +314,6 @@ mod tests { .collect::<Vec<_>>(); assert!(package_names.contains(&"@radroots/core-bindings")); assert!(package_names.contains(&"@radroots/event-bindings")); - assert!(package_names.contains(&"@radroots/event-index-bindings")); assert!(package_names.contains(&"@radroots/identity-bindings")); assert!(package_names.contains(&"@radroots/replica-schema-bindings")); assert!(package_names.contains(&"@radroots/trade-bindings")); diff --git a/tools/sdk_xtask_import/src/package_matrix.rs b/tools/sdk_xtask_import/src/package_matrix.rs @@ -20,7 +20,7 @@ pub struct WasmPackageSpec { pub out_dir: &'static str, } -pub const PACKAGE_SPECS: [PackageSpec; 6] = [ +pub const PACKAGE_SPECS: [PackageSpec; 5] = [ PackageSpec { key: "core", crate_name: "radroots_core_bindings", @@ -36,13 +36,6 @@ pub const PACKAGE_SPECS: [PackageSpec; 6] = [ package_dir: "packages/event-bindings", }, PackageSpec { - key: "event_index", - crate_name: "radroots_event_index_bindings", - crate_dir: "crates/event_index_bindings", - package_name: "@radroots/event-index-bindings", - package_dir: "packages/event-index-bindings", - }, - PackageSpec { key: "identity", crate_name: "radroots_identity_bindings", crate_dir: "crates/identity_bindings", @@ -172,7 +165,7 @@ mod tests { #[test] fn approved_package_count_is_stable() { - assert_eq!(package_specs().len(), 6); + assert_eq!(package_specs().len(), 5); assert_eq!(wasm_package_specs().len(), 3); }