cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit c115c20527d94d86c529136516877574b08b815c
parent 5ee94e3024f54d684f34e826119b842feda2cde9
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 17:34:58 +0000

trade: remove revision commands for validator receipts

Replace CLI RHI trust config with validator-set policy and bind status trust output to validator-set receipts.\n\nRemove active order revision command/runtime/renderer/view surfaces and rename validation receipt worker trust output to validator evidence.

Diffstat:
Msrc/cli/global.rs | 47-----------------------------------------------
Msrc/cli/input.rs | 33++-------------------------------
Msrc/cli/mod.rs | 111+------------------------------------------------------------------------------
Msrc/cli/trade.rs | 55-------------------------------------------------------
Msrc/main.rs | 9---------
Msrc/ops/exec/basket.rs | 3++-
Msrc/ops/exec/core.rs | 3++-
Msrc/ops/exec/farm.rs | 3++-
Msrc/ops/exec/listing.rs | 3++-
Msrc/ops/exec/market.rs | 3++-
Msrc/ops/exec/runtime.rs | 3++-
Msrc/ops/exec/trade.rs | 448+------------------------------------------------------------------------------
Msrc/ops/mod.rs | 185-------------------------------------------------------------------------------
Msrc/ops/target.rs | 3---
Msrc/out/terminal/renderers/trade.rs | 51---------------------------------------------------
Msrc/out/terminal/renderers/validation.rs | 50++++++++++++++++++++++++++------------------------
Msrc/registry/mod.rs | 28+---------------------------
Msrc/registry/trade.rs | 48------------------------------------------------
Msrc/runtime/config.rs | 379++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/runtime/order.rs | 795+++++--------------------------------------------------------------------------
Msrc/runtime/order/sdk_status.rs | 9+++------
Msrc/runtime/provider.rs | 3++-
Msrc/runtime/sdk.rs | 24++++++++----------------
Msrc/runtime/sync.rs | 3++-
Msrc/runtime/validation_receipt.rs | 197++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/view/runtime.rs | 184+++----------------------------------------------------------------------------
26 files changed, 557 insertions(+), 2123 deletions(-)

