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:
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()
+}