cli

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

commit 6246a9ad2e8b5c62195a288eeeb89a6a91a0ea96
parent 705eecaabaa0a83e93de9569e3e1ba789792f424
Author: triesap <tyson@radroots.org>
Date:   Sat,  4 Jul 2026 01:48:15 +0000

trade: surface validation trust decisions

- pass configured trusted RHI keys into SDK status
- expose trust decisions in status views and errors
- render terminal validation trust confidence
- align source guards with SDK trust policy use

Diffstat:
Msrc/ops/exec/trade.rs | 1+
Msrc/out/terminal/renderers/trade.rs | 18++++++++++++++++++
Msrc/runtime/order.rs | 22++++++++++++++--------
Msrc/runtime/order/sdk_status.rs | 36++++++++++++++++++++++++++++++++++--
Msrc/runtime/sdk.rs | 12+++++++++++-
Msrc/runtime/validation_receipt.rs | 8++++----
Msrc/view/runtime.rs | 32++++++++++++++++++++++++++++++++
7 files changed, 114 insertions(+), 15 deletions(-)

diff --git a/src/ops/exec/trade.rs b/src/ops/exec/trade.rs @@ -908,6 +908,7 @@ fn order_status_error_detail(view: &OrderStatusView) -> Value { "revision": &view.revision, "inventory": &view.inventory, "lifecycle": &view.lifecycle, + "sdk_receipt": &view.sdk_receipt, "reducer_issues": &view.reducer_issues, "target_relays": &view.target_relays, "connected_relays": &view.connected_relays, diff --git a/src/out/terminal/renderers/trade.rs b/src/out/terminal/renderers/trade.rs @@ -320,6 +320,24 @@ fn trade_status_document(envelope: &OutputEnvelope, result: &Value) -> TerminalD result, &["lifecycle", "terminal"], ); + common::push_path_field( + &mut document, + "Validation trust", + result, + &["sdk_receipt", "validation_trust", "state"], + ); + common::push_bool_field( + &mut document, + "Production committed", + result, + &["sdk_receipt", "validation_trust", "production_committed"], + ); + common::push_path_field( + &mut document, + "Trust reason", + 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); diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -1295,7 +1295,9 @@ pub fn status( config: &RuntimeConfig, args: &TradeStatusArgs, ) -> Result<OrderStatusView, CliSdkAdapterError> { - let request = TradeStatusRequest::parse(args.key.as_str())?; + let request = TradeStatusRequest::parse(args.key.as_str())?.try_with_trusted_rhi_pubkeys( + config.rhi.trusted_worker_pubkeys.iter().map(String::as_str), + )?; let session = CliSdkSession::connect(config)?; let receipt = session.block_on(session.sdk().trades().status(request))?; Ok(sdk_order_status_view(receipt)) @@ -1309,7 +1311,7 @@ fn decide_trade_via_sdk( let actor = sdk_trade_actor(seller, RadrootsActorRole::Seller, args.decision.command())?; let session = connect_sdk_for_trade_actor(config, seller, "trade decision")?; let locator = trade_locator_from_key(args.key.as_str())?; - let status = trade_status_for_locator(&session, locator.clone())?; + let status = trade_status_for_locator(config, &session, locator.clone())?; let status_view = sdk_order_status_view(status.clone()); let publish_mode = trade_publish_mode(config); let ack_policy = trade_ack_policy(publish_mode)?; @@ -1372,7 +1374,7 @@ fn propose_revision_via_sdk( 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(&session, locator.clone())?; + 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); @@ -1419,7 +1421,7 @@ fn decide_revision_via_sdk( 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(&session, locator.clone())?; + 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 { @@ -1484,7 +1486,7 @@ fn cancel_trade_via_sdk( let actor = sdk_trade_actor(&account, RadrootsActorRole::Buyer, "cancel")?; let session = connect_sdk_for_trade_actor(config, &account, "trade cancel")?; let locator = trade_locator_from_key(args.key.as_str())?; - let status = trade_status_for_locator(&session, locator.clone())?; + let status = trade_status_for_locator(config, &session, locator.clone())?; let status_view = sdk_order_status_view(status); let publish_mode = trade_publish_mode(config); let ack_policy = trade_ack_policy(publish_mode)?; @@ -1511,12 +1513,16 @@ fn cancel_trade_via_sdk( } fn trade_status_for_locator( + config: &RuntimeConfig, session: &CliSdkSession, locator: RadrootsTradeLocator, ) -> Result<TradeStatusReceipt, CliSdkAdapterError> { - Ok(session.block_on(session.sdk().trades().status( - TradeStatusRequest::new(locator).with_source(SdkTradeStatusSource::ResyncThenLocal), - ))?) + let request = TradeStatusRequest::new(locator) + .with_source(SdkTradeStatusSource::ResyncThenLocal) + .try_with_trusted_rhi_pubkeys( + config.rhi.trusted_worker_pubkeys.iter().map(String::as_str), + )?; + Ok(session.block_on(session.sdk().trades().status(request))?) } fn inventory_commitments_from_status( diff --git a/src/runtime/order/sdk_status.rs b/src/runtime/order/sdk_status.rs @@ -1,13 +1,13 @@ use radroots_events::ids::RadrootsEventId; use radroots_sdk::{ SdkTradeStatusIssue, TradeStatusEligibility, TradeStatusEvidenceSummary, TradeStatusKind, - TradeStatusNextActionKind, TradeStatusReceipt, + TradeStatusNextActionKind, TradeStatusReceipt, TradeValidationTrustDecision, }; use crate::view::runtime::{ OrderIssueView, OrderStatusEligibilityView, OrderStatusEvidenceSummaryView, OrderStatusLifecycleCancellationView, OrderStatusLifecycleView, OrderStatusSdkReceiptView, - OrderStatusView, OrderTradeLocatorView, + OrderStatusValidationTrustView, OrderStatusView, OrderTradeLocatorView, }; use super::{ORDER_ACTOR_CONTEXT_SDK_LOCAL, ORDER_STATUS_SDK_SOURCE}; @@ -85,6 +85,10 @@ fn sdk_order_status_receipt_view(receipt: &TradeStatusReceipt) -> OrderStatusSdk next_action: sdk_status_next_action(receipt.next_action).to_owned(), evidence: sdk_status_evidence_view(&receipt.evidence), eligibility: sdk_status_eligibility_view(&receipt.eligibility), + validation_trust: receipt + .validation_trust + .as_ref() + .map(sdk_status_validation_trust_view), } } @@ -97,12 +101,40 @@ fn sdk_status_evidence_view( has_request: evidence.has_request, has_decision: evidence.has_decision, has_agreement: evidence.has_agreement, + has_validation_receipt: evidence.has_validation_receipt, has_pending_revision: evidence.has_pending_revision, has_cancellation: evidence.has_cancellation, has_issues: evidence.has_issues, } } +fn sdk_status_validation_trust_view( + decision: &TradeValidationTrustDecision, +) -> 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, + 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), + result_event_id: decision.result_event_id.as_ref().map(ToString::to_string), + result_author: decision.result_author.as_ref().map(ToString::to_string), + proof_system: decision.proof_system.clone(), + validation_authority: decision + .validation_authority + .map(|authority| authority.as_str().to_owned()), + commitment_confidence: decision + .commitment_confidence + .map(|confidence| confidence.as_str().to_owned()), + cryptographic_proof_required: decision.cryptographic_proof_required, + cryptographic_proof_verified: decision.cryptographic_proof_verified, + production_committed: decision.production_committed, + reason_code: decision.reason_code.clone(), + reason: decision.reason.clone(), + } +} + fn sdk_status_eligibility_view(eligibility: &TradeStatusEligibility) -> OrderStatusEligibilityView { OrderStatusEligibilityView { can_decide: eligibility.can_decide, diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -931,7 +931,12 @@ mod tests { path: "src/runtime/order.rs", start: "pub fn status(\n config: &RuntimeConfig", end: "fn decide_trade_via_sdk(", - required_tokens: &["TradeStatusRequest::parse", "session.sdk().trades().status"], + required_tokens: &[ + "TradeStatusRequest::parse", + ".try_with_trusted_rhi_pubkeys(", + "config.rhi.trusted_worker_pubkeys", + "session.sdk().trades().status", + ], }, MigratedCliPathGuard { label: "order locator status helper", @@ -945,6 +950,8 @@ mod tests { ".sdk()", ".trades()", ".with_source(SdkTradeStatusSource::ResyncThenLocal)", + ".try_with_trusted_rhi_pubkeys(", + "config.rhi.trusted_worker_pubkeys", ], }, MigratedCliPathGuard { @@ -957,6 +964,9 @@ mod tests { "OrderStatusView", "OrderStatusLifecycleView", "OrderStatusSdkReceiptView", + "TradeValidationTrustDecision", + "validation_trust", + "has_validation_receipt", ], }, MigratedCliPathGuard { diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs @@ -854,7 +854,7 @@ fn none_proof_trust_summary( trusted_worker_summary("pending_rhi_validation", authority, confidence, false) } ("trusted_rhi_service_key", "committed_by_trusted_service") => { - trusted_worker_summary("trusted_service_validated", authority, confidence, true) + trusted_worker_summary("trusted_service_validated", authority, confidence, false) } ("cryptographic_proof_verified", "committed_by_cryptographic_proof") => { trusted_worker_summary("cryptographic_proof_verified", authority, confidence, true) @@ -1426,7 +1426,7 @@ mod tests { } #[test] - fn none_receipts_surface_trusted_service_confidence_as_production_verification() { + fn none_receipts_surface_trusted_service_confidence_without_production_verification() { let view = proof_verification_view_for_receipt( &deterministic_receipt(), ValidationReceiptWorkerEvidenceSelection { @@ -1448,7 +1448,7 @@ mod tests { view.commitment_confidence.as_deref(), Some("committed_by_trusted_service") ); - assert!(view.production_verification); + assert!(!view.production_verification); assert!(proof_state_is_verification_success(view.state.as_str())); } @@ -1492,7 +1492,7 @@ mod tests { } #[test] - fn production_verification_success_excludes_local_only_and_sp1_execute_checked() { + fn validation_success_labels_exclude_local_only_and_sp1_execute_checked() { assert!(!proof_state_is_verification_success( "local_only_deterministic_receipt" )); diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -2189,6 +2189,37 @@ pub struct OrderStatusSdkReceiptView { pub next_action: String, pub evidence: OrderStatusEvidenceSummaryView, pub eligibility: OrderStatusEligibilityView, + #[serde(skip_serializing_if = "Option::is_none")] + pub validation_trust: Option<OrderStatusValidationTrustView>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct OrderStatusValidationTrustView { + pub state: String, + pub trusted_rhi_pubkey_count: usize, + pub allow_deterministic_none: bool, + pub require_cryptographic_proof: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub receipt_event_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub receipt_author: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub result_event_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub result_author: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub proof_system: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub validation_authority: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub commitment_confidence: Option<String>, + pub cryptographic_proof_required: bool, + pub cryptographic_proof_verified: bool, + pub production_committed: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason_code: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option<String>, } #[derive(Debug, Clone, Serialize)] @@ -2198,6 +2229,7 @@ pub struct OrderStatusEvidenceSummaryView { pub has_request: bool, 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,