lib

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

commit 06151e82346a8fb359fc79ba062470aeb49e039e
parent bc3ec48e5af912884a900f78c2f6349486a62b94
Author: triesap <tyson@radroots.org>
Date:   Thu, 30 Jul 2026 06:59:01 +0000

trade: align package manifest and module root

- establish the approved trade module skeleton under the frozen package identity
- retire expired Nostr and transport development edges and their tier exceptions
- keep conformance signing protocol-neutral through the canonical event wire
- satisfy strict package linting after canonical event identifiers became Copy

Diffstat:
MCargo.lock | 2--
Mcontracts/releases/package_tiers.toml | 23-----------------------
Mcrates/trade/Cargo.toml | 7+------
Acrates/trade/src/evidence.rs | 1+
Mcrates/trade/src/identity.rs | 2+-
Mcrates/trade/src/lib.rs | 4++++
Acrates/trade/src/model.rs | 1+
Mcrates/trade/src/operational_listing/draft.rs | 2+-
Mcrates/trade/src/operational_listing/mutation.rs | 41+++++++++++++++++++++++++++++++----------
Mcrates/trade/src/operational_listing/validation.rs | 10+++++++---
Acrates/trade/src/reducer.rs | 1+
Acrates/trade/src/validation.rs | 1+
Mcrates/trade/src/workflow.rs | 153++++++++++++++++++++++++++++++++++++-------------------------------------------
Mcrates/trade/tests/operational_listing_conformance.rs | 4----
Acrates/trade/tests/package_boundary.rs | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 200 insertions(+), 133 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -5117,9 +5117,7 @@ dependencies = [ "radroots_event_codec", "radroots_event_store", "radroots_identity", - "radroots_nostr", "radroots_test_fixtures", - "radroots_transport", "serde", "serde_json", "sha2", diff --git a/contracts/releases/package_tiers.toml b/contracts/releases/package_tiers.toml @@ -53,26 +53,3 @@ packages = ["radroots_sdk"] id = "facade" rank = 6 packages = ["radroots"] - -# These exact development-only edges predate the final trade boundary and are -# removed by the manifest-alignment checkpoint. Normal, build, target, or -# feature-broadened forms remain forbidden. -[[temporary_exception]] -owner = "radroots_trade" -dependency = "radroots_nostr" -kind = "dev" -target = "all" -features = ["events", "std"] -uses_default_features = false -removal_step = 88 -rationale = "Existing trade conformance tests consume Nostr vectors until the Step 088 package boundary alignment." - -[[temporary_exception]] -owner = "radroots_trade" -dependency = "radroots_transport" -kind = "dev" -target = "all" -features = [] -uses_default_features = false -removal_step = 88 -rationale = "Existing trade workflow tests use transport test types until the Step 088 package boundary alignment." diff --git a/crates/trade/Cargo.toml b/crates/trade/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true authors.workspace = true rust-version.workspace = true license.workspace = true -description = "Trade domain model for Radroots" +description = "Deterministic trade validation, reduction, evidence, and workflow planning for Radroots" repository.workspace = true homepage.workspace = true documentation = "https://docs.rs/radroots_trade" @@ -82,11 +82,6 @@ thiserror = { workspace = true } [dev-dependencies] nostr = { workspace = true } radroots_test_fixtures = { workspace = true } -radroots_nostr = { workspace = true, default-features = false, features = [ - "std", - "events", -] } -radroots_transport = { workspace = true, default-features = false } sqlx = { workspace = true, default-features = false, features = [ "runtime-tokio", "sqlite-bundled", diff --git a/crates/trade/src/evidence.rs b/crates/trade/src/evidence.rs @@ -0,0 +1 @@ +//! Evidence types used by deterministic trade reduction. diff --git a/crates/trade/src/identity.rs b/crates/trade/src/identity.rs @@ -132,7 +132,7 @@ impl RadrootsTradeLocatorCandidate { pub fn locator(&self) -> RadrootsTradeLocator { RadrootsTradeLocator { trade_id: self.trade_id.clone(), - root_event_id: Some(self.root_event_id.clone()), + root_event_id: Some(self.root_event_id), listing_addr: Some(self.listing_addr.clone()), buyer_pubkey: Some(self.buyer_pubkey), seller_pubkey: Some(self.seller_pubkey), diff --git a/crates/trade/src/lib.rs b/crates/trade/src/lib.rs @@ -5,9 +5,13 @@ extern crate alloc; #[cfg(feature = "dto-bindgen")] pub mod dto; +pub mod evidence; pub mod identity; +pub mod model; pub mod operational_listing; pub mod prelude; +pub mod reducer; +pub mod validation; #[cfg(feature = "serde_json")] pub mod validation_receipt; pub mod workflow; diff --git a/crates/trade/src/model.rs b/crates/trade/src/model.rs @@ -0,0 +1 @@ +//! Native trade-domain models and validated value types. diff --git a/crates/trade/src/operational_listing/draft.rs b/crates/trade/src/operational_listing/draft.rs @@ -5,7 +5,7 @@ use core::fmt; #[cfg(not(feature = "std"))] -use alloc::{format, string::ToString, vec::Vec}; +use alloc::{format, vec::Vec}; #[cfg(feature = "std")] use std::vec::Vec; diff --git a/crates/trade/src/operational_listing/mutation.rs b/crates/trade/src/operational_listing/mutation.rs @@ -169,9 +169,11 @@ pub fn build_operational_listing_mutation_draft( #[cfg(test)] mod tests { + use nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp}; use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ contract::validate_event_contract_shape, + draft::EventDraft, envelope::kind::KIND_CLASSIFIED_LISTING, farm::FarmRef, farm::resource_area::ResourceAreaRef, @@ -181,12 +183,10 @@ mod tests { OperationalListingDeliveryMethod, OperationalListingProduct, OperationalListingPublicLocation, OperationalListingStatus, }, + wire::Nip01EventWire, }; use radroots_event_codec::verification::verify_nip01_event; use radroots_identity::PublicKey; - use radroots_nostr::prelude::{ - RadrootsNostrKeys, RadrootsNostrSecretKey, radroots_nostr_sign_frozen_draft, - }; use radroots_test_fixtures::{FIXTURE_ALICE_PUBLIC_KEY_HEX, FIXTURE_ALICE_SECRET_KEY_HEX}; use crate::operational_listing::draft::RadrootsOperationalListingCanonicalEdit; @@ -208,6 +208,31 @@ mod tests { InventoryBinId::parse(raw).expect("bin id") } + fn sign_draft(draft: &EventDraft) -> radroots_event::envelope::EventEnvelope { + let keys = Keys::parse(FIXTURE_ALICE_SECRET_KEY_HEX).expect("fixture signing key"); + assert_eq!(keys.public_key().to_hex(), draft.expected_pubkey().to_hex()); + let tags = draft + .tags_as_vec() + .into_iter() + .map(|tag| Tag::parse(tag).expect("draft tag")) + .collect::<Vec<_>>(); + let event = EventBuilder::new( + Kind::Custom(u16::try_from(draft.kind_u32()).expect("NIP-01 kind")), + draft.content(), + ) + .tags(tags) + .allow_self_tagging() + .custom_created_at(Timestamp::from_secs(draft.created_at_u64())) + .sign_with_keys(&keys) + .expect("signed listing event"); + assert_eq!(event.id.to_hex(), draft.expected_event_id_hex()); + let raw_json = event.as_json(); + Nip01EventWire::parse_json(raw_json.as_str()) + .expect("canonical event wire") + .into_envelope() + .expect("event envelope") + } + fn listing() -> OperationalListing { OperationalListing { d_tag: d_tag("AAAAAAAAAAAAAAAAAAAAAg"), @@ -472,14 +497,10 @@ mod tests { let draft = build_operational_listing_mutation_draft(&publish, 1_700_000_000).expect("draft"); - let keys = RadrootsNostrKeys::new( - RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX) - .expect("fixture secret key"), - ); - let signed = radroots_nostr_sign_frozen_draft(&keys, &draft).expect("signed listing event"); - validate_event_contract_shape(signed.envelope(), OPERATIONAL_LISTING_PUBLISHED_CONTRACT_ID) + let signed = sign_draft(&draft); + validate_event_contract_shape(&signed, OPERATIONAL_LISTING_PUBLISHED_CONTRACT_ID) .expect("operational listing contract"); - let verified = verify_nip01_event(signed.envelope().clone()).expect("verified listing"); + let verified = verify_nip01_event(signed).expect("verified listing"); let validated = validate_operational_listing_event(&verified).expect("validated listing"); assert_eq!(validated.seller_pubkey, SELLER); diff --git a/crates/trade/src/operational_listing/validation.rs b/crates/trade/src/operational_listing/validation.rs @@ -233,7 +233,7 @@ mod tests { OperationalListingValidationError, validate_operational_listing_event, validate_operational_listing_model, }; - use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; + use nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp}; use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ envelope::EventEnvelope, @@ -246,10 +246,10 @@ mod tests { OperationalListingDeliveryMethod, OperationalListingProduct, OperationalListingPublicLocation, }, + wire::Nip01EventWire, }; use radroots_event_codec::verification::{RadrootsSignatureVerifiedEvent, verify_nip01_event}; use radroots_identity::PublicKey; - use radroots_nostr::prelude::radroots_event_from_nostr; use radroots_test_fixtures::{ FIXTURE_ALICE_PUBLIC_KEY_HEX, FIXTURE_ALICE_SECRET_KEY_HEX, FIXTURE_BOB_PUBLIC_KEY_HEX, FIXTURE_BOB_SECRET_KEY_HEX, @@ -457,7 +457,11 @@ mod tests { .custom_created_at(Timestamp::from_secs(1)) .sign_with_keys(&keys) .expect("signed test event"); - let envelope = radroots_event_from_nostr(&event).expect("event adapter"); + let raw_json = event.as_json(); + let envelope = Nip01EventWire::parse_json(raw_json.as_str()) + .expect("canonical event wire") + .into_envelope() + .expect("event envelope"); verify_nip01_event(envelope).expect("verified test event") } diff --git a/crates/trade/src/reducer.rs b/crates/trade/src/reducer.rs @@ -0,0 +1 @@ +//! Deterministic trade reduction and conflict analysis. diff --git a/crates/trade/src/validation.rs b/crates/trade/src/validation.rs @@ -0,0 +1 @@ +//! Validation for canonical trade-domain inputs. diff --git a/crates/trade/src/workflow.rs b/crates/trade/src/workflow.rs @@ -291,11 +291,10 @@ impl RadrootsTradeProjectionV1 { self.unsupported_mutation_ids.sort(); self.unsupported_mutation_ids.dedup(); self.agreement_claims - .sort_by(|left, right| left.claim_mutation_id.cmp(&right.claim_mutation_id)); + .sort_by_key(|left| left.claim_mutation_id); self.issues.sort(); self.issues.dedup(); - self.attestations - .sort_by(|left, right| left.event_id.cmp(&right.event_id)); + self.attestations.sort_by_key(|left| left.event_id); self.projection_digest = projection_digest(self); } } @@ -433,11 +432,11 @@ struct CancellationRecord { } pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTradeProjectionV1 { - let mut projection = RadrootsTradeProjectionV1::empty(input.trade_id.clone()); + let mut projection = RadrootsTradeProjectionV1::empty(input.trade_id); let mut mutations = BTreeMap::<MutationId, TradeMutationEnvelopeV1>::new(); for record in input.mutations { - let mutation_id = match record.mutation.mutation_id.clone() { + let mutation_id = match record.mutation.mutation_id { Some(mutation_id) => mutation_id, None => { projection @@ -447,13 +446,11 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra } }; if record.mutation.schema_version != RADROOTS_TRADE_SCHEMA_VERSION { - projection - .unsupported_mutation_ids - .push(mutation_id.clone()); + projection.unsupported_mutation_ids.push(mutation_id); projection .issues .push(RadrootsTradeReducerIssueV1::UnsupportedSchema { - mutation_id: mutation_id.clone(), + mutation_id, schema_version: record.mutation.schema_version, }); mutations.entry(mutation_id).or_insert(record.mutation); @@ -463,7 +460,7 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra projection .issues .push(RadrootsTradeReducerIssueV1::InvalidMutation { - mutation_id: Some(mutation_id.clone()), + mutation_id: Some(mutation_id), reason: error.to_string(), }); continue; @@ -471,9 +468,7 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra if record.mutation.trade_id != input.trade_id { projection .issues - .push(RadrootsTradeReducerIssueV1::TradeIdentityMismatch { - mutation_id: mutation_id.clone(), - }); + .push(RadrootsTradeReducerIssueV1::TradeIdentityMismatch { mutation_id }); continue; } mutations.entry(mutation_id).or_insert(record.mutation); @@ -488,17 +483,17 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra for (mutation_id, mutation) in &mutations { if matches!(mutation.body, TradeMutationBodyV1::Proposal { .. }) { - root_proposals.push(mutation_id.clone()); + root_proposals.push(*mutation_id); } for parent in &mutation.parent_mutation_ids { - referenced_parents.insert(parent.clone()); + referenced_parents.insert(*parent); if !mutations.contains_key(parent) { - projection.missing_parent_ids.push(parent.clone()); + projection.missing_parent_ids.push(*parent); projection .issues .push(RadrootsTradeReducerIssueV1::MissingParent { - mutation_id: mutation_id.clone(), - parent_mutation_id: parent.clone(), + mutation_id: *mutation_id, + parent_mutation_id: *parent, }); } } @@ -506,9 +501,9 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra TradeMutationBodyV1::Proposal { candidate } | TradeMutationBodyV1::RevisionProposal { candidate } => { candidates_by_proposal.insert( - mutation_id.clone(), + *mutation_id, CandidateRecord { - proposal_mutation_id: mutation_id.clone(), + proposal_mutation_id: *mutation_id, author_pubkey: mutation.author_pubkey, candidate: candidate.clone(), }, @@ -521,10 +516,10 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra target_claim_mutation_id, reason: _, } => cancellations.push(CancellationRecord { - mutation_id: mutation_id.clone(), + mutation_id: *mutation_id, parent_mutation_ids: mutation.parent_mutation_ids.clone(), - target_candidate_id: target_candidate_id.clone(), - target_claim_mutation_id: target_claim_mutation_id.clone(), + target_candidate_id: *target_candidate_id, + target_claim_mutation_id: *target_claim_mutation_id, }), } } @@ -542,9 +537,9 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra decision, } => { decisions_by_proposal - .entry(proposal_mutation_id.clone()) + .entry(*proposal_mutation_id) .or_default() - .push(mutation_id.clone()); + .push(*mutation_id); apply_decision( DecisionApplication { mutation_id, @@ -597,7 +592,7 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra projection .issues .push(RadrootsTradeReducerIssueV1::DoubleAcceptance { - proposal_mutation_id: proposal_mutation_id.clone(), + proposal_mutation_id: *proposal_mutation_id, }); set_conflict( &mut projection.conflict_state, @@ -608,7 +603,7 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra projection .issues .push(RadrootsTradeReducerIssueV1::DecisionConflict { - proposal_mutation_id: proposal_mutation_id.clone(), + proposal_mutation_id: *proposal_mutation_id, }); set_conflict( &mut projection.conflict_state, @@ -653,7 +648,7 @@ pub fn reduce_trade_records(input: RadrootsTradeReductionInputV1) -> RadrootsTra projection .issues .push(RadrootsTradeReducerIssueV1::PrivateTermsUnavailable { - candidate_id: claim.candidate_id.clone(), + candidate_id: claim.candidate_id, }); } } @@ -678,14 +673,12 @@ fn apply_decision( decision, } = application; let Some(candidate_record) = candidates_by_proposal.get(proposal_mutation_id) else { - projection - .missing_proposal_ids - .push(proposal_mutation_id.clone()); + projection.missing_proposal_ids.push(*proposal_mutation_id); projection .issues .push(RadrootsTradeReducerIssueV1::MissingProposal { - decision_mutation_id: mutation_id.clone(), - proposal_mutation_id: proposal_mutation_id.clone(), + decision_mutation_id: *mutation_id, + proposal_mutation_id: *proposal_mutation_id, }); return; }; @@ -693,8 +686,8 @@ fn apply_decision( projection .issues .push(RadrootsTradeReducerIssueV1::CandidateIdMismatch { - decision_mutation_id: mutation_id.clone(), - proposal_mutation_id: proposal_mutation_id.clone(), + decision_mutation_id: *mutation_id, + proposal_mutation_id: *proposal_mutation_id, }); set_conflict( &mut projection.conflict_state, @@ -706,7 +699,7 @@ fn apply_decision( projection .issues .push(RadrootsTradeReducerIssueV1::DecisionAuthorMismatch { - decision_mutation_id: mutation_id.clone(), + decision_mutation_id: *mutation_id, }); return; } @@ -714,8 +707,8 @@ fn apply_decision( projection .issues .push(RadrootsTradeReducerIssueV1::DecisionParentMissing { - decision_mutation_id: mutation_id.clone(), - proposal_mutation_id: proposal_mutation_id.clone(), + decision_mutation_id: *mutation_id, + proposal_mutation_id: *proposal_mutation_id, }); set_conflict( &mut projection.conflict_state, @@ -730,7 +723,7 @@ fn apply_decision( projection .issues .push(RadrootsTradeReducerIssueV1::MissingSellerReservation { - decision_mutation_id: mutation_id.clone(), + decision_mutation_id: *mutation_id, }); return; }; @@ -742,11 +735,11 @@ fn apply_decision( projection, ) { claims.insert( - mutation_id.clone(), + *mutation_id, RadrootsTradeAgreementClaimV1 { - claim_mutation_id: mutation_id.clone(), - proposal_mutation_id: candidate_record.proposal_mutation_id.clone(), - candidate_id: candidate_id.clone(), + claim_mutation_id: *mutation_id, + proposal_mutation_id: candidate_record.proposal_mutation_id, + candidate_id: *candidate_id, candidate_author_pubkey: candidate_record.author_pubkey, accepted_by_pubkey: mutation.author_pubkey, reservation_commitment: reservation.assertion_commitment.clone(), @@ -781,7 +774,7 @@ fn validate_reservation( projection .issues .push(RadrootsTradeReducerIssueV1::ReservationCandidateMismatch { - decision_mutation_id: decision_mutation_id.clone(), + decision_mutation_id: *decision_mutation_id, }); valid = false; } @@ -789,7 +782,7 @@ fn validate_reservation( projection .issues .push(RadrootsTradeReducerIssueV1::ReservationAuthorityMismatch { - decision_mutation_id: decision_mutation_id.clone(), + decision_mutation_id: *decision_mutation_id, }); set_conflict( &mut projection.conflict_state, @@ -801,7 +794,7 @@ fn validate_reservation( projection .issues .push(RadrootsTradeReducerIssueV1::ReservationLineMismatch { - decision_mutation_id: decision_mutation_id.clone(), + decision_mutation_id: *decision_mutation_id, }); return false; } @@ -815,7 +808,7 @@ fn validate_reservation( projection .issues .push(RadrootsTradeReducerIssueV1::ReservationLineMismatch { - decision_mutation_id: decision_mutation_id.clone(), + decision_mutation_id: *decision_mutation_id, }); valid = false; break; @@ -864,15 +857,15 @@ fn apply_agreement_state( && claims.contains_key(target_claim_id) { if cancellation.parent_mutation_ids.contains(target_claim_id) { - projection.cancelled_claim_ids.push(target_claim_id.clone()); - if projection.active_agreement_claim_ids == [target_claim_id.clone()] { + projection.cancelled_claim_ids.push(*target_claim_id); + if projection.active_agreement_claim_ids == [*target_claim_id] { projection.agreement_state = RadrootsTradeAgreementStateV1::Cancelled; } } else { projection .issues .push(RadrootsTradeReducerIssueV1::CancellationConflict { - cancellation_mutation_id: cancellation.mutation_id.clone(), + cancellation_mutation_id: cancellation.mutation_id, }); projection.agreement_state = RadrootsTradeAgreementStateV1::Contested; set_conflict( @@ -939,11 +932,11 @@ fn ancestors_of( let mut ancestors = BTreeSet::new(); if let Some(mutation) = mutations.get(mutation_id) { for parent in &mutation.parent_mutation_ids { - ancestors.insert(parent.clone()); + ancestors.insert(*parent); ancestors.extend(ancestors_of(parent, mutations, memo)); } } - memo.insert(mutation_id.clone(), ancestors.clone()); + memo.insert(*mutation_id, ancestors.clone()); ancestors } @@ -977,7 +970,7 @@ fn reduce_private_terms_state( ) -> RadrootsTradePrivateTermsStateV1 { let private_terms_by_candidate = private_terms .iter() - .map(|record| (record.candidate_id.clone(), record.state)) + .map(|record| (record.candidate_id, record.state)) .collect::<BTreeMap<_, _>>(); let mut required_states = Vec::new(); for claim in &projection.agreement_claims { @@ -1061,7 +1054,7 @@ fn declined_candidate_ids( candidate_id, decision: TradeDecisionV1::Declined { .. }, .. - } => Some(candidate_id.clone()), + } => Some(*candidate_id), _ => None, }) .collect() @@ -1251,7 +1244,7 @@ mod tests { reservation_id: dtag(&format!("reservation-{marker}")), inventory_authority_id: candidate.seller_pubkey, inventory_epoch: 42, - candidate_id: candidate.candidate_id.clone().unwrap(), + candidate_id: candidate.candidate_id.unwrap(), commitments: candidate .lines .iter() @@ -1272,7 +1265,7 @@ mod tests { proposal: &TradeMutationEnvelopeV1, marker: char, ) -> TradeMutationEnvelopeV1 { - let proposal_id = proposal.mutation_id.clone().unwrap(); + let proposal_id = proposal.mutation_id.unwrap(); let candidate = match &proposal.body { TradeMutationBodyV1::Proposal { candidate } | TradeMutationBodyV1::RevisionProposal { candidate } => candidate.clone(), @@ -1287,13 +1280,13 @@ mod tests { buyer_pubkey: pubkey('a'), seller_pubkey: pubkey('b'), farm_id: dtag("farm-1"), - parent_mutation_ids: vec![proposal_id.clone()], + parent_mutation_ids: vec![proposal_id], author_pubkey: pubkey('b'), counterparty_pubkey: pubkey('a'), authored_at_unix_s: u64::from(marker), body: TradeMutationBodyV1::Decision { proposal_mutation_id: proposal_id, - candidate_id: candidate.candidate_id.clone().unwrap(), + candidate_id: candidate.candidate_id.unwrap(), decision: TradeDecisionV1::Accepted { reservation_assertion: Some(reservation(&candidate, marker)), }, @@ -1304,7 +1297,7 @@ mod tests { } fn declined_decision(proposal: &TradeMutationEnvelopeV1) -> TradeMutationEnvelopeV1 { - let proposal_id = proposal.mutation_id.clone().unwrap(); + let proposal_id = proposal.mutation_id.unwrap(); let candidate = match &proposal.body { TradeMutationBodyV1::Proposal { candidate } | TradeMutationBodyV1::RevisionProposal { candidate } => candidate.clone(), @@ -1319,13 +1312,13 @@ mod tests { buyer_pubkey: pubkey('a'), seller_pubkey: pubkey('b'), farm_id: dtag("farm-1"), - parent_mutation_ids: vec![proposal_id.clone()], + parent_mutation_ids: vec![proposal_id], author_pubkey: pubkey('b'), counterparty_pubkey: pubkey('a'), authored_at_unix_s: 102, body: TradeMutationBodyV1::Decision { proposal_mutation_id: proposal_id, - candidate_id: candidate.candidate_id.clone().unwrap(), + candidate_id: candidate.candidate_id.unwrap(), decision: TradeDecisionV1::Declined { reason: "unavailable".to_string(), }, @@ -1366,7 +1359,7 @@ mod tests { root: &TradeMutationEnvelopeV1, proposal: &TradeMutationEnvelopeV1, ) -> TradeMutationEnvelopeV1 { - let proposal_id = proposal.mutation_id.clone().unwrap(); + let proposal_id = proposal.mutation_id.unwrap(); let candidate = match &proposal.body { TradeMutationBodyV1::RevisionProposal { candidate } => candidate.clone(), _ => unreachable!(), @@ -1380,13 +1373,13 @@ mod tests { buyer_pubkey: pubkey('a'), seller_pubkey: pubkey('b'), farm_id: dtag("farm-1"), - parent_mutation_ids: vec![proposal_id.clone()], + parent_mutation_ids: vec![proposal_id], author_pubkey: pubkey('b'), counterparty_pubkey: pubkey('a'), authored_at_unix_s: 201, body: TradeMutationBodyV1::RevisionDecision { proposal_mutation_id: proposal_id, - candidate_id: candidate.candidate_id.clone().unwrap(), + candidate_id: candidate.candidate_id.unwrap(), decision: TradeDecisionV1::Accepted { reservation_assertion: Some(reservation(&candidate, '9')), }, @@ -1425,7 +1418,7 @@ mod tests { } fn root_id(envelope: &TradeMutationEnvelopeV1) -> MutationId { - envelope.mutation_id.clone().unwrap() + envelope.mutation_id.unwrap() } fn record(mutation: TradeMutationEnvelopeV1) -> RadrootsTradeMutationRecordV1 { @@ -1451,7 +1444,7 @@ mod tests { fn candidate_cancellation(root: &TradeMutationEnvelopeV1) -> TradeMutationEnvelopeV1 { let candidate_id = match &root.body { TradeMutationBodyV1::Proposal { candidate } => { - candidate.candidate_id.clone().expect("candidate id") + candidate.candidate_id.expect("candidate id") } _ => unreachable!(), }; @@ -1543,7 +1536,7 @@ mod tests { let proposal = proposal(); let mut decision = accepted_decision(&proposal, '1'); let missing_parent = MutationId::parse(hex_64('e')).unwrap(); - decision.parent_mutation_ids = vec![missing_parent.clone()]; + decision.parent_mutation_ids = vec![missing_parent]; let decision = canonical_trade_mutation_content(TradeMutationEnvelopeV1 { mutation_id: None, ..decision @@ -1891,7 +1884,7 @@ mod tests { let root = recanonicalize(root); let candidate_id = match &root.body { TradeMutationBodyV1::Proposal { candidate } => { - candidate.candidate_id.clone().expect("candidate id") + candidate.candidate_id.expect("candidate id") } _ => unreachable!(), }; @@ -1913,7 +1906,7 @@ mod tests { let mut input = RadrootsTradeReductionInputV1::new(trade_id()); input.mutations = vec![record(root.clone()), record(decision.clone())]; input.private_terms = vec![RadrootsTradePrivateTermsEvidenceV1 { - candidate_id: candidate_id.clone(), + candidate_id, state, }]; assert_eq!(reduce_trade_records(input).private_terms_state, expected); @@ -2014,7 +2007,7 @@ mod tests { revision_decline.root_mutation_id = Some(root_id(&root)); revision_decline.body = TradeMutationBodyV1::RevisionDecision { proposal_mutation_id, - candidate_id: candidate_id.clone(), + candidate_id, decision, }; let revision_decline = recanonicalize(revision_decline); @@ -2054,7 +2047,7 @@ mod tests { TradeMutationBodyV1::Proposal { candidate } => candidate.clone(), _ => unreachable!(), }; - let candidate_id = candidate.candidate_id.clone().expect("candidate id"); + let candidate_id = candidate.candidate_id.expect("candidate id"); let decision_mutation_id = root_id(&accepted_decision(&root, '9')); for field in 0..5 { @@ -2093,23 +2086,17 @@ mod tests { TradeMutationBodyV1::Proposal { candidate } => candidate.clone(), _ => unreachable!(), }; - let candidate_id = candidate.candidate_id.clone().expect("candidate id"); + let candidate_id = candidate.candidate_id.expect("candidate id"); let claim = |claim_mutation_id: MutationId| RadrootsTradeAgreementClaimV1 { claim_mutation_id, proposal_mutation_id: root_id(&root), - candidate_id: candidate_id.clone(), + candidate_id, candidate_author_pubkey: root.buyer_pubkey, accepted_by_pubkey: root.seller_pubkey, reservation_commitment: hex_64('a'), }; - let claims = BTreeMap::from([ - (first_id.clone(), claim(first_id.clone())), - (second_id.clone(), claim(second_id.clone())), - ]); - let mutations = BTreeMap::from([ - (first_id.clone(), first_decision), - (second_id.clone(), second_decision), - ]); + let claims = BTreeMap::from([(first_id, claim(first_id)), (second_id, claim(second_id))]); + let mutations = BTreeMap::from([(first_id, first_decision), (second_id, second_decision)]); let missing_claim = MutationId::parse(hex_64('e')).expect("missing claim"); let cancellations = vec![ CancellationRecord { @@ -2126,9 +2113,9 @@ mod tests { }, CancellationRecord { mutation_id: MutationId::parse(hex_64('5')).expect("cancellation"), - parent_mutation_ids: vec![first_id.clone()], + parent_mutation_ids: vec![first_id], target_candidate_id: None, - target_claim_mutation_id: Some(first_id.clone()), + target_claim_mutation_id: Some(first_id), }, ]; let mut projection = RadrootsTradeProjectionV1::empty(trade_id()); diff --git a/crates/trade/tests/operational_listing_conformance.rs b/crates/trade/tests/operational_listing_conformance.rs @@ -4,7 +4,6 @@ use nostr::secp256k1::Message; use nostr::{Event as NostrEvent, JsonUtil, Keys, SECP256K1}; use radroots_event::{id::ParseError, wire::Nip01EventWire}; use radroots_event_codec::verification::{RadrootsSignatureVerifiedEvent, verify_nip01_event}; -use radroots_nostr::prelude::radroots_event_from_nostr; use radroots_trade::operational_listing::{ parse_classified_listing_address, validation::validate_operational_listing_event, }; @@ -264,9 +263,6 @@ fn verified_event(vector: &Vector, keys: &Keys) -> RadrootsSignatureVerifiedEven let envelope = wire .into_envelope() .unwrap_or_else(|error| panic!("{} failed envelope conversion: {error}", vector.id)); - let adapted = radroots_event_from_nostr(&event) - .unwrap_or_else(|error| panic!("{} failed Nostr adapter conversion: {error}", vector.id)); - assert_eq!(envelope, adapted, "{} conversion drift", vector.id); verify_nip01_event(envelope) .unwrap_or_else(|error| panic!("{} failed typed verification: {error}", vector.id)) } diff --git a/crates/trade/tests/package_boundary.rs b/crates/trade/tests/package_boundary.rs @@ -0,0 +1,81 @@ +use std::collections::BTreeSet; + +#[allow(unused_imports)] +use radroots_trade::{evidence as _, model as _, reducer as _, validation as _, workflow as _}; + +const MANIFEST: &str = include_str!("../Cargo.toml"); +const ROOT: &str = include_str!("../src/lib.rs"); +const PACKAGE_TIERS: &str = include_str!("../../../contracts/releases/package_tiers.toml"); + +#[test] +fn manifest_has_final_identity_and_required_radroots_dependencies() { + assert!(MANIFEST.contains("name = \"radroots_trade\"")); + assert!(MANIFEST.contains("version = \"0.1.0-alpha\"")); + assert!(MANIFEST.contains("publish = false")); + assert!(MANIFEST.contains("[lib]\nname = \"radroots_trade\"")); + + let dependencies = table_keys(MANIFEST, "[dependencies]"); + for dependency in ["radroots_core", "radroots_event", "radroots_identity"] { + assert!( + dependencies.contains(dependency), + "missing required Radroots dependency {dependency}" + ); + } +} + +#[test] +fn crate_root_declares_every_approved_module() { + let declared = root_declarations("pub mod "); + for module in ["evidence", "model", "reducer", "validation", "workflow"] { + assert!( + declared.contains(module), + "missing approved module {module}" + ); + } +} + +#[test] +fn expired_upward_development_dependencies_are_absent() { + let dev_dependencies = table_keys(MANIFEST, "[dev-dependencies]"); + for dependency in ["radroots_nostr", "radroots_transport"] { + assert!( + !dev_dependencies.contains(dependency), + "expired development dependency remains: {dependency}" + ); + assert!( + !PACKAGE_TIERS.contains(&format!( + "owner = \"radroots_trade\"\ndependency = \"{dependency}\"" + )), + "expired tier exception remains: {dependency}" + ); + } +} + +fn table_keys<'a>(manifest: &'a str, heading: &str) -> BTreeSet<&'a str> { + let Some((_, table)) = manifest.split_once(heading) else { + return BTreeSet::new(); + }; + table + .lines() + .skip(1) + .take_while(|line| !line.trim_start().starts_with('[')) + .filter_map(|line| { + let line = line.trim(); + (line + .bytes() + .next() + .is_some_and(|byte| byte.is_ascii_lowercase() || byte == b'_') + && !line.starts_with('#')) + .then(|| line.split_once('=').map(|(key, _)| key.trim())) + .flatten() + }) + .collect() +} + +fn root_declarations(prefix: &str) -> BTreeSet<&str> { + ROOT.lines() + .map(str::trim) + .filter_map(|line| line.strip_prefix(prefix)) + .filter_map(|name| name.strip_suffix(';')) + .collect() +}