commit 9287b509039a6de36453164c750e7777a03a83a6
parent fb1dd87a07c5c8879b86614133be429fbbc2e2ae
Author: triesap <tyson@radroots.org>
Date: Fri, 26 Jun 2026 08:23:47 +0000
desktop: align SDK client workflow status
- Switch studio runtime construction to RadrootsClient and trades().
- Keep linked buyer and seller guards aligned with pending RHI workflow state.
- Seed relay-ingest listing fixture locality with deterministic public geohash data.
- Validate with full studio workspace all-feature tests.
Diffstat:
3 files changed, 82 insertions(+), 36 deletions(-)
diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs
@@ -2585,6 +2585,16 @@ impl DesktopAppRuntimeState {
reason: "seller order decision listing address is outside seller authority",
});
}
+ let lifecycle = self.resolve_order_lifecycle_evidence(&request)?;
+ if lifecycle.decision.is_some()
+ || lifecycle.status != RadrootsTradeWorkflowState::Requested
+ || lifecycle.cancellation_event_id.is_some()
+ || active_order_pending_revision_proposal(&lifecycle).is_some()
+ {
+ return Err(AppSqliteError::InvalidProjection {
+ reason: "seller order decision requires an undecided order",
+ });
+ }
let Some(order_export) =
sqlite_store.load_seller_order_decision_export(farm_id, order_id)?
else {
@@ -2869,7 +2879,12 @@ impl DesktopAppRuntimeState {
});
}
let lifecycle = self.resolve_order_lifecycle_evidence(&request)?;
- if lifecycle.decision.is_some() || lifecycle.status != RadrootsTradeWorkflowState::Requested
+ if lifecycle.decision.is_some()
+ || !matches!(
+ lifecycle.status,
+ RadrootsTradeWorkflowState::Requested
+ | RadrootsTradeWorkflowState::RevisionProposed
+ )
{
return Err(AppSqliteError::InvalidProjection {
reason: "buyer order revision requires active pre-agreement negotiation",
@@ -3003,15 +3018,13 @@ impl DesktopAppRuntimeState {
reason: "buyer order cancellation requires an open pre-agreement order",
});
}
+ if active_order_pending_revision_proposal(&lifecycle).is_some() {
+ return Err(AppSqliteError::InvalidProjection {
+ reason: "buyer order cancellation requires no pending seller proposal",
+ });
+ }
let prev_event_id = match lifecycle.status {
- RadrootsTradeWorkflowState::Requested => {
- if active_order_pending_revision_proposal(&lifecycle).is_some() {
- return Err(AppSqliteError::InvalidProjection {
- reason: "buyer order cancellation requires no pending seller proposal",
- });
- }
- request.request_event_id.clone()
- }
+ RadrootsTradeWorkflowState::Requested => request.request_event_id.clone(),
RadrootsTradeWorkflowState::RevisionProposed
| RadrootsTradeWorkflowState::AgreedPendingRhi
| RadrootsTradeWorkflowState::Committed => {
@@ -9828,6 +9841,7 @@ mod tests {
ProductsFilter, ProductsSort, ReminderDeliveryState, ReminderFeedProjection, ReminderKind,
SelectedAccountProjection, SelectedSurfaceProjection, SettingsPreference, SettingsSection,
ShellSection, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind, TodaySummary,
+ TradeAgreementStatus,
};
use serde_json::json;
use tokio::net::TcpListener;
@@ -10758,8 +10772,17 @@ mod tests {
fulfillment_location: Some("Relay barn".to_owned()),
status: ProductStatus::Published,
};
- let listing = super::listing_publish_payload_to_sdk_listing(&listing_payload)
+ let mut listing = super::listing_publish_payload_to_sdk_listing(&listing_payload)
.expect("listing payload should convert to SDK listing");
+ listing.location = Some(
+ radroots_sdk::protocol::listing::RadrootsListingPublicLocation {
+ primary: "Relay barn".to_owned(),
+ city: Some("San Francisco".to_owned()),
+ region: Some("CA".to_owned()),
+ country: Some("US".to_owned()),
+ geohash: "9q8yy".to_owned(),
+ },
+ );
let parts = radroots_sdk::protocol::listing::build_draft(&listing)
.expect("listing draft should build")
.into_wire_parts();
@@ -15380,7 +15403,11 @@ mod tests {
reason: "seller order decision requires an undecided order"
}
));
- assert_eq!(persisted_order_status(&runtime, order_id), "scheduled");
+ assert_eq!(persisted_order_status(&runtime, order_id), "needs_action");
+ assert_eq!(
+ persisted_order_workflow_agreement(&runtime, order_id),
+ "pending_rhi"
+ );
assert_eq!(relay.event_count(), 1);
cleanup_bootstrapped_runtime_paths(&paths);
@@ -16106,9 +16133,10 @@ mod tests {
.detail
.as_ref()
.expect("linked buyer order detail should exist");
- assert_eq!(row.status, BuyerOrderStatus::Scheduled);
+ assert_eq!(row.status, BuyerOrderStatus::Placed);
assert_eq!(detail.order_id, fixture.order_id);
- assert_eq!(detail.status, BuyerOrderStatus::Scheduled);
+ assert_eq!(detail.status, BuyerOrderStatus::Placed);
+ assert_eq!(detail.workflow.agreement, TradeAgreementStatus::PendingRhi);
assert_eq!(
detail.workflow.provenance.last_event_id.as_deref(),
Some(fixture.decision_event_id.as_str())
@@ -20744,6 +20772,24 @@ mod tests {
.expect("order status should load")
}
+ fn persisted_order_workflow_agreement(
+ runtime: &DesktopAppRuntime,
+ order_id: OrderId,
+ ) -> String {
+ runtime
+ .lock_state()
+ .sqlite_store
+ .as_ref()
+ .expect("sqlite store")
+ .connection()
+ .query_row(
+ "select workflow_agreement from orders where id = ?1 limit 1",
+ [order_id.to_string()],
+ |row| row.get::<_, String>(0),
+ )
+ .expect("order workflow agreement should load")
+ }
+
fn pending_order_sync_payloads(
runtime: &DesktopAppRuntime,
account_id: &str,
diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs
@@ -1126,7 +1126,7 @@ const STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS: &[SdkBoundaryForbiddenPattern] = &
},
SdkBoundaryForbiddenPattern {
pattern: "RadrootsSdkClient",
- reason: "app production sources must use the long-lived RadrootsSdk runtime boundary",
+ reason: "app production sources must use the long-lived RadrootsClient runtime boundary",
},
SdkBoundaryForbiddenPattern {
pattern: "RadrootsSdkConfig",
diff --git a/crates/runtime/src/sdk.rs b/crates/runtime/src/sdk.rs
@@ -31,7 +31,7 @@ use radroots_sdk::{
OrderDecisionEnqueueRequest, OrderDecisionReceipt, OrderEvidenceIngestRequest,
OrderRequestEvidenceIngestRequest, OrderRevisionDecisionEnqueueRequest,
OrderRevisionDecisionReceipt, OrderRevisionProposalEnqueueRequest,
- OrderRevisionProposalReceipt, OrderSubmitEnqueueRequest, OrderSubmitReceipt, RadrootsSdk,
+ OrderRevisionProposalReceipt, OrderSubmitEnqueueRequest, OrderSubmitReceipt, RadrootsClient,
RadrootsSdkError, RadrootsSdkStoragePaths, RestoreReceipt, RestoreRequest,
SdkBackupVerification, SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy, StorageStatusReceipt,
StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest,
@@ -1302,8 +1302,8 @@ fn run_degraded_worker(
);
}
-async fn build_sdk_runtime(config: &AppSdkConfig) -> Result<RadrootsSdk, RadrootsSdkError> {
- let mut builder = RadrootsSdk::builder()
+async fn build_sdk_runtime(config: &AppSdkConfig) -> Result<RadrootsClient, RadrootsSdkError> {
+ let mut builder = RadrootsClient::builder()
.directory_storage(config.storage_root.clone())
.relay_url_policy(config.relay_url_policy.into());
for relay_url in &config.relay_urls {
@@ -1330,7 +1330,7 @@ fn run_restore_preflight(
.with_overwrite(request.overwrite_existing_sdk_storage)
.dry_run();
let result = runtime
- .block_on(RadrootsSdk::restore(restore_request))
+ .block_on(RadrootsClient::restore(restore_request))
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))
.map(|receipt| {
let projection_lifecycle = mark_projections_stale(
@@ -1351,7 +1351,7 @@ fn run_restore_preflight(
}
async fn collect_sdk_diagnostics(
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
runtime: AppSdkRuntimeStatus,
) -> Result<AppSdkDiagnostics, RadrootsSdkError> {
let storage = sdk.storage_status(StorageStatusRequest::new()).await?;
@@ -1367,7 +1367,7 @@ async fn collect_sdk_diagnostics(
fn enqueue_farm_publish_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkFarmPublishRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1394,7 +1394,7 @@ fn enqueue_farm_publish_with_sdk(
fn enqueue_listing_publish_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkListingPublishRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1421,7 +1421,7 @@ fn enqueue_listing_publish_with_sdk(
fn enqueue_order_submit_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkOrderSubmitRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1440,7 +1440,7 @@ fn enqueue_order_submit_with_sdk(
}
let receipt = runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.enqueue_submit_with_explicit_signer(enqueue, &signer),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1449,7 +1449,7 @@ fn enqueue_order_submit_with_sdk(
fn enqueue_order_decision_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkOrderDecisionRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1461,7 +1461,7 @@ fn enqueue_order_decision_with_sdk(
let target_relays = sdk_relay_targets(request.target_relays, request.relay_url_policy)?;
runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.ingest_request_evidence(OrderRequestEvidenceIngestRequest::new(
request.request_event,
)),
@@ -1480,7 +1480,7 @@ fn enqueue_order_decision_with_sdk(
}
let receipt = runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.enqueue_decision_with_explicit_signer(enqueue, &signer),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1492,7 +1492,7 @@ fn enqueue_order_decision_with_sdk(
fn enqueue_order_revision_proposal_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkOrderRevisionProposalRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1517,7 +1517,7 @@ fn enqueue_order_revision_proposal_with_sdk(
}
let receipt = runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.enqueue_revision_proposal_with_explicit_signer(enqueue, &signer),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1529,7 +1529,7 @@ fn enqueue_order_revision_proposal_with_sdk(
fn enqueue_order_revision_decision_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkOrderRevisionDecisionRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1554,7 +1554,7 @@ fn enqueue_order_revision_decision_with_sdk(
}
let receipt = runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.enqueue_revision_decision_with_explicit_signer(enqueue, &signer),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1566,7 +1566,7 @@ fn enqueue_order_revision_decision_with_sdk(
fn enqueue_order_cancellation_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
request: AppSdkOrderCancellationRequest,
) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> {
let actor = sdk_actor_context(
@@ -1591,7 +1591,7 @@ fn enqueue_order_cancellation_with_sdk(
}
let receipt = runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.enqueue_cancellation_with_explicit_signer(enqueue, &signer),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1603,13 +1603,13 @@ fn enqueue_order_cancellation_with_sdk(
fn ingest_order_evidence_with_sdk(
runtime: &tokio::runtime::Runtime,
- sdk: &RadrootsSdk,
+ sdk: &RadrootsClient,
evidence_events: Vec<RadrootsNostrEvent>,
) -> Result<(), AppSdkRuntimeIssue> {
for event in evidence_events {
runtime
.block_on(
- sdk.orders()
+ sdk.trades()
.ingest_evidence(OrderEvidenceIngestRequest::new(event)),
)
.map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?;
@@ -1911,7 +1911,7 @@ mod tests {
};
use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrSecretKey};
use radroots_sdk::{
- BackupRequest, LISTING_PUBLISH_OPERATION_KIND, RadrootsSdk,
+ BackupRequest, LISTING_PUBLISH_OPERATION_KIND, RadrootsClient,
SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy,
};
@@ -2172,7 +2172,7 @@ mod tests {
.expect("tokio runtime");
let sdk = tokio
.block_on(
- RadrootsSdk::builder()
+ RadrootsClient::builder()
.directory_storage(backup_source_root.clone())
.relay_url_policy(SdkRuntimeRelayUrlPolicy::Localhost)
.relay_url("ws://127.0.0.1:8080")