app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 3470bf906340ff522d0972180bf82f14c7434550
parent 6655a36134f0e22237006c91cd210f5610e3f329
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 17:35:05 +0000

runtime: align studio with validator receipt flow

Remove Studio order revision publish/import/runtime surfaces after the validator-set receipt cutover.\n\nUpdate SDK storage path metadata, active order reducers, source guards, and UI affordances so Studio consumes the current SDK/runtime stack directly.

Diffstat:
Mcrates/desktop/src/runtime.rs | 1030+++++--------------------------------------------------------------------------
Mcrates/desktop/src/source_guards.rs | 23+----------------------
Mcrates/desktop/src/window.rs | 84+++++++++++--------------------------------------------------------------------
Mcrates/i18n/src/keys.rs | 5+++--
Mcrates/runtime/src/lib.rs | 4++--
Mcrates/runtime/src/sdk.rs | 315+++++++++++--------------------------------------------------------------------
Mcrates/store/src/interop.rs | 436+++++--------------------------------------------------------------------------
Mcrates/sync/src/lib.rs | 5++---
Mcrates/sync/src/publish.rs | 270++-----------------------------------------------------------------------------
9 files changed, 143 insertions(+), 2029 deletions(-)

diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs @@ -15,11 +15,10 @@ use radroots_event::{ farm::{RadrootsFarm, RadrootsFarmPublicLocation, RadrootsFarmRef}, ids::{ RadrootsDTag, RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, - RadrootsOrderId, RadrootsOrderRevisionId, RadrootsPublicKey, + RadrootsOrderId, RadrootsPublicKey, }, kinds::{ - KIND_FARM, KIND_LISTING, KIND_LISTING_DRAFT, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, - KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, + KIND_FARM, KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, KIND_PROFILE, }, listing::{ @@ -30,13 +29,11 @@ use radroots_event::{ order::{ RadrootsOrderDecision, RadrootsOrderEconomics, RadrootsOrderEventType, RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderRequest, - RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }, }; use radroots_event_codec::order::{ order_cancellation_from_event, order_decision_from_event, order_event_context_from_tags, - order_request_from_event, order_revision_decision_from_event, - order_revision_proposal_from_event, + order_request_from_event, }; use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; use radroots_nostr::prelude::{ @@ -54,9 +51,8 @@ use radroots_runtime_store::{ }; use radroots_sdk::{ FARM_PUBLISH_OPERATION_KIND, LISTING_PUBLISH_OPERATION_KIND, NostrRelayUrlPolicy, - TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, - TRADE_REVISION_DECISION_OPERATION_KIND, TRADE_REVISION_PROPOSAL_OPERATION_KIND, - TRADE_SUBMIT_OPERATION_KIND, TargetSet, + TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, TRADE_SUBMIT_OPERATION_KIND, + TargetSet, }; use radroots_sql_core::SqlxSqliteExecutor; use radroots_studio_app_core::{ @@ -66,9 +62,8 @@ use radroots_studio_app_core::{ AppSdkListingPublishRequest, AppSdkProjectionLifecycleState, AppSdkPublicFarmLocation, AppSdkRelayUrlPolicy, AppSdkRuntime, AppSdkRuntimeError, AppSdkRuntimeIssue, AppSdkRuntimeStatus, AppSdkStoragePaths, AppSdkTradeCancellationRequest, AppSdkTradeDecision, - AppSdkTradeDecisionRequest, AppSdkTradeProposeRequest, AppSdkTradeRevisionDecisionRequest, - AppSdkTradeRevisionProposalRequest, AppSdkWorkflowReceipt, AppSharedAccountsPaths, - PackDayExportWriteError, prepare_pack_day_export_bundle_at_data_root, + AppSdkTradeDecisionRequest, AppSdkTradeProposeRequest, AppSdkWorkflowReceipt, + AppSharedAccountsPaths, PackDayExportWriteError, prepare_pack_day_export_bundle_at_data_root, shared_runtime_store_database_path_from_shared_accounts, write_prepared_pack_day_export_bundle, }; use radroots_studio_app_remote_signer::{ @@ -96,12 +91,10 @@ use radroots_studio_app_state::{ use radroots_studio_app_sync::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, - AppOrderRequestItemPayload, AppOrderRequestPublishPayload, - AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, - AppPublishContext, AppPublishPayload, AppPublishedOperationReceipt, - AppRelayIngestScopeFreshness, AppSyncProjection, AppSyncRequest, AppSyncResult, - AppSyncRunStatus, AppSyncTransport, AppSyncTransportError, SyncCheckpointStatus, - SyncConflictSeverity, SyncTrigger, + AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppPublishContext, + AppPublishPayload, AppPublishedOperationReceipt, AppRelayIngestScopeFreshness, + AppSyncProjection, AppSyncRequest, AppSyncResult, AppSyncRunStatus, AppSyncTransport, + AppSyncTransportError, SyncCheckpointStatus, SyncConflictSeverity, SyncTrigger, }; #[cfg(test)] use radroots_studio_app_sync::{PendingSyncOperation, SyncAggregateRef, SyncOperationKind}; @@ -126,8 +119,7 @@ use radroots_trade::identity::RadrootsTradeLocator; use radroots_trade::listing::parse_public_listing_address; use radroots_trade::order::{ RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderReductionInputs, - RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, reduce_order_events, + RadrootsOrderRequestRecord, reduce_order_events, }; use radroots_trade::workflow::RadrootsTradeWorkflowState; use serde_json::json; @@ -171,11 +163,8 @@ const APP_DIRECT_RELAY_INGEST_KINDS: &[u16] = &[ KIND_PROFILE as u16, KIND_FARM as u16, KIND_LISTING as u16, - KIND_LISTING_DRAFT as u16, KIND_ORDER_REQUEST as u16, KIND_ORDER_DECISION as u16, - KIND_ORDER_REVISION_PROPOSAL as u16, - KIND_ORDER_REVISION_DECISION as u16, KIND_ORDER_CANCELLATION as u16, ]; @@ -239,27 +228,12 @@ struct ResolvedAppOrderDecisionEvidence { } #[derive(Clone, Debug, Eq, PartialEq)] -struct ResolvedAppOrderRevisionProposalEvidence { - event: RadrootsEventEnvelope, - event_id: String, - payload: RadrootsOrderRevisionProposal, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct ResolvedAppOrderRevisionDecisionEvidence { - event_id: String, - payload: RadrootsOrderRevisionDecision, -} - -#[derive(Clone, Debug, Eq, PartialEq)] struct ResolvedAppOrderLifecycleEvidence { request_event_id: String, status: RadrootsTradeWorkflowState, agreement_event_id: Option<String>, last_event_id: Option<String>, decision: Option<ResolvedAppOrderDecisionEvidence>, - revision_proposals: Vec<ResolvedAppOrderRevisionProposalEvidence>, - revision_decisions: Vec<ResolvedAppOrderRevisionDecisionEvidence>, cancellation_event_id: Option<String>, } @@ -267,8 +241,6 @@ struct ResolvedAppOrderLifecycleEvidence { struct AppActiveOrderEvidenceBuckets { requests: Vec<RadrootsOrderRequestRecord>, decisions: Vec<RadrootsOrderDecisionRecord>, - revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, - revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, cancellations: Vec<RadrootsOrderCancellationRecord>, } @@ -376,8 +348,6 @@ fn publish_payload_context(publish_payload: &AppPublishPayload) -> &AppPublishCo AppPublishPayload::Listing(payload) => &payload.context, AppPublishPayload::OrderRequest(payload) => &payload.context, AppPublishPayload::OrderDecision(payload) => &payload.context, - AppPublishPayload::OrderRevisionProposal(payload) => &payload.context, - AppPublishPayload::OrderRevisionDecision(payload) => &payload.context, AppPublishPayload::OrderCancellation(payload) => &payload.context, } } @@ -851,24 +821,6 @@ impl DesktopAppRuntime { ) } - pub fn publish_order_revision_proposal( - &self, - order_id: OrderId, - items: Vec<RadrootsOrderItem>, - economics: RadrootsOrderEconomics, - reason: &str, - confirm_public_note: bool, - ) -> Result<bool, AppSqliteError> { - self.lock_state_mut() - .publish_seller_order_revision_proposal( - order_id, - items, - economics, - reason, - confirm_public_note, - ) - } - pub fn publish_buyer_order_cancel( &self, order_id: OrderId, @@ -878,23 +830,6 @@ impl DesktopAppRuntime { .publish_buyer_order_cancellation(order_id, confirm_public_note) } - pub fn publish_buyer_order_revision_accept( - &self, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.lock_state_mut() - .publish_buyer_order_revision_accept(order_id) - } - - pub fn publish_buyer_order_revision_decline( - &self, - order_id: OrderId, - confirm_public_note: bool, - ) -> Result<bool, AppSqliteError> { - self.lock_state_mut() - .publish_buyer_order_revision_decline(order_id, confirm_public_note) - } - pub fn open_pack_day( &self, fulfillment_window_id: Option<FulfillmentWindowId>, @@ -1213,17 +1148,6 @@ fn start_desktop_sdk_runtime( AppSdkRuntime::start(AppSdkConfig::from_desktop_paths(paths, nostr_relay_urls)) } -fn sdk_storage_path_pair(paths: Option<&AppSdkStoragePaths>) -> (Option<PathBuf>, Option<PathBuf>) { - paths - .map(|paths| { - ( - Some(paths.event_store_path.clone()), - Some(paths.outbox_path.clone()), - ) - }) - .unwrap_or((None, None)) -} - fn desktop_app_sdk_issue_from_runtime_error( error: &AppSdkRuntimeError, ) -> DesktopAppSdkIssueSummary { @@ -1285,8 +1209,9 @@ pub struct DesktopAppSdkStatusSummary { pub projection_lifecycle_state: AppSdkProjectionLifecycleState, pub projection_lifecycle_reason: Option<String>, pub storage_root: PathBuf, - pub event_store_path: Option<PathBuf>, - pub outbox_path: Option<PathBuf>, + pub runtime_path: Option<PathBuf>, + pub private_path: Option<PathBuf>, + pub studio_path: Option<PathBuf>, pub relay_target_count: usize, pub relay_url_policy: AppSdkRelayUrlPolicy, pub last_issue: Option<DesktopAppSdkIssueSummary>, @@ -1294,14 +1219,26 @@ pub struct DesktopAppSdkStatusSummary { impl DesktopAppSdkStatusSummary { fn from_status(status: &AppSdkRuntimeStatus) -> Self { - let (event_store_path, outbox_path) = sdk_storage_path_pair(status.storage_paths.as_ref()); + let runtime_path = status + .storage_paths + .as_ref() + .map(|paths| paths.runtime_path.clone()); + let private_path = status + .storage_paths + .as_ref() + .map(|paths| paths.private_path.clone()); + let studio_path = status + .storage_paths + .as_ref() + .map(|paths| paths.studio_path.clone()); Self { lifecycle_state: status.state, projection_lifecycle_state: status.projection_lifecycle.state, projection_lifecycle_reason: status.projection_lifecycle.reason.clone(), storage_root: status.storage_root.clone(), - event_store_path, - outbox_path, + runtime_path, + private_path, + studio_path, relay_target_count: status.relay_urls.len(), relay_url_policy: status.relay_url_policy, last_issue: status @@ -2614,7 +2551,6 @@ impl DesktopAppRuntimeState { if lifecycle.decision.is_some() || lifecycle.status != RadrootsTradeWorkflowState::Requested || lifecycle.cancellation_event_id.is_some() - || active_order_pending_revision_proposal(&lifecycle).is_some() { return Err(AppSqliteError::InvalidProjection { reason: "seller order decision requires an undecided order", @@ -2716,291 +2652,6 @@ impl DesktopAppRuntimeState { Ok(true) } - fn prepare_seller_order_revision_proposal( - &mut self, - order_id: OrderId, - items: Vec<RadrootsOrderItem>, - economics: RadrootsOrderEconomics, - reason: &str, - confirm_public_note: bool, - ) -> Result<AppOrderRevisionProposalPublishPayload, AppSqliteError> { - let _ = self.import_shared_runtime_store()?; - let relay_urls = normalized_app_sync_relay_urls(&self.nostr_relay_urls).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "seller order revision requires valid configured relays", - } - })?; - if relay_urls.is_empty() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires configured relays", - }); - } - self.refresh_configured_relay_state_before_order_lifecycle()?; - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires local state", - }); - }; - let Some(farm_id) = self.selected_farm_id() else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires a selected farm", - }); - }; - let Some(selected_account) = self - .state_store - .identity_projection() - .selected_account - .as_ref() - else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires a selected seller account", - }); - }; - let account_id = selected_account.account.account_id.clone(); - let seller_pubkey = self.runtime_store_owner_pubkey(selected_account).ok_or( - AppSqliteError::InvalidProjection { - reason: "seller order revision requires a selected seller public key", - }, - )?; - let request = self.resolve_seller_order_request_evidence(order_id)?; - if request.payload.seller_pubkey.trim() != seller_pubkey.as_str() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision seller account does not match order seller", - }); - } - let listing_address = parse_public_listing_address(request.payload.listing_addr.as_str()) - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "seller order revision listing address is invalid", - })?; - if listing_address.seller_pubkey.as_str() != seller_pubkey.as_str() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision listing address is outside seller authority", - }); - } - let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - if lifecycle.decision.is_some() || lifecycle.status != RadrootsTradeWorkflowState::Requested - { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires an undecided order", - }); - } - if lifecycle.cancellation_event_id.is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires an undecided order", - }); - } - let Some(order_detail) = sqlite_store.load_order_detail(farm_id, order_id)? else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires a visible seller order", - }); - }; - if order_detail.status != OrderStatus::NeedsAction { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires an undecided order", - }); - } - if active_order_pending_revision_proposal(&lifecycle).is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires no pending revision proposal", - }); - } - let reason = reason.trim(); - if reason.is_empty() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires a non-empty reason", - }); - } - let payload = AppOrderRevisionProposalPublishPayload { - context: AppPublishContext::new(account_id, "seller_order_revision_proposal"), - app_order_id: order_id, - farm_id, - trade_order_id: request.payload.order_id.to_string(), - request_event_id: request.request_event_id, - revision_id: format!("app-revision-{}", d_tag_from_uuid(Uuid::now_v7())), - listing_addr: request.payload.listing_addr.to_string(), - buyer_pubkey: request.payload.buyer_pubkey.to_string(), - seller_pubkey: request.payload.seller_pubkey.to_string(), - items, - economics, - reason: reason.to_owned(), - confirm_public_note, - }; - AppPublishPayload::OrderRevisionProposal(payload.clone()) - .validate() - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "seller order revision publish payload is invalid", - })?; - Ok(payload) - } - - fn publish_seller_order_revision_proposal( - &mut self, - order_id: OrderId, - items: Vec<RadrootsOrderItem>, - economics: RadrootsOrderEconomics, - reason: &str, - confirm_public_note: bool, - ) -> Result<bool, AppSqliteError> { - let payload = self.prepare_seller_order_revision_proposal( - order_id, - items, - economics, - reason, - confirm_public_note, - )?; - let source_record_id = order_revision_proposal_sdk_source_record_id(&payload); - self.enqueue_order_revision_proposal_payload_via_sdk( - &payload, - AppSdkWorkflowReceiptSourceKind::LocalOutbox, - source_record_id.as_str(), - )?; - let _ = self.refresh_selected_account_sync()?; - Ok(true) - } - - fn prepare_buyer_order_revision_decision( - &mut self, - order_id: OrderId, - decision: RadrootsOrderRevisionOutcome, - confirm_public_note: bool, - ) -> Result<AppOrderRevisionDecisionPublishPayload, AppSqliteError> { - let _ = self.import_shared_runtime_store()?; - let relay_urls = normalized_app_sync_relay_urls(&self.nostr_relay_urls).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "buyer order revision requires valid configured relays", - } - })?; - if relay_urls.is_empty() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires configured relays", - }); - } - self.refresh_configured_relay_state_before_order_lifecycle()?; - let buyer_context = self.state_store.identity_projection().buyer_context(); - let BuyerContext::Account(account_id) = &buyer_context else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a selected buyer account", - }); - }; - let Some(selected_account) = self.selected_buyer_account(&buyer_context) else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a selected buyer account", - }); - }; - let buyer_pubkey = self.runtime_store_owner_pubkey(selected_account).ok_or( - AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a selected buyer public key", - }, - )?; - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires local state", - }); - }; - let buyer_order_scope = selected_buyer_order_scope(self.state_store.identity_projection()); - let Some(detail) = - sqlite_store.load_buyer_order_detail_for_scope(&buyer_order_scope, order_id)? - else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a visible buyer order", - }); - }; - if matches!( - detail.status, - BuyerOrderStatus::Ready | BuyerOrderStatus::Completed | BuyerOrderStatus::Declined - ) { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires an active negotiated order", - }); - } - let request = self.resolve_seller_order_request_evidence(order_id)?; - if request.payload.buyer_pubkey.trim() != buyer_pubkey.as_str() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision buyer account does not match order buyer", - }); - } - let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - if lifecycle.decision.is_some() - || !matches!(lifecycle.status, RadrootsTradeWorkflowState::Requested) - { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires active pre-agreement negotiation", - }); - } - if lifecycle.cancellation_event_id.is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires active pre-agreement negotiation", - }); - } - let Some(proposal) = active_order_pending_revision_proposal(&lifecycle) else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a pending seller proposal", - }); - }; - let payload = AppOrderRevisionDecisionPublishPayload { - context: AppPublishContext::new(account_id.clone(), "buyer_order_revision_decision"), - app_order_id: order_id, - farm_id: detail.farm_id, - trade_order_id: request.payload.order_id.to_string(), - request_event_id: request.request_event_id, - revision_id: proposal.payload.revision_id.to_string(), - listing_addr: request.payload.listing_addr.to_string(), - buyer_pubkey: request.payload.buyer_pubkey.to_string(), - seller_pubkey: request.payload.seller_pubkey.to_string(), - decision, - confirm_public_note, - }; - AppPublishPayload::OrderRevisionDecision(payload.clone()) - .validate() - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "buyer order revision publish payload is invalid", - })?; - Ok(payload) - } - - fn publish_buyer_order_revision_accept( - &mut self, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.publish_buyer_order_revision_decision( - order_id, - RadrootsOrderRevisionOutcome::Accepted, - false, - ) - } - - fn publish_buyer_order_revision_decline( - &mut self, - order_id: OrderId, - confirm_public_note: bool, - ) -> Result<bool, AppSqliteError> { - self.publish_buyer_order_revision_decision( - order_id, - RadrootsOrderRevisionOutcome::Declined { - reason: "buyer kept order as placed".to_owned(), - }, - confirm_public_note, - ) - } - - fn publish_buyer_order_revision_decision( - &mut self, - order_id: OrderId, - decision: RadrootsOrderRevisionOutcome, - confirm_public_note: bool, - ) -> Result<bool, AppSqliteError> { - let payload = - self.prepare_buyer_order_revision_decision(order_id, decision, confirm_public_note)?; - let source_record_id = order_revision_decision_sdk_source_record_id(&payload); - self.enqueue_order_revision_decision_payload_via_sdk( - &payload, - AppSdkWorkflowReceiptSourceKind::LocalOutbox, - source_record_id.as_str(), - )?; - let _ = self.refresh_selected_account_sync()?; - Ok(true) - } - fn prepare_buyer_order_cancellation( &mut self, order_id: OrderId, @@ -3064,11 +2715,6 @@ impl DesktopAppRuntimeState { reason: "buyer order cancellation requires an open pre-agreement order", }); } - if active_order_pending_revision_proposal(&lifecycle).is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no pending seller proposal", - }); - } match lifecycle.status { RadrootsTradeWorkflowState::Requested => {} RadrootsTradeWorkflowState::AgreedPendingValidation @@ -4922,101 +4568,6 @@ impl DesktopAppRuntimeState { } } - fn enqueue_order_revision_proposal_payload_via_sdk( - &self, - payload: &AppOrderRevisionProposalPublishPayload, - source_kind: AppSdkWorkflowReceiptSourceKind, - source_record_id: &str, - ) -> Result<(), AppSqliteError> { - let operation_kind = TRADE_REVISION_PROPOSAL_OPERATION_KIND; - let actor_pubkey = self - .local_signing_identity_for_publish_payload(&AppPublishPayload::OrderRevisionProposal( - payload.clone(), - )) - .and_then(|identity| { - let actor_pubkey = identity.public_key_hex(); - let request = AppSdkTradeRevisionProposalRequest { - actor_account_id: payload.context.account_id.clone(), - actor_pubkey: actor_pubkey.clone(), - signer_keys: identity.into_keys(), - locator: trade_locator_from_revision_proposal_payload(payload)?, - revision_id: publish_revision_id(payload.revision_id.as_str())?, - items: payload.items.clone(), - economics: payload.economics.clone(), - reason: payload.reason.clone(), - confirm_public_note: payload.confirm_public_note, - idempotency_key: Some(sdk_idempotency_key(source_record_id)), - }; - self.enqueue_app_sdk_trade_revision_proposal(request) - .map(|receipt| (actor_pubkey, receipt)) - .map_err(sync_transport_error_from_sdk_runtime_error) - }); - match actor_pubkey { - Ok((actor_pubkey, receipt)) => self.record_app_sdk_workflow_success( - source_kind, - source_record_id, - operation_kind, - actor_pubkey.as_str(), - &receipt, - ), - Err(error) => self.record_app_sdk_workflow_failure( - source_kind, - source_record_id, - operation_kind, - None, - sync_transport_error_detail_json(&error), - ), - } - } - - fn enqueue_order_revision_decision_payload_via_sdk( - &self, - payload: &AppOrderRevisionDecisionPublishPayload, - source_kind: AppSdkWorkflowReceiptSourceKind, - source_record_id: &str, - ) -> Result<(), AppSqliteError> { - let operation_kind = TRADE_REVISION_DECISION_OPERATION_KIND; - let actor_pubkey = self - .local_signing_identity_for_publish_payload(&AppPublishPayload::OrderRevisionDecision( - payload.clone(), - )) - .and_then(|identity| { - let actor_pubkey = identity.public_key_hex(); - let request = AppSdkTradeRevisionDecisionRequest { - actor_account_id: payload.context.account_id.clone(), - actor_pubkey: actor_pubkey.clone(), - signer_keys: identity.into_keys(), - locator: trade_locator_from_revision_decision_payload(payload)?, - revision_id: publish_revision_id(payload.revision_id.as_str())?, - decision: payload.decision.clone(), - evidence: self - .order_revision_decision_sdk_evidence(payload) - .map_err(|error| AppSyncTransportError::failed(error.to_string()))?, - confirm_public_note: payload.confirm_public_note, - idempotency_key: Some(sdk_idempotency_key(source_record_id)), - }; - self.enqueue_app_sdk_trade_revision_decision(request) - .map(|receipt| (actor_pubkey, receipt)) - .map_err(sync_transport_error_from_sdk_runtime_error) - }); - match actor_pubkey { - Ok((actor_pubkey, receipt)) => self.record_app_sdk_workflow_success( - source_kind, - source_record_id, - operation_kind, - actor_pubkey.as_str(), - &receipt, - ), - Err(error) => self.record_app_sdk_workflow_failure( - source_kind, - source_record_id, - operation_kind, - None, - sync_transport_error_detail_json(&error), - ), - } - } - fn enqueue_order_cancellation_payload_via_sdk( &self, payload: &AppOrderCancellationPublishPayload, @@ -5061,25 +4612,6 @@ impl DesktopAppRuntimeState { } } - fn order_revision_decision_sdk_evidence( - &self, - payload: &AppOrderRevisionDecisionPublishPayload, - ) -> Result<Vec<RadrootsEventEnvelope>, AppSqliteError> { - let request = self.resolve_seller_order_request_evidence(payload.app_order_id)?; - let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - let Some(proposal) = active_order_pending_revision_proposal(&lifecycle) else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires a pending seller proposal", - }); - }; - if proposal.payload.revision_id.to_string() != payload.revision_id { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision evidence does not match requested revision", - }); - } - Ok(vec![request.request_event, proposal.event.clone()]) - } - fn local_signing_identity_for_publish_payload( &self, payload: &AppPublishPayload, @@ -5131,20 +4663,6 @@ impl DesktopAppRuntimeState { self.with_app_sdk_runtime(|runtime| runtime.trade_decide(request)) } - fn enqueue_app_sdk_trade_revision_proposal( - &self, - request: AppSdkTradeRevisionProposalRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.with_app_sdk_runtime(|runtime| runtime.trade_revision_propose(request)) - } - - fn enqueue_app_sdk_trade_revision_decision( - &self, - request: AppSdkTradeRevisionDecisionRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.with_app_sdk_runtime(|runtime| runtime.trade_revision_decide(request)) - } - fn enqueue_app_sdk_trade_cancellation( &self, request: AppSdkTradeCancellationRequest, @@ -5651,7 +5169,6 @@ impl DesktopAppRuntimeState { }); let mut buckets = AppActiveOrderEvidenceBuckets::default(); - let mut signed_events_by_id = BTreeMap::new(); let request_event_id = active_order_event_id(request.request_event_id.as_str(), "request_event_id")?; let request_author_pubkey = active_order_pubkey( @@ -5671,7 +5188,6 @@ impl DesktopAppRuntimeState { } let event_id = active_order_event_id(event.id_str(), "event_id")?; let author_pubkey = active_order_pubkey(event.author_str(), "author_pubkey")?; - signed_events_by_id.insert(event_id.to_string(), event.clone()); match event.kind_u32() { KIND_ORDER_DECISION => { let envelope = order_decision_from_event(&event).map_err(|_| { @@ -5683,47 +5199,11 @@ impl DesktopAppRuntimeState { buckets.decisions.push(RadrootsOrderDecisionRecord { event_id, author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } - KIND_ORDER_REVISION_PROPOSAL => { - let Ok(envelope) = order_revision_proposal_from_event(&event) else { - return Err(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - }); - }; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets - .revision_proposals - .push(RadrootsOrderRevisionProposalRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } - KIND_ORDER_REVISION_DECISION => { - let Ok(envelope) = order_revision_decision_from_event(&event) else { - return Err(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - }); - }; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets - .revision_decisions - .push(RadrootsOrderRevisionDecisionRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); + counterparty_pubkey: context.0, + root_event_id: context.1, + prev_event_id: context.2, + payload: envelope.payload, + }); } KIND_ORDER_CANCELLATION => { let Ok(envelope) = order_cancellation_from_event(&event) else { @@ -5750,8 +5230,6 @@ impl DesktopAppRuntimeState { RadrootsOrderReductionInputs { requests: buckets.requests.clone(), decisions: buckets.decisions.clone(), - revision_proposals: buckets.revision_proposals.clone(), - revision_decisions: buckets.revision_decisions.clone(), cancellations: buckets.cancellations.clone(), }, ); @@ -5794,31 +5272,6 @@ impl DesktopAppRuntimeState { .last_event_id .map(|event_id| event_id.to_string()), decision, - revision_proposals: buckets - .revision_proposals - .into_iter() - .map(|proposal| { - let event = signed_events_by_id - .get(proposal.event_id.as_str()) - .cloned() - .ok_or(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - })?; - Ok(ResolvedAppOrderRevisionProposalEvidence { - event, - event_id: proposal.event_id.to_string(), - payload: proposal.payload, - }) - }) - .collect::<Result<Vec<_>, AppSqliteError>>()?, - revision_decisions: buckets - .revision_decisions - .into_iter() - .map(|decision| ResolvedAppOrderRevisionDecisionEvidence { - event_id: decision.event_id.to_string(), - payload: decision.payload, - }) - .collect(), cancellation_event_id: projection .cancellation_event_id .map(|event_id| event_id.to_string()), @@ -5830,12 +5283,7 @@ impl DesktopAppRuntimeState { ) -> Result<Vec<RadrootsEventEnvelope>, AppSqliteError> { let mut events = Vec::new(); let mut seen_event_ids = BTreeSet::new(); - let kinds = [ - KIND_ORDER_DECISION, - KIND_ORDER_REVISION_PROPOSAL, - KIND_ORDER_REVISION_DECISION, - KIND_ORDER_CANCELLATION, - ]; + let kinds = [KIND_ORDER_DECISION, KIND_ORDER_CANCELLATION]; if let Some(sqlite_store) = self.sqlite_store.as_ref() { for kind in kinds { @@ -7213,7 +6661,7 @@ fn direct_relay_event_records( fn direct_relay_event_farm_id(kind: u16, tags: &[Vec<String>]) -> Option<String> { match kind { kind if kind == KIND_FARM as u16 => relay_event_tag_value(tags, "d", 1), - kind if kind == KIND_LISTING as u16 || kind == KIND_LISTING_DRAFT as u16 => { + kind if kind == KIND_LISTING as u16 => { relay_event_tag_value(tags, "a", 1).and_then(|address| relay_address_d_tag(&address)) } _ => None, @@ -7226,7 +6674,7 @@ fn direct_relay_event_listing_addr( listing_d_tag: Option<&str>, ) -> Option<String> { match kind { - kind if kind == KIND_LISTING as u16 || kind == KIND_LISTING_DRAFT as u16 => { + kind if kind == KIND_LISTING as u16 => { listing_d_tag.map(|d_tag| format!("{kind}:{event_pubkey}:{d_tag}")) } _ => None, @@ -7443,24 +6891,6 @@ fn order_decision_sdk_source_record_id(payload: &AppOrderDecisionPublishPayload) format!("app:order_decision:{}", payload.app_order_id) } -fn order_revision_proposal_sdk_source_record_id( - payload: &AppOrderRevisionProposalPublishPayload, -) -> String { - format!( - "app:order_revision_proposal:{}:{}", - payload.app_order_id, payload.revision_id - ) -} - -fn order_revision_decision_sdk_source_record_id( - payload: &AppOrderRevisionDecisionPublishPayload, -) -> String { - format!( - "app:order_revision_decision:{}:{}", - payload.app_order_id, payload.revision_id - ) -} - fn order_cancellation_sdk_source_record_id(payload: &AppOrderCancellationPublishPayload) -> String { format!("app:order_cancellation:{}", payload.app_order_id) } @@ -9545,38 +8975,6 @@ fn active_order_event_record_context( Ok((context.counterparty_pubkey, root_event_id, prev_event_id)) } -fn active_order_pending_revision_proposal( - lifecycle: &ResolvedAppOrderLifecycleEvidence, -) -> Option<&ResolvedAppOrderRevisionProposalEvidence> { - let mut parent_event_id = lifecycle.request_event_id.as_str(); - loop { - let proposals = lifecycle - .revision_proposals - .iter() - .filter(|proposal| proposal.payload.prev_event_id == parent_event_id) - .collect::<Vec<_>>(); - let proposal = match proposals.as_slice() { - [] => return None, - [proposal] => *proposal, - _ => return None, - }; - let decisions = lifecycle - .revision_decisions - .iter() - .filter(|decision| { - decision.payload.prev_event_id == proposal.event_id - && decision.payload.revision_id == proposal.payload.revision_id - }) - .collect::<Vec<_>>(); - let decision = match decisions.as_slice() { - [] => return Some(proposal), - [decision] => *decision, - _ => return None, - }; - parent_event_id = decision.event_id.as_str(); - } -} - fn insert_seller_order_request_evidence( order_id: &OrderId, event: &RadrootsEventEnvelope, @@ -9661,11 +9059,6 @@ fn publish_order_id(value: &str) -> Result<RadrootsOrderId, AppSyncTransportErro RadrootsOrderId::parse(value).map_err(|error| AppSyncTransportError::failed(error.to_string())) } -fn publish_revision_id(value: &str) -> Result<RadrootsOrderRevisionId, AppSyncTransportError> { - RadrootsOrderRevisionId::parse(value) - .map_err(|error| AppSyncTransportError::failed(error.to_string())) -} - fn publish_event_id(value: &str) -> Result<RadrootsEventId, AppSyncTransportError> { RadrootsEventId::parse(value).map_err(|error| AppSyncTransportError::failed(error.to_string())) } @@ -9713,30 +9106,6 @@ fn trade_locator_from_decision_payload( ) } -fn trade_locator_from_revision_proposal_payload( - payload: &AppOrderRevisionProposalPublishPayload, -) -> Result<RadrootsTradeLocator, AppSyncTransportError> { - trade_locator_from_parts( - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - ) -} - -fn trade_locator_from_revision_decision_payload( - payload: &AppOrderRevisionDecisionPublishPayload, -) -> Result<RadrootsTradeLocator, AppSyncTransportError> { - trade_locator_from_parts( - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - ) -} - fn trade_locator_from_cancellation_payload( payload: &AppOrderCancellationPublishPayload, ) -> Result<RadrootsTradeLocator, AppSyncTransportError> { @@ -9841,13 +9210,12 @@ mod tests { }; use radroots_event::ids::{ RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, - RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, + RadrootsOrderQuoteId, RadrootsPublicKey, }; use radroots_event::order::{ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, - RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }; use radroots_event::{ RadrootsEventEnvelope, RadrootsEventEnvelopeParts, RadrootsEventPtr, @@ -9855,7 +9223,6 @@ mod tests { }; use radroots_event_codec::order::{ order_cancellation_event_build, order_decision_event_build, order_request_event_build, - order_revision_proposal_event_build, }; use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; use radroots_nostr::prelude::{ @@ -9874,8 +9241,7 @@ mod tests { }; use radroots_sdk::{ LISTING_PUBLISH_OPERATION_KIND, TRADE_CANCELLATION_OPERATION_KIND, - TRADE_DECISION_OPERATION_KIND, TRADE_REVISION_DECISION_OPERATION_KIND, - TRADE_SUBMIT_OPERATION_KIND, + TRADE_DECISION_OPERATION_KIND, TRADE_SUBMIT_OPERATION_KIND, }; use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor}; use radroots_studio_app_core::{ @@ -9901,7 +9267,6 @@ mod tests { AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, - AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, AppPublishedOperationReceipt, AppRelayIngestScopeFreshness, AppRelayIngestScopeStatus, AppSyncRequest, AppSyncResult, AppSyncRunStatus, AppSyncTransport, AppSyncTransportError, PendingSyncOperation, @@ -10374,10 +9739,6 @@ mod tests { RadrootsOrderId::parse(value).expect("order id") } - fn test_revision_id(value: &str) -> RadrootsOrderRevisionId { - RadrootsOrderRevisionId::parse(value).expect("revision id") - } - fn test_quote_id(value: &str) -> RadrootsOrderQuoteId { RadrootsOrderQuoteId::parse(value).expect("quote id") } @@ -10697,69 +10058,6 @@ mod tests { buyer_identity.public_key_hex(), seller_identity.public_key_hex(), ); - let revision_economics = RadrootsOrderEconomics { - quote_id: test_quote_id("quote-revision-1"), - quote_version: 2, - pricing_basis: RadrootsOrderPricingBasis::ListingEvent, - currency: RadrootsCoreCurrency::USD, - items: vec![RadrootsOrderEconomicItem { - bin_id: test_bin_id("bin-1"), - bin_count: 3, - quantity_amount: RadrootsCoreDecimal::from(1u32), - quantity_unit: RadrootsCoreUnit::Each, - unit_price_amount: RadrootsCoreDecimal::from(8u32), - unit_price_currency: RadrootsCoreCurrency::USD, - line_subtotal: RadrootsCoreMoney::from_minor_units_u32( - 2400, - RadrootsCoreCurrency::USD, - ), - }], - discounts: Vec::new(), - adjustments: Vec::new(), - subtotal: RadrootsCoreMoney::from_minor_units_u32(2400, RadrootsCoreCurrency::USD), - discount_total: RadrootsCoreMoney::zero(RadrootsCoreCurrency::USD), - adjustment_total: RadrootsCoreMoney::zero(RadrootsCoreCurrency::USD), - total: RadrootsCoreMoney::from_minor_units_u32(2400, RadrootsCoreCurrency::USD), - }; - let revision_proposal = - AppPublishPayload::OrderRevisionProposal(AppOrderRevisionProposalPublishPayload { - context: AppPublishContext::new( - seller_account_id.to_string(), - "seller_order_revision_proposal", - ), - app_order_id: common.0, - farm_id: common.1, - trade_order_id: common.2.clone(), - request_event_id: common.3.clone(), - revision_id: "revision-1".to_owned(), - listing_addr: common.4.clone(), - buyer_pubkey: common.5.clone(), - seller_pubkey: common.6.clone(), - items: vec![RadrootsOrderItem { - bin_id: test_bin_id("bin-1"), - bin_count: 3, - }], - economics: revision_economics, - reason: "harvest count updated".to_owned(), - confirm_public_note: false, - }); - let revision_decision = - AppPublishPayload::OrderRevisionDecision(AppOrderRevisionDecisionPublishPayload { - context: AppPublishContext::new( - buyer_account_id.to_string(), - "buyer_order_revision_decision", - ), - app_order_id: common.0, - farm_id: common.1, - trade_order_id: common.2.clone(), - request_event_id: common.3.clone(), - revision_id: "revision-1".to_owned(), - listing_addr: common.4.clone(), - buyer_pubkey: common.5.clone(), - seller_pubkey: common.6.clone(), - decision: RadrootsOrderRevisionOutcome::Accepted, - confirm_public_note: false, - }); let cancellation = AppPublishPayload::OrderCancellation(AppOrderCancellationPublishPayload { context: AppPublishContext::new( @@ -10776,7 +10074,7 @@ mod tests { reason: "buyer cancelled order".to_owned(), confirm_public_note: false, }); - let operations = [revision_proposal, revision_decision, cancellation] + let operations = [cancellation] .into_iter() .map(|payload| { PendingSyncOperation::from_publish_payload(payload, "2026-05-24T12:00:00Z") @@ -13714,8 +13012,8 @@ mod tests { .storage_paths .as_ref() .expect("sdk storage paths") - .event_store_path, - paths.app.data.join("sdk").join("event_store.sqlite") + .runtime_path, + paths.app.data.join("sdk").join("runtime.sqlite") ); let diagnostics = runtime .sdk_diagnostics() @@ -13755,8 +13053,16 @@ mod tests { ); assert_eq!(sdk_status.storage_root, paths.app.data.join("sdk")); assert_eq!( - sdk_status.event_store_path.as_ref(), - Some(&paths.app.data.join("sdk").join("event_store.sqlite")) + sdk_status.runtime_path.as_ref(), + Some(&paths.app.data.join("sdk").join("runtime.sqlite")) + ); + assert_eq!( + sdk_status.private_path.as_ref(), + Some(&paths.app.data.join("sdk").join("private.sqlite")) + ); + assert_eq!( + sdk_status.studio_path.as_ref(), + Some(&paths.app.data.join("sdk").join("studio.sqlite")) ); assert_eq!(sdk_status.relay_target_count, 1); assert!( @@ -15681,51 +14987,6 @@ mod tests { } #[test] - fn runtime_rejects_seller_order_revision_with_reducer_invalid_parent_evidence() { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime("seller_order_revision_invalid_parent", 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - append_signed_order_revision_proposal_record_with_prev( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-stale-revision", - request_event_id, - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - ); - - let error = runtime - .publish_order_revision_proposal( - order_id, - revision_test_order_items(), - revision_test_order_economics(), - "harvest count updated", - false, - ) - .expect_err("seller revision proposal should reject reducer-invalid parent evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&paths); - } - - #[test] fn runtime_places_supported_buyer_order_into_shared_runtime_store() { let (runtime, paths) = bootstrapped_runtime("buyer_order_local_event"); assert!( @@ -16347,50 +15608,6 @@ mod tests { } #[test] - fn runtime_rejects_linked_buyer_cancellation_from_pending_revision_proposal() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_request_runtime("linked_buyer_order_cancel_revision"); - let proposal_key = "linked-buyer-order-cancel-revision-proposal"; - append_signed_order_revision_proposal_record_with_prev( - &fixture.paths, - fixture.trade_order_id.as_str(), - proposal_key, - fixture.request_event_id.as_str(), - fixture.request_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - ); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_runtime_store() - .expect("linked buyer revision proposal should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id, false) - .expect_err("linked buyer cancellation should reject from pending proposal"); - - assert_invalid_projection_reason( - error, - "buyer order cancellation requires no pending seller proposal", - ); - assert_eq!(relay.event_count(), 0); - let cancellation_events = - shared_order_events_by_kind(&fixture.paths, 3432, fixture.buyer_pubkey.as_str()); - assert!(cancellation_events.is_empty()); - - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] fn runtime_rejects_linked_buyer_cancellation_after_agreement() { let relay = ThreadedAckRelay::spawn(); let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_after_agreement"); @@ -16470,62 +15687,6 @@ mod tests { } #[test] - fn runtime_publishes_linked_buyer_revision_decision_from_reducer_valid_parent() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_request_runtime("linked_buyer_order_revision"); - let proposal_key = "linked-buyer-order-revision-proposal"; - let proposal_event_id = append_signed_order_revision_proposal_record_with_prev( - &fixture.paths, - fixture.trade_order_id.as_str(), - proposal_key, - fixture.request_event_id.as_str(), - fixture.request_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - ); - let revision_id = format!("revision-{proposal_key}"); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - restore_sdk_runtime_with_relays( - &fixture.runtime, - &fixture.paths, - vec![relay.url().to_owned()], - ); - fixture - .runtime - .refresh_shared_runtime_store() - .expect("linked buyer runtime store should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - seed_relay_with_shared_event_id(&relay, &fixture.paths, fixture.request_event_id.as_str()); - seed_relay_with_shared_event_id(&relay, &fixture.paths, proposal_event_id.as_str()); - - assert!( - fixture - .runtime - .publish_buyer_order_revision_accept(fixture.order_id) - .expect("linked buyer revision decision should publish") - ); - - assert_eq!(relay.event_count(), 2); - let revision_decision_events = - shared_order_events_by_kind(&fixture.paths, 3425, fixture.buyer_pubkey.as_str()); - assert!(revision_decision_events.is_empty()); - assert_order_revision_decision_sdk_workflow_receipt( - &fixture.runtime, - fixture.order_id, - revision_id.as_str(), - AppSdkWorkflowReceiptState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] fn runtime_repeat_personal_order_readds_only_currently_eligible_items() { let runtime = memory_runtime(); let (account_id, farm_id) = provision_ready_farmer_account(&runtime); @@ -20522,77 +19683,6 @@ mod tests { ) } - fn append_signed_order_revision_proposal_record_with_prev( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - prev_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - ) -> String { - let request_event_id = test_event_id(request_event_id); - let prev_event_id = test_event_id(prev_event_id); - let payload = RadrootsOrderRevisionProposal { - revision_id: test_revision_id(format!("revision-{event_key}").as_str()), - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - buyer_pubkey: test_pubkey(buyer_pubkey), - seller_pubkey: test_pubkey(seller_pubkey), - root_event_id: request_event_id.clone(), - prev_event_id: prev_event_id.clone(), - items: revision_test_order_items(), - economics: revision_test_order_economics(), - reason: "harvest count updated".to_owned(), - }; - let parts = - order_revision_proposal_event_build(&request_event_id, &prev_event_id, &payload) - .expect("order revision proposal draft should build"); - let record_id = format!("app:signed_event:revision-proposal:{event_key}"); - append_trade_signed_event_record( - paths, - record_id.as_str(), - seller_pubkey, - listing_addr, - parts, - ) - } - - fn revision_test_order_items() -> Vec<RadrootsOrderItem> { - vec![RadrootsOrderItem { - bin_id: test_bin_id("seller-order-primary-bin"), - bin_count: 3, - }] - } - - fn revision_test_order_economics() -> RadrootsOrderEconomics { - RadrootsOrderEconomics { - quote_id: test_quote_id("quote-revision-test"), - quote_version: 2, - pricing_basis: RadrootsOrderPricingBasis::ListingEvent, - currency: RadrootsCoreCurrency::USD, - items: vec![RadrootsOrderEconomicItem { - bin_id: test_bin_id("seller-order-primary-bin"), - bin_count: 3, - quantity_amount: RadrootsCoreDecimal::from(1u32), - quantity_unit: RadrootsCoreUnit::Each, - unit_price_amount: RadrootsCoreDecimal::from(8u32), - unit_price_currency: RadrootsCoreCurrency::USD, - line_subtotal: RadrootsCoreMoney::from_minor_units_u32( - 2400, - RadrootsCoreCurrency::USD, - ), - }], - discounts: Vec::new(), - adjustments: Vec::new(), - subtotal: RadrootsCoreMoney::from_minor_units_u32(2400, RadrootsCoreCurrency::USD), - discount_total: RadrootsCoreMoney::zero(RadrootsCoreCurrency::USD), - adjustment_total: RadrootsCoreMoney::zero(RadrootsCoreCurrency::USD), - total: RadrootsCoreMoney::from_minor_units_u32(2400, RadrootsCoreCurrency::USD), - } - } - fn assert_order_lifecycle_evidence_invalid(error: AppSqliteError) { assert!( matches!( @@ -21055,20 +20145,6 @@ mod tests { } } - fn assert_order_revision_decision_sdk_workflow_receipt( - runtime: &DesktopAppRuntime, - order_id: OrderId, - revision_id: &str, - expected_state: AppSdkWorkflowReceiptState, - ) { - assert_order_sdk_workflow_receipt( - runtime, - format!("app:order_revision_decision:{order_id}:{revision_id}").as_str(), - TRADE_REVISION_DECISION_OPERATION_KIND, - expected_state, - ); - } - fn assert_order_cancellation_sdk_workflow_receipt( runtime: &DesktopAppRuntime, order_id: OrderId, diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs @@ -81,12 +81,10 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "buyer-order-review-confirm-public-note", "buyer-order-review-place-order", "buyer-listing-open", - "buyer-order-accept-change", "buyer-order-confirm-replace", "buyer-order-cancel", "buyer-order-detail-back", "buyer-order-keep-current", - "buyer-order-keep-order", "buyer-order-repeat-demand", "buyer-orders-retry-coordination", "personal_orders", @@ -99,8 +97,6 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "buyer.order_open_failed", "buyer.order_cancel_failed", "buyer.order_coordination_retry_failed", - "buyer.order_revision_accept_failed", - "buyer.order_revision_decline_failed", "buyer.repeat_demand_failed", "buyer.section_select_failed", "buyer_notice", @@ -1090,11 +1086,8 @@ const FORBIDDEN_PAYMENT_ACTION_COPY_TERMS: &[&str] = &[ const FORBIDDEN_PRODUCTION_EVENT_KIND_LITERALS: &[(&str, &str)] = &[ ("30340", "KIND_FARM"), ("30402", "KIND_LISTING"), - ("30403", "KIND_LISTING_DRAFT"), ("3422", "KIND_ORDER_REQUEST"), ("3423", "KIND_ORDER_DECISION"), - ("3424", "KIND_ORDER_REVISION_PROPOSAL"), - ("3425", "KIND_ORDER_REVISION_DECISION"), ("3426", "KIND_TRADE_QUESTION"), ("3427", "KIND_TRADE_ANSWER"), ("3428", "KIND_TRADE_DISCOUNT_REQUEST"), @@ -1246,14 +1239,6 @@ const STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS: &[SdkBoundaryForbiddenPattern] = & reason: "app production sources must not call direct SDK order publish APIs", }, SdkBoundaryForbiddenPattern { - pattern: "publish_order_revision_proposal_with_identity", - reason: "app production sources must not call direct SDK order publish APIs", - }, - SdkBoundaryForbiddenPattern { - pattern: "publish_order_revision_decision_with_identity", - reason: "app production sources must not call direct SDK order publish APIs", - }, - SdkBoundaryForbiddenPattern { pattern: "publish_order_cancellation_with_identity", reason: "app production sources must not call direct SDK order publish APIs", }, @@ -1622,8 +1607,6 @@ fn app_sdk_trade_mutation_requests_stay_locator_and_resync_owned() { for request_struct in [ "AppSdkTradeProposeRequest", "AppSdkTradeDecisionRequest", - "AppSdkTradeRevisionProposalRequest", - "AppSdkTradeRevisionDecisionRequest", "AppSdkTradeCancellationRequest", ] { let request = struct_block(source.as_str(), request_struct); @@ -1635,8 +1618,6 @@ fn app_sdk_trade_mutation_requests_stay_locator_and_resync_owned() { for request_struct in [ "AppSdkTradeDecisionRequest", - "AppSdkTradeRevisionProposalRequest", - "AppSdkTradeRevisionDecisionRequest", "AppSdkTradeCancellationRequest", ] { let request = struct_block(source.as_str(), request_struct); @@ -1735,7 +1716,7 @@ fn app_store_active_order_projection_is_root_aware_and_non_authoritative() { let projected_order_id = source_segment( source.as_str(), "pub fn projected_order_id_from_trade_request(", - "fn active_order_revision_status(", + "fn active_order_change_status(", ); assert!(projected_order_id.contains("root_event_id: &str")); assert!(projected_order_id.contains("radroots-cli-order-root")); @@ -1743,8 +1724,6 @@ fn app_store_active_order_projection_is_root_aware_and_non_authoritative() { for forbidden in [ "TradeAcceptRequest::new", "TradeDeclineRequest::new", - "TradeRevisionProposalRequest::new", - "TradeRevisionDecisionRequest::new", "TradeCancelRequest::new", "TradeEvidenceMode::", ] { diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs @@ -2910,41 +2910,6 @@ impl HomeView { } } - fn accept_buyer_order_revision(&mut self, order_id: OrderId, cx: &mut Context<Self>) { - match self.runtime.publish_buyer_order_revision_accept(order_id) { - Ok(true) => cx.notify(), - Ok(false) => {} - Err(runtime_error) => { - error!( - target: "personal_orders", - event = "buyer.order_revision_accept_failed", - error = %runtime_error, - order_id = %order_id, - "failed to accept buyer order change" - ); - } - } - } - - fn decline_buyer_order_revision(&mut self, order_id: OrderId, cx: &mut Context<Self>) { - match self - .runtime - .publish_buyer_order_revision_decline(order_id, false) - { - Ok(true) => cx.notify(), - Ok(false) => {} - Err(runtime_error) => { - error!( - target: "personal_orders", - event = "buyer.order_revision_decline_failed", - error = %runtime_error, - order_id = %order_id, - "failed to keep buyer order" - ); - } - } - } - fn open_products_stock_editor( &mut self, product_id: ProductId, @@ -8571,12 +8536,16 @@ fn about_runtime_rows(runtime: &DesktopAppRuntimeSummary) -> Vec<LabelValueRow> sdk_status.storage_root.display().to_string(), )); rows.push(LabelValueRow::new( - app_shared_text(AppTextKey::MetadataSdkEventStorePath), - path_or_none(sdk_status.event_store_path.as_ref()), + app_shared_text(AppTextKey::MetadataSdkRuntimePath), + path_or_none(sdk_status.runtime_path.as_ref()), + )); + rows.push(LabelValueRow::new( + app_shared_text(AppTextKey::MetadataSdkPrivatePath), + path_or_none(sdk_status.private_path.as_ref()), )); rows.push(LabelValueRow::new( - app_shared_text(AppTextKey::MetadataSdkOutboxPath), - path_or_none(sdk_status.outbox_path.as_ref()), + app_shared_text(AppTextKey::MetadataSdkStudioPath), + path_or_none(sdk_status.studio_path.as_ref()), )); rows.push(LabelValueRow::new( app_shared_text(AppTextKey::MetadataSdkRelayUrlPolicy), @@ -11879,38 +11848,6 @@ fn buyer_order_detail_card( this.child(home_body_text(app_shared_text(AppTextKey::ValueNone))) }), )) - .when( - detail.status == BuyerOrderStatus::Scheduled - && detail.workflow.revision == TradeRevisionStatus::ChangeProposed, - |this| { - this.child( - app_stack_h(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child(action_button_primary( - "buyer-order-accept-change", - app_shared_text(AppTextKey::PersonalOrdersActionAcceptChange), - cx.listener({ - let order_id = detail.order_id; - move |this, _, _, cx| { - this.accept_buyer_order_revision(order_id, cx) - } - }), - cx, - )) - .child(action_button_compact( - "buyer-order-keep-order", - app_shared_text(AppTextKey::PersonalOrdersActionKeepOrder), - cx.listener({ - let order_id = detail.order_id; - move |this, _, _, cx| { - this.decline_buyer_order_revision(order_id, cx) - } - }), - cx, - )), - ) - }, - ) .when(detail.status == BuyerOrderStatus::Placed, |this| { this.child(action_button_compact( "buyer-order-cancel", @@ -18850,8 +18787,9 @@ mod tests { projection_lifecycle_state: AppSdkProjectionLifecycleState::Current, projection_lifecycle_reason: None, storage_root: storage_root.clone(), - event_store_path: Some(storage_root.join("event_store.sqlite")), - outbox_path: Some(storage_root.join("outbox.sqlite")), + runtime_path: Some(storage_root.join("runtime.sqlite")), + private_path: Some(storage_root.join("private.sqlite")), + studio_path: Some(storage_root.join("studio.sqlite")), relay_target_count: 2, relay_url_policy: AppSdkRelayUrlPolicy::Localhost, last_issue: None, diff --git a/crates/i18n/src/keys.rs b/crates/i18n/src/keys.rs @@ -784,8 +784,9 @@ define_app_text_keys! { MetadataSdkIssueRetryable => "metadata.sdk_issue_retryable", MetadataSdkRecoveryAction => "metadata.sdk_recovery_action", MetadataSdkStorageRoot => "metadata.sdk_storage_root", - MetadataSdkEventStorePath => "metadata.sdk_event_store_path", - MetadataSdkOutboxPath => "metadata.sdk_outbox_path", + MetadataSdkRuntimePath => "metadata.sdk_runtime_path", + MetadataSdkPrivatePath => "metadata.sdk_private_path", + MetadataSdkStudioPath => "metadata.sdk_studio_path", MetadataSdkRelayUrlPolicy => "metadata.sdk_relay_url_policy", ValueNone => "value.none", ValueYes => "value.yes", diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs @@ -47,7 +47,7 @@ pub use sdk::{ AppSdkStorageDiagnostics, AppSdkStoragePaths, AppSdkSyncDiagnostics, AppSdkSyncEventStoreDiagnostics, AppSdkSyncOutboxDiagnostics, AppSdkSyncTransportTargetDiagnostics, AppSdkTradeCancellationRequest, AppSdkTradeDecision, - AppSdkTradeDecisionRequest, AppSdkTradeProposeRequest, AppSdkTradeRevisionDecisionRequest, - AppSdkTradeRevisionProposalRequest, AppSdkWorkflowReceipt, app_sdk_storage_root_from_data_root, + AppSdkTradeDecisionRequest, AppSdkTradeProposeRequest, AppSdkWorkflowReceipt, + app_sdk_storage_root_from_data_root, }; pub use startup::{AppStartupEvent, AppStartupEventMetadata, launch_startup_event}; diff --git a/crates/runtime/src/sdk.rs b/crates/runtime/src/sdk.rs @@ -12,19 +12,17 @@ use std::{ use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner}; use radroots_event::{ - RadrootsEventEnvelope, RadrootsEventPtr, + RadrootsEventPtr, contract::RadrootsActorRole, - draft::{RadrootsSignedEvent, RadrootsSignedEventParts}, farm::RadrootsFarm, ids::{ - RadrootsAddressableCoordinate, RadrootsListingAddress, RadrootsOrderId, - RadrootsOrderRevisionId, RadrootsPublicKey, + RadrootsAddressableCoordinate, RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey, }, kinds::KIND_FARM, listing::RadrootsListing, order::{ RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, - RadrootsOrderRevisionOutcome, + RadrootsOrderRequest, }, }; use radroots_nostr::prelude::RadrootsNostrKeys; @@ -37,14 +35,11 @@ use radroots_sdk::{ RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkStoragePaths, RestoreReceipt, RestoreRequest, SatisfactionPolicy, SdkBackupVerification, SdkPublicLocality, StorageStatusReceipt, StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest, - TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, - TRADE_REVISION_DECISION_OPERATION_KIND, TRADE_REVISION_PROPOSAL_OPERATION_KIND, - TRADE_SUBMIT_OPERATION_KIND, TargetPolicy, TradeAcceptRequest, TradeCancelRequest, - TradeCancellationPlan, TradeCancellationReceipt, TradeDecisionPlan, TradeDecisionReceipt, - TradeDeclineRequest, TradeEvidenceIngestRequest, TradeEvidenceMode, TradeMutationOutcome, - TradeProposeRequest, TradeRevisionDecisionPlan, TradeRevisionDecisionReceipt, - TradeRevisionDecisionRequest, TradeRevisionProposalPlan, TradeRevisionProposalReceipt, - TradeRevisionProposalRequest, TradeSubmitPlan, TradeSubmitReceipt, TransportProfile, + TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, TRADE_SUBMIT_OPERATION_KIND, + TargetPolicy, TradeAcceptRequest, TradeCancelRequest, TradeCancellationPlan, + TradeCancellationReceipt, TradeDecisionPlan, TradeDecisionReceipt, TradeDeclineRequest, + TradeEvidenceMode, TradeMutationOutcome, TradeProposeRequest, TradeSubmitPlan, + TradeSubmitReceipt, TransportProfile, }; use radroots_trade::identity::RadrootsTradeLocator; use serde::Serialize; @@ -86,8 +81,9 @@ pub struct AppSdkConfig { #[derive(Clone, Debug, Eq, PartialEq)] pub struct AppSdkStoragePaths { - pub event_store_path: PathBuf, - pub outbox_path: PathBuf, + pub runtime_path: PathBuf, + pub private_path: PathBuf, + pub studio_path: PathBuf, } #[derive(Clone, Debug, PartialEq)] @@ -282,31 +278,6 @@ pub struct AppSdkTradeDecisionRequest { pub idempotency_key: Option<String>, } -pub struct AppSdkTradeRevisionProposalRequest { - pub actor_account_id: String, - pub actor_pubkey: String, - pub signer_keys: RadrootsNostrKeys, - pub locator: RadrootsTradeLocator, - pub revision_id: RadrootsOrderRevisionId, - pub items: Vec<RadrootsOrderItem>, - pub economics: RadrootsOrderEconomics, - pub reason: String, - pub confirm_public_note: bool, - pub idempotency_key: Option<String>, -} - -pub struct AppSdkTradeRevisionDecisionRequest { - pub actor_account_id: String, - pub actor_pubkey: String, - pub signer_keys: RadrootsNostrKeys, - pub locator: RadrootsTradeLocator, - pub revision_id: RadrootsOrderRevisionId, - pub decision: RadrootsOrderRevisionOutcome, - pub evidence: Vec<RadrootsEventEnvelope>, - pub confirm_public_note: bool, - pub idempotency_key: Option<String>, -} - pub struct AppSdkTradeCancellationRequest { pub actor_account_id: String, pub actor_pubkey: String, @@ -336,8 +307,9 @@ pub struct AppSdkRestorePreflightReceipt { pub state: String, pub destination_paths: Option<AppSdkStoragePaths>, pub restored_paths: Option<AppSdkStoragePaths>, - pub event_store_path: PathBuf, - pub outbox_path: PathBuf, + pub runtime_path: PathBuf, + pub private_path: PathBuf, + pub studio_path: PathBuf, pub manifest_path: PathBuf, pub verification: AppSdkBackupVerificationDiagnostics, pub source_storage: AppSdkStorageDiagnostics, @@ -429,14 +401,6 @@ enum AppSdkWorkerCommand { AppSdkTradeDecisionRequest, mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, ), - TradeRevisionProposal( - AppSdkTradeRevisionProposalRequest, - mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, - ), - TradeRevisionDecision( - AppSdkTradeRevisionDecisionRequest, - mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, - ), TradeCancellation( AppSdkTradeCancellationRequest, mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, @@ -462,8 +426,6 @@ impl fmt::Debug for AppSdkWorkerCommand { Self::EnqueueListingPublish(_, _) => formatter.write_str("EnqueueListingPublish"), Self::TradePropose(_, _) => formatter.write_str("TradePropose"), Self::TradeDecision(_, _) => formatter.write_str("TradeDecision"), - Self::TradeRevisionProposal(_, _) => formatter.write_str("TradeRevisionProposal"), - Self::TradeRevisionDecision(_, _) => formatter.write_str("TradeRevisionDecision"), Self::TradeCancellation(_, _) => formatter.write_str("TradeCancellation"), Self::BeginProjectionRebuild(_) => formatter.write_str("BeginProjectionRebuild"), Self::CompleteProjectionRebuild(_) => formatter.write_str("CompleteProjectionRebuild"), @@ -631,24 +593,6 @@ impl AppSdkRuntime { }) } - pub fn trade_revision_propose( - &self, - request: AppSdkTradeRevisionProposalRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.run_command(|response_sender| { - AppSdkWorkerCommand::TradeRevisionProposal(request, response_sender) - }) - } - - pub fn trade_revision_decide( - &self, - request: AppSdkTradeRevisionDecisionRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.run_command(|response_sender| { - AppSdkWorkerCommand::TradeRevisionDecision(request, response_sender) - }) - } - pub fn trade_cancel( &self, request: AppSdkTradeCancellationRequest, @@ -773,8 +717,9 @@ impl From<AppSdkRelayUrlPolicy> for NostrRelayUrlPolicy { impl From<&RadrootsSdkStoragePaths> for AppSdkStoragePaths { fn from(paths: &RadrootsSdkStoragePaths) -> Self { Self { - event_store_path: paths.event_store_path.clone(), - outbox_path: paths.outbox_path.clone(), + runtime_path: paths.runtime_path.clone(), + private_path: paths.private_path.clone(), + studio_path: paths.studio_path.clone(), } } } @@ -988,8 +933,9 @@ impl AppSdkRestorePreflightReceipt { .restored_paths .as_ref() .map(AppSdkStoragePaths::from), - event_store_path: receipt.event_store_path, - outbox_path: receipt.outbox_path, + runtime_path: receipt.runtime_path, + private_path: receipt.private_path, + studio_path: receipt.studio_path, manifest_path: receipt.manifest_path, verification: receipt.verification.into(), source_storage: receipt.manifest.source_status.into(), @@ -1195,28 +1141,6 @@ fn run_app_sdk_worker( }; send_worker_result(&shared, response_sender, result); } - AppSdkWorkerCommand::TradeRevisionProposal(request, response_sender) => { - let result = if let Some(issue) = lifecycle_busy_issue(&shared) { - Err(issue) - } else { - match sdk.as_ref() { - Some(_) => trade_revision_propose_with_sdk(&runtime, &config, request), - None => Err(runtime_unavailable_issue(&shared)), - } - }; - send_worker_result(&shared, response_sender, result); - } - AppSdkWorkerCommand::TradeRevisionDecision(request, response_sender) => { - let result = if let Some(issue) = lifecycle_busy_issue(&shared) { - Err(issue) - } else { - match sdk.as_ref() { - Some(_) => trade_revision_decide_with_sdk(&runtime, &config, request), - None => Err(runtime_unavailable_issue(&shared)), - } - }; - send_worker_result(&shared, response_sender, result); - } AppSdkWorkerCommand::TradeCancellation(request, response_sender) => { let result = if let Some(issue) = lifecycle_busy_issue(&shared) { Err(issue) @@ -1330,20 +1254,6 @@ fn run_degraded_worker( Err(runtime_unavailable_issue(&shared)), ); } - AppSdkWorkerCommand::TradeRevisionProposal(_, response_sender) => { - send_worker_result( - &shared, - response_sender, - Err(runtime_unavailable_issue(&shared)), - ); - } - AppSdkWorkerCommand::TradeRevisionDecision(_, response_sender) => { - send_worker_result( - &shared, - response_sender, - Err(runtime_unavailable_issue(&shared)), - ); - } AppSdkWorkerCommand::TradeCancellation(_, response_sender) => { send_worker_result( &shared, @@ -1572,14 +1482,24 @@ fn trade_propose_with_sdk( RadrootsActorRole::Buyer, )?; let sdk = runtime.block_on(build_sdk_runtime_with_signer(config, request.signer_keys))?; + let buyer_pubkey = + RadrootsPublicKey::parse(request.actor_pubkey.as_str()).map_err(|error| { + AppSdkRuntimeIssue::from_sdk_error(&RadrootsSdkError::InvalidRequest { + message: format!("trade proposal buyer public key is invalid: {error}"), + }) + })?; + let order = RadrootsOrderRequest { + order_id: request.order_id, + listing_addr: request.listing_addr, + buyer_pubkey, + seller_pubkey: request.seller_pubkey, + items: request.items, + economics: request.economics, + }; let mut sdk_request = TradeProposeRequest::new( actor, request.listing_event, - request.order_id, - request.listing_addr, - request.seller_pubkey, - request.items, - request.economics, + order, app_trade_target_policy(), app_trade_publish_mode(), app_trade_satisfaction_policy(), @@ -1657,115 +1577,6 @@ fn trade_decision_with_sdk( app_sdk_trade_decision_receipt(outcome, request.actor_pubkey) } -fn trade_revision_propose_with_sdk( - runtime: &tokio::runtime::Runtime, - config: &AppSdkConfig, - request: AppSdkTradeRevisionProposalRequest, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - let actor = sdk_actor_context( - request.actor_pubkey.as_str(), - request.actor_account_id.as_str(), - RadrootsActorRole::Seller, - )?; - let sdk = runtime.block_on(build_sdk_runtime_with_signer(config, request.signer_keys))?; - let mut sdk_request = TradeRevisionProposalRequest::new( - actor, - request.locator, - request.revision_id, - request.items, - request.economics, - request.reason, - app_trade_target_policy(), - app_trade_publish_mode(), - app_trade_satisfaction_policy(), - TradeEvidenceMode::ResyncBeforeMutation, - ) - .with_privacy_confirmation(app_trade_privacy_confirmation(request.confirm_public_note)); - if let Some(idempotency_key) = request.idempotency_key.as_deref() { - sdk_request = sdk_request - .try_with_idempotency_key(idempotency_key) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - } - let outcome = runtime - .block_on(sdk.trades().seller().propose_revision(sdk_request)) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - app_sdk_trade_revision_proposal_receipt(outcome, request.actor_pubkey) -} - -fn trade_revision_decide_with_sdk( - runtime: &tokio::runtime::Runtime, - config: &AppSdkConfig, - request: AppSdkTradeRevisionDecisionRequest, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - let actor = sdk_actor_context( - request.actor_pubkey.as_str(), - request.actor_account_id.as_str(), - RadrootsActorRole::Buyer, - )?; - let sdk = runtime.block_on(build_sdk_runtime_with_signer(config, request.signer_keys))?; - let evidence = request - .evidence - .into_iter() - .map(signed_trade_evidence_from_envelope) - .map(|event| event.map(TradeEvidenceIngestRequest::new)) - .collect::<Result<Vec<_>, _>>()?; - let mut sdk_request = TradeRevisionDecisionRequest::new( - actor, - request.locator, - request.revision_id, - request.decision.clone(), - app_trade_target_policy(), - app_trade_publish_mode(), - app_trade_satisfaction_policy(), - TradeEvidenceMode::require_explicit_evidence(evidence), - ) - .with_privacy_confirmation(app_trade_privacy_confirmation(request.confirm_public_note)); - if let Some(idempotency_key) = request.idempotency_key.as_deref() { - sdk_request = sdk_request - .try_with_idempotency_key(idempotency_key) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - } - let outcome = match request.decision { - RadrootsOrderRevisionOutcome::Accepted => runtime - .block_on(sdk.trades().buyer().accept_revision(sdk_request)) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?, - RadrootsOrderRevisionOutcome::Declined { .. } => runtime - .block_on(sdk.trades().buyer().decline_revision(sdk_request)) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?, - }; - app_sdk_trade_revision_decision_receipt(outcome, request.actor_pubkey) -} - -fn signed_trade_evidence_from_envelope( - event: RadrootsEventEnvelope, -) -> Result<RadrootsSignedEvent, AppSdkRuntimeIssue> { - let raw_json = json!({ - "id": event.id_str(), - "pubkey": event.author_str(), - "created_at": event.created_at_u64(), - "kind": event.kind_u32(), - "tags": event.tags_as_vec(), - "content": event.content(), - "sig": event.sig_str(), - }) - .to_string(); - RadrootsSignedEvent::new(RadrootsSignedEventParts { - id: event.id_str().to_owned(), - pubkey: event.author_str().to_owned(), - created_at: event.created_at_u64(), - kind: event.kind_u32(), - tags: event.tags_as_vec(), - content: event.content().to_owned(), - sig: event.sig_str().to_owned(), - raw_json, - }) - .map_err(|error| { - AppSdkRuntimeIssue::from_sdk_error(&RadrootsSdkError::InvalidRequest { - message: format!("trade revision evidence signed event is invalid: {error}"), - }) - }) -} - fn trade_cancel_with_sdk( runtime: &tokio::runtime::Runtime, config: &AppSdkConfig, @@ -1896,50 +1707,6 @@ fn app_sdk_trade_decision_receipt( } } -fn app_sdk_trade_revision_proposal_receipt( - outcome: TradeMutationOutcome<TradeRevisionProposalPlan, TradeRevisionProposalReceipt>, - actor_pubkey: String, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - match outcome { - TradeMutationOutcome::Enqueued { receipt } - | TradeMutationOutcome::Published { receipt, .. } => Ok(AppSdkWorkflowReceipt { - operation_kind: TRADE_REVISION_PROPOSAL_OPERATION_KIND.to_owned(), - expected_event_id: receipt.expected_event_id.as_str().to_owned(), - signed_event_id: receipt.signed_event_id.as_str().to_owned(), - outbox_operation_id: receipt.outbox_operation_id, - outbox_event_id: receipt.outbox_event_id, - state: sdk_mutation_state_key(receipt.state).to_owned(), - idempotency_digest_prefix: receipt.idempotency_digest_prefix, - actor_pubkey, - }), - TradeMutationOutcome::DryRun { .. } => { - Err(unexpected_trade_dry_run_issue("trade.revision.propose")) - } - } -} - -fn app_sdk_trade_revision_decision_receipt( - outcome: TradeMutationOutcome<TradeRevisionDecisionPlan, TradeRevisionDecisionReceipt>, - actor_pubkey: String, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - match outcome { - TradeMutationOutcome::Enqueued { receipt } - | TradeMutationOutcome::Published { receipt, .. } => Ok(AppSdkWorkflowReceipt { - operation_kind: TRADE_REVISION_DECISION_OPERATION_KIND.to_owned(), - expected_event_id: receipt.expected_event_id.as_str().to_owned(), - signed_event_id: receipt.signed_event_id.as_str().to_owned(), - outbox_operation_id: receipt.outbox_operation_id, - outbox_event_id: receipt.outbox_event_id, - state: sdk_mutation_state_key(receipt.state).to_owned(), - idempotency_digest_prefix: receipt.idempotency_digest_prefix, - actor_pubkey, - }), - TradeMutationOutcome::DryRun { .. } => { - Err(unexpected_trade_dry_run_issue("trade.revision.decide")) - } - } -} - fn app_sdk_trade_cancellation_receipt( outcome: TradeMutationOutcome<TradeCancellationPlan, TradeCancellationReceipt>, actor_pubkey: String, @@ -2201,12 +1968,16 @@ mod tests { .storage_paths .expect("storage paths should be present"); assert_eq!( - storage_paths.event_store_path, - storage_root.join("event_store.sqlite") + storage_paths.runtime_path, + storage_root.join("runtime.sqlite") + ); + assert_eq!( + storage_paths.private_path, + storage_root.join("private.sqlite") ); assert_eq!( - storage_paths.outbox_path, - storage_root.join("outbox.sqlite") + storage_paths.studio_path, + storage_root.join("studio.sqlite") ); let storage = runtime .storage_status() diff --git a/crates/store/src/interop.rs b/crates/store/src/interop.rs @@ -5,23 +5,15 @@ use radroots_event::{ ids::{RadrootsEventId, RadrootsOrderId, RadrootsPublicKey}, kinds::{ KIND_FARM as RADROOTS_KIND_FARM, KIND_LISTING as RADROOTS_KIND_LISTING, - KIND_LISTING_DRAFT as RADROOTS_KIND_LISTING_DRAFT, KIND_ORDER_CANCELLATION as RADROOTS_KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION as RADROOTS_KIND_ORDER_DECISION, - KIND_ORDER_REQUEST as RADROOTS_KIND_ORDER_REQUEST, - KIND_ORDER_REVISION_DECISION as RADROOTS_KIND_ORDER_REVISION_DECISION, - KIND_ORDER_REVISION_PROPOSAL as RADROOTS_KIND_ORDER_REVISION_PROPOSAL, - KIND_TRADE_VALIDATION_RECEIPT, - }, - order::{ - RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRequest, - RadrootsOrderRevisionOutcome, + KIND_ORDER_REQUEST as RADROOTS_KIND_ORDER_REQUEST, KIND_TRADE_VALIDATION_RECEIPT, }, + order::{RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRequest}, }; use radroots_event_codec::order::{ order_cancellation_from_event, order_decision_from_event, order_event_context_from_tags, - order_request_from_event, order_revision_decision_from_event, - order_revision_proposal_from_event, + order_request_from_event, }; use radroots_runtime_store::{ PublishOutboxStatus, RelayDeliveryEvidence, RelayDeliveryState, RuntimeStore, @@ -37,8 +29,7 @@ use radroots_studio_app_view::{ }; use radroots_trade::order::{ RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderProjection, - RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, reduce_order_events, + RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, reduce_order_events, }; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptTags, validation_receipt_from_event, @@ -56,20 +47,12 @@ const RUNTIME_STORE_BATCH_LIMIT: u32 = 500; const APP_LOCAL_INTEROP_CURSOR_ID: &str = "radroots_studio_app_sqlite_projection_v1"; const KIND_FARM: i64 = RADROOTS_KIND_FARM as i64; const KIND_LISTING: i64 = RADROOTS_KIND_LISTING as i64; -const KIND_LISTING_DRAFT: i64 = RADROOTS_KIND_LISTING_DRAFT as i64; const KIND_ORDER_REQUEST: i64 = RADROOTS_KIND_ORDER_REQUEST as i64; const KIND_ORDER_DECISION: i64 = RADROOTS_KIND_ORDER_DECISION as i64; -const KIND_ORDER_REVISION: i64 = RADROOTS_KIND_ORDER_REVISION_PROPOSAL as i64; -const KIND_ORDER_REVISION_DECISION: i64 = RADROOTS_KIND_ORDER_REVISION_DECISION as i64; const KIND_ORDER_CANCEL: i64 = RADROOTS_KIND_ORDER_CANCELLATION as i64; const KIND_VALIDATION_RECEIPT: i64 = KIND_TRADE_VALIDATION_RECEIPT as i64; -const ACTIVE_ORDER_EVENT_KINDS: [i64; 5] = [ - KIND_ORDER_REQUEST, - KIND_ORDER_DECISION, - KIND_ORDER_REVISION, - KIND_ORDER_REVISION_DECISION, - KIND_ORDER_CANCEL, -]; +const ACTIVE_ORDER_EVENT_KINDS: [i64; 3] = + [KIND_ORDER_REQUEST, KIND_ORDER_DECISION, KIND_ORDER_CANCEL]; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AppLocalInteropImportReport { @@ -762,7 +745,7 @@ impl<'a> AppLocalInteropRepository<'a> { ) -> Result<Option<ProjectionRecord>, AppSqliteError> { match record.event_kind { Some(KIND_FARM) => self.import_signed_farm(record), - Some(KIND_LISTING | KIND_LISTING_DRAFT) => self.import_signed_listing(record), + Some(KIND_LISTING) => self.import_signed_listing(record), Some(KIND_VALIDATION_RECEIPT) => self.import_signed_validation_receipt(record), Some(kind) if active_order_event_kind(kind) => self.import_signed_active_order(record), _ => Ok(Some(ProjectionRecord { @@ -1185,8 +1168,6 @@ impl<'a> AppLocalInteropRepository<'a> { ); let buckets = ActiveOrderEvidenceBuckets::from_evidence(evidence); let requests = buckets.requests.clone(); - let revision_proposals = buckets.revision_proposals.clone(); - let revision_decisions = buckets.revision_decisions.clone(); let reducer_order_id = raw_order_id .parse::<RadrootsOrderId>() @@ -1199,8 +1180,6 @@ impl<'a> AppLocalInteropRepository<'a> { RadrootsOrderReductionInputs { requests: buckets.requests, decisions: buckets.decisions, - revision_proposals: buckets.revision_proposals, - revision_decisions: buckets.revision_decisions, cancellations: buckets.cancellations, }, ); @@ -1210,16 +1189,9 @@ impl<'a> AppLocalInteropRepository<'a> { .find(|request| request.event_id == event_id) .map(|request| &request.payload) }); - let revision = - active_order_revision_status(&projection, &revision_proposals, &revision_decisions); - let agreement_source = request_payload.map(|request| { - active_order_agreement_source( - request, - &projection, - &revision_proposals, - &revision_decisions, - ) - }); + let revision = active_order_change_status(&projection); + let agreement_source = + request_payload.map(|request| active_order_agreement_source(request, &projection)); self.apply_active_order_projection( order_id, &projection, @@ -2813,8 +2785,6 @@ struct ExistingOrderLineMetadata { enum ActiveOrderEvidence { Request(RadrootsOrderRequestRecord), Decision(RadrootsOrderDecisionRecord), - RevisionProposal(RadrootsOrderRevisionProposalRecord), - RevisionDecision(RadrootsOrderRevisionDecisionRecord), Cancellation(RadrootsOrderCancellationRecord), } @@ -2823,8 +2793,6 @@ impl ActiveOrderEvidence { match self { Self::Request(record) => record.event_id.as_str(), Self::Decision(record) => record.event_id.as_str(), - Self::RevisionProposal(record) => record.event_id.as_str(), - Self::RevisionDecision(record) => record.event_id.as_str(), Self::Cancellation(record) => record.event_id.as_str(), } } @@ -2833,8 +2801,6 @@ impl ActiveOrderEvidence { match self { Self::Request(record) => record.event_id.as_str(), Self::Decision(record) => record.root_event_id.as_str(), - Self::RevisionProposal(record) => record.root_event_id.as_str(), - Self::RevisionDecision(record) => record.root_event_id.as_str(), Self::Cancellation(record) => record.root_event_id.as_str(), } } @@ -2851,16 +2817,6 @@ impl ActiveOrderEvidence { record.payload.buyer_pubkey.as_str(), record.root_event_id.as_str(), ), - Self::RevisionProposal(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - record.root_event_id.as_str(), - ), - Self::RevisionDecision(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - record.root_event_id.as_str(), - ), Self::Cancellation(record) => ( record.payload.order_id.as_str(), record.payload.buyer_pubkey.as_str(), @@ -2874,8 +2830,6 @@ impl ActiveOrderEvidence { struct ActiveOrderEvidenceBuckets { requests: Vec<RadrootsOrderRequestRecord>, decisions: Vec<RadrootsOrderDecisionRecord>, - revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, - revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, cancellations: Vec<RadrootsOrderCancellationRecord>, } @@ -2886,12 +2840,6 @@ impl ActiveOrderEvidenceBuckets { match record { ActiveOrderEvidence::Request(record) => buckets.requests.push(record), ActiveOrderEvidence::Decision(record) => buckets.decisions.push(record), - ActiveOrderEvidence::RevisionProposal(record) => { - buckets.revision_proposals.push(record); - } - ActiveOrderEvidence::RevisionDecision(record) => { - buckets.revision_decisions.push(record); - } ActiveOrderEvidence::Cancellation(record) => buckets.cancellations.push(record), } } @@ -3078,36 +3026,6 @@ fn active_order_evidence_from_event(event: &RadrootsEventEnvelope) -> Option<Act payload: envelope.payload, })) } - KIND_ORDER_REVISION => { - let envelope = order_revision_proposal_from_event(event).ok()?; - let tags = event.tags_as_vec(); - let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; - Some(ActiveOrderEvidence::RevisionProposal( - RadrootsOrderRevisionProposalRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - }, - )) - } - KIND_ORDER_REVISION_DECISION => { - let envelope = order_revision_decision_from_event(event).ok()?; - let tags = event.tags_as_vec(); - let context = order_event_context_from_tags(envelope.message_type, &tags).ok()?; - Some(ActiveOrderEvidence::RevisionDecision( - RadrootsOrderRevisionDecisionRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - }, - )) - } KIND_ORDER_CANCEL => { let envelope = order_cancellation_from_event(event).ok()?; let tags = event.tags_as_vec(); @@ -3325,76 +3243,14 @@ fn projected_order_id(order_id: &str, buyer_pubkey: &str, root_event_id: &str) - projected_order_id_from_trade_request(order_id, buyer_pubkey, root_event_id) } -fn active_order_revision_status( - projection: &RadrootsOrderProjection, - revision_proposals: &[RadrootsOrderRevisionProposalRecord], - revision_decisions: &[RadrootsOrderRevisionDecisionRecord], -) -> TradeRevisionStatus { - let Some(mut parent_event_id) = projection - .decision_event_id - .clone() - .or_else(|| projection.request_event_id.clone()) - else { - return TradeRevisionStatus::None; - }; - let mut status = TradeRevisionStatus::None; - loop { - let matching_proposals = revision_proposals - .iter() - .filter(|proposal| proposal.prev_event_id == parent_event_id) - .collect::<Vec<_>>(); - let proposal = match matching_proposals.as_slice() { - [] => return status, - [proposal] => *proposal, - _ => return TradeRevisionStatus::None, - }; - let matching_decisions = revision_decisions - .iter() - .filter(|decision| decision.prev_event_id == proposal.event_id) - .collect::<Vec<_>>(); - let decision = match matching_decisions.as_slice() { - [] => return TradeRevisionStatus::ChangeProposed, - [decision] => *decision, - _ => return TradeRevisionStatus::None, - }; - if decision.payload.revision_id != proposal.payload.revision_id { - return TradeRevisionStatus::None; - } - status = match &decision.payload.decision { - RadrootsOrderRevisionOutcome::Accepted => TradeRevisionStatus::Updated, - RadrootsOrderRevisionOutcome::Declined { .. } => TradeRevisionStatus::KeptAsPlaced, - }; - parent_event_id.clone_from(&decision.event_id); - } +fn active_order_change_status(_projection: &RadrootsOrderProjection) -> TradeRevisionStatus { + TradeRevisionStatus::None } fn active_order_agreement_source( request: &RadrootsOrderRequest, - projection: &RadrootsOrderProjection, - revision_proposals: &[RadrootsOrderRevisionProposalRecord], - revision_decisions: &[RadrootsOrderRevisionDecisionRecord], + _projection: &RadrootsOrderProjection, ) -> ActiveOrderAgreementSource { - if let Some(agreement_event_id) = projection.agreement_event_id.as_deref() - && projection.decision_event_id.as_deref() != Some(agreement_event_id) - && let Some(revision_decision) = revision_decisions.iter().find(|decision| { - decision.event_id == agreement_event_id - && matches!( - &decision.payload.decision, - RadrootsOrderRevisionOutcome::Accepted - ) - }) - && let Some(revision_proposal) = revision_proposals.iter().find(|proposal| { - proposal.event_id == revision_decision.prev_event_id - && proposal.payload.revision_id == revision_decision.payload.revision_id - }) - { - return ActiveOrderAgreementSource { - listing_addr: revision_proposal.payload.listing_addr.to_string(), - seller_pubkey: revision_proposal.payload.seller_pubkey.to_string(), - items: revision_proposal.payload.items.clone(), - economics: revision_proposal.payload.economics.clone(), - }; - } ActiveOrderAgreementSource { listing_addr: request.listing_addr.to_string(), seller_pubkey: request.seller_pubkey.to_string(), @@ -3884,20 +3740,18 @@ mod tests { RadrootsEventEnvelope, RadrootsEventEnvelopeParts, RadrootsEventPtr, ids::{ RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, - RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, + RadrootsOrderQuoteId, RadrootsPublicKey, }, order::{ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPricingBasis, - RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, - RadrootsOrderRevisionProposal, + RadrootsOrderRequest, }, wire::RadrootsNip01EventWireParts as WireEventParts, }; use radroots_event_codec::order::{ order_cancellation_event_build, order_decision_event_build, order_request_event_build, - order_revision_decision_event_build, order_revision_proposal_event_build, }; use radroots_runtime_store::{ PublishOutboxStatus, RelayDeliveryEvidence, RuntimeStore, RuntimeStoreRecordFamily, @@ -3907,9 +3761,8 @@ mod tests { use radroots_studio_app_view::{ BuyerContext, BuyerOrderStatus, FarmId, FarmOrderMethod, OrderId, OrderStatus, OrdersFilter, OrdersScreenQueryState, ProductAvailabilityState, ProductId, - TradeAgreementStatus, TradeInventoryStatus, TradeRevisionStatus, - TradeValidationReceiptProofSystem, TradeValidationReceiptResult, - TradeValidationReceiptType, TradeWorkflowSource, + TradeAgreementStatus, TradeInventoryStatus, TradeValidationReceiptProofSystem, + TradeValidationReceiptResult, TradeValidationReceiptType, TradeWorkflowSource, }; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, @@ -4401,10 +4254,6 @@ mod tests { raw.parse().expect("valid order id") } - fn typed_revision_id(raw: &str) -> RadrootsOrderRevisionId { - raw.parse().expect("valid revision id") - } - fn typed_quote_id(raw: &str) -> RadrootsOrderQuoteId { raw.parse().expect("valid quote id") } @@ -4508,62 +4357,6 @@ mod tests { } } - fn revision_proposal_payload( - revision_id: &str, - order_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - root_event_id: &str, - prev_event_id: &str, - ) -> RadrootsOrderRevisionProposal { - let mut request = - order_request_payload(order_id, listing_addr, buyer_pubkey, seller_pubkey); - request.items[0].bin_count = 3; - request.economics.quote_id = - typed_quote_id(format!("quote-{order_id}-{revision_id}").as_str()); - request.economics.quote_version = 2; - request.economics.items[0].bin_count = 3; - request.economics.items[0].line_subtotal = usd("24"); - request.economics.subtotal = usd("24"); - request.economics.total = usd("24"); - RadrootsOrderRevisionProposal { - revision_id: typed_revision_id(revision_id), - order_id: typed_order_id(order_id), - listing_addr: typed_listing_addr(listing_addr), - buyer_pubkey: typed_pubkey(buyer_pubkey), - seller_pubkey: typed_pubkey(seller_pubkey), - root_event_id: typed_event_id(root_event_id), - prev_event_id: typed_event_id(prev_event_id), - items: request.items, - economics: request.economics, - reason: "seller confirmed updated pickup details".to_owned(), - } - } - - #[expect(clippy::too_many_arguments)] - fn revision_decision_payload( - revision_id: &str, - order_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - root_event_id: &str, - prev_event_id: &str, - decision: RadrootsOrderRevisionOutcome, - ) -> RadrootsOrderRevisionDecision { - RadrootsOrderRevisionDecision { - revision_id: typed_revision_id(revision_id), - order_id: typed_order_id(order_id), - listing_addr: typed_listing_addr(listing_addr), - buyer_pubkey: typed_pubkey(buyer_pubkey), - seller_pubkey: typed_pubkey(seller_pubkey), - root_event_id: typed_event_id(root_event_id), - prev_event_id: typed_event_id(prev_event_id), - decision, - } - } - fn order_cancel_payload( order_id: &str, listing_addr: &str, @@ -4814,6 +4607,13 @@ mod tests { listing_event_id: listing_event_id.to_owned(), root_event_id: root_event_id.to_owned(), target_event_id: target_event_id.to_owned(), + validator_set_addr: radroots_trade::validation_receipt::validator_set_address_from_str( + "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde", + ) + .expect("validator set address"), + validator_set_event_id: + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + .to_owned(), statement_type: RadrootsValidationReceiptType::TradeTransition, }, version: VALIDATION_RECEIPT_VERSION, @@ -6091,194 +5891,6 @@ mod tests { } #[test] - fn active_order_revision_projects_through_cli_reducer_state() { - let app_store = - AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); - let events = runtime_store_store(); - let farm_key = "EEEEEEEEEEEEEEEEEEEEEE"; - let listing_key = "AAAAAAAAAAAAAAAAAAAAAw"; - let seller_pubkey = test_pubkey("seller-pubkey"); - let seller_pubkey = seller_pubkey.as_str(); - let buyer_pubkey = test_pubkey("app-buyer-pubkey"); - let buyer_pubkey = buyer_pubkey.as_str(); - let order_id_raw = "active-revision-order-1"; - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - events - .append_record(&signed_market_listing_record( - "active-revision-listing", - seller_pubkey, - farm_key, - listing_key, - "Revision Eggs", - "9", - "active", - "pickup", - "North barn pickup", - 4_102_444_800, - 4_102_531_200, - RuntimeStoreRecordStatus::Published, - PublishOutboxStatus::Acknowledged, - )) - .expect("append revision listing"); - app_store - .import_shared_runtime_store_from_store(&events) - .expect("import revision listing"); - - let request_payload = order_request_payload( - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - ); - let request_parts = order_request_event_build( - &listing_event_ptr("active-revision-listing-event"), - &request_payload, - ) - .expect("build revision order request"); - let request_event = - event_from_parts("active-revision-request-event", buyer_pubkey, request_parts); - events - .append_record(&signed_order_event_record( - "app:signed_event:active-revision:request", - &request_event, - listing_addr.as_str(), - SourceRuntime::App, - Some("acct_revision"), - )) - .expect("append revision order request"); - app_store - .import_shared_runtime_store_from_store(&events) - .expect("import revision order request"); - - let proposal_payload = revision_proposal_payload( - "revision-1", - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - request_event.id_str(), - request_event.id_str(), - ); - let proposal_parts = order_revision_proposal_event_build( - &typed_event_id(request_event.id_str()), - &typed_event_id(request_event.id_str()), - &proposal_payload, - ) - .expect("build revision proposal"); - let proposal_event = event_from_parts( - "active-revision-proposal-event", - seller_pubkey, - proposal_parts, - ); - events - .append_record(&signed_order_event_record( - "cli:signed_event:active-revision:proposal", - &proposal_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append revision proposal"); - app_store - .import_shared_runtime_store_from_store(&events) - .expect("import revision proposal"); - - let seller_farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); - let order_id = projected_order_id(order_id_raw, buyer_pubkey, request_event.id_str()); - let buyer_context = BuyerContext::account("acct_revision"); - let seller_orders = app_store - .load_orders_list( - seller_farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load revision seller orders after proposal"); - assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsAction); - assert_eq!( - seller_orders.rows[0].workflow.revision, - TradeRevisionStatus::ChangeProposed - ); - let buyer_detail = app_store - .load_buyer_order_detail(&buyer_context, order_id) - .expect("load revision buyer detail after proposal") - .expect("revision buyer detail after proposal"); - assert_eq!( - buyer_detail.workflow.revision, - TradeRevisionStatus::ChangeProposed - ); - assert_eq!(buyer_detail.economics.total_minor_units, Some(1600)); - - let revision_decision_payload = revision_decision_payload( - "revision-1", - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - request_event.id_str(), - proposal_event.id_str(), - RadrootsOrderRevisionOutcome::Accepted, - ); - let revision_decision_parts = order_revision_decision_event_build( - &typed_event_id(request_event.id_str()), - &typed_event_id(proposal_event.id_str()), - &revision_decision_payload, - ) - .expect("build revision decision"); - let revision_decision_event = event_from_parts( - "active-revision-decision-response-event", - buyer_pubkey, - revision_decision_parts, - ); - events - .append_record(&signed_order_event_record( - "app:signed_event:active-revision:decision-response", - &revision_decision_event, - listing_addr.as_str(), - SourceRuntime::App, - Some("acct_revision"), - )) - .expect("append revision decision"); - app_store - .import_shared_runtime_store_from_store(&events) - .expect("import revision decision"); - let seller_orders = app_store - .load_orders_list( - seller_farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load revision seller orders after decision"); - assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsAction); - assert_eq!( - seller_orders.rows[0].workflow.agreement, - TradeAgreementStatus::AgreedPendingValidation - ); - assert_eq!( - seller_orders.rows[0].workflow.revision, - TradeRevisionStatus::Updated - ); - let seller_detail = app_store - .load_order_detail(seller_farm_id, order_id) - .expect("load revision seller detail after decision") - .expect("revision seller detail after decision"); - let buyer_detail = app_store - .load_buyer_order_detail(&buyer_context, order_id) - .expect("load revision buyer detail after decision") - .expect("revision buyer detail after decision"); - assert_eq!( - seller_detail.workflow.revision, - TradeRevisionStatus::Updated - ); - assert_eq!(seller_detail.economics.total_minor_units, Some(2400)); - assert_eq!(buyer_detail.workflow.revision, TradeRevisionStatus::Updated); - assert_eq!(buyer_detail.economics.total_minor_units, Some(2400)); - } - - #[test] fn active_order_pre_agreement_cancellation_projects_through_cli_reducer_state() { let app_store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); diff --git a/crates/sync/src/lib.rs b/crates/sync/src/lib.rs @@ -5,9 +5,8 @@ mod publish; pub use publish::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, - AppOrderRequestItemPayload, AppOrderRequestPublishPayload, - AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, - AppPublishContext, AppPublishPayload, AppPublishPayloadJsonError, AppPublishValidationFailure, + AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppPublishContext, + AppPublishPayload, AppPublishPayloadJsonError, AppPublishValidationFailure, AppPublishValidationFailureSet, AppPublishWorkKind, }; diff --git a/crates/sync/src/publish.rs b/crates/sync/src/publish.rs @@ -1,10 +1,6 @@ -use radroots_event::order::{ - RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRevisionOutcome, -}; use radroots_sdk::{ FARM_PUBLISH_OPERATION_KIND, LISTING_PUBLISH_OPERATION_KIND, TRADE_CANCELLATION_OPERATION_KIND, - TRADE_DECISION_OPERATION_KIND, TRADE_REVISION_DECISION_OPERATION_KIND, - TRADE_REVISION_PROPOSAL_OPERATION_KIND, TRADE_SUBMIT_OPERATION_KIND, + TRADE_DECISION_OPERATION_KIND, TRADE_SUBMIT_OPERATION_KIND, }; use radroots_studio_app_view::{ FarmId, FarmReadiness, FulfillmentWindowId, OrderId, ProductId, ProductStatus, @@ -21,8 +17,6 @@ pub enum AppPublishWorkKind { Listing, OrderRequest, OrderDecision, - OrderRevisionProposal, - OrderRevisionDecision, OrderCancellation, } @@ -33,8 +27,6 @@ impl AppPublishWorkKind { Self::Listing => "listing", Self::OrderRequest => "order_request", Self::OrderDecision => "order_decision", - Self::OrderRevisionProposal => "order_revision_proposal", - Self::OrderRevisionDecision => "order_revision_decision", Self::OrderCancellation => "order_cancellation", } } @@ -45,8 +37,6 @@ impl AppPublishWorkKind { Self::Listing => LISTING_PUBLISH_OPERATION_KIND, Self::OrderRequest => TRADE_SUBMIT_OPERATION_KIND, Self::OrderDecision => TRADE_DECISION_OPERATION_KIND, - Self::OrderRevisionProposal => TRADE_REVISION_PROPOSAL_OPERATION_KIND, - Self::OrderRevisionDecision => TRADE_REVISION_DECISION_OPERATION_KIND, Self::OrderCancellation => TRADE_CANCELLATION_OPERATION_KIND, } } @@ -182,38 +172,6 @@ pub struct AppOrderDecisionPublishPayload { } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AppOrderRevisionProposalPublishPayload { - pub context: AppPublishContext, - pub app_order_id: OrderId, - pub farm_id: FarmId, - pub trade_order_id: String, - pub request_event_id: String, - pub revision_id: String, - pub listing_addr: String, - pub buyer_pubkey: String, - pub seller_pubkey: String, - pub items: Vec<RadrootsOrderItem>, - pub economics: RadrootsOrderEconomics, - pub reason: String, - pub confirm_public_note: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AppOrderRevisionDecisionPublishPayload { - pub context: AppPublishContext, - pub app_order_id: OrderId, - pub farm_id: FarmId, - pub trade_order_id: String, - pub request_event_id: String, - pub revision_id: String, - pub listing_addr: String, - pub buyer_pubkey: String, - pub seller_pubkey: String, - pub decision: RadrootsOrderRevisionOutcome, - pub confirm_public_note: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AppOrderCancellationPublishPayload { pub context: AppPublishContext, pub app_order_id: OrderId, @@ -234,8 +192,6 @@ pub enum AppPublishPayload { Listing(AppListingPublishPayload), OrderRequest(AppOrderRequestPublishPayload), OrderDecision(AppOrderDecisionPublishPayload), - OrderRevisionProposal(AppOrderRevisionProposalPublishPayload), - OrderRevisionDecision(AppOrderRevisionDecisionPublishPayload), OrderCancellation(AppOrderCancellationPublishPayload), } @@ -246,8 +202,6 @@ impl AppPublishPayload { Self::Listing(_) => AppPublishWorkKind::Listing, Self::OrderRequest(_) => AppPublishWorkKind::OrderRequest, Self::OrderDecision(_) => AppPublishWorkKind::OrderDecision, - Self::OrderRevisionProposal(_) => AppPublishWorkKind::OrderRevisionProposal, - Self::OrderRevisionDecision(_) => AppPublishWorkKind::OrderRevisionDecision, Self::OrderCancellation(_) => AppPublishWorkKind::OrderCancellation, } } @@ -262,8 +216,6 @@ impl AppPublishPayload { Self::Listing(payload) => SyncAggregateRef::Product(payload.product_id), Self::OrderRequest(payload) => SyncAggregateRef::Order(payload.order_id), Self::OrderDecision(payload) => SyncAggregateRef::Order(payload.app_order_id), - Self::OrderRevisionProposal(payload) => SyncAggregateRef::Order(payload.app_order_id), - Self::OrderRevisionDecision(payload) => SyncAggregateRef::Order(payload.app_order_id), Self::OrderCancellation(payload) => SyncAggregateRef::Order(payload.app_order_id), } } @@ -430,51 +382,6 @@ impl AppPublishPayload { } } } - Self::OrderRevisionProposal(payload) => { - validate_lifecycle_order_fields( - &payload.context, - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - &mut failures, - ); - if payload.revision_id.trim().is_empty() { - failures.push(AppPublishValidationFailure::MissingOrderRevisionId); - } - if payload.items.is_empty() - || payload - .items - .iter() - .any(|item| item.bin_id.trim().is_empty() || item.bin_count == 0) - { - failures.push(AppPublishValidationFailure::MissingOrderRevisionItems); - } - if payload.economics.validate().is_err() { - failures.push(AppPublishValidationFailure::InvalidOrderRevisionEconomics); - } - if payload.reason.trim().is_empty() { - failures.push(AppPublishValidationFailure::MissingOrderRevisionReason); - } - } - Self::OrderRevisionDecision(payload) => { - validate_lifecycle_order_fields( - &payload.context, - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - &mut failures, - ); - if payload.revision_id.trim().is_empty() { - failures.push(AppPublishValidationFailure::MissingOrderRevisionId); - } - if payload.decision.validate().is_err() { - failures.push(AppPublishValidationFailure::MissingOrderRevisionDecisionReason); - } - } Self::OrderCancellation(payload) => { validate_lifecycle_order_fields( &payload.context, @@ -567,11 +474,6 @@ pub enum AppPublishValidationFailure { MissingOrderRequestEventId, MissingOrderDecisionInventory, MissingOrderDeclineReason, - MissingOrderRevisionId, - MissingOrderRevisionItems, - InvalidOrderRevisionEconomics, - MissingOrderRevisionReason, - MissingOrderRevisionDecisionReason, MissingOrderCancellationReason, } @@ -605,11 +507,6 @@ impl AppPublishValidationFailure { Self::MissingOrderRequestEventId => "missing_order_request_event_id", Self::MissingOrderDecisionInventory => "missing_order_decision_inventory", Self::MissingOrderDeclineReason => "missing_order_decline_reason", - Self::MissingOrderRevisionId => "missing_order_revision_id", - Self::MissingOrderRevisionItems => "missing_order_revision_items", - Self::InvalidOrderRevisionEconomics => "invalid_order_revision_economics", - Self::MissingOrderRevisionReason => "missing_order_revision_reason", - Self::MissingOrderRevisionDecisionReason => "missing_order_revision_decision_reason", Self::MissingOrderCancellationReason => "missing_order_cancellation_reason", } } @@ -664,20 +561,14 @@ mod tests { use super::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, AppOrderRequestItemPayload, - AppOrderRequestPublishPayload, AppOrderRevisionDecisionPublishPayload, - AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, + AppOrderRequestPublishPayload, AppPublishContext, AppPublishPayload, AppPublishValidationFailure, AppPublishWorkKind, FARM_PUBLISH_OPERATION_KIND, TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, - TRADE_REVISION_DECISION_OPERATION_KIND, TRADE_REVISION_PROPOSAL_OPERATION_KIND, }; use crate::{ PendingSyncOperation, PendingSyncOperationState, SyncAggregateRef, SyncOperationKind, }; - use radroots_event::order::{ - RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRevisionOutcome, - }; use radroots_studio_app_view::{FarmId, FarmReadiness, OrderId, ProductId, ProductStatus}; - use serde_json::json; #[test] fn publish_payload_serializes_with_stable_kind_and_sdk_target() { @@ -720,19 +611,15 @@ mod tests { AppPublishWorkKind::Listing, AppPublishWorkKind::OrderRequest, AppPublishWorkKind::OrderDecision, - AppPublishWorkKind::OrderRevisionProposal, - AppPublishWorkKind::OrderRevisionDecision, AppPublishWorkKind::OrderCancellation, ]; - assert_eq!(work_kinds.len(), 7); + assert_eq!(work_kinds.len(), 5); assert_eq!(work_kinds[0].storage_key(), "farm_profile"); assert_eq!(work_kinds[1].storage_key(), "listing"); assert_eq!(work_kinds[2].storage_key(), "order_request"); assert_eq!(work_kinds[3].storage_key(), "order_decision"); - assert_eq!(work_kinds[4].storage_key(), "order_revision_proposal"); - assert_eq!(work_kinds[5].storage_key(), "order_revision_decision"); - assert_eq!(work_kinds[6].storage_key(), "order_cancellation"); + assert_eq!(work_kinds[4].storage_key(), "order_cancellation"); } #[test] @@ -955,115 +842,6 @@ mod tests { } #[test] - fn order_revision_publish_payloads_report_stable_validation_reason_codes() { - let order_id = OrderId::generate(); - let farm_id = FarmId::generate(); - let economics = revision_economics(); - let valid_proposal = - AppPublishPayload::OrderRevisionProposal(AppOrderRevisionProposalPublishPayload { - context: AppPublishContext::new("acct_seller", "seller_order_revision_proposal"), - app_order_id: order_id, - farm_id, - trade_order_id: "order-1".to_owned(), - request_event_id: "request-event-1".to_owned(), - revision_id: "revision-1".to_owned(), - listing_addr: "30402:seller:listing".to_owned(), - buyer_pubkey: "buyer".to_owned(), - seller_pubkey: "seller".to_owned(), - items: vec![RadrootsOrderItem { - bin_id: "bin-1".parse().expect("valid bin id"), - bin_count: 2, - }], - economics: economics.clone(), - reason: "harvest count updated".to_owned(), - confirm_public_note: false, - }); - let invalid_proposal = - AppPublishPayload::OrderRevisionProposal(AppOrderRevisionProposalPublishPayload { - context: AppPublishContext::new("", ""), - app_order_id: order_id, - farm_id, - trade_order_id: " ".to_owned(), - request_event_id: String::new(), - revision_id: String::new(), - listing_addr: String::new(), - buyer_pubkey: String::new(), - seller_pubkey: String::new(), - items: Vec::new(), - economics: economics.clone(), - reason: " ".to_owned(), - confirm_public_note: false, - }); - let invalid_decision = - AppPublishPayload::OrderRevisionDecision(AppOrderRevisionDecisionPublishPayload { - context: AppPublishContext::new("", ""), - app_order_id: order_id, - farm_id, - trade_order_id: " ".to_owned(), - request_event_id: String::new(), - revision_id: String::new(), - listing_addr: String::new(), - buyer_pubkey: String::new(), - seller_pubkey: String::new(), - decision: RadrootsOrderRevisionOutcome::Declined { - reason: " ".to_owned(), - }, - confirm_public_note: false, - }); - - assert_eq!( - valid_proposal.work_kind().sdk_operation(), - TRADE_REVISION_PROPOSAL_OPERATION_KIND - ); - assert_eq!(valid_proposal.validation_failures(), Vec::new()); - assert_eq!( - invalid_decision.work_kind().sdk_operation(), - TRADE_REVISION_DECISION_OPERATION_KIND - ); - - let proposal_reason_codes: Vec<&str> = invalid_proposal - .validation_failures() - .into_iter() - .map(AppPublishValidationFailure::storage_key) - .collect(); - let decision_reason_codes: Vec<&str> = invalid_decision - .validation_failures() - .into_iter() - .map(AppPublishValidationFailure::storage_key) - .collect(); - - assert_eq!( - proposal_reason_codes, - vec![ - "missing_account_id", - "missing_source", - "missing_order_trade_order_id", - "missing_order_request_event_id", - "missing_order_listing_address", - "missing_order_buyer_pubkey", - "missing_order_seller_pubkey", - "missing_order_revision_id", - "missing_order_revision_items", - "missing_order_revision_reason", - ] - ); - assert_eq!( - decision_reason_codes, - vec![ - "missing_account_id", - "missing_source", - "missing_order_trade_order_id", - "missing_order_request_event_id", - "missing_order_listing_address", - "missing_order_buyer_pubkey", - "missing_order_seller_pubkey", - "missing_order_revision_id", - "missing_order_revision_decision_reason", - ] - ); - } - - #[test] fn existing_raw_payload_outbox_work_rejects_publish_payload() { let pending_operation = PendingSyncOperation { operation_key: "product:greens:upsert".to_owned(), @@ -1080,44 +858,4 @@ mod tests { assert!(!pending_operation.is_retry()); assert!(pending_operation.publish_payload().is_err()); } - - fn revision_economics() -> RadrootsOrderEconomics { - serde_json::from_value(json!({ - "quote_id": "quote-revision-1", - "quote_version": 2, - "pricing_basis": "listing_event", - "currency": "USD", - "items": [{ - "bin_id": "bin-1", - "bin_count": 2, - "quantity_amount": "1", - "quantity_unit": "each", - "unit_price_amount": "8", - "unit_price_currency": "USD", - "line_subtotal": { - "amount": "16", - "currency": "USD" - } - }], - "discounts": [], - "adjustments": [], - "subtotal": { - "amount": "16", - "currency": "USD" - }, - "discount_total": { - "amount": "0", - "currency": "USD" - }, - "adjustment_total": { - "amount": "0", - "currency": "USD" - }, - "total": { - "amount": "16", - "currency": "USD" - } - })) - .expect("revision economics fixture should decode") - } }