diff --git a/src/cli/global.rs b/src/cli/global.rs @@ -290,53 +290,6 @@ pub struct TradeCancelArgs { } #[derive(Debug, Clone)] -pub struct TradeRevisionProposeArgs { - pub key: String, - pub reason: String, - pub confirm_public_note: bool, - pub bin_id: Option<String>, - pub bin_count: Option<u32>, - pub adjustment_id: Option<String>, - pub adjustment_effect: Option<String>, - pub adjustment_amount: Option<String>, - pub adjustment_currency: Option<String>, - pub adjustment_reason: Option<String>, - pub idempotency_key: Option<String>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TradeRevisionDecisionArg { - Accept, - Decline, -} - -impl TradeRevisionDecisionArg { - pub fn as_str(self) -> &'static str { - match self { - Self::Accept => "accepted", - Self::Decline => "declined", - } - } - - pub fn command(self) -> &'static str { - match self { - Self::Accept => "accept", - Self::Decline => "decline", - } - } -} - -#[derive(Debug, Clone)] -pub struct TradeRevisionDecisionArgs { - pub key: String, - pub revision_id: String, - pub decision: TradeRevisionDecisionArg, - pub reason: Option<String>, - pub idempotency_key: Option<String>, - pub confirm_public_note: bool, -} - -#[derive(Debug, Clone)] pub struct TradeStatusArgs { pub key: String, } diff --git a/src/cli/input.rs b/src/cli/input.rs @@ -45,8 +45,8 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { FarmLocationCommand, FarmProfileCommand, ListingAppCommand, ListingCommand, MarketCommand, MarketListingCommand, MarketProductCommand, MeshCommand, MeshScopeCommand, StoreBackupCommand, StoreCommand, TradeAppCommand, TradeCommand, TradeEventCommand, - TradeRevisionCommand, TradeStatusCommand, TransportCommand, TransportOutboxCommand, - TransportProfileCommand, ValidationCommand, ValidationReceiptCommand, + TradeStatusCommand, TransportCommand, TransportOutboxCommand, TransportProfileCommand, + ValidationCommand, ValidationReceiptCommand, }; let mut input = OperationData::new(); @@ -272,35 +272,6 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { insert_string(&mut input, "reason", &args.reason); insert_bool(&mut input, "confirm_public_note", args.confirm_public_note); } - TradeCommand::Revision(revision) => match &revision.command { - TradeRevisionCommand::Propose(args) => { - insert_string(&mut input, "trade_id", &args.trade_id); - insert_string(&mut input, "reason", &args.reason); - insert_bool(&mut input, "confirm_public_note", args.confirm_public_note); - insert_string(&mut input, "bin_id", &args.bin_id); - if let Some(bin_count) = args.bin_count { - input.insert( - "bin_count".to_owned(), - Value::Number(serde_json::Number::from(bin_count)), - ); - } - insert_string(&mut input, "adjustment_id", &args.adjustment_id); - insert_string(&mut input, "adjustment_effect", &args.adjustment_effect); - insert_string(&mut input, "adjustment_amount", &args.adjustment_amount); - insert_string(&mut input, "adjustment_currency", &args.adjustment_currency); - insert_string(&mut input, "adjustment_reason", &args.adjustment_reason); - } - TradeRevisionCommand::Accept(args) => { - insert_string(&mut input, "trade_id", &args.trade_id); - insert_string(&mut input, "revision_id", &args.revision_id); - } - TradeRevisionCommand::Decline(args) => { - insert_string(&mut input, "trade_id", &args.trade_id); - insert_string(&mut input, "revision_id", &args.revision_id); - insert_string(&mut input, "reason", &args.reason); - insert_bool(&mut input, "confirm_public_note", args.confirm_public_note); - } - }, TradeCommand::Status(status) => match &status.command { TradeStatusCommand::Get(args) => { insert_string(&mut input, "trade_id", &args.trade_id) diff --git a/src/cli/mod.rs b/src/cli/mod.rs @@ -261,11 +261,6 @@ impl TargetCommand { TradeCommand::Accept(_) => "trade.accept", TradeCommand::Decline(_) => "trade.decline", TradeCommand::Cancel(_) => "trade.cancel", - TradeCommand::Revision(revision) => match &revision.command { - TradeRevisionCommand::Propose(_) => "trade.revision.propose", - TradeRevisionCommand::Accept(_) => "trade.revision.accept", - TradeRevisionCommand::Decline(_) => "trade.revision.decline", - }, TradeCommand::Status(status) => match &status.command { TradeStatusCommand::Get(_) => "trade.status.get", }, @@ -292,8 +287,7 @@ mod tests { use super::{ AccountCommand, FarmCommand, FarmLocationCommand, ListingCommand, TargetCliArgs, - TargetOutputFormat, TradeCommand, TradeRevisionCommand, ValidationCommand, - ValidationReceiptCommand, + TargetOutputFormat, TradeCommand, ValidationCommand, ValidationReceiptCommand, }; use crate::registry::OPERATION_REGISTRY; @@ -658,109 +652,6 @@ mod tests { } #[test] - fn target_parser_accepts_order_revision_propose_inputs() { - let parsed = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "propose", - "ord_test", - "--reason", - "update count", - "--bin-id", - "bin-1", - "--bin-count", - "3", - "--adjustment-id", - "adj_revision", - "--adjustment-effect", - "increase", - "--adjustment-amount", - "2", - "--adjustment-currency", - "USD", - "--adjustment-reason", - "packing change", - "--confirm-public-note", - ]) - .expect("target args parse"); - - assert_eq!(parsed.command.operation_id(), "trade.revision.propose"); - let crate::cli::TargetCommand::Trade(trade) = parsed.command else { - panic!("expected trade command") - }; - let TradeCommand::Revision(revision) = trade.command else { - panic!("expected trade revision command") - }; - let TradeRevisionCommand::Propose(args) = revision.command else { - panic!("expected trade revision propose command") - }; - assert_eq!(args.trade_id.as_deref(), Some("ord_test")); - assert_eq!(args.reason.as_deref(), Some("update count")); - assert_eq!(args.bin_id.as_deref(), Some("bin-1")); - assert_eq!(args.bin_count, Some(3)); - assert_eq!(args.adjustment_id.as_deref(), Some("adj_revision")); - assert_eq!(args.adjustment_effect.as_deref(), Some("increase")); - assert!(args.confirm_public_note); - } - - #[test] - fn target_parser_accepts_order_revision_decision_inputs() { - let accepted = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "accept", - "ord_test", - "--revision-id", - "rev_test", - ]) - .expect("target args parse"); - - assert_eq!(accepted.command.operation_id(), "trade.revision.accept"); - let crate::cli::TargetCommand::Trade(trade) = accepted.command else { - panic!("expected trade command") - }; - let TradeCommand::Revision(revision) = trade.command else { - panic!("expected trade revision command") - }; - let TradeRevisionCommand::Accept(args) = revision.command else { - panic!("expected trade revision accept command") - }; - assert_eq!(args.trade_id.as_deref(), Some("ord_test")); - assert_eq!(args.revision_id.as_deref(), Some("rev_test")); - - let declined = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "decline", - "ord_test", - "--revision-id", - "rev_test", - "--reason", - "keep original trade", - "--confirm-public-note", - ]) - .expect("target args parse"); - - assert_eq!(declined.command.operation_id(), "trade.revision.decline"); - let crate::cli::TargetCommand::Trade(trade) = declined.command else { - panic!("expected trade command") - }; - let TradeCommand::Revision(revision) = trade.command else { - panic!("expected trade revision command") - }; - let TradeRevisionCommand::Decline(args) = revision.command else { - panic!("expected trade revision decline command") - }; - assert_eq!(args.trade_id.as_deref(), Some("ord_test")); - assert_eq!(args.revision_id.as_deref(), Some("rev_test")); - assert_eq!(args.reason.as_deref(), Some("keep original trade")); - assert!(args.confirm_public_note); - } - - #[test] fn target_parser_accepts_validation_receipt_commands() { let get = TargetCliArgs::try_parse_from([ "radroots", diff --git a/src/cli/trade.rs b/src/cli/trade.rs @@ -18,7 +18,6 @@ pub enum TradeCommand { Accept(TradeKeyArgs), Decline(TradeDeclineArgs), Cancel(TradeCancelArgs), - Revision(TradeRevisionArgs), Status(TradeStatusArgs), Event(TradeEventArgs), } @@ -79,60 +78,6 @@ pub struct TradeCancelArgs { } #[derive(Debug, Clone, Args)] -pub struct TradeRevisionArgs { - #[command(subcommand)] - pub command: TradeRevisionCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum TradeRevisionCommand { - Propose(TradeRevisionProposeArgs), - Accept(TradeRevisionDecisionArgs), - Decline(TradeRevisionDeclineArgs), -} - -#[derive(Debug, Clone, Args)] -pub struct TradeRevisionProposeArgs { - pub trade_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, - #[arg(long)] - pub confirm_public_note: bool, - #[arg(long)] - pub bin_id: Option<String>, - #[arg(long)] - pub bin_count: Option<u32>, - #[arg(long)] - pub adjustment_id: Option<String>, - #[arg(long)] - pub adjustment_effect: Option<String>, - #[arg(long)] - pub adjustment_amount: Option<String>, - #[arg(long)] - pub adjustment_currency: Option<String>, - #[arg(long)] - pub adjustment_reason: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct TradeRevisionDecisionArgs { - pub trade_id: Option<String>, - #[arg(long)] - pub revision_id: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct TradeRevisionDeclineArgs { - pub trade_id: Option<String>, - #[arg(long)] - pub revision_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, - #[arg(long)] - pub confirm_public_note: bool, -} - -#[derive(Debug, Clone, Args)] pub struct TradeStatusArgs { #[command(subcommand)] pub command: TradeStatusCommand, diff --git a/src/main.rs b/src/main.rs @@ -316,15 +316,6 @@ fn execute_request( TargetOperationRequest::TradeCancel(request) => { execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::TradeRevisionPropose(request) => { - execute_with(TradeOperationService::new(config), request) - } - TargetOperationRequest::TradeRevisionAccept(request) => { - execute_with(TradeOperationService::new(config), request) - } - TargetOperationRequest::TradeRevisionDecline(request) => { - execute_with(TradeOperationService::new(config), request) - } TargetOperationRequest::TradeStatusGet(request) => { execute_with(TradeOperationService::new(config), request) } diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs @@ -1929,7 +1929,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/core.rs b/src/ops/exec/core.rs @@ -1452,7 +1452,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/farm.rs b/src/ops/exec/farm.rs @@ -900,7 +900,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/listing.rs b/src/ops/exec/listing.rs @@ -625,7 +625,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/market.rs b/src/ops/exec/market.rs @@ -689,7 +689,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/runtime.rs b/src/ops/exec/runtime.rs @@ -493,7 +493,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/exec/trade.rs b/src/ops/exec/trade.rs @@ -5,8 +5,7 @@ use serde_json::{Value, json}; use crate::cli::global::{ RecordLookupArgs, TradeAppRecordExportArgs, TradeCancelArgs, TradeDecisionArg, - TradeDecisionArgs, TradeRebindArgs, TradeRevisionDecisionArg, TradeRevisionDecisionArgs, - TradeRevisionProposeArgs, TradeStatusArgs, TradeSubmitArgs, + TradeDecisionArgs, TradeRebindArgs, TradeStatusArgs, TradeSubmitArgs, }; use crate::ops::{ OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload, @@ -15,17 +14,14 @@ use crate::ops::{ TradeCancelRequest, TradeCancelResult, TradeDeclineRequest, TradeDeclineResult, TradeEventListRequest, TradeEventListResult, TradeEventWatchRequest, TradeEventWatchResult, TradeGetRequest, TradeGetResult, TradeListRequest, TradeListResult, TradeRebindRequest, - TradeRebindResult, TradeRevisionAcceptRequest, TradeRevisionAcceptResult, - TradeRevisionDeclineRequest, TradeRevisionDeclineResult, TradeRevisionProposeRequest, - TradeRevisionProposeResult, TradeStatusGetRequest, TradeStatusGetResult, TradeSubmitRequest, + TradeRebindResult, TradeStatusGetRequest, TradeStatusGetResult, TradeSubmitRequest, TradeSubmitResult, }; use crate::runtime::RuntimeError; use crate::runtime::config::RuntimeConfig; use crate::view::runtime::{ CommandDisposition, OrderAppRecordExportView, OrderCancellationView, OrderDecisionView, - OrderRebindView, OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusView, - OrderSubmitView, + OrderRebindView, OrderStatusView, OrderSubmitView, }; const TRADE_EVENT_WATCH_DEFERRED_REASON: &str = "Nostr-backed trade event watch is not implemented"; @@ -288,137 +284,6 @@ impl OperationService<TradeCancelRequest> for TradeOperationService<'_> { } } -impl OperationService<TradeRevisionProposeRequest> for TradeOperationService<'_> { - type Result = TradeRevisionProposeResult; - - fn execute( - &self, - request: OperationRequest<TradeRevisionProposeRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let args = TradeRevisionProposeArgs { - key: required_trade_key(&request)?, - reason, - confirm_public_note: bool_input(&request, "confirm_public_note").unwrap_or(false), - bin_id: string_input(&request, "bin_id"), - bin_count: u32_input(&request, "bin_count"), - adjustment_id: string_input(&request, "adjustment_id"), - adjustment_effect: string_input(&request, "adjustment_effect"), - adjustment_amount: string_input(&request, "adjustment_amount"), - adjustment_currency: string_input(&request, "adjustment_currency"), - adjustment_reason: string_input(&request, "adjustment_reason"), - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_propose(&config, &args).map_err(|error| { - OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) - })?; - revision_proposal_result::<TradeRevisionProposeResult>(request.operation_id(), &view) - } -} - -impl OperationService<TradeRevisionAcceptRequest> for TradeOperationService<'_> { - type Result = TradeRevisionAcceptResult; - - fn execute( - &self, - request: OperationRequest<TradeRevisionAcceptRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = TradeRevisionDecisionArgs { - key: required_trade_key(&request)?, - revision_id: required_string_input(&request, "revision_id")?, - decision: TradeRevisionDecisionArg::Accept, - reason: None, - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - confirm_public_note: false, - }; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { - OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) - })?; - revision_decision_result::<TradeRevisionAcceptResult>(request.operation_id(), &view) - } -} - -impl OperationService<TradeRevisionDeclineRequest> for TradeOperationService<'_> { - type Result = TradeRevisionDeclineResult; - - fn execute( - &self, - request: OperationRequest<TradeRevisionDeclineRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - let args = TradeRevisionDecisionArgs { - key: required_trade_key(&request)?, - revision_id: required_string_input(&request, "revision_id")?, - decision: TradeRevisionDecisionArg::Decline, - reason: Some(reason), - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - confirm_public_note: bool_input(&request, "confirm_public_note").unwrap_or(false), - }; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { - OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) - })?; - revision_decision_result::<TradeRevisionDeclineResult>(request.operation_id(), &view) - } -} - impl OperationService<TradeStatusGetRequest> for TradeOperationService<'_> { type Result = TradeStatusGetResult; @@ -659,201 +524,6 @@ fn order_cancellation_error_detail(view: &OrderCancellationView) -> Value { }) } -fn revision_proposal_result<R>( - operation_id: &str, - view: &OrderRevisionProposalView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision propose failed validation with state `{}`", - view.state - ) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_revision_proposal_error_detail(view), - )) - } - disposition => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision propose finished with state `{}`", - view.state - ) - }); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_revision_proposal_error_detail(view); - if !view.failed_transport_targets.is_empty() - && view.attempted_transport_endpoints.is_empty() - { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_revision_proposal_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_revision_proposal_error_detail(view: &OrderRevisionProposalView) -> Value { - json!({ - "state": &view.state, - "trade_id": &view.order_id, - "locator": &view.locator, - "revision_id": &view.revision_id, - "listing_addr": &view.listing_addr, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "items": &view.items, - "economics": &view.economics, - "inventory": &view.inventory, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "dry_run": view.dry_run, - "target_transport_endpoints": &view.target_transport_endpoints, - "attempted_transport_endpoints": &view.attempted_transport_endpoints, - "accepted_transport_endpoints": &view.accepted_transport_endpoints, - "failed_transport_targets": &view.failed_transport_targets, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn revision_decision_result<R>( - operation_id: &str, - view: &OrderRevisionDecisionView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision {} failed validation with state `{}`", - view.decision.as_deref().unwrap_or("decision"), - view.state - ) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_revision_decision_error_detail(view), - )) - } - disposition => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision {} finished with state `{}`", - view.decision.as_deref().unwrap_or("decision"), - view.state - ) - }); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_revision_decision_error_detail(view); - if !view.failed_transport_targets.is_empty() - && view.attempted_transport_endpoints.is_empty() - { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_revision_decision_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_revision_decision_error_detail(view: &OrderRevisionDecisionView) -> Value { - json!({ - "state": &view.state, - "trade_id": &view.order_id, - "locator": &view.locator, - "revision_id": &view.revision_id, - "decision": &view.decision, - "listing_addr": &view.listing_addr, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "agreement_event_id": &view.agreement_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "economics": &view.economics, - "inventory": &view.inventory, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "dry_run": view.dry_run, - "target_transport_endpoints": &view.target_transport_endpoints, - "attempted_transport_endpoints": &view.attempted_transport_endpoints, - "accepted_transport_endpoints": &view.accepted_transport_endpoints, - "failed_transport_targets": &view.failed_transport_targets, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - fn status_result<R>( operation_id: &str, view: &OrderStatusView, @@ -915,7 +585,6 @@ fn order_status_error_detail(view: &OrderStatusView) -> Value { "buyer_pubkey": &view.buyer_pubkey, "seller_pubkey": &view.seller_pubkey, "last_event_id": &view.last_event_id, - "revision": &view.revision, "inventory": &view.inventory, "lifecycle": &view.lifecycle, "sdk_receipt": &view.sdk_receipt, @@ -1232,18 +901,6 @@ where string_input(request, key).map(PathBuf::from) } -fn u32_input<P>(request: &OperationRequest<P>, key: &str) -> Option<u32> -where - P: OperationRequestPayload + OperationRequestData, -{ - request - .payload - .input() - .get(key) - .and_then(Value::as_u64) - .and_then(|value| u32::try_from(value).ok()) -} - fn bool_input<P>(request: &OperationRequest<P>, key: &str) -> Option<bool> where P: OperationRequestPayload + OperationRequestData, @@ -1274,7 +931,6 @@ mod tests { OperationAdapter, OperationContext, OperationData, OperationRequest, TradeAcceptRequest, TradeAcceptResult, TradeCancelRequest, TradeDeclineRequest, TradeDeclineResult, TradeEventListRequest, TradeEventWatchRequest, TradeGetRequest, TradeListRequest, - TradeRevisionAcceptRequest, TradeRevisionDeclineRequest, TradeRevisionProposeRequest, TradeStatusGetRequest, TradeSubmitRequest, }; use crate::runtime::config::{ @@ -1534,101 +1190,6 @@ mod tests { } #[test] - fn order_revision_propose_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(TradeOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - TradeRevisionProposeRequest::from_data(data(&[("trade_id", "ord_pending")])), - ) - .expect("order revision request"); - let error = service.execute(revision).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_revision_propose_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(TradeOperationService::new(&config)); - let mut input = data(&[ - ("trade_id", "ord_pending"), - ("reason", "update count"), - ("bin_id", "bin-1"), - ]); - input.insert("bin_count".to_owned(), Value::from(3)); - let revision = OperationRequest::new( - OperationContext::default(), - TradeRevisionProposeRequest::from_data(input), - ) - .expect("order revision request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_revision_accept_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(TradeOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - TradeRevisionAcceptRequest::from_data(data(&[ - ("trade_id", "ord_pending"), - ("revision_id", "rev_pending"), - ])), - ) - .expect("order revision accept request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_revision_decline_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(TradeOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - TradeRevisionDeclineRequest::from_data(data(&[ - ("trade_id", "ord_pending"), - ("revision_id", "rev_pending"), - ])), - ) - .expect("order revision decline request"); - let error = service.execute(revision).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_revision_decline_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(TradeOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - TradeRevisionDeclineRequest::from_data(data(&[ - ("trade_id", "ord_pending"), - ("revision_id", "rev_pending"), - ("reason", "keep original order"), - ])), - ) - .expect("order revision decline request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] fn order_status_get_uses_local_sdk_projection_without_relay() { let dir = tempdir().expect("tempdir"); let config = sample_config(dir.path()); @@ -1848,7 +1409,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/ops/mod.rs b/src/ops/mod.rs @@ -277,191 +277,6 @@ mod tests { #[test] fn adapter_maps_order_lifecycle_inputs() { - let revision = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "propose", - "ord_test", - "--reason", - "update count", - "--bin-id", - "bin-1", - "--bin-count", - "3", - "--adjustment-id", - "adj-weather", - "--adjustment-effect", - "increase", - "--adjustment-amount", - "1.25", - "--adjustment-currency", - "USD", - "--adjustment-reason", - "weather delay", - "--confirm-public-note", - ]) - .expect("target args parse"); - let request = - TargetOperationRequest::from_target_args(&revision).expect("operation request"); - let TargetOperationRequest::TradeRevisionPropose(request) = request else { - panic!("expected order revision propose request") - }; - assert_eq!(request.operation_id(), "trade.revision.propose"); - assert_eq!( - request - .payload - .input - .get("trade_id") - .and_then(Value::as_str), - Some("ord_test") - ); - assert_eq!( - request.payload.input.get("reason").and_then(Value::as_str), - Some("update count") - ); - assert_eq!( - request.payload.input.get("bin_id").and_then(Value::as_str), - Some("bin-1") - ); - assert_eq!( - request - .payload - .input - .get("bin_count") - .and_then(Value::as_u64), - Some(3) - ); - assert_eq!( - request - .payload - .input - .get("adjustment_id") - .and_then(Value::as_str), - Some("adj-weather") - ); - assert_eq!( - request - .payload - .input - .get("adjustment_effect") - .and_then(Value::as_str), - Some("increase") - ); - assert_eq!( - request - .payload - .input - .get("adjustment_amount") - .and_then(Value::as_str), - Some("1.25") - ); - assert_eq!( - request - .payload - .input - .get("adjustment_currency") - .and_then(Value::as_str), - Some("USD") - ); - assert_eq!( - request - .payload - .input - .get("adjustment_reason") - .and_then(Value::as_str), - Some("weather delay") - ); - assert_eq!( - request - .payload - .input - .get("confirm_public_note") - .and_then(Value::as_bool), - Some(true) - ); - - let revision_accept = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "accept", - "ord_test", - "--revision-id", - "rev_test", - ]) - .expect("target args parse"); - let request = - TargetOperationRequest::from_target_args(&revision_accept).expect("operation request"); - let TargetOperationRequest::TradeRevisionAccept(request) = request else { - panic!("expected order revision accept request") - }; - assert_eq!(request.operation_id(), "trade.revision.accept"); - assert_eq!( - request - .payload - .input - .get("trade_id") - .and_then(Value::as_str), - Some("ord_test") - ); - assert_eq!( - request - .payload - .input - .get("revision_id") - .and_then(Value::as_str), - Some("rev_test") - ); - - let revision_decline = TargetCliArgs::try_parse_from([ - "radroots", - "trade", - "revision", - "decline", - "ord_test", - "--revision-id", - "rev_test", - "--reason", - "keep original order", - "--confirm-public-note", - ]) - .expect("target args parse"); - let request = - TargetOperationRequest::from_target_args(&revision_decline).expect("operation request"); - let TargetOperationRequest::TradeRevisionDecline(request) = request else { - panic!("expected order revision decline request") - }; - assert_eq!(request.operation_id(), "trade.revision.decline"); - assert_eq!( - request - .payload - .input - .get("trade_id") - .and_then(Value::as_str), - Some("ord_test") - ); - assert_eq!( - request - .payload - .input - .get("revision_id") - .and_then(Value::as_str), - Some("rev_test") - ); - assert_eq!( - request.payload.input.get("reason").and_then(Value::as_str), - Some("keep original order") - ); - assert_eq!( - request - .payload - .input - .get("confirm_public_note") - .and_then(Value::as_bool), - Some(true) - ); - let cancel = TargetCliArgs::try_parse_from([ "radroots", "trade", diff --git a/src/ops/target.rs b/src/ops/target.rs @@ -249,9 +249,6 @@ target_operation_contracts! { TradeAccept => (TradeAcceptRequest, TradeAcceptResult, "trade.accept"), TradeDecline => (TradeDeclineRequest, TradeDeclineResult, "trade.decline"), TradeCancel => (TradeCancelRequest, TradeCancelResult, "trade.cancel"), - TradeRevisionPropose => (TradeRevisionProposeRequest, TradeRevisionProposeResult, "trade.revision.propose"), - TradeRevisionAccept => (TradeRevisionAcceptRequest, TradeRevisionAcceptResult, "trade.revision.accept"), - TradeRevisionDecline => (TradeRevisionDeclineRequest, TradeRevisionDeclineResult, "trade.revision.decline"), TradeStatusGet => (TradeStatusGetRequest, TradeStatusGetResult, "trade.status.get"), TradeEventList => (TradeEventListRequest, TradeEventListResult, "trade.event.list"), TradeEventWatch => (TradeEventWatchRequest, TradeEventWatchResult, "trade.event.watch"), diff --git a/src/out/terminal/renderers/trade.rs b/src/out/terminal/renderers/trade.rs @@ -20,9 +20,6 @@ pub fn register(registry: TerminalRendererRegistry) -> TerminalRendererRegistry .register("trade.accept", &TRADE_RENDERER) .register("trade.decline", &TRADE_RENDERER) .register("trade.cancel", &TRADE_RENDERER) - .register("trade.revision.propose", &TRADE_RENDERER) - .register("trade.revision.accept", &TRADE_RENDERER) - .register("trade.revision.decline", &TRADE_RENDERER) .register("trade.status.get", &TRADE_RENDERER) .register("trade.event.list", &TRADE_RENDERER) .register("trade.event.watch", &TRADE_RENDERER) @@ -50,10 +47,6 @@ impl TerminalOperationRenderer for TradeRenderer { "trade.submit" => trade_publish_document(envelope, result), "trade.accept" | "trade.decline" => trade_decision_document(envelope, result), "trade.cancel" => trade_cancel_document(envelope, result), - "trade.revision.propose" => trade_revision_propose_document(envelope, result), - "trade.revision.accept" | "trade.revision.decline" => { - trade_revision_decision_document(envelope, result) - } _ => trade_detail_document(envelope, result), } } @@ -111,41 +104,6 @@ fn trade_cancel_document(envelope: &OutputEnvelope, result: &Value) -> TerminalD document } -fn trade_revision_propose_document(envelope: &OutputEnvelope, result: &Value) -> TerminalDocument { - let mut document = common::document_with_status_title(envelope, "Trade revision propose"); - push_trade_identity_fields(&mut document, result); - common::push_path_field(&mut document, "Revision", result, &["revision_id"]); - common::push_bool_field(&mut document, "Dry run", result, &["dry_run"]); - common::push_path_field(&mut document, "Request", result, &["request_event_id"]); - common::push_path_field(&mut document, "Decision", result, &["decision_event_id"]); - common::push_path_field(&mut document, "Event", result, &["event_id"]); - common::push_path_field(&mut document, "Signer", result, &["signer_mode"]); - push_relay_field(&mut document, result); - push_trade_items_section(&mut document, result); - push_inventory_section(&mut document, result); - push_issue_sections(&mut document, result); - document -} - -fn trade_revision_decision_document(envelope: &OutputEnvelope, result: &Value) -> TerminalDocument { - let mut document = common::document_with_status_title( - envelope, - trade_revision_title(envelope.operation_id.as_str()), - ); - push_trade_identity_fields(&mut document, result); - common::push_path_field(&mut document, "Revision", result, &["revision_id"]); - common::push_path_field(&mut document, "Decision", result, &["decision"]); - common::push_bool_field(&mut document, "Dry run", result, &["dry_run"]); - common::push_path_field(&mut document, "Request", result, &["request_event_id"]); - common::push_path_field(&mut document, "Agreement", result, &["agreement_event_id"]); - common::push_path_field(&mut document, "Event", result, &["event_id"]); - common::push_path_field(&mut document, "Signer", result, &["signer_mode"]); - push_relay_field(&mut document, result); - push_inventory_section(&mut document, result); - push_issue_sections(&mut document, result); - document -} - fn trade_detail_document(envelope: &OutputEnvelope, result: &Value) -> TerminalDocument { let mut document = common::document_with_title(envelope, "Trade"); push_trade_identity_fields(&mut document, result); @@ -338,7 +296,6 @@ fn trade_status_document(envelope: &OutputEnvelope, result: &Value) -> TerminalD result, &["sdk_receipt", "validation_trust", "reason_code"], ); - common::push_path_field(&mut document, "Revision", result, &["revision", "state"]); push_relay_field(&mut document, result); push_inventory_section(&mut document, result); push_ambiguity_candidates_section(&mut document, result); @@ -632,14 +589,6 @@ fn trade_decision_title(operation_id: &str) -> &'static str { } } -fn trade_revision_title(operation_id: &str) -> &'static str { - match operation_id { - "trade.revision.accept" => "Trade revision accept", - "trade.revision.decline" => "Trade revision decline", - _ => "Trade revision decision", - } -} - #[cfg(test)] mod tests { use serde_json::json; diff --git a/src/out/terminal/renderers/validation.rs b/src/out/terminal/renderers/validation.rs @@ -174,21 +174,23 @@ fn push_relay_field(document: &mut TerminalDocument, result: &Value) { fn proof_state_label(proof: &Value) -> String { match common::string(proof, &["state"]).as_deref() { - Some("local_only_deterministic_receipt") => return "local-only deterministic".to_owned(), - Some("pending_rhi_validation") => return "pending RHI".to_owned(), - Some("trusted_service_validated") => return "trusted service".to_owned(), - Some("trusted_service_and_proof_verified") => return "trusted service + proof".to_owned(), + Some("validator_set_deterministic_receipt") => { + return "validator-set deterministic".to_owned(); + } + Some("pending_validator_validation") => return "pending validator".to_owned(), + Some("validator_set_validated") => return "validator set".to_owned(), + Some("validator_set_and_proof_verified") => return "validator set + proof".to_owned(), Some("sp1_inline_proof_verified") => return "cryptographic proof".to_owned(), Some("cryptographic_proof_verified") => return "cryptographic proof".to_owned(), Some("sp1_execute_checked") => return "SP1 execute checked".to_owned(), - Some("worker_evidence_trust_metadata_missing") => { + Some("validator_evidence_trust_metadata_missing") => { return "trust metadata missing".to_owned(); } - Some("worker_evidence_trust_metadata_mismatch") => { + Some("validator_evidence_trust_metadata_mismatch") => { return "trust metadata mismatch".to_owned(); } - Some("validation_receipt_worker_evidence_invalid") => { - return "worker evidence invalid".to_owned(); + Some("validation_receipt_validator_evidence_invalid") => { + return "validator evidence invalid".to_owned(); } _ => {} } @@ -203,16 +205,16 @@ fn proof_state_label(proof: &Value) -> String { fn proof_summary_from_summary(receipt: &Value) -> String { match common::string(receipt, &["proof_verification_state"]).as_deref() { - Some("local_only_deterministic_receipt") => "local-only".to_owned(), - Some("pending_rhi_validation") => "pending RHI".to_owned(), - Some("trusted_service_validated") => "trusted service".to_owned(), - Some("trusted_service_and_proof_verified") => "trusted+proof".to_owned(), + Some("validator_set_deterministic_receipt") => "validator-set".to_owned(), + Some("pending_validator_validation") => "pending validator".to_owned(), + Some("validator_set_validated") => "validator set".to_owned(), + Some("validator_set_and_proof_verified") => "validator+proof".to_owned(), Some("sp1_inline_proof_verified") => "proof verified".to_owned(), Some("cryptographic_proof_verified") => "proof verified".to_owned(), Some("sp1_execute_checked") => "execute checked".to_owned(), - Some("worker_evidence_trust_metadata_missing") => "trust missing".to_owned(), - Some("worker_evidence_trust_metadata_mismatch") => "trust mismatch".to_owned(), - Some("validation_receipt_worker_evidence_invalid") => "invalid".to_owned(), + Some("validator_evidence_trust_metadata_missing") => "trust missing".to_owned(), + Some("validator_evidence_trust_metadata_mismatch") => "trust mismatch".to_owned(), + Some("validation_receipt_validator_evidence_invalid") => "invalid".to_owned(), Some(_) => "available".to_owned(), None => String::new(), } @@ -300,10 +302,10 @@ mod tests { "order_id": "trade_test", "validation_state": "invalid", "proof_verification": { - "state": "local_only_deterministic_receipt", + "state": "validator_set_deterministic_receipt", "proof_system": "none", - "validation_authority": "dev_deterministic_only", - "commitment_confidence": "local_only", + "validation_authority": "validator_set_deterministic", + "commitment_confidence": "committed_by_validator_set", "production_verification": false, "cryptographic_proof_required": false, "cryptographic_proof_verified": false, @@ -315,8 +317,8 @@ mod tests { let document = VALIDATION_RENDERER.render(&envelope, &TerminalRenderContext::default()); let rendered = render_terminal_document(&document, &TerminalRenderContext::default()); - assert!(rendered.contains("Proof local-only deterministic")); - assert!(rendered.contains("Authority dev_deterministic_only")); + assert!(rendered.contains("Proof validator-set deterministic")); + assert!(rendered.contains("Authority validator_set_deterministic")); assert!(rendered.contains("Confidence local_only")); assert!(!rendered.contains("Proof verified")); } @@ -335,13 +337,13 @@ mod tests { { "receipt_event_id": "receipt_local", "result": "valid", - "proof_verification_state": "local_only_deterministic_receipt", + "proof_verification_state": "validator_set_deterministic_receipt", "receipt_type": "trade_transition" }, { "receipt_event_id": "receipt_trusted", "result": "valid", - "proof_verification_state": "trusted_service_validated", + "proof_verification_state": "validator_set_validated", "receipt_type": "trade_transition" } ], @@ -352,8 +354,8 @@ mod tests { let document = VALIDATION_RENDERER.render(&envelope, &TerminalRenderContext::default()); let rendered = render_terminal_document(&document, &TerminalRenderContext::default()); - assert!(rendered.contains("local-only")); - assert!(rendered.contains("trusted service")); + assert!(rendered.contains("validator-set")); + assert!(rendered.contains("validator set")); assert!(!rendered.contains("not required")); } } diff --git a/src/registry/mod.rs b/src/registry/mod.rs @@ -173,9 +173,6 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[ trade::TRADE_ACCEPT, trade::TRADE_DECLINE, trade::TRADE_CANCEL, - trade::TRADE_REVISION_PROPOSE, - trade::TRADE_REVISION_ACCEPT, - trade::TRADE_REVISION_DECLINE, trade::TRADE_STATUS_GET, trade::TRADE_EVENT_LIST, trade::TRADE_EVENT_WATCH, @@ -208,12 +205,7 @@ pub fn network_requirement(operation_id: &str) -> NetworkRequirement { | "validation.receipt.verify" => NetworkRequirement::External { dry_run_requires_network: false, }, - "trade.accept" - | "trade.decline" - | "trade.cancel" - | "trade.revision.propose" - | "trade.revision.accept" - | "trade.revision.decline" => NetworkRequirement::External { + "trade.accept" | "trade.decline" | "trade.cancel" => NetworkRequirement::External { dry_run_requires_network: true, }, _ => NetworkRequirement::Local, @@ -229,9 +221,6 @@ pub fn requires_local_signer_mode(operation_id: &str) -> bool { | "trade.accept" | "trade.decline" | "trade.cancel" - | "trade.revision.propose" - | "trade.revision.accept" - | "trade.revision.decline" ) } @@ -341,9 +330,6 @@ mod tests { "trade.accept", "trade.decline", "trade.cancel", - "trade.revision.propose", - "trade.revision.accept", - "trade.revision.decline", "trade.status.get", "trade.event.list", "trade.event.watch", @@ -395,9 +381,6 @@ mod tests { "trade.accept", "trade.decline", "trade.cancel", - "trade.revision.propose", - "trade.revision.accept", - "trade.revision.decline", ]; const INTENTIONALLY_UNSUPPORTED_MUTATING_DRY_RUN_OPERATION_IDS: &[&str] = &[]; @@ -470,9 +453,6 @@ mod tests { "trade.accept", "trade.decline", "trade.cancel", - "trade.revision.propose", - "trade.revision.accept", - "trade.revision.decline", ] .into_iter() .collect::<BTreeSet<_>>(); @@ -574,9 +554,6 @@ mod tests { "trade.accept", "trade.decline", "trade.cancel", - "trade.revision.propose", - "trade.revision.accept", - "trade.revision.decline", "transport.outbox.push", "trade.event.list", "validation.receipt.get", @@ -602,9 +579,6 @@ mod tests { "trade.accept", "trade.decline", "trade.cancel", - "trade.revision.propose", - "trade.revision.accept", - "trade.revision.decline", "transport.outbox.push", ] .into_iter() diff --git a/src/registry/trade.rs b/src/registry/trade.rs @@ -144,54 +144,6 @@ pub const TRADE_CANCEL: OperationSpec = operation!( true ); -pub const TRADE_REVISION_PROPOSE: OperationSpec = operation!( - "trade.revision.propose", - "radroots trade revision propose", - "trade", - "trade_revision_propose", - "TradeRevisionProposeRequest", - "TradeRevisionProposeResult", - "Propose seller-authored trade revision.", - Seller, - true, - Required, - High, - false, - true -); - -pub const TRADE_REVISION_ACCEPT: OperationSpec = operation!( - "trade.revision.accept", - "radroots trade revision accept", - "trade", - "trade_revision_accept", - "TradeRevisionAcceptRequest", - "TradeRevisionAcceptResult", - "Accept a seller-authored trade revision.", - Buyer, - true, - Required, - High, - false, - true -); - -pub const TRADE_REVISION_DECLINE: OperationSpec = operation!( - "trade.revision.decline", - "radroots trade revision decline", - "trade", - "trade_revision_decline", - "TradeRevisionDeclineRequest", - "TradeRevisionDeclineResult", - "Decline a seller-authored trade revision.", - Buyer, - true, - Required, - High, - false, - true -); - pub const TRADE_STATUS_GET: OperationSpec = operation!( "trade.status.get", "radroots trade status get", diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -56,7 +56,20 @@ const ENV_CLI_MYC_EXECUTABLE: &str = "RADROOTS_CLI_MYC_EXECUTABLE"; const ENV_CLI_MYC_STATUS_TIMEOUT_MS: &str = "RADROOTS_CLI_MYC_STATUS_TIMEOUT_MS"; const ENV_CLI_HYF_ENABLED: &str = "RADROOTS_CLI_HYF_ENABLED"; const ENV_CLI_HYF_EXECUTABLE: &str = "RADROOTS_CLI_HYF_EXECUTABLE"; -const ENV_CLI_RHI_TRUSTED_WORKER_PUBKEYS: &str = "RADROOTS_CLI_RHI_TRUSTED_WORKER_PUBKEYS"; +const ENV_CLI_RHI_VALIDATOR_SET_ID: &str = "RADROOTS_CLI_RHI_VALIDATOR_SET_ID"; +const ENV_CLI_RHI_VALIDATOR_PUBKEY: &str = "RADROOTS_CLI_RHI_VALIDATOR_PUBKEY"; +const ENV_CLI_RHI_VALIDATOR_SET_ADDR: &str = "RADROOTS_CLI_RHI_VALIDATOR_SET_ADDR"; +const ENV_CLI_RHI_VALIDATOR_SET_EVENT_ID: &str = "RADROOTS_CLI_RHI_VALIDATOR_SET_EVENT_ID"; +const ENV_CLI_RHI_VALIDATOR_SET_VALID_FROM: &str = "RADROOTS_CLI_RHI_VALIDATOR_SET_VALID_FROM"; +const ENV_CLI_RHI_VALIDATOR_SET_VALID_UNTIL: &str = "RADROOTS_CLI_RHI_VALIDATOR_SET_VALID_UNTIL"; +const ENV_CLI_RHI_VALIDATOR_SET_PROTOCOL_CONTRACT_HASH: &str = + "RADROOTS_CLI_RHI_VALIDATOR_SET_PROTOCOL_CONTRACT_HASH"; +const ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_NAME: &str = + "RADROOTS_CLI_RHI_VALIDATOR_SET_OPERATOR_NAME"; +const ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_CONTACT: &str = + "RADROOTS_CLI_RHI_VALIDATOR_SET_OPERATOR_CONTACT"; +const ENV_CLI_RHI_REQUIRE_CRYPTOGRAPHIC_PROOF: &str = + "RADROOTS_CLI_RHI_REQUIRE_CRYPTOGRAPHIC_PROOF"; const ENV_CLI_MESH_SCOPE: &str = "RADROOTS_CLI_MESH_SCOPE"; const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[ ENV_CLI_OUTPUT_FORMAT, @@ -81,7 +94,16 @@ const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[ ENV_CLI_MYC_STATUS_TIMEOUT_MS, ENV_CLI_HYF_ENABLED, ENV_CLI_HYF_EXECUTABLE, - ENV_CLI_RHI_TRUSTED_WORKER_PUBKEYS, + ENV_CLI_RHI_VALIDATOR_SET_ID, + ENV_CLI_RHI_VALIDATOR_PUBKEY, + ENV_CLI_RHI_VALIDATOR_SET_ADDR, + ENV_CLI_RHI_VALIDATOR_SET_EVENT_ID, + ENV_CLI_RHI_VALIDATOR_SET_VALID_FROM, + ENV_CLI_RHI_VALIDATOR_SET_VALID_UNTIL, + ENV_CLI_RHI_VALIDATOR_SET_PROTOCOL_CONTRACT_HASH, + ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_NAME, + ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_CONTACT, + ENV_CLI_RHI_REQUIRE_CRYPTOGRAPHIC_PROOF, ENV_CLI_MESH_SCOPE, ]; @@ -445,7 +467,21 @@ pub struct RpcConfig { #[derive(Debug, Clone, PartialEq, Eq)] pub struct RhiConfig { - pub trusted_worker_pubkeys: Vec<String>, + pub validator_set: Option<RhiValidatorSetConfig>, + pub require_cryptographic_proof: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RhiValidatorSetConfig { + pub set_id: String, + pub validator_pubkey: String, + pub validator_set_addr: String, + pub validator_set_event_id: String, + pub valid_from: u64, + pub valid_until: u64, + pub protocol_contract_hash: String, + pub operator_name: String, + pub operator_contact: Option<String>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -567,7 +603,22 @@ struct RpcFileConfig { #[derive(Debug, Default, Deserialize)] #[serde(default, deny_unknown_fields)] struct RhiFileConfig { - trusted_worker_pubkeys: Option<Vec<String>>, + validator_set: Option<RhiValidatorSetFileConfig>, + require_cryptographic_proof: Option<bool>, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(default, deny_unknown_fields)] +struct RhiValidatorSetFileConfig { + set_id: Option<String>, + validator_pubkey: Option<String>, + validator_set_addr: Option<String>, + validator_set_event_id: Option<String>, + valid_from: Option<u64>, + valid_until: Option<u64>, + protocol_contract_hash: Option<String>, + operator_name: Option<String>, + operator_contact: Option<String>, } #[derive(Debug, Default, Deserialize)] @@ -1202,47 +1253,224 @@ fn resolve_rhi_config( user_config: Option<&CliConfigFile>, workspace_config: Option<&CliConfigFile>, ) -> Result<RhiConfig, RuntimeError> { - let trusted_worker_pubkeys = - if let Some(value) = env_value(env, env_file, &[ENV_CLI_RHI_TRUSTED_WORKER_PUBKEYS]) { - parse_pubkey_env_value(value.as_str(), ENV_CLI_RHI_TRUSTED_WORKER_PUBKEYS)? - } else if let Some(values) = user_config + let validator_set = + resolve_rhi_validator_set_config(env, env_file, user_config, workspace_config)?; + let require_cryptographic_proof = + if let Some(value) = env_value(env, env_file, &[ENV_CLI_RHI_REQUIRE_CRYPTOGRAPHIC_PROOF]) { + parse_bool_env(ENV_CLI_RHI_REQUIRE_CRYPTOGRAPHIC_PROOF, value.as_str())? + } else if let Some(value) = user_config .and_then(|config| config.rhi.as_ref()) - .and_then(|rhi| rhi.trusted_worker_pubkeys.clone()) + .and_then(|rhi| rhi.require_cryptographic_proof) { - normalize_pubkeys(values, "user config [rhi].trusted_worker_pubkeys")? - } else if let Some(values) = workspace_config + value + } else if let Some(value) = workspace_config .and_then(|config| config.rhi.as_ref()) - .and_then(|rhi| rhi.trusted_worker_pubkeys.clone()) + .and_then(|rhi| rhi.require_cryptographic_proof) { - normalize_pubkeys(values, "workspace config [rhi].trusted_worker_pubkeys")? + value } else { - Vec::new() + false }; Ok(RhiConfig { - trusted_worker_pubkeys, + validator_set, + require_cryptographic_proof, }) } -fn parse_pubkey_env_value(value: &str, key: &str) -> Result<Vec<String>, RuntimeError> { - let entries = value - .split(',') - .map(str::trim) - .filter(|entry| !entry.is_empty()) - .map(ToOwned::to_owned) - .collect::<Vec<_>>(); - normalize_pubkeys(entries, key) +fn resolve_rhi_validator_set_config( + env: &dyn Environment, + env_file: &EnvFileValues, + user_config: Option<&CliConfigFile>, + workspace_config: Option<&CliConfigFile>, +) -> Result<Option<RhiValidatorSetConfig>, RuntimeError> { + let env_keys = [ + ENV_CLI_RHI_VALIDATOR_SET_ID, + ENV_CLI_RHI_VALIDATOR_PUBKEY, + ENV_CLI_RHI_VALIDATOR_SET_ADDR, + ENV_CLI_RHI_VALIDATOR_SET_EVENT_ID, + ENV_CLI_RHI_VALIDATOR_SET_VALID_FROM, + ENV_CLI_RHI_VALIDATOR_SET_VALID_UNTIL, + ENV_CLI_RHI_VALIDATOR_SET_PROTOCOL_CONTRACT_HASH, + ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_NAME, + ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_CONTACT, + ]; + if env_keys + .iter() + .any(|key| env_value(env, env_file, &[*key]).is_some()) + { + return Ok(Some(validate_rhi_validator_set_config( + RhiValidatorSetConfig { + set_id: required_env_value(env, env_file, ENV_CLI_RHI_VALIDATOR_SET_ID)?, + validator_pubkey: required_env_value(env, env_file, ENV_CLI_RHI_VALIDATOR_PUBKEY)?, + validator_set_addr: required_env_value( + env, + env_file, + ENV_CLI_RHI_VALIDATOR_SET_ADDR, + )?, + validator_set_event_id: required_env_value( + env, + env_file, + ENV_CLI_RHI_VALIDATOR_SET_EVENT_ID, + )?, + valid_from: parse_u64_value( + ENV_CLI_RHI_VALIDATOR_SET_VALID_FROM, + required_env_value(env, env_file, ENV_CLI_RHI_VALIDATOR_SET_VALID_FROM)? + .as_str(), + ) + .map_err(|err| RuntimeError::Config(err.to_string()))?, + valid_until: parse_u64_value( + ENV_CLI_RHI_VALIDATOR_SET_VALID_UNTIL, + required_env_value(env, env_file, ENV_CLI_RHI_VALIDATOR_SET_VALID_UNTIL)? + .as_str(), + ) + .map_err(|err| RuntimeError::Config(err.to_string()))?, + protocol_contract_hash: required_env_value( + env, + env_file, + ENV_CLI_RHI_VALIDATOR_SET_PROTOCOL_CONTRACT_HASH, + )?, + operator_name: required_env_value( + env, + env_file, + ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_NAME, + )?, + operator_contact: env_value( + env, + env_file, + &[ENV_CLI_RHI_VALIDATOR_SET_OPERATOR_CONTACT], + ), + }, + "environment RHI validator_set", + )?)); + } + if let Some(config) = user_config + .and_then(|config| config.rhi.as_ref()) + .and_then(|rhi| rhi.validator_set.as_ref()) + { + return Ok(Some(rhi_validator_set_from_file_config( + config, + "user config [rhi.validator_set]", + )?)); + } + if let Some(config) = workspace_config + .and_then(|config| config.rhi.as_ref()) + .and_then(|rhi| rhi.validator_set.as_ref()) + { + return Ok(Some(rhi_validator_set_from_file_config( + config, + "workspace config [rhi.validator_set]", + )?)); + } + Ok(None) } -fn normalize_pubkeys(values: Vec<String>, source: &str) -> Result<Vec<String>, RuntimeError> { - let mut normalized = Vec::new(); - for value in values { - let pubkey = validate_pubkey(value.as_str(), source)?; - if !normalized.iter().any(|existing| existing == &pubkey) { - normalized.push(pubkey); - } +fn required_env_value( + env: &dyn Environment, + env_file: &EnvFileValues, + key: &'static str, +) -> Result<String, RuntimeError> { + env_value(env, env_file, &[key]).ok_or_else(|| { + RuntimeError::Config(format!( + "{key} is required when any RHI validator set environment key is set" + )) + }) +} + +fn rhi_validator_set_from_file_config( + config: &RhiValidatorSetFileConfig, + source: &'static str, +) -> Result<RhiValidatorSetConfig, RuntimeError> { + validate_rhi_validator_set_config( + RhiValidatorSetConfig { + set_id: required_file_value(config.set_id.clone(), source, "set_id")?, + validator_pubkey: required_file_value( + config.validator_pubkey.clone(), + source, + "validator_pubkey", + )?, + validator_set_addr: required_file_value( + config.validator_set_addr.clone(), + source, + "validator_set_addr", + )?, + validator_set_event_id: required_file_value( + config.validator_set_event_id.clone(), + source, + "validator_set_event_id", + )?, + valid_from: config + .valid_from + .ok_or_else(|| missing_file_value(source, "valid_from"))?, + valid_until: config + .valid_until + .ok_or_else(|| missing_file_value(source, "valid_until"))?, + protocol_contract_hash: required_file_value( + config.protocol_contract_hash.clone(), + source, + "protocol_contract_hash", + )?, + operator_name: required_file_value( + config.operator_name.clone(), + source, + "operator_name", + )?, + operator_contact: config.operator_contact.clone(), + }, + source, + ) +} + +fn required_file_value( + value: Option<String>, + source: &'static str, + field: &'static str, +) -> Result<String, RuntimeError> { + value.ok_or_else(|| missing_file_value(source, field)) +} + +fn missing_file_value(source: &'static str, field: &'static str) -> RuntimeError { + RuntimeError::Config(format!("{source}.{field} is required")) +} + +fn validate_rhi_validator_set_config( + mut config: RhiValidatorSetConfig, + source: &'static str, +) -> Result<RhiValidatorSetConfig, RuntimeError> { + config.set_id = validate_uuidv7(config.set_id.as_str(), source, "set_id")?; + config.validator_pubkey = validate_pubkey( + config.validator_pubkey.as_str(), + format!("{source}.validator_pubkey").as_str(), + )?; + config.validator_set_event_id = validate_event_id( + config.validator_set_event_id.as_str(), + format!("{source}.validator_set_event_id").as_str(), + )?; + config.protocol_contract_hash = validate_hash32( + config.protocol_contract_hash.as_str(), + format!("{source}.protocol_contract_hash").as_str(), + )?; + config.validator_set_addr = validate_validator_set_address( + config.validator_set_addr.as_str(), + config.set_id.as_str(), + format!("{source}.validator_set_addr").as_str(), + )?; + if config.valid_from >= config.valid_until { + return Err(RuntimeError::Config(format!( + "{source}.valid_from must be less than {source}.valid_until" + ))); } - Ok(normalized) + config.operator_name = validate_required_text( + config.operator_name.as_str(), + format!("{source}.operator_name").as_str(), + )?; + if let Some(operator_contact) = config.operator_contact.as_ref() { + validate_required_text( + operator_contact.as_str(), + format!("{source}.operator_contact").as_str(), + )?; + } + Ok(config) } fn validate_pubkey(value: &str, source: &str) -> Result<String, RuntimeError> { @@ -1255,6 +1483,92 @@ fn validate_pubkey(value: &str, source: &str) -> Result<String, RuntimeError> { Ok(trimmed.to_ascii_lowercase()) } +fn validate_event_id(value: &str, source: &str) -> Result<String, RuntimeError> { + validate_pubkey(value, source) +} + +fn validate_hash32(value: &str, source: &str) -> Result<String, RuntimeError> { + let trimmed = value.trim(); + let Some(hex) = trimmed.strip_prefix("0x") else { + return Err(RuntimeError::Config(format!( + "{source} must be a 0x-prefixed 32-byte lowercase hex digest" + ))); + }; + if hex.len() != 64 || !hex.chars().all(|char| char.is_ascii_hexdigit()) { + return Err(RuntimeError::Config(format!( + "{source} must be a 0x-prefixed 32-byte lowercase hex digest" + ))); + } + Ok(format!("0x{}", hex.to_ascii_lowercase())) +} + +fn validate_uuidv7(value: &str, source: &str, field: &str) -> Result<String, RuntimeError> { + let trimmed = value.trim().to_ascii_lowercase(); + let bytes = trimmed.as_bytes(); + if bytes.len() != 36 + || bytes[8] != b'-' + || bytes[13] != b'-' + || bytes[18] != b'-' + || bytes[23] != b'-' + || bytes[14] != b'7' + || !matches!(bytes[19], b'8'..=b'9' | b'a'..=b'b') + { + return Err(RuntimeError::Config(format!( + "{source}.{field} must be a UUIDv7" + ))); + } + for (index, byte) in bytes.iter().enumerate() { + if matches!(index, 8 | 13 | 18 | 23) { + continue; + } + if !byte.is_ascii_digit() && !(b'a'..=b'f').contains(byte) { + return Err(RuntimeError::Config(format!( + "{source}.{field} must be a UUIDv7" + ))); + } + } + Ok(trimmed) +} + +fn validate_validator_set_address( + value: &str, + expected_set_id: &str, + source: &str, +) -> Result<String, RuntimeError> { + let trimmed = value.trim(); + let mut parts = trimmed.split(':'); + let Some(kind) = parts.next() else { + return Err(RuntimeError::Config(format!( + "{source} must be a 30381 address" + ))); + }; + let Some(pubkey) = parts.next() else { + return Err(RuntimeError::Config(format!( + "{source} must be a 30381 address" + ))); + }; + let Some(set_id) = parts.next() else { + return Err(RuntimeError::Config(format!( + "{source} must be a 30381 address" + ))); + }; + if parts.next().is_some() || kind != "30381" || set_id != expected_set_id { + return Err(RuntimeError::Config(format!( + "{source} must be a 30381 address" + ))); + } + let pubkey = validate_pubkey(pubkey, source)?; + Ok(format!("30381:{pubkey}:{set_id}")) +} + +fn validate_required_text(value: &str, source: &str) -> Result<String, RuntimeError> { + let trimmed = value.trim(); + if trimmed.is_empty() { + return Err(RuntimeError::Config(format!("{source} must not be empty"))); + } + Ok(trimmed.to_owned()) +} + fn resolve_capability_bindings( user_config: Option<&CliConfigFile>, workspace_config: Option<&CliConfigFile>, @@ -2456,7 +2770,8 @@ mod tests { PathBuf::from(DEFAULT_HYF_EXECUTABLE) ); assert_eq!(resolved.rpc.url, DEFAULT_RPC_URL); - assert_eq!(resolved.rhi.trusted_worker_pubkeys, Vec::<String>::new()); + assert_eq!(resolved.rhi.validator_set, None); + assert!(!resolved.rhi.require_cryptographic_proof); } #[test] diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -49,16 +49,17 @@ use radroots_runtime_store::{ }; use radroots_sdk::{ PrivacyPreflightConfirmation, ProductSensitivityField, PublishMode, PushOutboxEventReceipt, - PushOutboxEventState, PushOutboxReceipt, PushOutboxTargetOutcomeKind, SatisfactionPolicy, - SdkMutationState, SdkTradeStatusSource, TargetPolicy, TradeAcceptRequest, TradeCancelRequest, - TradeCancellationPlan, TradeCancellationReceipt, TradeDecisionPlan, TradeDecisionReceipt, - TradeDeclineRequest, TradeEvidenceMode, TradeMutationOutcome, TradeProposeRequest, - TradeStatusReceipt, TradeStatusRequest, TradeSubmitPlan, TradeSubmitReceipt, - TradeWorkflowEnqueueReceipt, + PushOutboxEventState, PushOutboxReceipt, PushOutboxTargetOutcomeKind, + RadrootsTradeValidationTrustPolicy, SatisfactionPolicy, SdkMutationState, SdkTradeStatusSource, + TargetPolicy, TradeAcceptRequest, TradeCancelRequest, TradeCancellationPlan, + TradeCancellationReceipt, TradeDecisionPlan, TradeDecisionReceipt, TradeDeclineRequest, + TradeEvidenceMode, TradeMutationOutcome, TradeProposeRequest, TradeStatusReceipt, + TradeStatusRequest, TradeSubmitPlan, TradeSubmitReceipt, TradeWorkflowEnqueueReceipt, }; use radroots_sql_core::SqlxSqliteExecutor; use radroots_trade::identity::RadrootsTradeLocator; use radroots_trade::order::canonicalize_order_request_for_signer; +use radroots_trade::validation_receipt::{RadrootsValidatorSetV1, validator_set_address_from_str}; use radroots_transport_nostr::{ RadrootsRelayFetchFailure, RadrootsRelayFetchedEventsReceipt, RadrootsRelayTransportError, RadrootsRelayUrl, RadrootsRelayUrlPolicy, @@ -68,8 +69,7 @@ use serde_json::{Value, json}; use crate::cli::global::{ OrderDraftCreateArgs, RecordLookupArgs, TradeAppRecordExportArgs, TradeCancelArgs, - TradeDecisionArg, TradeDecisionArgs, TradeRebindArgs, TradeRevisionDecisionArg, - TradeRevisionDecisionArgs, TradeRevisionProposeArgs, TradeStatusArgs, TradeSubmitArgs, + TradeDecisionArg, TradeDecisionArgs, TradeRebindArgs, TradeStatusArgs, TradeSubmitArgs, }; use crate::runtime::RuntimeError; use crate::runtime::account; @@ -87,8 +87,8 @@ use crate::view::runtime::{ OrderAppRecordExportView, OrderAppRecordListView, OrderAppRecordSummaryView, OrderCancellationView, OrderDecisionView, OrderDraftItemView, OrderEventListEntryView, OrderEventListView, OrderGetView, OrderIssueView, OrderListView, OrderNewView, OrderRebindView, - OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusView, OrderSubmitView, - OrderSummaryView, OrderTradeLocatorView, TransportTargetFailureView, + OrderStatusView, OrderSubmitView, OrderSummaryView, OrderTradeLocatorView, + TransportTargetFailureView, }; use self::sdk_status::sdk_order_status_view; @@ -98,8 +98,6 @@ const ORDER_SOURCE: &str = "local trade drafts · local first"; const ORDER_APP_RECORD_SOURCE: &str = "app-authored shared local trade records"; const ORDER_SUBMIT_SOURCE: &str = "SDK trade submit · local key"; const ORDER_DECISION_SOURCE: &str = "SDK trade decision · local key"; -const ORDER_REVISION_PROPOSAL_SOURCE: &str = "SDK trade revision proposal · local key"; -const ORDER_REVISION_DECISION_SOURCE: &str = "SDK trade revision decision · local key"; const ORDER_CANCELLATION_SOURCE: &str = "SDK trade cancellation · local key"; const ORDER_EVENT_LIST_SOURCE: &str = "shared Nostr transport fetch · selected seller identity"; const ORDER_STATUS_SDK_SOURCE: &str = "SDK local trade projection"; @@ -223,13 +221,6 @@ fn protocol_listing_addr(value: &str, field: &str) -> Result<RadrootsListingAddr }) } -#[cfg(any())] -fn protocol_revision_id(value: &str, field: &str) -> Result<RadrootsOrderRevisionId, RuntimeError> { - value.parse().map_err(|error| { - RuntimeError::Config(format!("{field} is not a valid trade revision id: {error}")) - }) -} - fn protocol_quote_id(value: &str, field: &str) -> Result<RadrootsOrderQuoteId, RuntimeError> { value.parse().map_err(|error| { RuntimeError::Config(format!("{field} is not a valid order quote id: {error}")) @@ -1232,37 +1223,6 @@ pub fn decide( decide_trade_via_sdk(config, args, &seller) } -pub fn revision_propose( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, -) -> Result<OrderRevisionProposalView, CliSdkAdapterError> { - if let Some(view) = order_revision_args_preflight_view(config, args) { - return Ok(view); - } - let mut view = order_revision_base_view(config, args, "unavailable", config.output.dry_run); - view.reason = Some( - "trade revision proposal is not supported by the current SDK trade runtime".to_owned(), - ); - view.actions = vec![format!("radroots trade status get {}", args.key)]; - Ok(view) -} - -pub fn revision_decide( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, -) -> Result<OrderRevisionDecisionView, CliSdkAdapterError> { - if let Some(view) = order_revision_decision_args_preflight_view(config, args) { - return Ok(view); - } - let mut view = - order_revision_decision_base_view(config, args, "unavailable", config.output.dry_run); - view.reason = Some( - "trade revision decision is not supported by the current SDK trade runtime".to_owned(), - ); - view.actions = vec![format!("radroots trade status get {}", args.key)]; - Ok(view) -} - pub fn cancel( config: &RuntimeConfig, args: &TradeCancelArgs, @@ -1284,9 +1244,8 @@ pub fn status( config: &RuntimeConfig, args: &TradeStatusArgs, ) -> Result<OrderStatusView, CliSdkAdapterError> { - let request = TradeStatusRequest::parse(args.key.as_str())?.try_with_trusted_rhi_pubkeys( - config.rhi.trusted_worker_pubkeys.iter().map(String::as_str), - )?; + let request = TradeStatusRequest::parse(args.key.as_str())? + .with_validation_trust_policy(trade_validation_trust_policy(config)?); let session = CliSdkSession::connect(config)?; let receipt = session.block_on(session.sdk().trades().status(request))?; Ok(sdk_order_status_view(receipt)) @@ -1355,114 +1314,6 @@ fn decide_trade_via_sdk( )) } -#[cfg(any())] -fn propose_revision_via_sdk( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, - seller: &account::AccountRecordView, -) -> Result<OrderRevisionProposalView, CliSdkAdapterError> { - let actor = sdk_trade_actor(seller, RadrootsActorRole::Seller, "revision propose")?; - let session = connect_sdk_for_trade_actor(config, seller, "trade revision propose")?; - let locator = trade_locator_from_key(args.key.as_str())?; - let status = trade_status_for_locator(config, &session, locator.clone())?; - let status_view = sdk_order_status_view(status); - let revision = revision_request_parts_from_status(args, &status_view)?; - let publish_mode = trade_publish_mode(config); - let ack_policy = trade_satisfaction_policy(publish_mode)?; - let mut request = TradeRevisionProposalRequest::new( - actor, - locator, - revision.revision_id.clone(), - revision.items.clone(), - revision.economics.clone(), - args.reason.trim(), - trade_target_policy(), - publish_mode, - ack_policy, - TradeEvidenceMode::ResyncBeforeMutation, - ) - .with_privacy_confirmation(trade_privacy_confirmation(args.confirm_public_note)); - if let Some(idempotency_key) = args.idempotency_key.as_deref() { - request = request.try_with_idempotency_key(idempotency_key)?; - } - let outcome = session.block_on(session.sdk().trades().seller().propose_revision(request))?; - Ok(sdk_trade_revision_outcome_view( - config, - args, - &status_view, - revision, - outcome, - )) -} - -#[cfg(any())] -fn decide_revision_via_sdk( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, - actor_context: OrderBuyerWriteActorContext, -) -> Result<OrderRevisionDecisionView, CliSdkAdapterError> { - let account = match actor_context.bound { - Some(bound) => bound.account, - None => account::resolve_account(config)?.ok_or_else(|| { - RuntimeError::Config( - "trade revision decision requires a selected buyer account".to_owned(), - ) - })?, - }; - let actor = sdk_trade_actor(&account, RadrootsActorRole::Buyer, "revision decision")?; - let session = connect_sdk_for_trade_actor(config, &account, "trade revision decision")?; - let locator = trade_locator_from_key(args.key.as_str())?; - let status = trade_status_for_locator(config, &session, locator.clone())?; - let status_view = sdk_order_status_view(status); - let revision_id = protocol_revision_id(args.revision_id.trim(), "revision_id")?; - let decision = match args.decision { - TradeRevisionDecisionArg::Accept => RadrootsOrderRevisionOutcome::Accepted, - TradeRevisionDecisionArg::Decline => RadrootsOrderRevisionOutcome::Declined { - reason: args - .reason - .as_deref() - .map(str::trim) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - RuntimeError::Config( - "trade revision decline requires a non-empty reason".to_owned(), - ) - })? - .to_owned(), - }, - }; - let publish_mode = trade_publish_mode(config); - let ack_policy = trade_satisfaction_policy(publish_mode)?; - let mut request = TradeRevisionDecisionRequest::new( - actor, - locator, - revision_id, - decision, - trade_target_policy(), - publish_mode, - ack_policy, - TradeEvidenceMode::ResyncBeforeMutation, - ) - .with_privacy_confirmation(trade_privacy_confirmation(args.confirm_public_note)); - if let Some(idempotency_key) = args.idempotency_key.as_deref() { - request = request.try_with_idempotency_key(idempotency_key)?; - } - let outcome = match args.decision { - TradeRevisionDecisionArg::Accept => { - session.block_on(session.sdk().trades().buyer().accept_revision(request))? - } - TradeRevisionDecisionArg::Decline => { - session.block_on(session.sdk().trades().buyer().decline_revision(request))? - } - }; - Ok(sdk_trade_revision_decision_outcome_view( - config, - args, - &status_view, - outcome, - )) -} - fn cancel_trade_via_sdk( config: &RuntimeConfig, args: &TradeCancelArgs, @@ -1510,12 +1361,41 @@ fn trade_status_for_locator( ) -> Result<TradeStatusReceipt, CliSdkAdapterError> { let request = TradeStatusRequest::new(locator) .with_source(SdkTradeStatusSource::ResyncThenLocal) - .try_with_trusted_rhi_pubkeys( - config.rhi.trusted_worker_pubkeys.iter().map(String::as_str), - )?; + .with_validation_trust_policy(trade_validation_trust_policy(config)?); Ok(session.block_on(session.sdk().trades().status(request))?) } +fn trade_validation_trust_policy( + config: &RuntimeConfig, +) -> Result<RadrootsTradeValidationTrustPolicy, CliSdkAdapterError> { + let policy = RadrootsTradeValidationTrustPolicy::production() + .with_require_cryptographic_proof(config.rhi.require_cryptographic_proof); + let Some(validator_set_config) = config.rhi.validator_set.as_ref() else { + return Ok(policy); + }; + let validator_set = RadrootsValidatorSetV1 { + set_id: validator_set_config.set_id.clone(), + validator_pubkey: RadrootsPublicKey::parse(validator_set_config.validator_pubkey.as_str()) + .map_err(|error| { + RuntimeError::Config(format!("invalid RHI validator pubkey: {error}")) + })?, + threshold: 1, + valid_from: validator_set_config.valid_from, + valid_until: validator_set_config.valid_until, + protocol_contract_hash: validator_set_config.protocol_contract_hash.clone(), + operator_name: validator_set_config.operator_name.clone(), + operator_contact: validator_set_config.operator_contact.clone(), + }; + let validator_set_addr = + validator_set_address_from_str(validator_set_config.validator_set_addr.as_str()) + .map_err(|error| RuntimeError::Config(error.to_string()))?; + Ok(policy.with_validator_set( + validator_set, + validator_set_addr, + validator_set_config.validator_set_event_id.clone(), + )) +} + fn inventory_commitments_from_status( status: &TradeStatusReceipt, ) -> Result<Vec<RadrootsOrderInventoryCommitment>, RuntimeError> { @@ -1532,39 +1412,6 @@ fn inventory_commitments_from_status( .collect()) } -#[cfg(any())] -#[derive(Debug, Clone)] -struct SdkRevisionRequestParts { - revision_id: RadrootsOrderRevisionId, - items: Vec<RadrootsOrderItem>, - economics: RadrootsOrderEconomics, -} - -#[cfg(any())] -fn revision_request_parts_from_status( - args: &TradeRevisionProposeArgs, - status: &OrderStatusView, -) -> Result<SdkRevisionRequestParts, RuntimeError> { - let revision_id = protocol_revision_id(next_revision_id().as_str(), "revision_id")?; - let economics = status.economics.clone().ok_or_else(|| { - RuntimeError::Config("accepted trade is missing current agreement economics".to_owned()) - })?; - let economics = revised_order_economics(args, revision_id.as_str(), &economics)?; - let items = economics - .items - .iter() - .map(|item| RadrootsOrderItem { - bin_id: item.bin_id.clone(), - bin_count: item.bin_count, - }) - .collect::<Vec<_>>(); - Ok(SdkRevisionRequestParts { - revision_id, - items, - economics, - }) -} - fn sdk_trade_decision_outcome_view( config: &RuntimeConfig, args: &TradeDecisionArgs, @@ -1597,85 +1444,6 @@ fn sdk_trade_decision_outcome_view( } } -#[cfg(any())] -fn sdk_trade_revision_outcome_view( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, - status: &OrderStatusView, - revision: SdkRevisionRequestParts, - outcome: TradeMutationOutcome<TradeRevisionProposalPlan, TradeRevisionProposalReceipt>, -) -> OrderRevisionProposalView { - match outcome { - TradeMutationOutcome::DryRun { plan } => { - let mut view = order_revision_base_view(config, args, "dry_run", true); - apply_order_revision_status(&mut view, status); - view.revision_id = Some(revision.revision_id.to_string()); - view.root_event_id = Some(plan.root_event_id.to_string()); - view.prev_event_id = Some(plan.previous_event_id.to_string()); - view.event_id = Some(plan.expected_event_id.to_string()); - view.event_kind = Some(KIND_ORDER_REVISION_PROPOSAL); - view.items = revision - .items - .iter() - .map(|item| OrderDraftItemView { - bin_id: item.bin_id.to_string(), - bin_count: item.bin_count, - }) - .collect(); - view.economics = Some(revision.economics); - view.target_transport_endpoints = config.transport.nostr_relay_urls.clone(); - view.reason = - Some("dry run requested; seller revision proposal publication skipped".to_owned()); - view.actions = vec![format!("radroots trade status get {}", status.order_id)]; - view - } - TradeMutationOutcome::Enqueued { receipt } => { - sdk_enqueued_order_revision_view(config, args, status, revision, receipt, None) - } - TradeMutationOutcome::Published { receipt, publish } => sdk_enqueued_order_revision_view( - config, - args, - status, - revision, - receipt, - Some(&publish), - ), - } -} - -#[cfg(any())] -fn sdk_trade_revision_decision_outcome_view( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, - status: &OrderStatusView, - outcome: TradeMutationOutcome<TradeRevisionDecisionPlan, TradeRevisionDecisionReceipt>, -) -> OrderRevisionDecisionView { - match outcome { - TradeMutationOutcome::DryRun { plan } => { - let mut view = order_revision_decision_base_view(config, args, "dry_run", true); - apply_order_revision_decision_status(&mut view, status); - view.revision_id = Some(args.revision_id.trim().to_owned()); - view.root_event_id = Some(plan.root_event_id.to_string()); - view.prev_event_id = Some(plan.previous_event_id.to_string()); - view.event_id = Some(plan.expected_event_id.to_string()); - view.event_kind = Some(KIND_ORDER_REVISION_DECISION); - view.target_transport_endpoints = config.transport.nostr_relay_urls.clone(); - view.reason = Some(format!( - "dry run requested; buyer revision {} publication skipped", - args.decision.command() - )); - view.actions = vec![format!("radroots trade status get {}", status.order_id)]; - view - } - TradeMutationOutcome::Enqueued { receipt } => { - sdk_enqueued_order_revision_decision_view(config, args, status, receipt, None) - } - TradeMutationOutcome::Published { receipt, publish } => { - sdk_enqueued_order_revision_decision_view(config, args, status, receipt, Some(&publish)) - } - } -} - fn sdk_trade_cancellation_outcome_view( config: &RuntimeConfig, args: &TradeCancelArgs, @@ -1930,87 +1698,6 @@ fn order_decision_base_view( } } -fn order_revision_base_view( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, - state: &str, - dry_run: bool, -) -> OrderRevisionProposalView { - OrderRevisionProposalView { - state: state.to_owned(), - source: ORDER_REVISION_PROPOSAL_SOURCE.to_owned(), - order_id: args.key.clone(), - locator: order_locator_view_from_key(args.key.as_str()), - revision_id: None, - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - request_event_id: None, - decision_event_id: None, - root_event_id: None, - prev_event_id: None, - event_id: None, - event_kind: None, - items: Vec::new(), - economics: None, - inventory: None, - dry_run, - target_transport_endpoints: config.transport.nostr_relay_urls.clone(), - attempted_transport_endpoints: Vec::new(), - accepted_transport_endpoints: Vec::new(), - failed_transport_targets: Vec::new(), - fetched_count: 0, - decoded_count: 0, - skipped_count: 0, - idempotency_key: args.idempotency_key.clone(), - signer_mode: Some(config.signer.backend.as_str().to_owned()), - reason: None, - issues: Vec::new(), - actions: Vec::new(), - } -} - -fn order_revision_decision_base_view( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, - state: &str, - dry_run: bool, -) -> OrderRevisionDecisionView { - OrderRevisionDecisionView { - state: state.to_owned(), - source: ORDER_REVISION_DECISION_SOURCE.to_owned(), - order_id: args.key.clone(), - locator: order_locator_view_from_key(args.key.as_str()), - revision_id: Some(args.revision_id.trim().to_owned()).filter(|value| !value.is_empty()), - decision: Some(args.decision.as_str().to_owned()), - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - request_event_id: None, - decision_event_id: None, - agreement_event_id: None, - root_event_id: None, - prev_event_id: None, - event_id: None, - event_kind: None, - economics: None, - inventory: None, - dry_run, - target_transport_endpoints: config.transport.nostr_relay_urls.clone(), - attempted_transport_endpoints: Vec::new(), - accepted_transport_endpoints: Vec::new(), - failed_transport_targets: Vec::new(), - fetched_count: 0, - decoded_count: 0, - skipped_count: 0, - idempotency_key: args.idempotency_key.clone(), - signer_mode: Some(config.signer.backend.as_str().to_owned()), - reason: args.reason.as_ref().map(|reason| reason.trim().to_owned()), - issues: Vec::new(), - actions: Vec::new(), - } -} - fn order_cancellation_base_view( config: &RuntimeConfig, args: &TradeCancelArgs, @@ -2098,290 +1785,6 @@ fn apply_order_decision_status(view: &mut OrderDecisionView, status: &OrderStatu view.inventory = status.inventory.clone(); } -#[cfg(any())] -fn apply_order_revision_status(view: &mut OrderRevisionProposalView, status: &OrderStatusView) { - view.order_id = status.order_id.clone(); - view.locator = order_locator_view_from_status(status); - view.listing_addr = status.listing_addr.clone(); - view.buyer_pubkey = status.buyer_pubkey.clone(); - view.seller_pubkey = status.seller_pubkey.clone(); - view.request_event_id = status.request_event_id.clone(); - view.decision_event_id = status.decision_event_id.clone(); - view.root_event_id = status.request_event_id.clone(); - view.prev_event_id = status.last_event_id.clone(); - view.economics = status.economics.clone(); - view.inventory = status.inventory.clone(); - view.target_transport_endpoints = status.target_transport_endpoints.clone(); - view.attempted_transport_endpoints = status.attempted_transport_endpoints.clone(); - view.failed_transport_targets = status.failed_transport_targets.clone(); - view.fetched_count = status.fetched_count; - view.decoded_count = status.decoded_count; - view.skipped_count = status.skipped_count; - view.issues = status.reducer_issues.clone(); -} - -#[cfg(any())] -fn apply_order_revision_decision_status( - view: &mut OrderRevisionDecisionView, - status: &OrderStatusView, -) { - view.order_id = status.order_id.clone(); - view.locator = order_locator_view_from_status(status); - view.listing_addr = status.listing_addr.clone(); - view.buyer_pubkey = status.buyer_pubkey.clone(); - view.seller_pubkey = status.seller_pubkey.clone(); - view.request_event_id = status.request_event_id.clone(); - view.decision_event_id = status.decision_event_id.clone(); - view.agreement_event_id = status.agreement_event_id.clone(); - view.root_event_id = status.request_event_id.clone(); - view.prev_event_id = status.last_event_id.clone(); - view.economics = status.economics.clone(); - view.inventory = status.inventory.clone(); - view.target_transport_endpoints = status.target_transport_endpoints.clone(); - view.attempted_transport_endpoints = status.attempted_transport_endpoints.clone(); - view.failed_transport_targets = status.failed_transport_targets.clone(); - view.fetched_count = status.fetched_count; - view.decoded_count = status.decoded_count; - view.skipped_count = status.skipped_count; - view.issues = status.reducer_issues.clone(); -} - -fn order_revision_args_preflight_view( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, -) -> Option<OrderRevisionProposalView> { - let mut issues = Vec::new(); - let has_bin_id = args.bin_id.as_deref().and_then(non_empty_ref).is_some(); - let has_bin_count = args.bin_count.is_some(); - if has_bin_id != has_bin_count { - issues.push(issue_with_code( - "revision_item_change_incomplete", - "bin_id", - "`bin_id` and `bin_count` must be supplied together", - )); - } - if args.bin_count == Some(0) { - issues.push(issue_with_code( - "revision_bin_count_invalid", - "bin_count", - "bin_count must be greater than zero", - )); - } - - let adjustment_inputs = [ - args.adjustment_id.as_deref(), - args.adjustment_effect.as_deref(), - args.adjustment_amount.as_deref(), - args.adjustment_currency.as_deref(), - args.adjustment_reason.as_deref(), - ]; - let adjustment_supplied = adjustment_inputs - .iter() - .any(|value| value.and_then(non_empty_ref).is_some()); - let adjustment_complete = adjustment_inputs - .iter() - .all(|value| value.and_then(non_empty_ref).is_some()); - if adjustment_supplied && !adjustment_complete { - issues.push(issue_with_code( - "revision_adjustment_incomplete", - "adjustment", - "all revision adjustment fields must be supplied together", - )); - } - - if !has_bin_id && !adjustment_supplied { - issues.push(issue_with_code( - "revision_no_changes", - "revision", - "trade revision propose requires a bin-count change or revision adjustment", - )); - } - - if issues.is_empty() { - return None; - } - let mut view = order_revision_base_view(config, args, "invalid", config.output.dry_run); - view.reason = Some(format!( - "trade revision propose inputs for `{}` failed validation", - args.key - )); - view.issues = issues; - Some(view) -} - -fn order_revision_decision_args_preflight_view( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, -) -> Option<OrderRevisionDecisionView> { - let mut issues = Vec::new(); - if args.revision_id.trim().is_empty() { - issues.push(issue_with_code( - "revision_id_required", - "revision_id", - "trade revision decision requires --revision-id", - )); - } - if args.decision == TradeRevisionDecisionArg::Decline - && args - .reason - .as_deref() - .map(str::trim) - .filter(|reason| !reason.is_empty()) - .is_none() - { - issues.push(issue_with_code( - "revision_decline_reason_required", - "reason", - "trade revision decline requires a non-empty reason", - )); - } - - if issues.is_empty() { - return None; - } - let mut view = - order_revision_decision_base_view(config, args, "invalid", config.output.dry_run); - view.reason = Some(format!( - "trade revision {} inputs for `{}` failed validation", - args.decision.command(), - args.key - )); - view.issues = issues; - Some(view) -} - -#[cfg(any())] -fn revised_order_economics( - args: &TradeRevisionProposeArgs, - revision_id: &str, - current: &RadrootsOrderEconomics, -) -> Result<RadrootsOrderEconomics, RuntimeError> { - let mut current_canonical = current.clone(); - current_canonical.canonicalize(); - let mut economics = current_canonical.clone(); - let mut changed = false; - economics.quote_id = protocol_quote_id(format!("revision_{revision_id}").as_str(), "quote_id")?; - economics.quote_version = economics - .quote_version - .checked_add(1) - .ok_or_else(|| RuntimeError::Config("revision quote_version overflowed".to_owned()))?; - - if let Some(bin_id) = args.bin_id.as_deref().and_then(non_empty_ref) { - let bin_id = protocol_inventory_bin_id(bin_id, "revision bin_id")?; - let bin_count = args.bin_count.ok_or_else(|| { - RuntimeError::Config("revision bin_count is required with bin_id".to_owned()) - })?; - let Some(item) = economics - .items - .iter_mut() - .find(|item| item.bin_id == bin_id) - else { - return Err(RuntimeError::Config(format!( - "revision bin `{bin_id}` is not part of the current agreement" - ))); - }; - if item.bin_count != bin_count { - changed = true; - } - item.bin_count = bin_count; - item.line_subtotal = RadrootsCoreMoney::new( - item.unit_price_amount * item.quantity_amount * RadrootsCoreDecimal::from(bin_count), - item.unit_price_currency, - ); - } - - if let Some(line) = revision_adjustment_line(args, economics.currency)? { - changed = true; - if economics - .adjustments - .iter() - .any(|existing| existing.id == line.id) - { - return Err(RuntimeError::Config(format!( - "revision adjustment id `{}` already exists in current agreement economics", - line.id - ))); - } - economics.adjustments.push(line); - } - - economics.canonicalize(); - economics - .validate() - .map_err(|error| RuntimeError::Config(format!("build revision economics: {error}")))?; - if !changed { - return Err(RuntimeError::Config( - "trade revision propose requires a changed item count or adjustment".to_owned(), - )); - } - Ok(economics) -} - -#[cfg(any())] -fn revision_adjustment_line( - args: &TradeRevisionProposeArgs, - expected_currency: RadrootsCoreCurrency, -) -> Result<Option<RadrootsOrderEconomicLine>, RuntimeError> { - let Some(id) = args.adjustment_id.as_deref().and_then(non_empty_ref) else { - return Ok(None); - }; - let effect = match args - .adjustment_effect - .as_deref() - .and_then(non_empty_ref) - .ok_or_else(|| RuntimeError::Config("revision adjustment effect is required".to_owned()))? - { - "increase" => RadrootsOrderEconomicEffect::Increase, - "decrease" => RadrootsOrderEconomicEffect::Decrease, - other => { - return Err(RuntimeError::Config(format!( - "revision adjustment effect `{other}` is invalid" - ))); - } - }; - let currency = parse_economics_currency( - args.adjustment_currency - .as_deref() - .and_then(non_empty_ref) - .ok_or_else(|| { - RuntimeError::Config("revision adjustment currency is required".to_owned()) - })?, - "revision_adjustment_currency", - )?; - if currency != expected_currency { - return Err(RuntimeError::Config( - "revision adjustment currency must match current agreement currency".to_owned(), - )); - } - let amount = decimal_from_adjustment( - args.adjustment_amount - .as_deref() - .and_then(non_empty_ref) - .ok_or_else(|| { - RuntimeError::Config("revision adjustment amount is required".to_owned()) - })?, - "revision_adjustment_amount", - )?; - if amount.is_zero() { - return Err(RuntimeError::Config( - "revision adjustment amount must be greater than zero".to_owned(), - )); - } - let reason = args - .adjustment_reason - .as_deref() - .and_then(non_empty_ref) - .ok_or_else(|| RuntimeError::Config("revision adjustment reason is required".to_owned()))?; - Ok(Some(RadrootsOrderEconomicLine { - id: id.to_owned(), - kind: RadrootsOrderEconomicLineKind::RevisionAdjustment, - actor: RadrootsOrderEconomicActor::Seller, - effect, - amount: RadrootsCoreMoney::new(amount, currency), - reason: reason.to_owned(), - })) -} - fn sdk_enqueued_order_decision_view( config: &RuntimeConfig, args: &TradeDecisionArgs, @@ -2482,103 +1885,6 @@ fn sdk_order_decision_actions(push_event: Option<&PushOutboxEventReceipt>) -> Ve Vec::new() } -#[cfg(any())] -fn sdk_enqueued_order_revision_view( - config: &RuntimeConfig, - args: &TradeRevisionProposeArgs, - status: &OrderStatusView, - revision: SdkRevisionRequestParts, - enqueue: TradeRevisionProposalReceipt, - push: Option<&PushOutboxReceipt>, -) -> OrderRevisionProposalView { - let push_event = - push.and_then(|push| sdk_push_event_for_event_id(&enqueue.signed_event_id, push)); - let mut view = order_revision_base_view( - config, - args, - sdk_order_lifecycle_state("proposed", push_event).as_str(), - false, - ); - apply_order_revision_status(&mut view, status); - view.locator = order_locator_view_from_locator(&enqueue.locator); - view.revision_id = Some(revision.revision_id.to_string()); - view.root_event_id = Some(enqueue.root_event_id.to_string()); - view.prev_event_id = Some(enqueue.previous_event_id.to_string()); - view.items = revision - .items - .iter() - .map(|item| OrderDraftItemView { - bin_id: item.bin_id.to_string(), - bin_count: item.bin_count, - }) - .collect(); - view.economics = Some(revision.economics); - view.event_id = Some(enqueue.signed_event_id.as_str().to_owned()); - view.event_kind = Some(KIND_ORDER_REVISION_PROPOSAL); - view.target_transport_endpoints = push_event - .map(sdk_push_target_transport_endpoints) - .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); - view.attempted_transport_endpoints = push_event - .map(sdk_push_attempted_transport_endpoints) - .unwrap_or_default(); - view.accepted_transport_endpoints = push_event - .map(sdk_push_accepted_transport_endpoints) - .unwrap_or_default(); - view.failed_transport_targets = push_event - .map(sdk_push_failed_transport_targets) - .unwrap_or_default(); - view.reason = - sdk_order_lifecycle_reason("trade revision proposal", &enqueue.workflow, push_event); - view.actions = sdk_order_lifecycle_actions(push_event); - view -} - -#[cfg(any())] -fn sdk_enqueued_order_revision_decision_view( - config: &RuntimeConfig, - args: &TradeRevisionDecisionArgs, - status: &OrderStatusView, - enqueue: TradeRevisionDecisionReceipt, - push: Option<&PushOutboxReceipt>, -) -> OrderRevisionDecisionView { - let push_event = - push.and_then(|push| sdk_push_event_for_event_id(&enqueue.signed_event_id, push)); - let success_state = args.decision.as_str(); - let mut view = order_revision_decision_base_view( - config, - args, - sdk_order_lifecycle_state(success_state, push_event).as_str(), - false, - ); - apply_order_revision_decision_status(&mut view, status); - view.locator = order_locator_view_from_locator(&enqueue.locator); - view.revision_id = Some(args.revision_id.trim().to_owned()); - view.root_event_id = Some(enqueue.root_event_id.to_string()); - view.prev_event_id = Some(enqueue.previous_event_id.to_string()); - view.decision = Some(args.decision.as_str().to_owned()); - view.event_id = Some(enqueue.signed_event_id.as_str().to_owned()); - view.event_kind = Some(KIND_ORDER_REVISION_DECISION); - if args.decision == TradeRevisionDecisionArg::Accept { - view.agreement_event_id = Some(enqueue.signed_event_id.as_str().to_owned()); - } - view.target_transport_endpoints = push_event - .map(sdk_push_target_transport_endpoints) - .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); - view.attempted_transport_endpoints = push_event - .map(sdk_push_attempted_transport_endpoints) - .unwrap_or_default(); - view.accepted_transport_endpoints = push_event - .map(sdk_push_accepted_transport_endpoints) - .unwrap_or_default(); - view.failed_transport_targets = push_event - .map(sdk_push_failed_transport_targets) - .unwrap_or_default(); - view.reason = - sdk_order_lifecycle_reason("trade revision decision", &enqueue.workflow, push_event); - view.actions = sdk_order_lifecycle_actions(push_event); - view -} - fn sdk_enqueued_order_cancellation_view( config: &RuntimeConfig, args: &TradeCancelArgs, @@ -5459,19 +4765,6 @@ fn next_order_id() -> String { ) } -#[cfg(any())] -fn next_revision_id() -> String { - let nanos = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_nanos()) - .unwrap_or_default(); - let counter = ORDER_COUNTER.fetch_add(1, Ordering::Relaxed) as u128; - format!( - "rev_{}", - encode_base64url_no_pad((nanos ^ counter).to_be_bytes()) - ) -} - fn is_valid_order_id(value: &str) -> bool { if let Some(encoded) = value.strip_prefix("ord_") { return encoded.len() == 22 && is_d_tag_base64url(encoded); diff --git a/src/runtime/order/sdk_status.rs b/src/runtime/order/sdk_status.rs @@ -52,7 +52,6 @@ pub(super) fn sdk_order_status_view(receipt: TradeStatusReceipt) -> OrderStatusV seller_pubkey: receipt.seller_pubkey.as_ref().map(ToString::to_string), economics: receipt.economics.clone(), last_event_id: sdk_event_id_string(receipt.last_event_id.as_ref()), - revision: None, inventory: None, lifecycle: Some(lifecycle), sdk_receipt, @@ -112,7 +111,6 @@ fn sdk_status_evidence_view( has_decision: evidence.has_decision, has_agreement: evidence.has_agreement, has_validation_receipt: evidence.has_validation_receipt, - has_pending_revision: false, has_cancellation: evidence.has_cancellation, has_issues: evidence.has_issues, } @@ -123,8 +121,9 @@ fn sdk_status_validation_trust_view( ) -> OrderStatusValidationTrustView { OrderStatusValidationTrustView { state: decision.state.as_str().to_owned(), - trusted_rhi_pubkey_count: decision.trusted_rhi_pubkey_count, - allow_deterministic_none: decision.allow_deterministic_none, + validator_count: decision.validator_count, + validator_set_addr: decision.validator_set_addr.clone(), + validator_set_event_id: decision.validator_set_event_id.clone(), require_cryptographic_proof: decision.require_cryptographic_proof, receipt_event_id: decision.receipt_event_id.as_ref().map(ToString::to_string), receipt_author: decision.receipt_author.as_ref().map(ToString::to_string), @@ -148,8 +147,6 @@ fn sdk_status_validation_trust_view( fn sdk_status_eligibility_view(eligibility: &TradeStatusEligibility) -> OrderStatusEligibilityView { OrderStatusEligibilityView { can_decide: eligibility.can_decide, - can_propose_revision: false, - can_decide_revision: false, can_cancel: eligibility.can_cancel, } } diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs @@ -350,7 +350,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: bindings, } diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -1117,8 +1117,8 @@ mod tests { end: "fn decide_trade_via_sdk(", required_tokens: &[ "TradeStatusRequest::parse", - ".try_with_trusted_rhi_pubkeys(", - "config.rhi.trusted_worker_pubkeys", + ".with_validation_trust_policy(", + "trade_validation_trust_policy(config)?", "session.sdk().trades().status", ], }, @@ -1134,8 +1134,8 @@ mod tests { ".sdk()", ".trades()", ".with_source(SdkTradeStatusSource::ResyncThenLocal)", - ".try_with_trusted_rhi_pubkeys(", - "config.rhi.trusted_worker_pubkeys", + ".with_validation_trust_policy(", + "trade_validation_trust_policy(config)?", ], }, MigratedCliPathGuard { @@ -1200,7 +1200,7 @@ mod tests { label: "order decision", path: "src/runtime/order.rs", start: "fn decide_trade_via_sdk(", - end: "fn propose_revision_via_sdk(", + end: "fn cancel_trade_via_sdk(", required_tokens: &[ "decide_trade_via_sdk", "TradeAcceptRequest::new", @@ -1213,19 +1213,12 @@ mod tests { MigratedCliPathGuard { label: "order lifecycle", path: "src/runtime/order.rs", - start: "fn propose_revision_via_sdk(", + start: "fn cancel_trade_via_sdk(", end: "fn trade_status_for_locator(", required_tokens: &[ - "propose_revision_via_sdk", - "decide_revision_via_sdk", "cancel_trade_via_sdk", - "TradeRevisionProposalRequest::new", - "TradeRevisionDecisionRequest::new", "TradeCancelRequest::new", "TradeEvidenceMode::ResyncBeforeMutation", - "session.sdk().trades().seller().propose_revision", - "session.sdk().trades().buyer().accept_revision", - "session.sdk().trades().buyer().decline_revision", "session.sdk().trades().buyer().cancel_trade", ], }, @@ -1286,8 +1279,6 @@ mod tests { "radroots_sdk::protocol::order", "build_order_request_draft", "build_order_decision_draft", - "build_order_revision_proposal_draft", - "build_order_revision_decision_draft", "build_order_cancellation_draft", "parse_order_root_tag", "parse_order_prev_tag", @@ -2844,7 +2835,8 @@ struct FixtureConfig;\n\ url: "http://127.0.0.1:7070".to_owned(), }, rhi: RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs @@ -3217,7 +3217,8 @@ mod tests { url: "http://127.0.0.1:7070".into(), }, rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), + validator_set: None, + require_cryptographic_proof: false, }, capability_bindings: Vec::new(), } diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs @@ -142,14 +142,14 @@ pub struct ValidationReceiptProofVerificationView { pub verifying_key_hash: Option<String>, pub proof_reference: Option<String>, pub inline_proof_present: bool, - pub worker_evidence: Option<ValidationReceiptWorkerEvidenceView>, - pub untrusted_worker_evidence: Option<ValidationReceiptWorkerEvidenceView>, + pub validator_evidence: Option<ValidationReceiptValidatorEvidenceView>, + pub untrusted_validator_evidence: Option<ValidationReceiptValidatorEvidenceView>, pub reason_code: Option<String>, pub reason: Option<String>, } #[derive(Debug, Clone, Serialize)] -pub struct ValidationReceiptWorkerEvidenceView { +pub struct ValidationReceiptValidatorEvidenceView { pub result_event_id: String, pub author: String, pub validation_authority: Option<String>, @@ -166,9 +166,9 @@ pub struct ValidationReceiptWorkerEvidenceView { } #[derive(Clone, Debug, Default)] -struct ValidationReceiptWorkerEvidenceSelection { - trusted: Option<ValidationReceiptWorkerEvidenceView>, - untrusted: Option<ValidationReceiptWorkerEvidenceView>, +struct ValidationReceiptValidatorEvidenceSelection { + trusted: Option<ValidationReceiptValidatorEvidenceView>, + untrusted: Option<ValidationReceiptValidatorEvidenceView>, } #[derive(Debug, Clone, Serialize)] @@ -387,7 +387,7 @@ fn inspected_event_view( let order_id = sdk_receipt.tags.order_id.clone(); let proof_verification = proof_verification_view_for_receipt( &sdk_receipt.receipt, - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); let accepted = match intent { ValidationReceiptCommandIntent::Inspect => { @@ -481,7 +481,7 @@ fn list_from_sdk_receipt( for sdk_event in sdk_receipt.receipts { let proof_verification = proof_verification_view_for_receipt( &sdk_event.receipt, - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); if proof_state_is_invalid(proof_verification.state.as_str()) { invalid_receipts.push(ValidationReceiptInvalidCandidateView { @@ -741,14 +741,14 @@ struct ValidationReceiptTrustSummary { fn local_only_trust_summary() -> ValidationReceiptTrustSummary { ValidationReceiptTrustSummary { - state: "local_only_deterministic_receipt", + state: "validator_set_deterministic_receipt", validation_authority: Some( - RadrootsTradeValidationAuthority::DevDeterministicOnly + RadrootsTradeValidationAuthority::ValidatorSetDeterministic .as_str() .to_owned(), ), commitment_confidence: Some( - RadrootsTradeCommitmentConfidence::LocalOnly + RadrootsTradeCommitmentConfidence::CommittedByValidatorSet .as_str() .to_owned(), ), @@ -766,38 +766,38 @@ fn sp1_execute_checked_trust_summary() -> ValidationReceiptTrustSummary { production_verification: false, reason_code: Some("validation_receipt_trust_metadata_missing"), reason: Some( - "trusted worker evidence reports SP1 execution but omits validation authority or commitment confidence", + "validator evidence reports SP1 execution but omits validation authority or commitment confidence", ), } } fn missing_trust_metadata_summary() -> ValidationReceiptTrustSummary { ValidationReceiptTrustSummary { - state: "worker_evidence_trust_metadata_missing", + state: "validator_evidence_trust_metadata_missing", validation_authority: None, commitment_confidence: None, production_verification: false, reason_code: Some("validation_receipt_trust_metadata_missing"), - reason: Some("trusted worker evidence omits validation authority or commitment confidence"), + reason: Some("validator evidence omits validation authority or commitment confidence"), } } fn mismatched_trust_metadata_summary() -> ValidationReceiptTrustSummary { ValidationReceiptTrustSummary { - state: "worker_evidence_trust_metadata_mismatch", + state: "validator_evidence_trust_metadata_mismatch", validation_authority: None, commitment_confidence: None, production_verification: false, reason_code: Some("validation_receipt_trust_metadata_mismatch"), reason: Some( - "trusted worker evidence validation authority does not match commitment confidence", + "validator evidence validation authority does not match commitment confidence", ), } } -fn invalid_worker_evidence_summary() -> ValidationReceiptTrustSummary { +fn invalid_validator_evidence_summary() -> ValidationReceiptTrustSummary { ValidationReceiptTrustSummary { - state: "validation_receipt_worker_evidence_invalid", + state: "validation_receipt_validator_evidence_invalid", validation_authority: None, commitment_confidence: Some( RadrootsTradeCommitmentConfidence::Invalid @@ -805,12 +805,12 @@ fn invalid_worker_evidence_summary() -> ValidationReceiptTrustSummary { .to_owned(), ), production_verification: false, - reason_code: Some("validation_receipt_worker_evidence_invalid"), - reason: Some("trusted worker evidence marks the validation receipt invalid"), + reason_code: Some("validation_receipt_validator_evidence_invalid"), + reason: Some("validator evidence marks the validation receipt invalid"), } } -fn trusted_worker_summary( +fn trusted_validator_summary( state: &'static str, authority: &str, confidence: &str, @@ -827,9 +827,9 @@ fn trusted_worker_summary( } fn none_proof_trust_summary( - worker_evidence: &ValidationReceiptWorkerEvidenceSelection, + validator_evidence: &ValidationReceiptValidatorEvidenceSelection, ) -> ValidationReceiptTrustSummary { - let Some(evidence) = worker_evidence.trusted.as_ref() else { + let Some(evidence) = validator_evidence.trusted.as_ref() else { return local_only_trust_summary(); }; let authority = evidence.validation_authority.as_deref(); @@ -845,45 +845,43 @@ fn none_proof_trust_summary( return missing_trust_metadata_summary(); }; match (authority, confidence) { - ("dev_deterministic_only", "local_only") => local_only_trust_summary(), - ("trusted_rhi_service_key", "pending_rhi") => { - trusted_worker_summary("pending_rhi_validation", authority, confidence, false) + ("validator_set_deterministic", "awaiting_validation") => { + trusted_validator_summary("pending_validator_validation", authority, confidence, false) } - ("trusted_rhi_service_key", "committed_by_trusted_service") => { - trusted_worker_summary("trusted_service_validated", authority, confidence, false) + ("validator_set_deterministic", "committed_by_validator_set") => { + trusted_validator_summary("validator_set_validated", authority, confidence, false) } ("cryptographic_proof_verified", "committed_by_cryptographic_proof") => { - trusted_worker_summary("cryptographic_proof_verified", authority, confidence, true) + trusted_validator_summary("cryptographic_proof_verified", authority, confidence, true) } - ("trusted_service_and_proof_verified", "committed_by_trusted_service_and_proof") => { - trusted_worker_summary( - "trusted_service_and_proof_verified", + ("validator_set_and_proof_verified", "committed_by_validator_set_and_proof") => { + trusted_validator_summary( + "validator_set_and_proof_verified", authority, confidence, true, ) } - (_, "invalid") => invalid_worker_evidence_summary(), + (_, "invalid") => invalid_validator_evidence_summary(), _ => mismatched_trust_metadata_summary(), } } fn verified_sp1_trust_summary( - worker_evidence: &ValidationReceiptWorkerEvidenceSelection, + validator_evidence: &ValidationReceiptValidatorEvidenceSelection, ) -> ValidationReceiptTrustSummary { - if let Some(evidence) = worker_evidence.trusted.as_ref() - && evidence.validation_authority.as_deref() == Some("trusted_service_and_proof_verified") - && evidence.commitment_confidence.as_deref() - == Some("committed_by_trusted_service_and_proof") + if let Some(evidence) = validator_evidence.trusted.as_ref() + && evidence.validation_authority.as_deref() == Some("validator_set_and_proof_verified") + && evidence.commitment_confidence.as_deref() == Some("committed_by_validator_set_and_proof") { - return trusted_worker_summary( - "trusted_service_and_proof_verified", - "trusted_service_and_proof_verified", - "committed_by_trusted_service_and_proof", + return trusted_validator_summary( + "validator_set_and_proof_verified", + "validator_set_and_proof_verified", + "committed_by_validator_set_and_proof", true, ); } - trusted_worker_summary( + trusted_validator_summary( "sp1_inline_proof_verified", "cryptographic_proof_verified", "committed_by_cryptographic_proof", @@ -893,12 +891,12 @@ fn verified_sp1_trust_summary( fn proof_verification_view_for_receipt( receipt: &RadrootsTradeValidationReceipt, - worker_evidence: ValidationReceiptWorkerEvidenceSelection, + validator_evidence: ValidationReceiptValidatorEvidenceSelection, ) -> ValidationReceiptProofVerificationView { let proof = &receipt.proof; let cryptographic_proof_required = proof.system != RadrootsValidationReceiptProofSystem::None; if proof.system == RadrootsValidationReceiptProofSystem::None { - let trust = none_proof_trust_summary(&worker_evidence); + let trust = none_proof_trust_summary(&validator_evidence); return ValidationReceiptProofVerificationView { state: trust.state.to_owned(), verifier: "radroots_cli_validation_receipt_v1".to_owned(), @@ -915,8 +913,8 @@ fn proof_verification_view_for_receipt( verifying_key_hash: proof.verifying_key_hash.clone(), proof_reference: proof.proof_reference.clone(), inline_proof_present: proof.inline_proof_base64.is_some(), - worker_evidence: worker_evidence.trusted, - untrusted_worker_evidence: worker_evidence.untrusted, + validator_evidence: validator_evidence.trusted, + untrusted_validator_evidence: validator_evidence.untrusted, reason_code: trust.reason_code.map(str::to_owned), reason: trust.reason.map(str::to_owned), }; @@ -924,7 +922,7 @@ fn proof_verification_view_for_receipt( if proof.proof_reference.is_some() { return sp1_unverified_proof_view( receipt, - worker_evidence, + validator_evidence, "sp1_reference_unresolved", "unverified", "reference_unresolved", @@ -935,7 +933,7 @@ fn proof_verification_view_for_receipt( if proof.inline_proof_base64.is_none() { return sp1_unverified_proof_view( receipt, - worker_evidence, + validator_evidence, "sp1_proof_material_missing", "unverified", "missing_proof_material", @@ -946,7 +944,7 @@ fn proof_verification_view_for_receipt( if proof.system != RadrootsValidationReceiptProofSystem::Sp1Core { return sp1_unverified_proof_view( receipt, - worker_evidence, + validator_evidence, "sp1_metadata_consistent_unverified", "unverified", "metadata_consistent_unverified", @@ -957,7 +955,7 @@ fn proof_verification_view_for_receipt( match verify_inline_sp1_receipt(receipt) { Ok(()) => { - let trust = verified_sp1_trust_summary(&worker_evidence); + let trust = verified_sp1_trust_summary(&validator_evidence); ValidationReceiptProofVerificationView { state: trust.state.to_owned(), verifier: "radroots_cli_validation_receipt_v1".to_owned(), @@ -974,8 +972,8 @@ fn proof_verification_view_for_receipt( verifying_key_hash: proof.verifying_key_hash.clone(), proof_reference: proof.proof_reference.clone(), inline_proof_present: proof.inline_proof_base64.is_some(), - worker_evidence: worker_evidence.trusted, - untrusted_worker_evidence: worker_evidence.untrusted, + validator_evidence: validator_evidence.trusted, + untrusted_validator_evidence: validator_evidence.untrusted, reason_code: trust.reason_code.map(str::to_owned), reason: trust.reason.map(str::to_owned), } @@ -985,7 +983,7 @@ fn proof_verification_view_for_receipt( let reason = error.to_string(); sp1_unverified_proof_view( receipt, - worker_evidence, + validator_evidence, mapped.state, mapped.public_values_hash_binding, mapped.proof_metadata_binding, @@ -998,7 +996,7 @@ fn proof_verification_view_for_receipt( fn sp1_unverified_proof_view( receipt: &RadrootsTradeValidationReceipt, - worker_evidence: ValidationReceiptWorkerEvidenceSelection, + validator_evidence: ValidationReceiptValidatorEvidenceSelection, state: &str, public_values_hash_binding: &str, proof_metadata_binding: &str, @@ -1022,8 +1020,8 @@ fn sp1_unverified_proof_view( verifying_key_hash: proof.verifying_key_hash.clone(), proof_reference: proof.proof_reference.clone(), inline_proof_present: proof.inline_proof_base64.is_some(), - worker_evidence: worker_evidence.trusted, - untrusted_worker_evidence: worker_evidence.untrusted, + validator_evidence: validator_evidence.trusted, + untrusted_validator_evidence: validator_evidence.untrusted, reason_code: reason_code.map(str::to_owned), reason: reason.map(str::to_owned), } @@ -1158,17 +1156,17 @@ fn proof_state_is_invalid(state: &str) -> bool { | "sp1_program_hash_mismatch" | "sp1_verifying_key_hash_mismatch" | "sp1_proof_invalid" - | "validation_receipt_worker_evidence_invalid" - | "worker_evidence_trust_metadata_mismatch" + | "validation_receipt_validator_evidence_invalid" + | "validator_evidence_trust_metadata_mismatch" ) } fn proof_state_is_verification_success(state: &str) -> bool { matches!( state, - "trusted_service_validated" + "validator_set_validated" | "cryptographic_proof_verified" - | "trusted_service_and_proof_verified" + | "validator_set_and_proof_verified" | "sp1_inline_proof_verified" ) } @@ -1256,7 +1254,7 @@ fn receipt_result_label(value: RadrootsValidationReceiptResult) -> &'static str #[cfg(test)] mod tests { use super::{ - ValidationReceiptWorkerEvidenceSelection, ValidationReceiptWorkerEvidenceView, + ValidationReceiptValidatorEvidenceSelection, ValidationReceiptValidatorEvidenceView, proof_state_from_sp1_error, proof_state_is_invalid, proof_state_is_verification_success, proof_verification_view_for_receipt, validation_receipt_invalid_reason_code, }; @@ -1324,12 +1322,12 @@ mod tests { }) } - fn worker_evidence( + fn validator_evidence( validation_authority: Option<&str>, commitment_confidence: Option<&str>, sp1_execute_checked: bool, - ) -> ValidationReceiptWorkerEvidenceView { - ValidationReceiptWorkerEvidenceView { + ) -> ValidationReceiptValidatorEvidenceView { + ValidationReceiptValidatorEvidenceView { result_event_id: "result-1".to_owned(), author: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned(), validation_authority: validation_authority.map(str::to_owned), @@ -1353,15 +1351,18 @@ mod tests { fn none_receipts_report_local_only_without_crypto_claim() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); - assert_eq!(view.state, "local_only_deterministic_receipt"); + assert_eq!(view.state, "validator_set_deterministic_receipt"); assert_eq!( view.validation_authority.as_deref(), - Some("dev_deterministic_only") + Some("validator_set_deterministic") + ); + assert_eq!( + view.commitment_confidence.as_deref(), + Some("committed_by_validator_set") ); - assert_eq!(view.commitment_confidence.as_deref(), Some("local_only")); assert!(!view.production_verification); assert!(!view.cryptographic_proof_required); assert!(!view.cryptographic_proof_verified); @@ -1371,8 +1372,8 @@ mod tests { fn none_receipts_surface_advisory_sp1_execute_evidence() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), - ValidationReceiptWorkerEvidenceSelection { - trusted: Some(worker_evidence(None, None, true)), + ValidationReceiptValidatorEvidenceSelection { + trusted: Some(validator_evidence(None, None, true)), untrusted: None, }, ); @@ -1388,39 +1389,39 @@ mod tests { } #[test] - fn none_receipts_surface_trusted_service_confidence_without_production_verification() { + fn none_receipts_surface_validator_set_confidence_without_production_verification() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), - ValidationReceiptWorkerEvidenceSelection { - trusted: Some(worker_evidence( - Some("trusted_rhi_service_key"), - Some("committed_by_trusted_service"), + ValidationReceiptValidatorEvidenceSelection { + trusted: Some(validator_evidence( + Some("validator_set_deterministic"), + Some("committed_by_validator_set"), false, )), untrusted: None, }, ); - assert_eq!(view.state, "trusted_service_validated"); + assert_eq!(view.state, "validator_set_validated"); assert_eq!( view.validation_authority.as_deref(), - Some("trusted_rhi_service_key") + Some("validator_set_deterministic") ); assert_eq!( view.commitment_confidence.as_deref(), - Some("committed_by_trusted_service") + Some("committed_by_validator_set") ); assert!(!view.production_verification); assert!(proof_state_is_verification_success(view.state.as_str())); } #[test] - fn invalid_worker_evidence_marks_receipt_invalid() { + fn invalid_validator_evidence_marks_receipt_invalid() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), - ValidationReceiptWorkerEvidenceSelection { - trusted: Some(worker_evidence( - Some("trusted_rhi_service_key"), + ValidationReceiptValidatorEvidenceSelection { + trusted: Some(validator_evidence( + Some("validator_set_deterministic"), Some("invalid"), false, )), @@ -1428,45 +1429,45 @@ mod tests { }, ); - assert_eq!(view.state, "validation_receipt_worker_evidence_invalid"); + assert_eq!(view.state, "validation_receipt_validator_evidence_invalid"); assert!(proof_state_is_invalid(view.state.as_str())); assert!(!view.production_verification); } #[test] - fn untrusted_worker_evidence_does_not_upgrade_deterministic_receipts() { + fn untrusted_validator_evidence_does_not_upgrade_deterministic_receipts() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), - ValidationReceiptWorkerEvidenceSelection { + ValidationReceiptValidatorEvidenceSelection { trusted: None, - untrusted: Some(worker_evidence( - Some("trusted_rhi_service_key"), - Some("committed_by_trusted_service"), + untrusted: Some(validator_evidence( + Some("validator_set_deterministic"), + Some("committed_by_validator_set"), true, )), }, ); - assert_eq!(view.state, "local_only_deterministic_receipt"); + assert_eq!(view.state, "validator_set_deterministic_receipt"); assert!(!view.production_verification); - assert!(view.worker_evidence.is_none()); - assert!(view.untrusted_worker_evidence.is_some()); + assert!(view.validator_evidence.is_none()); + assert!(view.untrusted_validator_evidence.is_some()); } #[test] fn validation_success_labels_exclude_local_only_and_sp1_execute_checked() { assert!(!proof_state_is_verification_success( - "local_only_deterministic_receipt" + "validator_set_deterministic_receipt" )); assert!(!proof_state_is_verification_success("sp1_execute_checked")); assert!(proof_state_is_verification_success( - "trusted_service_validated" + "validator_set_validated" )); assert!(proof_state_is_verification_success( "sp1_inline_proof_verified" )); assert!(proof_state_is_verification_success( - "trusted_service_and_proof_verified" + "validator_set_and_proof_verified" )); } @@ -1478,7 +1479,7 @@ mod tests { let view = proof_verification_view_for_receipt( &receipt, - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); assert_eq!(view.state, "sp1_reference_unresolved"); @@ -1492,7 +1493,7 @@ mod tests { fn invalid_inline_sp1_material_reports_invalid_proof_state() { let view = proof_verification_view_for_receipt( &receipt_with_proof(sp1_proof_with_material()), - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); assert_eq!(view.state, "sp1_proof_invalid"); @@ -1506,7 +1507,7 @@ mod tests { fn inline_sp1_material_reports_unavailable_verifier_without_sp1_verify_feature() { let view = proof_verification_view_for_receipt( &receipt_with_proof(sp1_proof_with_material()), - ValidationReceiptWorkerEvidenceSelection::default(), + ValidationReceiptValidatorEvidenceSelection::default(), ); assert_eq!(view.state, "sp1_verifier_unavailable"); diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -1981,159 +1981,6 @@ impl OrderCancellationView { } #[derive(Debug, Clone, Serialize)] -pub struct OrderRevisionProposalView { - pub state: String, - pub source: String, - #[serde(rename = "trade_id")] - pub order_id: String, - pub locator: OrderTradeLocatorView, - #[serde(skip_serializing_if = "Option::is_none")] - pub revision_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub listing_addr: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub buyer_pubkey: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub seller_pubkey: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub request_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub decision_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub root_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub prev_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub event_kind: Option<u32>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub items: Vec<OrderDraftItemView>, - #[serde(skip_serializing_if = "Option::is_none")] - pub economics: Option<RadrootsOrderEconomics>, - #[serde(skip_serializing_if = "Option::is_none")] - pub inventory: Option<OrderInventoryView>, - #[serde(default)] - pub dry_run: bool, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub target_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub attempted_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub accepted_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub failed_transport_targets: Vec<TransportTargetFailureView>, - #[serde(default)] - pub fetched_count: usize, - #[serde(default)] - pub decoded_count: usize, - #[serde(default)] - pub skipped_count: usize, - #[serde(skip_serializing_if = "Option::is_none")] - pub idempotency_key: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub signer_mode: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub issues: Vec<OrderIssueView>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub actions: Vec<String>, -} - -impl OrderRevisionProposalView { - pub fn disposition(&self) -> CommandDisposition { - match self.state.as_str() { - "missing" => CommandDisposition::NotFound, - "invalid" | "requested" | "declined" | "order_declined" | "fulfilled" | "terminal" - | "forked" => CommandDisposition::ValidationFailed, - "unconfigured" => CommandDisposition::Unconfigured, - "unavailable" => CommandDisposition::ExternalUnavailable, - "error" => CommandDisposition::InternalError, - _ => CommandDisposition::Success, - } - } -} - -#[derive(Debug, Clone, Serialize)] -pub struct OrderRevisionDecisionView { - pub state: String, - pub source: String, - #[serde(rename = "trade_id")] - pub order_id: String, - pub locator: OrderTradeLocatorView, - #[serde(skip_serializing_if = "Option::is_none")] - pub revision_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub decision: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub listing_addr: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub buyer_pubkey: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub seller_pubkey: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub request_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub decision_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub agreement_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub root_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub prev_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub event_kind: Option<u32>, - #[serde(skip_serializing_if = "Option::is_none")] - pub economics: Option<RadrootsOrderEconomics>, - #[serde(skip_serializing_if = "Option::is_none")] - pub inventory: Option<OrderInventoryView>, - #[serde(default)] - pub dry_run: bool, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub target_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub attempted_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub accepted_transport_endpoints: Vec<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub failed_transport_targets: Vec<TransportTargetFailureView>, - #[serde(default)] - pub fetched_count: usize, - #[serde(default)] - pub decoded_count: usize, - #[serde(default)] - pub skipped_count: usize, - #[serde(skip_serializing_if = "Option::is_none")] - pub idempotency_key: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub signer_mode: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option<String>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub issues: Vec<OrderIssueView>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub actions: Vec<String>, -} - -impl OrderRevisionDecisionView { - pub fn disposition(&self) -> CommandDisposition { - match self.state.as_str() { - "missing" => CommandDisposition::NotFound, - "invalid" | "requested" | "order_declined" | "fulfilled" | "terminal" | "forked" => { - CommandDisposition::ValidationFailed - } - "unconfigured" => CommandDisposition::Unconfigured, - "unavailable" => CommandDisposition::ExternalUnavailable, - "error" => CommandDisposition::InternalError, - _ => CommandDisposition::Success, - } - } -} - -#[derive(Debug, Clone, Serialize)] pub struct OrderStatusView { pub state: String, pub source: String, @@ -2160,8 +2007,6 @@ pub struct OrderStatusView { #[serde(skip_serializing_if = "Option::is_none")] pub last_event_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] - pub revision: Option<OrderStatusRevisionView>, - #[serde(skip_serializing_if = "Option::is_none")] pub inventory: Option<OrderInventoryView>, pub lifecycle: Option<OrderStatusLifecycleView>, #[serde(skip_serializing_if = "Option::is_none")] @@ -2200,8 +2045,11 @@ pub struct OrderStatusSdkReceiptView { #[derive(Debug, Clone, Serialize)] pub struct OrderStatusValidationTrustView { pub state: String, - pub trusted_rhi_pubkey_count: usize, - pub allow_deterministic_none: bool, + pub validator_count: usize, + #[serde(skip_serializing_if = "Option::is_none")] + pub validator_set_addr: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub validator_set_event_id: Option<String>, pub require_cryptographic_proof: bool, #[serde(skip_serializing_if = "Option::is_none")] pub receipt_event_id: Option<String>, @@ -2234,7 +2082,6 @@ pub struct OrderStatusEvidenceSummaryView { pub has_decision: bool, pub has_agreement: bool, pub has_validation_receipt: bool, - pub has_pending_revision: bool, pub has_cancellation: bool, pub has_issues: bool, } @@ -2242,31 +2089,10 @@ pub struct OrderStatusEvidenceSummaryView { #[derive(Debug, Clone, Serialize)] pub struct OrderStatusEligibilityView { pub can_decide: bool, - pub can_propose_revision: bool, - pub can_decide_revision: bool, pub can_cancel: bool, } #[derive(Debug, Clone, Serialize)] -pub struct OrderStatusRevisionView { - pub state: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub revision_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub proposal_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub decision_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub root_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub prev_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub agreement_event_id: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option<String>, -} - -#[derive(Debug, Clone, Serialize)] pub struct OrderStatusLifecycleView { pub phase: String, #[serde(default)]