lib

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

commit f106f77c5503a796bec922cf0820690b485d4406
parent f93da764fe9ace20c63285819057665193100e6a
Author: triesap <tyson@radroots.org>
Date:   Thu, 30 Jul 2026 07:20:29 +0000

trade: remove authority and signing dependencies

- replace actor-context canonicalization with validated public identity input
- remove the authority dependency and feature propagation from trade
- keep listing consistency checks deterministic and side-effect free
- enforce the authority-free boundary with package regression coverage

Diffstat:
MCargo.lock | 1-
Mcrates/trade/Cargo.toml | 2--
Mcrates/trade/src/operational_listing/draft.rs | 59++++++++++++++---------------------------------------------
Mcrates/trade/tests/package_boundary.rs | 14++++++++++++++
4 files changed, 28 insertions(+), 48 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -5111,7 +5111,6 @@ dependencies = [ "dto_bindgen_core", "hex", "nostr", - "radroots_authority", "radroots_core", "radroots_event", "radroots_event_codec", diff --git a/crates/trade/Cargo.toml b/crates/trade/Cargo.toml @@ -24,7 +24,6 @@ dto-bindgen = [ "dep:dto_bindgen_core", ] std = [ - "radroots_authority/std", "radroots_core/std", "radroots_event/std", "radroots_event_codec/std", @@ -56,7 +55,6 @@ serde_json = [ ] [dependencies] -radroots_authority = { workspace = true, default-features = false } radroots_core = { workspace = true, default-features = false } radroots_event = { workspace = true, default-features = false } radroots_event_codec = { workspace = true, default-features = false } diff --git a/crates/trade/src/operational_listing/draft.rs b/crates/trade/src/operational_listing/draft.rs @@ -10,9 +10,7 @@ use alloc::{format, vec::Vec}; #[cfg(feature = "std")] use std::vec::Vec; -use radroots_authority::RadrootsActorContext; use radroots_event::{ - contract::AuthorRole, envelope::kind::KIND_CLASSIFIED_LISTING, id::{ClassifiedListingAddress, InventoryBinId, ParseError}, listing::operational::OperationalListing, @@ -92,9 +90,6 @@ pub enum RadrootsOperationalListingEditError { InvalidFarmPubkey(PublicKeyError), InvalidClassifiedListingAddress(ParseError), InvalidModel(OperationalListingValidationError), - ActorRoleUnsatisfied { - required_role: AuthorRole, - }, FarmPubkeyMismatch { expected_pubkey: PublicKey, actual_pubkey: PublicKey, @@ -119,10 +114,6 @@ impl fmt::Display for RadrootsOperationalListingEditError { Self::InvalidModel(error) => { write!(f, "invalid listing edit model: {error}") } - Self::ActorRoleUnsatisfied { required_role } => write!( - f, - "listing edit actor does not satisfy required role {required_role:?}" - ), Self::FarmPubkeyMismatch { .. } => { f.write_str("listing edit farm pubkey does not match seller") } @@ -166,17 +157,15 @@ fn listing_addr(kind: u32, seller_pubkey: &PublicKey, d_tag: &str) -> Classified .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( - actor: &RadrootsActorContext, + seller_pubkey: PublicKey, mut document: RadrootsOperationalListingEditDocumentV1, ) -> Result<RadrootsOperationalListingCanonicalEdit, RadrootsOperationalListingEditError> { - if !actor.satisfies(AuthorRole::Seller) { - return Err(RadrootsOperationalListingEditError::ActorRoleUnsatisfied { - required_role: AuthorRole::Seller, - }); - } - - let seller_pubkey = *actor.pubkey(); let farm_pubkey = document.listing.farm.pubkey.as_str(); if farm_pubkey.is_empty() { document.listing.farm.pubkey = seller_pubkey.to_hex(); @@ -187,10 +176,8 @@ pub fn canonicalize_operational_listing_edit( #[cfg(test)] mod tests { - use radroots_authority::RadrootsActorContext; use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ - contract::AuthorRole, envelope::kind::KIND_CLASSIFIED_LISTING, farm::FarmRef, id::{ClassifiedListingAddress, DTag, InventoryBinId}, @@ -273,12 +260,8 @@ mod tests { } } - fn seller_actor() -> RadrootsActorContext { - RadrootsActorContext::explicit_pubkey(SELLER, [AuthorRole::Seller]).expect("actor") - } - - fn buyer_actor() -> RadrootsActorContext { - RadrootsActorContext::explicit_pubkey(SELLER, [AuthorRole::Buyer]).expect("actor") + fn seller_pubkey() -> PublicKey { + PublicKey::from_hex(SELLER).expect("seller public key") } #[test] @@ -297,7 +280,7 @@ mod tests { let document: RadrootsOperationalListingEditDocumentV1 = serde_json::from_str(&json).expect("deserialize document"); - let canonical = canonicalize_operational_listing_edit(&seller_actor(), document) + let canonical = canonicalize_operational_listing_edit(seller_pubkey(), document) .expect("canonical draft"); assert_eq!(canonical.seller_pubkey().to_hex(), SELLER); @@ -342,7 +325,7 @@ mod tests { listing.farm.pubkey.clear(); let document = RadrootsOperationalListingEditDocumentV1::new(listing); - let canonical = canonicalize_operational_listing_edit(&seller_actor(), document) + let canonical = canonicalize_operational_listing_edit(seller_pubkey(), document) .expect("canonical draft"); assert_eq!(canonical.seller_pubkey().to_hex(), SELLER); @@ -354,26 +337,12 @@ mod tests { } #[test] - fn canonicalize_operational_listing_edit_rejects_non_seller_actor() { - let document = RadrootsOperationalListingEditDocumentV1::new(listing()); - - let error = canonicalize_operational_listing_edit(&buyer_actor(), document).unwrap_err(); - - assert_eq!( - error, - RadrootsOperationalListingEditError::ActorRoleUnsatisfied { - required_role: AuthorRole::Seller - } - ); - } - - #[test] fn canonicalize_operational_listing_edit_rejects_mismatched_farm_pubkey() { let mut listing = listing(); listing.farm.pubkey = OTHER.to_string(); let document = RadrootsOperationalListingEditDocumentV1::new(listing); - let error = canonicalize_operational_listing_edit(&seller_actor(), document).unwrap_err(); + let error = canonicalize_operational_listing_edit(seller_pubkey(), document).unwrap_err(); assert!(matches!( error, @@ -387,7 +356,7 @@ mod tests { listing.farm.pubkey = "bad".to_string(); let document = RadrootsOperationalListingEditDocumentV1::new(listing); - let error = canonicalize_operational_listing_edit(&seller_actor(), document).unwrap_err(); + let error = canonicalize_operational_listing_edit(seller_pubkey(), document).unwrap_err(); assert!(matches!( error, @@ -452,7 +421,7 @@ mod tests { listing.primary_bin_id = bin_id("bin-2"); let document = RadrootsOperationalListingEditDocumentV1::new(listing); - let error = canonicalize_operational_listing_edit(&seller_actor(), document).unwrap_err(); + let error = canonicalize_operational_listing_edit(seller_pubkey(), document).unwrap_err(); assert_eq!( error, @@ -487,7 +456,7 @@ mod tests { listing.bins.push(listing.bins[0].clone()); let document = RadrootsOperationalListingEditDocumentV1::new(listing); - let error = canonicalize_operational_listing_edit(&seller_actor(), document).unwrap_err(); + let error = canonicalize_operational_listing_edit(seller_pubkey(), document).unwrap_err(); assert_eq!( error, diff --git a/crates/trade/tests/package_boundary.rs b/crates/trade/tests/package_boundary.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use radroots_trade::{evidence as _, model as _, reducer as _, validation as _, workflow as _}; const MANIFEST: &str = include_str!("../Cargo.toml"); +const DRAFT: &str = include_str!("../src/operational_listing/draft.rs"); const IDENTITY: &str = include_str!("../src/identity.rs"); const MODEL: &str = include_str!("../src/model.rs"); const ROOT: &str = include_str!("../src/lib.rs"); @@ -72,6 +73,19 @@ fn protocol_trade_id_is_singular_and_business_order_id_is_distinct() { assert!(MODEL.contains("No conversion exists between them.")); } +#[test] +fn trade_canonicalization_accepts_validated_identity_without_authority_or_signing() { + let dependencies = table_keys(MANIFEST, "[dependencies]"); + + assert!(!dependencies.contains("radroots_authority")); + assert!(!MANIFEST.contains("radroots_authority/std")); + assert!(!DRAFT.contains("radroots_authority")); + assert!(!DRAFT.contains("RadrootsActorContext")); + assert!(!DRAFT.contains("ActorRoleUnsatisfied")); + assert!(DRAFT.contains("seller_pubkey: PublicKey")); + assert!(DRAFT.contains("performs no signing or authorization")); +} + fn table_keys<'a>(manifest: &'a str, heading: &str) -> BTreeSet<&'a str> { let Some((_, table)) = manifest.split_once(heading) else { return BTreeSet::new();