commit aadecc93d40c200e594190ed9c5863df811b5f67
parent 9287b509039a6de36453164c750e7777a03a83a6
Author: triesap <tyson@radroots.org>
Date: Fri, 26 Jun 2026 13:48:12 +0000
runtime: refactor trade workflow projection
Diffstat:
20 files changed, 796 insertions(+), 409 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -5339,6 +5339,7 @@ dependencies = [
"radroots_outbox",
"radroots_relay_transport",
"radroots_runtime_paths",
+ "radroots_sp1_guest_trade",
"radroots_trade",
"serde",
"serde_json",
@@ -5353,6 +5354,16 @@ name = "radroots_secret_vault"
version = "0.1.0-alpha.2"
[[package]]
+name = "radroots_sp1_guest_trade"
+version = "0.1.0-alpha.2"
+dependencies = [
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 1.0.69",
+]
+
+[[package]]
name = "radroots_sql_core"
version = "0.1.0-alpha.2"
dependencies = [
@@ -5531,6 +5542,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
+ "sqlx",
"thiserror 1.0.69",
]
diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs
@@ -39,10 +39,11 @@ use radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager;
use radroots_sdk::protocol::events::{
RadrootsNostrEvent as SdkRadrootsNostrEvent, RadrootsNostrEventPtr,
};
-use radroots_sdk::protocol::farm::{RadrootsFarm, RadrootsFarmRef};
+use radroots_sdk::protocol::farm::{RadrootsFarm, RadrootsFarmPublicLocation, RadrootsFarmRef};
use radroots_sdk::protocol::listing::{
RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
- RadrootsListingDeliveryMethod, RadrootsListingProduct, RadrootsListingStatus,
+ RadrootsListingDeliveryMethod, RadrootsListingProduct, RadrootsListingPublicLocation,
+ RadrootsListingStatus,
};
use radroots_sdk::protocol::order::{
RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
@@ -59,9 +60,10 @@ use radroots_sql_core::SqliteExecutor;
use radroots_studio_app_core::{
AppBuildIdentity, AppDesktopRuntimePaths, AppRuntimeCapture, AppRuntimeMode,
AppRuntimePathsError, AppRuntimeSnapshot, AppSdkConfig, AppSdkDiagnostics,
- AppSdkFarmPublishRequest, AppSdkLifecycleState, AppSdkListingPublishRequest,
- AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, AppSdkOrderRevisionDecisionRequest,
- AppSdkOrderRevisionProposalRequest, AppSdkOrderSubmitRequest, AppSdkProjectionLifecycleState,
+ AppSdkFarmPublicLocationRequest, AppSdkFarmPublishRequest, AppSdkLifecycleState,
+ AppSdkListingPublishRequest, AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest,
+ AppSdkOrderRevisionDecisionRequest, AppSdkOrderRevisionProposalRequest,
+ AppSdkOrderSubmitRequest, AppSdkProjectionLifecycleState, AppSdkPublicFarmLocation,
AppSdkRelayUrlPolicy, AppSdkRuntime, AppSdkRuntimeError, AppSdkRuntimeIssue,
AppSdkRuntimeStatus, AppSdkStoragePaths, AppSdkWorkflowReceipt, AppSharedAccountsPaths,
PackDayExportWriteError, prepare_pack_day_export_bundle_at_data_root,
@@ -4688,11 +4690,16 @@ impl DesktopAppRuntimeState {
))
.and_then(|identity| {
let actor_pubkey = identity.public_key_hex();
+ let public_location =
+ self.sdk_public_farm_location(actor_pubkey.as_str(), payload.farm_id)?;
let request = AppSdkFarmPublishRequest {
actor_account_id: payload.context.account_id.clone(),
actor_pubkey: actor_pubkey.clone(),
signer_keys: identity.into_keys(),
- farm: farm_profile_publish_payload_to_sdk_farm(payload),
+ farm: farm_profile_publish_payload_to_sdk_farm(
+ payload,
+ public_location.as_ref(),
+ ),
target_relays: normalized_app_sync_relay_urls(&self.nostr_relay_urls)?,
relay_url_policy: sdk_relay_url_policy_for_targets(&self.nostr_relay_urls),
idempotency_key: Some(sdk_idempotency_key(source_record_id)),
@@ -4732,11 +4739,20 @@ impl DesktopAppRuntimeState {
))
.and_then(|identity| {
let actor_pubkey = identity.public_key_hex();
+ let public_location = match payload.farm_id {
+ Some(farm_id) => {
+ self.sdk_public_farm_location(actor_pubkey.as_str(), farm_id)?
+ }
+ None => None,
+ };
let request = AppSdkListingPublishRequest {
actor_account_id: payload.context.account_id.clone(),
actor_pubkey: actor_pubkey.clone(),
signer_keys: identity.into_keys(),
- listing: listing_publish_payload_to_sdk_listing(payload)?,
+ listing: listing_publish_payload_to_sdk_listing(
+ payload,
+ public_location.as_ref(),
+ )?,
target_relays: normalized_app_sync_relay_urls(&self.nostr_relay_urls)?,
relay_url_policy: sdk_relay_url_policy_for_targets(&self.nostr_relay_urls),
idempotency_key: Some(sdk_idempotency_key(source_record_id)),
@@ -5052,6 +5068,19 @@ impl DesktopAppRuntimeState {
signing_identity_for_publish_payload(accounts_manager, payload)
}
+ fn sdk_public_farm_location(
+ &self,
+ actor_pubkey: &str,
+ farm_id: FarmId,
+ ) -> Result<Option<AppSdkPublicFarmLocation>, AppSyncTransportError> {
+ let request = AppSdkFarmPublicLocationRequest {
+ actor_pubkey: actor_pubkey.to_owned(),
+ farm_d_tag: d_tag_from_uuid(farm_id.as_uuid()),
+ };
+ self.with_app_sdk_runtime(|runtime| runtime.farm_public_location(request))
+ .map_err(sync_transport_error_from_sdk_runtime_error)
+ }
+
fn enqueue_app_sdk_farm_publish(
&self,
request: AppSdkFarmPublishRequest,
@@ -7314,6 +7343,7 @@ fn listing_fulfillment_location(
fn farm_profile_publish_payload_to_sdk_farm(
payload: &AppFarmProfilePublishPayload,
+ public_location: Option<&AppSdkPublicFarmLocation>,
) -> RadrootsFarm {
RadrootsFarm {
d_tag: d_tag_from_uuid(payload.farm_id.as_uuid()),
@@ -7322,7 +7352,7 @@ fn farm_profile_publish_payload_to_sdk_farm(
website: None,
picture: None,
banner: None,
- location: None,
+ location: public_location.map(public_farm_location_to_protocol),
tags: payload.readiness.map(|readiness| match readiness {
FarmReadiness::Incomplete => vec!["radroots:readiness:incomplete".to_owned()],
FarmReadiness::Ready => vec!["radroots:readiness:ready".to_owned()],
@@ -7513,6 +7543,7 @@ fn sync_transport_error_detail_json(error: &AppSyncTransportError) -> serde_json
fn listing_publish_payload_to_sdk_listing(
payload: &AppListingPublishPayload,
+ public_location: Option<&AppSdkPublicFarmLocation>,
) -> Result<RadrootsListing, AppSyncTransportError> {
let currency = payload
.price_currency
@@ -7598,12 +7629,36 @@ fn listing_publish_payload_to_sdk_listing(
delivery_method: Some(parse_app_listing_delivery_method(
payload.fulfillment_method.as_deref().unwrap_or_default(),
)?),
- location: None,
+ location: public_location.map(public_listing_location_to_protocol),
published_at: None,
images: None,
})
}
+fn public_farm_location_to_protocol(
+ location: &AppSdkPublicFarmLocation,
+) -> RadrootsFarmPublicLocation {
+ RadrootsFarmPublicLocation {
+ primary: location.primary.clone(),
+ city: location.city.clone(),
+ region: location.region.clone(),
+ country: location.country.clone(),
+ geohash: location.geohash5.clone(),
+ }
+}
+
+fn public_listing_location_to_protocol(
+ location: &AppSdkPublicFarmLocation,
+) -> RadrootsListingPublicLocation {
+ RadrootsListingPublicLocation {
+ primary: location.primary.clone(),
+ city: location.city.clone(),
+ region: location.region.clone(),
+ country: location.country.clone(),
+ geohash: location.geohash5.clone(),
+ }
+}
+
fn listing_publish_payload_availability(
payload: &AppListingPublishPayload,
) -> Result<Option<RadrootsListingAvailability>, AppSyncTransportError> {
@@ -9796,8 +9851,8 @@ mod tests {
use radroots_sql_core::{SqlExecutor, SqliteExecutor};
use radroots_studio_app_core::{
AppDesktopRuntimePaths, AppRuntimeHostEnvironment, AppRuntimePlatform,
- AppSdkLifecycleState, AppSdkProjectionLifecycleState, AppSharedAccountsPaths,
- SHARED_ACCOUNTS_STORE_FILE_NAME, SHARED_IDENTITY_FILE_NAME,
+ AppSdkLifecycleState, AppSdkProjectionLifecycleState, AppSdkPublicFarmLocation,
+ AppSharedAccountsPaths, SHARED_ACCOUNTS_STORE_FILE_NAME, SHARED_IDENTITY_FILE_NAME,
};
use radroots_studio_app_remote_signer::{
RadrootsAppRemoteSignerPendingSession, RadrootsAppRemoteSignerSessionRecord,
@@ -10772,17 +10827,16 @@ mod tests {
fulfillment_location: Some("Relay barn".to_owned()),
status: ProductStatus::Published,
};
- 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 public_location = AppSdkPublicFarmLocation {
+ primary: "Relay barn".to_owned(),
+ city: Some("San Francisco".to_owned()),
+ region: Some("CA".to_owned()),
+ country: Some("US".to_owned()),
+ geohash5: "9q8yy".to_owned(),
+ };
+ let listing =
+ super::listing_publish_payload_to_sdk_listing(&listing_payload, Some(&public_location))
+ .expect("listing payload should convert to SDK listing");
let parts = radroots_sdk::protocol::listing::build_draft(&listing)
.expect("listing draft should build")
.into_wire_parts();
@@ -15406,7 +15460,7 @@ mod tests {
assert_eq!(persisted_order_status(&runtime, order_id), "needs_action");
assert_eq!(
persisted_order_workflow_agreement(&runtime, order_id),
- "pending_rhi"
+ "agreed_pending_rhi"
);
assert_eq!(relay.event_count(), 1);
@@ -16136,7 +16190,10 @@ mod tests {
assert_eq!(row.status, BuyerOrderStatus::Placed);
assert_eq!(detail.order_id, fixture.order_id);
assert_eq!(detail.status, BuyerOrderStatus::Placed);
- assert_eq!(detail.workflow.agreement, TradeAgreementStatus::PendingRhi);
+ assert_eq!(
+ detail.workflow.agreement,
+ TradeAgreementStatus::AgreedPendingRhi
+ );
assert_eq!(
detail.workflow.provenance.last_event_id.as_deref(),
Some(fixture.decision_event_id.as_str())
diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs
@@ -712,12 +712,13 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[
"AppTextKey::TradeWorkflowAxisRevision",
"AppTextKey::TradeWorkflowAxisInventory",
"AppTextKey::TradeWorkflowAxisSource",
- "AppTextKey::TradeWorkflowAgreementOrdered",
- "AppTextKey::TradeWorkflowAgreementPendingRhi",
- "AppTextKey::TradeWorkflowAgreementConfirmed",
+ "AppTextKey::TradeWorkflowAgreementRequested",
+ "AppTextKey::TradeWorkflowAgreementRevisionProposed",
+ "AppTextKey::TradeWorkflowAgreementAgreedPendingRhi",
+ "AppTextKey::TradeWorkflowAgreementCommitted",
"AppTextKey::TradeWorkflowAgreementDeclined",
"AppTextKey::TradeWorkflowAgreementCancelled",
- "AppTextKey::TradeWorkflowAgreementNeedsReview",
+ "AppTextKey::TradeWorkflowAgreementInvalid",
"AppTextKey::TradeWorkflowRevisionNone",
"AppTextKey::TradeWorkflowRevisionChangeProposed",
"AppTextKey::TradeWorkflowRevisionUpdated",
diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs
@@ -11615,12 +11615,17 @@ fn trade_workflow_value_badge(value_key: AppTextKey) -> AnyElement {
fn trade_agreement_status_key(status: TradeAgreementStatus) -> AppTextKey {
match status {
- TradeAgreementStatus::Ordered => AppTextKey::TradeWorkflowAgreementOrdered,
- TradeAgreementStatus::PendingRhi => AppTextKey::TradeWorkflowAgreementPendingRhi,
- TradeAgreementStatus::Confirmed => AppTextKey::TradeWorkflowAgreementConfirmed,
+ TradeAgreementStatus::Requested => AppTextKey::TradeWorkflowAgreementRequested,
+ TradeAgreementStatus::RevisionProposed => {
+ AppTextKey::TradeWorkflowAgreementRevisionProposed
+ }
+ TradeAgreementStatus::AgreedPendingRhi => {
+ AppTextKey::TradeWorkflowAgreementAgreedPendingRhi
+ }
+ TradeAgreementStatus::Committed => AppTextKey::TradeWorkflowAgreementCommitted,
TradeAgreementStatus::Declined => AppTextKey::TradeWorkflowAgreementDeclined,
TradeAgreementStatus::Cancelled => AppTextKey::TradeWorkflowAgreementCancelled,
- TradeAgreementStatus::NeedsReview => AppTextKey::TradeWorkflowAgreementNeedsReview,
+ TradeAgreementStatus::Invalid => AppTextKey::TradeWorkflowAgreementInvalid,
}
}
@@ -16723,16 +16728,20 @@ mod tests {
fn trade_workflow_badge_keys_cover_refactored_status_axes() {
for (status, key) in [
(
- TradeAgreementStatus::Ordered,
- AppTextKey::TradeWorkflowAgreementOrdered,
+ TradeAgreementStatus::Requested,
+ AppTextKey::TradeWorkflowAgreementRequested,
+ ),
+ (
+ TradeAgreementStatus::RevisionProposed,
+ AppTextKey::TradeWorkflowAgreementRevisionProposed,
),
(
- TradeAgreementStatus::PendingRhi,
- AppTextKey::TradeWorkflowAgreementPendingRhi,
+ TradeAgreementStatus::AgreedPendingRhi,
+ AppTextKey::TradeWorkflowAgreementAgreedPendingRhi,
),
(
- TradeAgreementStatus::Confirmed,
- AppTextKey::TradeWorkflowAgreementConfirmed,
+ TradeAgreementStatus::Committed,
+ AppTextKey::TradeWorkflowAgreementCommitted,
),
(
TradeAgreementStatus::Declined,
@@ -16743,8 +16752,8 @@ mod tests {
AppTextKey::TradeWorkflowAgreementCancelled,
),
(
- TradeAgreementStatus::NeedsReview,
- AppTextKey::TradeWorkflowAgreementNeedsReview,
+ TradeAgreementStatus::Invalid,
+ AppTextKey::TradeWorkflowAgreementInvalid,
),
] {
assert_eq!(trade_agreement_status_key(status), key);
diff --git a/crates/i18n/src/keys.rs b/crates/i18n/src/keys.rs
@@ -431,12 +431,13 @@ define_app_text_keys! {
TradeWorkflowAxisRevision => "trade.workflow.axis.revision",
TradeWorkflowAxisInventory => "trade.workflow.axis.inventory",
TradeWorkflowAxisSource => "trade.workflow.axis.source",
- TradeWorkflowAgreementOrdered => "trade.workflow.agreement.ordered",
- TradeWorkflowAgreementPendingRhi => "trade.workflow.agreement.pending_rhi",
- TradeWorkflowAgreementConfirmed => "trade.workflow.agreement.confirmed",
+ TradeWorkflowAgreementRequested => "trade.workflow.agreement.requested",
+ TradeWorkflowAgreementRevisionProposed => "trade.workflow.agreement.revision_proposed",
+ TradeWorkflowAgreementAgreedPendingRhi => "trade.workflow.agreement.agreed_pending_rhi",
+ TradeWorkflowAgreementCommitted => "trade.workflow.agreement.committed",
TradeWorkflowAgreementDeclined => "trade.workflow.agreement.declined",
TradeWorkflowAgreementCancelled => "trade.workflow.agreement.cancelled",
- TradeWorkflowAgreementNeedsReview => "trade.workflow.agreement.needs_review",
+ TradeWorkflowAgreementInvalid => "trade.workflow.agreement.invalid",
TradeWorkflowRevisionNone => "trade.workflow.revision.none",
TradeWorkflowRevisionChangeProposed => "trade.workflow.revision.change_proposed",
TradeWorkflowRevisionUpdated => "trade.workflow.revision.updated",
diff --git a/crates/i18n/src/lib.rs b/crates/i18n/src/lib.rs
@@ -635,20 +635,24 @@ mod tests {
);
assert_eq!(app_text(AppTextKey::TradeWorkflowAxisSource), "Source");
assert_eq!(
- app_text(AppTextKey::TradeWorkflowAgreementOrdered),
- "Ordered"
+ app_text(AppTextKey::TradeWorkflowAgreementRequested),
+ "Requested"
+ );
+ assert_eq!(
+ app_text(AppTextKey::TradeWorkflowAgreementRevisionProposed),
+ "Revision proposed"
);
assert_eq!(
- app_text(AppTextKey::TradeWorkflowAgreementPendingRhi),
+ app_text(AppTextKey::TradeWorkflowAgreementAgreedPendingRhi),
"Pending validation"
);
assert_eq!(
- app_text(AppTextKey::TradeWorkflowAgreementConfirmed),
- "Confirmed"
+ app_text(AppTextKey::TradeWorkflowAgreementCommitted),
+ "Committed"
);
assert_eq!(
- app_text(AppTextKey::TradeWorkflowAgreementNeedsReview),
- "Needs review"
+ app_text(AppTextKey::TradeWorkflowAgreementInvalid),
+ "Invalid"
);
assert_eq!(
app_text(AppTextKey::TradeWorkflowRevisionChangeProposed),
diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs
@@ -37,11 +37,12 @@ pub use runtime::{
};
pub use sdk::{
APP_SDK_DEFAULT_COMMAND_QUEUE_CAPACITY, APP_SDK_STORAGE_DIR_NAME, AppSdkConfig,
- AppSdkDiagnostics, AppSdkEventStoreDiagnostics, AppSdkFarmPublishRequest,
- AppSdkIntegrityDiagnostics, AppSdkLifecycleState, AppSdkListingPublishRequest,
- AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, AppSdkOrderRevisionDecisionRequest,
- AppSdkOrderRevisionProposalRequest, AppSdkOrderSubmitRequest, AppSdkOutboxDiagnostics,
- AppSdkProjectionLifecycleState, AppSdkProjectionLifecycleStatus, AppSdkRelayUrlPolicy,
+ AppSdkDiagnostics, AppSdkEventStoreDiagnostics, AppSdkFarmPublicLocationRequest,
+ AppSdkFarmPublishRequest, AppSdkIntegrityDiagnostics, AppSdkLifecycleState,
+ AppSdkListingPublishRequest, AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest,
+ AppSdkOrderRevisionDecisionRequest, AppSdkOrderRevisionProposalRequest,
+ AppSdkOrderSubmitRequest, AppSdkOutboxDiagnostics, AppSdkProjectionLifecycleState,
+ AppSdkProjectionLifecycleStatus, AppSdkPublicFarmLocation, AppSdkRelayUrlPolicy,
AppSdkRestorePreflightReceipt, AppSdkRestorePreflightRequest, AppSdkRuntime,
AppSdkRuntimeError, AppSdkRuntimeIssue, AppSdkRuntimeStatus, AppSdkSqliteStoreDiagnostics,
AppSdkStorageDiagnostics, AppSdkStoragePaths, AppSdkSyncDiagnostics,
diff --git a/crates/runtime/src/sdk.rs b/crates/runtime/src/sdk.rs
@@ -15,6 +15,8 @@ use radroots_events::{
RadrootsNostrEvent, RadrootsNostrEventPtr,
contract::RadrootsActorRole,
farm::RadrootsFarm,
+ ids::RadrootsAddressableCoordinate,
+ kinds::KIND_FARM,
listing::RadrootsListing,
order::{
RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderRequest,
@@ -33,8 +35,8 @@ use radroots_sdk::{
OrderRevisionDecisionReceipt, OrderRevisionProposalEnqueueRequest,
OrderRevisionProposalReceipt, OrderSubmitEnqueueRequest, OrderSubmitReceipt, RadrootsClient,
RadrootsSdkError, RadrootsSdkStoragePaths, RestoreReceipt, RestoreRequest,
- SdkBackupVerification, SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy, StorageStatusReceipt,
- StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest,
+ SdkBackupVerification, SdkPublicLocality, SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy,
+ StorageStatusReceipt, StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest,
};
use radroots_sdk::{SdkMutationState, SdkRelayTargetPolicy};
use serde::Serialize;
@@ -203,6 +205,21 @@ pub struct AppSdkRestorePreflightRequest {
pub overwrite_existing_sdk_storage: bool,
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct AppSdkFarmPublicLocationRequest {
+ pub actor_pubkey: String,
+ pub farm_d_tag: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct AppSdkPublicFarmLocation {
+ pub primary: String,
+ pub city: Option<String>,
+ pub region: Option<String>,
+ pub country: Option<String>,
+ pub geohash5: String,
+}
+
pub struct AppSdkFarmPublishRequest {
pub actor_account_id: String,
pub actor_pubkey: String,
@@ -377,6 +394,10 @@ enum AppSdkWorkerCommand {
AppSdkRestorePreflightRequest,
mpsc::Sender<Result<AppSdkRestorePreflightReceipt, AppSdkRuntimeIssue>>,
),
+ FarmPublicLocation(
+ AppSdkFarmPublicLocationRequest,
+ mpsc::Sender<Result<Option<AppSdkPublicFarmLocation>, AppSdkRuntimeIssue>>,
+ ),
EnqueueFarmPublish(
AppSdkFarmPublishRequest,
mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>,
@@ -421,6 +442,7 @@ impl fmt::Debug for AppSdkWorkerCommand {
Self::SyncStatus(_) => formatter.write_str("SyncStatus"),
Self::Diagnostics(_) => formatter.write_str("Diagnostics"),
Self::RestorePreflight(_, _) => formatter.write_str("RestorePreflight"),
+ Self::FarmPublicLocation(_, _) => formatter.write_str("FarmPublicLocation"),
Self::EnqueueFarmPublish(_, _) => formatter.write_str("EnqueueFarmPublish"),
Self::EnqueueListingPublish(_, _) => formatter.write_str("EnqueueListingPublish"),
Self::EnqueueOrderSubmit(_, _) => formatter.write_str("EnqueueOrderSubmit"),
@@ -553,6 +575,15 @@ impl AppSdkRuntime {
})
}
+ pub fn farm_public_location(
+ &self,
+ request: AppSdkFarmPublicLocationRequest,
+ ) -> Result<Option<AppSdkPublicFarmLocation>, AppSdkRuntimeError> {
+ self.run_command(|response_sender| {
+ AppSdkWorkerCommand::FarmPublicLocation(request, response_sender)
+ })
+ }
+
pub fn enqueue_farm_publish(
&self,
request: AppSdkFarmPublishRequest,
@@ -790,6 +821,18 @@ impl AppSdkRuntimeIssue {
}
}
+impl From<SdkPublicLocality> for AppSdkPublicFarmLocation {
+ fn from(value: SdkPublicLocality) -> Self {
+ Self {
+ primary: value.primary,
+ city: value.city,
+ region: value.region,
+ country: value.country,
+ geohash5: value.geohash5,
+ }
+ }
+}
+
impl fmt::Display for AppSdkRuntimeIssue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}: {}", self.code, self.message)
@@ -1081,6 +1124,17 @@ fn run_app_sdk_worker(
};
send_worker_result(&shared, response_sender, result);
}
+ AppSdkWorkerCommand::FarmPublicLocation(request, response_sender) => {
+ let result = if let Some(issue) = lifecycle_busy_issue(&shared) {
+ Err(issue)
+ } else {
+ match sdk.as_ref() {
+ Some(sdk) => farm_public_location_with_sdk(&runtime, sdk, request),
+ None => Err(runtime_unavailable_issue(&shared)),
+ }
+ };
+ send_worker_result(&shared, response_sender, result);
+ }
AppSdkWorkerCommand::EnqueueFarmPublish(request, response_sender) => {
let result = if let Some(issue) = lifecycle_busy_issue(&shared) {
Err(issue)
@@ -1229,6 +1283,13 @@ fn run_degraded_worker(
Err(runtime_unavailable_issue(&shared)),
);
}
+ AppSdkWorkerCommand::FarmPublicLocation(_, response_sender) => {
+ send_worker_result(
+ &shared,
+ response_sender,
+ Err(runtime_unavailable_issue(&shared)),
+ );
+ }
AppSdkWorkerCommand::EnqueueFarmPublish(_, response_sender) => {
send_worker_result(
&shared,
@@ -1365,6 +1426,26 @@ async fn collect_sdk_diagnostics(
})
}
+fn farm_public_location_with_sdk(
+ runtime: &tokio::runtime::Runtime,
+ sdk: &RadrootsClient,
+ request: AppSdkFarmPublicLocationRequest,
+) -> Result<Option<AppSdkPublicFarmLocation>, AppSdkRuntimeIssue> {
+ let farm_addr = RadrootsAddressableCoordinate::parse(format!(
+ "{KIND_FARM}:{}:{}",
+ request.actor_pubkey, request.farm_d_tag
+ ))
+ .map_err(|error| {
+ AppSdkRuntimeIssue::from_sdk_error(&RadrootsSdkError::InvalidRequest {
+ message: format!("farm public location address is invalid: {error}"),
+ })
+ })?;
+ runtime
+ .block_on(sdk.farms().private_location(&farm_addr))
+ .map(|location| location.map(|receipt| receipt.public_locality.into()))
+ .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))
+}
+
fn enqueue_farm_publish_with_sdk(
runtime: &tokio::runtime::Runtime,
sdk: &RadrootsClient,
diff --git a/crates/store/migrations/0023_order_workflow_display_projection.sql b/crates/store/migrations/0023_order_workflow_display_projection.sql
@@ -1,6 +1,6 @@
ALTER TABLE orders
- ADD COLUMN workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK (
- workflow_agreement IN ('ordered', 'pending_rhi', 'confirmed', 'declined', 'cancelled', 'needs_review')
+ ADD COLUMN workflow_agreement TEXT NOT NULL DEFAULT 'requested' CHECK (
+ workflow_agreement IN ('requested', 'revision_proposed', 'agreed_pending_rhi', 'committed', 'declined', 'cancelled', 'invalid')
);
ALTER TABLE orders
@@ -19,11 +19,12 @@ ALTER TABLE orders
UPDATE orders
SET
workflow_agreement = CASE status
- WHEN 'scheduled' THEN 'confirmed'
- WHEN 'packed' THEN 'confirmed'
- WHEN 'completed' THEN 'confirmed'
+ WHEN 'scheduled' THEN 'committed'
+ WHEN 'packed' THEN 'committed'
+ WHEN 'completed' THEN 'committed'
WHEN 'declined' THEN 'declined'
- ELSE 'ordered'
+ WHEN 'needs_review' THEN 'invalid'
+ ELSE 'requested'
END,
workflow_inventory = CASE status
WHEN 'scheduled' THEN 'reserved'
diff --git a/crates/store/migrations/0025_order_workflow_agreement_projection.sql b/crates/store/migrations/0025_order_workflow_agreement_projection.sql
@@ -5,9 +5,9 @@ DROP INDEX IF EXISTS idx_orders_farm_status;
DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at;
DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at;
-ALTER TABLE order_lines RENAME TO order_lines_agreement_legacy;
-ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_agreement_legacy;
-ALTER TABLE orders RENAME TO orders_agreement_legacy;
+ALTER TABLE order_lines RENAME TO order_lines_agreement_previous;
+ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_agreement_previous;
+ALTER TABLE orders RENAME TO orders_agreement_previous;
CREATE TABLE orders (
id TEXT PRIMARY KEY NOT NULL,
@@ -26,8 +26,8 @@ CREATE TABLE orders (
workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK (
workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed')
),
- workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK (
- workflow_agreement IN ('ordered', 'pending_rhi', 'confirmed', 'declined', 'cancelled', 'needs_review')
+ workflow_agreement TEXT NOT NULL DEFAULT 'requested' CHECK (
+ workflow_agreement IN ('requested', 'revision_proposed', 'agreed_pending_rhi', 'committed', 'declined', 'cancelled', 'invalid')
),
workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK (
workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review')
@@ -69,11 +69,17 @@ SELECT
buyer_phone,
buyer_order_note,
workflow_revision,
- workflow_agreement,
+ CASE workflow_agreement
+ WHEN 'ordered' THEN 'requested'
+ WHEN 'pending_rhi' THEN 'agreed_pending_rhi'
+ WHEN 'confirmed' THEN 'committed'
+ WHEN 'needs_review' THEN 'invalid'
+ ELSE workflow_agreement
+ END,
workflow_inventory,
workflow_provenance_source,
workflow_provenance_last_event_id
-FROM orders_agreement_legacy;
+FROM orders_agreement_previous;
CREATE TABLE order_lines (
id TEXT PRIMARY KEY NOT NULL,
@@ -128,7 +134,7 @@ SELECT
listing_event_id,
seller_pubkey,
listing_relays_json
-FROM order_lines_agreement_legacy;
+FROM order_lines_agreement_previous;
CREATE TABLE buyer_order_coordination_records (
order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
@@ -166,7 +172,7 @@ SELECT
created_at,
updated_at,
synced_at
-FROM buyer_order_coordination_records_agreement_legacy;
+FROM buyer_order_coordination_records_agreement_previous;
CREATE INDEX idx_orders_farm_status ON orders(farm_id, status);
CREATE INDEX idx_orders_farm_window_status_updated_at
@@ -181,6 +187,6 @@ CREATE INDEX idx_buyer_order_coordination_context_state_updated_at
CREATE INDEX idx_buyer_order_coordination_state_updated_at
ON buyer_order_coordination_records(state, updated_at);
-DROP TABLE order_lines_agreement_legacy;
-DROP TABLE buyer_order_coordination_records_agreement_legacy;
-DROP TABLE orders_agreement_legacy;
+DROP TABLE order_lines_agreement_previous;
+DROP TABLE buyer_order_coordination_records_agreement_previous;
+DROP TABLE orders_agreement_previous;
diff --git a/crates/store/migrations/0029_order_workflow_pending_rhi_agreement.sql b/crates/store/migrations/0029_order_workflow_pending_rhi_agreement.sql
@@ -1,248 +0,0 @@
-DROP INDEX IF EXISTS idx_order_validation_receipts_order_time;
-DROP INDEX IF EXISTS idx_order_validation_receipts_root_order;
-DROP INDEX IF EXISTS idx_order_lines_order_sort;
-DROP INDEX IF EXISTS idx_buyer_order_coordination_context_state_updated_at;
-DROP INDEX IF EXISTS idx_buyer_order_coordination_state_updated_at;
-DROP INDEX IF EXISTS idx_orders_farm_status;
-DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at;
-DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at;
-
-ALTER TABLE order_validation_receipts RENAME TO order_validation_receipts_pending_rhi_legacy;
-ALTER TABLE order_lines RENAME TO order_lines_pending_rhi_legacy;
-ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_pending_rhi_legacy;
-ALTER TABLE orders RENAME TO orders_pending_rhi_legacy;
-
-CREATE TABLE orders (
- id TEXT PRIMARY KEY NOT NULL,
- farm_id TEXT NOT NULL REFERENCES farms(id) ON DELETE CASCADE,
- fulfillment_window_id TEXT REFERENCES fulfillment_windows(id) ON DELETE SET NULL,
- order_number TEXT NOT NULL,
- customer_display_name TEXT NOT NULL,
- status TEXT NOT NULL CHECK (
- status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'needs_review')
- ),
- updated_at TEXT NOT NULL,
- buyer_context_key TEXT,
- buyer_email TEXT NOT NULL DEFAULT '',
- buyer_phone TEXT NOT NULL DEFAULT '',
- buyer_order_note TEXT NOT NULL DEFAULT '',
- workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK (
- workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed')
- ),
- workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK (
- workflow_agreement IN ('ordered', 'pending_rhi', 'confirmed', 'declined', 'cancelled', 'needs_review')
- ),
- workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK (
- workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review')
- ),
- workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK (
- workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown')
- ),
- workflow_provenance_last_event_id TEXT
-);
-
-INSERT INTO orders (
- id,
- farm_id,
- fulfillment_window_id,
- order_number,
- customer_display_name,
- status,
- updated_at,
- buyer_context_key,
- buyer_email,
- buyer_phone,
- buyer_order_note,
- workflow_revision,
- workflow_agreement,
- workflow_inventory,
- workflow_provenance_source,
- workflow_provenance_last_event_id
-)
-SELECT
- id,
- farm_id,
- fulfillment_window_id,
- order_number,
- customer_display_name,
- status,
- updated_at,
- buyer_context_key,
- buyer_email,
- buyer_phone,
- buyer_order_note,
- workflow_revision,
- workflow_agreement,
- workflow_inventory,
- workflow_provenance_source,
- workflow_provenance_last_event_id
-FROM orders_pending_rhi_legacy;
-
-CREATE TABLE order_lines (
- id TEXT PRIMARY KEY NOT NULL,
- order_id TEXT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
- title TEXT NOT NULL,
- quantity_value INTEGER NOT NULL CHECK (quantity_value >= 0),
- quantity_unit_label TEXT NOT NULL DEFAULT '',
- quantity_display TEXT NOT NULL,
- sort_index INTEGER NOT NULL DEFAULT 0,
- listing_bin_id TEXT,
- unit_price_minor_units INTEGER CHECK (
- unit_price_minor_units IS NULL OR unit_price_minor_units >= 0
- ),
- price_currency TEXT NOT NULL DEFAULT 'USD',
- farm_key TEXT,
- listing_addr TEXT,
- listing_event_id TEXT,
- seller_pubkey TEXT,
- listing_relays_json TEXT
-);
-
-INSERT INTO order_lines (
- id,
- order_id,
- title,
- quantity_value,
- quantity_unit_label,
- quantity_display,
- sort_index,
- listing_bin_id,
- unit_price_minor_units,
- price_currency,
- farm_key,
- listing_addr,
- listing_event_id,
- seller_pubkey,
- listing_relays_json
-)
-SELECT
- id,
- order_id,
- title,
- quantity_value,
- quantity_unit_label,
- quantity_display,
- sort_index,
- listing_bin_id,
- unit_price_minor_units,
- price_currency,
- farm_key,
- listing_addr,
- listing_event_id,
- seller_pubkey,
- listing_relays_json
-FROM order_lines_pending_rhi_legacy;
-
-CREATE TABLE buyer_order_coordination_records (
- order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
- buyer_context_key TEXT NOT NULL,
- record_id TEXT,
- state TEXT NOT NULL CHECK (state IN ('pending', 'synced', 'failed')),
- payload_json TEXT,
- attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
- last_error_message TEXT,
- created_at TEXT NOT NULL,
- updated_at TEXT NOT NULL,
- synced_at TEXT
-);
-
-INSERT INTO buyer_order_coordination_records (
- order_id,
- buyer_context_key,
- record_id,
- state,
- payload_json,
- attempt_count,
- last_error_message,
- created_at,
- updated_at,
- synced_at
-)
-SELECT
- order_id,
- buyer_context_key,
- record_id,
- state,
- payload_json,
- attempt_count,
- last_error_message,
- created_at,
- updated_at,
- synced_at
-FROM buyer_order_coordination_records_pending_rhi_legacy;
-
-CREATE TABLE order_validation_receipts (
- event_id TEXT PRIMARY KEY NOT NULL,
- order_id TEXT REFERENCES orders(id) ON DELETE CASCADE,
- raw_order_id TEXT NOT NULL,
- root_event_id TEXT NOT NULL,
- listing_event_id TEXT NOT NULL,
- target_event_id TEXT NOT NULL,
- receipt_type TEXT NOT NULL CHECK (
- receipt_type IN ('listing_validation', 'trade_transition', 'inventory_state', 'state_checkpoint')
- ),
- result TEXT NOT NULL CHECK (
- result IN ('valid', 'needs_review')
- ),
- proof_system TEXT NOT NULL CHECK (
- proof_system IN ('none', 'sp1_core', 'sp1_compressed', 'sp1_groth16', 'sp1_plonk')
- ),
- event_set_root TEXT NOT NULL,
- reducer_output_root TEXT NOT NULL,
- public_values_hash TEXT NOT NULL,
- event_created_at INTEGER NOT NULL CHECK (event_created_at >= 0)
-);
-
-INSERT INTO order_validation_receipts (
- event_id,
- order_id,
- raw_order_id,
- root_event_id,
- listing_event_id,
- target_event_id,
- receipt_type,
- result,
- proof_system,
- event_set_root,
- reducer_output_root,
- public_values_hash,
- event_created_at
-)
-SELECT
- event_id,
- order_id,
- raw_order_id,
- root_event_id,
- listing_event_id,
- target_event_id,
- receipt_type,
- result,
- proof_system,
- event_set_root,
- reducer_output_root,
- public_values_hash,
- event_created_at
-FROM order_validation_receipts_pending_rhi_legacy;
-
-CREATE INDEX idx_orders_farm_status ON orders(farm_id, status);
-CREATE INDEX idx_orders_farm_window_status_updated_at
- ON orders(farm_id, fulfillment_window_id, status, updated_at DESC, id DESC);
-CREATE INDEX idx_orders_buyer_context_updated_at
- ON orders(buyer_context_key, updated_at DESC, id DESC)
- WHERE buyer_context_key IS NOT NULL AND trim(buyer_context_key) <> '';
-CREATE INDEX idx_order_lines_order_sort
- ON order_lines(order_id, sort_index, id);
-CREATE INDEX idx_buyer_order_coordination_context_state_updated_at
- ON buyer_order_coordination_records(buyer_context_key, state, updated_at);
-CREATE INDEX idx_buyer_order_coordination_state_updated_at
- ON buyer_order_coordination_records(state, updated_at);
-CREATE INDEX idx_order_validation_receipts_order_time
- ON order_validation_receipts(order_id, event_created_at DESC, event_id DESC)
- WHERE order_id IS NOT NULL;
-CREATE INDEX idx_order_validation_receipts_root_order
- ON order_validation_receipts(root_event_id, raw_order_id);
-
-DROP TABLE order_validation_receipts_pending_rhi_legacy;
-DROP TABLE order_lines_pending_rhi_legacy;
-DROP TABLE buyer_order_coordination_records_pending_rhi_legacy;
-DROP TABLE orders_pending_rhi_legacy;
diff --git a/crates/store/migrations/0029_order_workflow_validation_receipt_agreement.sql b/crates/store/migrations/0029_order_workflow_validation_receipt_agreement.sql
@@ -0,0 +1,254 @@
+DROP INDEX IF EXISTS idx_order_validation_receipts_order_time;
+DROP INDEX IF EXISTS idx_order_validation_receipts_root_order;
+DROP INDEX IF EXISTS idx_order_lines_order_sort;
+DROP INDEX IF EXISTS idx_buyer_order_coordination_context_state_updated_at;
+DROP INDEX IF EXISTS idx_buyer_order_coordination_state_updated_at;
+DROP INDEX IF EXISTS idx_orders_farm_status;
+DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at;
+DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at;
+
+ALTER TABLE order_validation_receipts RENAME TO order_validation_receipts_workflow_previous;
+ALTER TABLE order_lines RENAME TO order_lines_workflow_previous;
+ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_workflow_previous;
+ALTER TABLE orders RENAME TO orders_workflow_previous;
+
+CREATE TABLE orders (
+ id TEXT PRIMARY KEY NOT NULL,
+ farm_id TEXT NOT NULL REFERENCES farms(id) ON DELETE CASCADE,
+ fulfillment_window_id TEXT REFERENCES fulfillment_windows(id) ON DELETE SET NULL,
+ order_number TEXT NOT NULL,
+ customer_display_name TEXT NOT NULL,
+ status TEXT NOT NULL CHECK (
+ status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'needs_review')
+ ),
+ updated_at TEXT NOT NULL,
+ buyer_context_key TEXT,
+ buyer_email TEXT NOT NULL DEFAULT '',
+ buyer_phone TEXT NOT NULL DEFAULT '',
+ buyer_order_note TEXT NOT NULL DEFAULT '',
+ workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK (
+ workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed')
+ ),
+ workflow_agreement TEXT NOT NULL DEFAULT 'requested' CHECK (
+ workflow_agreement IN ('requested', 'revision_proposed', 'agreed_pending_rhi', 'committed', 'declined', 'cancelled', 'invalid')
+ ),
+ workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK (
+ workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review')
+ ),
+ workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK (
+ workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown')
+ ),
+ workflow_provenance_last_event_id TEXT
+);
+
+INSERT INTO orders (
+ id,
+ farm_id,
+ fulfillment_window_id,
+ order_number,
+ customer_display_name,
+ status,
+ updated_at,
+ buyer_context_key,
+ buyer_email,
+ buyer_phone,
+ buyer_order_note,
+ workflow_revision,
+ workflow_agreement,
+ workflow_inventory,
+ workflow_provenance_source,
+ workflow_provenance_last_event_id
+)
+SELECT
+ id,
+ farm_id,
+ fulfillment_window_id,
+ order_number,
+ customer_display_name,
+ status,
+ updated_at,
+ buyer_context_key,
+ buyer_email,
+ buyer_phone,
+ buyer_order_note,
+ workflow_revision,
+ CASE workflow_agreement
+ WHEN 'ordered' THEN 'requested'
+ WHEN 'pending_rhi' THEN 'agreed_pending_rhi'
+ WHEN 'confirmed' THEN 'committed'
+ WHEN 'needs_review' THEN 'invalid'
+ ELSE workflow_agreement
+ END,
+ workflow_inventory,
+ workflow_provenance_source,
+ workflow_provenance_last_event_id
+FROM orders_workflow_previous;
+
+CREATE TABLE order_lines (
+ id TEXT PRIMARY KEY NOT NULL,
+ order_id TEXT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
+ title TEXT NOT NULL,
+ quantity_value INTEGER NOT NULL CHECK (quantity_value >= 0),
+ quantity_unit_label TEXT NOT NULL DEFAULT '',
+ quantity_display TEXT NOT NULL,
+ sort_index INTEGER NOT NULL DEFAULT 0,
+ listing_bin_id TEXT,
+ unit_price_minor_units INTEGER CHECK (
+ unit_price_minor_units IS NULL OR unit_price_minor_units >= 0
+ ),
+ price_currency TEXT NOT NULL DEFAULT 'USD',
+ farm_key TEXT,
+ listing_addr TEXT,
+ listing_event_id TEXT,
+ seller_pubkey TEXT,
+ listing_relays_json TEXT
+);
+
+INSERT INTO order_lines (
+ id,
+ order_id,
+ title,
+ quantity_value,
+ quantity_unit_label,
+ quantity_display,
+ sort_index,
+ listing_bin_id,
+ unit_price_minor_units,
+ price_currency,
+ farm_key,
+ listing_addr,
+ listing_event_id,
+ seller_pubkey,
+ listing_relays_json
+)
+SELECT
+ id,
+ order_id,
+ title,
+ quantity_value,
+ quantity_unit_label,
+ quantity_display,
+ sort_index,
+ listing_bin_id,
+ unit_price_minor_units,
+ price_currency,
+ farm_key,
+ listing_addr,
+ listing_event_id,
+ seller_pubkey,
+ listing_relays_json
+FROM order_lines_workflow_previous;
+
+CREATE TABLE buyer_order_coordination_records (
+ order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
+ buyer_context_key TEXT NOT NULL,
+ record_id TEXT,
+ state TEXT NOT NULL CHECK (state IN ('pending', 'synced', 'failed')),
+ payload_json TEXT,
+ attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
+ last_error_message TEXT,
+ created_at TEXT NOT NULL,
+ updated_at TEXT NOT NULL,
+ synced_at TEXT
+);
+
+INSERT INTO buyer_order_coordination_records (
+ order_id,
+ buyer_context_key,
+ record_id,
+ state,
+ payload_json,
+ attempt_count,
+ last_error_message,
+ created_at,
+ updated_at,
+ synced_at
+)
+SELECT
+ order_id,
+ buyer_context_key,
+ record_id,
+ state,
+ payload_json,
+ attempt_count,
+ last_error_message,
+ created_at,
+ updated_at,
+ synced_at
+FROM buyer_order_coordination_records_workflow_previous;
+
+CREATE TABLE order_validation_receipts (
+ event_id TEXT PRIMARY KEY NOT NULL,
+ order_id TEXT REFERENCES orders(id) ON DELETE CASCADE,
+ raw_order_id TEXT NOT NULL,
+ root_event_id TEXT NOT NULL,
+ listing_event_id TEXT NOT NULL,
+ target_event_id TEXT NOT NULL,
+ receipt_type TEXT NOT NULL CHECK (
+ receipt_type IN ('listing_validation', 'trade_transition', 'inventory_state', 'state_checkpoint')
+ ),
+ result TEXT NOT NULL CHECK (
+ result IN ('valid', 'needs_review')
+ ),
+ proof_system TEXT NOT NULL CHECK (
+ proof_system IN ('none', 'sp1_core', 'sp1_compressed', 'sp1_groth16', 'sp1_plonk')
+ ),
+ event_set_root TEXT NOT NULL,
+ reducer_output_root TEXT NOT NULL,
+ public_values_hash TEXT NOT NULL,
+ event_created_at INTEGER NOT NULL CHECK (event_created_at >= 0)
+);
+
+INSERT INTO order_validation_receipts (
+ event_id,
+ order_id,
+ raw_order_id,
+ root_event_id,
+ listing_event_id,
+ target_event_id,
+ receipt_type,
+ result,
+ proof_system,
+ event_set_root,
+ reducer_output_root,
+ public_values_hash,
+ event_created_at
+)
+SELECT
+ event_id,
+ order_id,
+ raw_order_id,
+ root_event_id,
+ listing_event_id,
+ target_event_id,
+ receipt_type,
+ result,
+ proof_system,
+ event_set_root,
+ reducer_output_root,
+ public_values_hash,
+ event_created_at
+FROM order_validation_receipts_workflow_previous;
+
+CREATE INDEX idx_orders_farm_status ON orders(farm_id, status);
+CREATE INDEX idx_orders_farm_window_status_updated_at
+ ON orders(farm_id, fulfillment_window_id, status, updated_at DESC, id DESC);
+CREATE INDEX idx_orders_buyer_context_updated_at
+ ON orders(buyer_context_key, updated_at DESC, id DESC)
+ WHERE buyer_context_key IS NOT NULL AND trim(buyer_context_key) <> '';
+CREATE INDEX idx_order_lines_order_sort
+ ON order_lines(order_id, sort_index, id);
+CREATE INDEX idx_buyer_order_coordination_context_state_updated_at
+ ON buyer_order_coordination_records(buyer_context_key, state, updated_at);
+CREATE INDEX idx_buyer_order_coordination_state_updated_at
+ ON buyer_order_coordination_records(state, updated_at);
+CREATE INDEX idx_order_validation_receipts_order_time
+ ON order_validation_receipts(order_id, event_created_at DESC, event_id DESC)
+ WHERE order_id IS NOT NULL;
+CREATE INDEX idx_order_validation_receipts_root_order
+ ON order_validation_receipts(root_event_id, raw_order_id);
+
+DROP TABLE order_validation_receipts_workflow_previous;
+DROP TABLE order_lines_workflow_previous;
+DROP TABLE buyer_order_coordination_records_workflow_previous;
+DROP TABLE orders_workflow_previous;
diff --git a/crates/store/src/interop.rs b/crates/store/src/interop.rs
@@ -30,10 +30,10 @@ use radroots_local_events::{
use radroots_sql_core::{SqlExecutor, SqliteExecutor};
use radroots_studio_app_view::{
FarmId, FarmOrderMethod, FarmReadiness, FarmSetupDraft, FarmSetupProjection, FarmSummary,
- FulfillmentWindowId, OrderId, PickupLocationId, ProductId, ProductStatus,
- TradeProvenanceProjection, TradeRevisionStatus, TradeValidationReceiptProofSystem,
- TradeValidationReceiptResult, TradeValidationReceiptType, TradeWorkflowProjection,
- TradeWorkflowSource, order_status_from_active_order_projection,
+ FulfillmentWindowId, OrderId, OrderStatus, PickupLocationId, ProductId, ProductStatus,
+ TradeAgreementStatus, TradeInventoryStatus, TradeProvenanceProjection, TradeRevisionStatus,
+ TradeValidationReceiptProofSystem, TradeValidationReceiptResult, TradeValidationReceiptType,
+ TradeWorkflowProjection, TradeWorkflowSource, order_status_from_active_order_projection,
};
use radroots_trade::order::{
RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderProjection,
@@ -1318,6 +1318,7 @@ impl<'a> AppLocalInteropRepository<'a> {
{
self.replace_active_order_agreement_lines(workflow.order_id, agreement_source)?;
}
+ self.apply_validation_receipt_workflow_projection(workflow.order_id)?;
Ok(())
}
@@ -1389,6 +1390,9 @@ impl<'a> AppLocalInteropRepository<'a> {
operation: "upsert local interop validation receipt projection",
source,
})?;
+ if let Some(order_id) = order_id {
+ self.apply_validation_receipt_workflow_projection(order_id)?;
+ }
Ok(())
}
@@ -1434,6 +1438,109 @@ impl<'a> AppLocalInteropRepository<'a> {
operation: "attach local interop validation receipts to request",
source,
})?;
+ self.apply_validation_receipt_workflow_projection(order_id)?;
+ Ok(())
+ }
+
+ fn apply_validation_receipt_workflow_projection(
+ &self,
+ order_id: OrderId,
+ ) -> Result<(), AppSqliteError> {
+ let current: Option<(String, String, Option<String>)> = self
+ .connection
+ .query_row(
+ "SELECT status, workflow_agreement, workflow_provenance_last_event_id
+ FROM orders
+ WHERE id = ?1",
+ params![order_id.to_string()],
+ |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
+ )
+ .optional()
+ .map_err(|source| AppSqliteError::Query {
+ operation: "load order workflow before validation receipt projection",
+ source,
+ })?;
+ let Some((status, agreement, Some(target_event_id))) = current else {
+ return Ok(());
+ };
+ if !matches!(
+ agreement.as_str(),
+ value if value == TradeAgreementStatus::AgreedPendingRhi.storage_key()
+ || value == TradeAgreementStatus::Committed.storage_key()
+ || value == TradeAgreementStatus::Invalid.storage_key()
+ ) {
+ return Ok(());
+ }
+ if !matches!(
+ status.as_str(),
+ value if value == OrderStatus::NeedsAction.storage_key()
+ || value == OrderStatus::Scheduled.storage_key()
+ || value == OrderStatus::NeedsReview.storage_key()
+ ) {
+ return Ok(());
+ }
+ let latest_receipt: Option<String> = self
+ .connection
+ .query_row(
+ "SELECT result
+ FROM order_validation_receipts
+ WHERE order_id = ?1
+ AND receipt_type = 'trade_transition'
+ AND target_event_id = ?2
+ ORDER BY event_created_at DESC, event_id DESC
+ LIMIT 1",
+ params![order_id.to_string(), target_event_id.as_str()],
+ |row| row.get(0),
+ )
+ .optional()
+ .map_err(|source| AppSqliteError::Query {
+ operation: "load latest validation receipt workflow projection",
+ source,
+ })?;
+ let Some(result) = latest_receipt else {
+ return Ok(());
+ };
+ let (status, agreement, inventory) = match result.as_str() {
+ "valid" => (
+ OrderStatus::Scheduled.storage_key(),
+ TradeAgreementStatus::Committed.storage_key(),
+ TradeInventoryStatus::Reserved.storage_key(),
+ ),
+ "needs_review" => (
+ OrderStatus::NeedsReview.storage_key(),
+ TradeAgreementStatus::Invalid.storage_key(),
+ TradeInventoryStatus::NeedsReview.storage_key(),
+ ),
+ _ => {
+ return Err(AppSqliteError::DecodeEnum {
+ field: "order_validation_receipts.result",
+ value: result,
+ });
+ }
+ };
+ self.connection
+ .execute(
+ "UPDATE orders
+ SET status = ?2,
+ workflow_agreement = ?3,
+ workflow_inventory = ?4,
+ workflow_provenance_source = ?5,
+ workflow_provenance_last_event_id = ?6,
+ updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
+ WHERE id = ?1",
+ params![
+ order_id.to_string(),
+ status,
+ agreement,
+ inventory,
+ TradeWorkflowSource::LocalEvents.storage_key(),
+ target_event_id,
+ ],
+ )
+ .map_err(|source| AppSqliteError::Query {
+ operation: "apply validation receipt workflow projection",
+ source,
+ })?;
Ok(())
}
@@ -5076,7 +5183,7 @@ mod tests {
);
assert_eq!(
buyer_orders.rows[0].workflow.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(
buyer_orders.rows[0].workflow.inventory,
@@ -5336,7 +5443,7 @@ mod tests {
assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsAction);
assert_eq!(
seller_orders.rows[0].workflow.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(
seller_orders.rows[0].workflow.inventory,
@@ -5346,8 +5453,72 @@ mod tests {
}
#[test]
- fn validation_receipts_project_passively_on_buyer_and_seller_order_details() {
- let fixture = validation_receipt_order_fixture("validation-receipt-passive");
+ fn valid_validation_receipt_commits_buyer_and_seller_order_details() {
+ let fixture = validation_receipt_order_fixture("validation-receipt-valid-commit");
+ let valid_event = validation_receipt_event(
+ hex_event_id(29).as_str(),
+ fixture.seller_pubkey.as_str(),
+ fixture.order_id_raw.as_str(),
+ fixture.listing_event_id.as_str(),
+ fixture.request_event_id.as_str(),
+ fixture.decision_event_id.as_str(),
+ RadrootsValidationReceiptResult::Valid,
+ 1_777_665_603,
+ );
+ fixture
+ .events
+ .append_record(&signed_order_event_record(
+ "cli:signed_event:validation-receipt:valid-commit",
+ &valid_event,
+ fixture.listing_addr.as_str(),
+ SourceRuntime::Cli,
+ None,
+ ))
+ .expect("append valid validation receipt");
+ fixture
+ .app_store
+ .import_shared_local_events_from_store(&fixture.events)
+ .expect("import valid validation receipt");
+
+ let buyer_detail = fixture
+ .app_store
+ .load_buyer_order_detail(&fixture.buyer_context, fixture.order_id)
+ .expect("load buyer committed validation receipt detail")
+ .expect("buyer committed validation receipt detail");
+ let seller_detail = fixture
+ .app_store
+ .load_order_detail(fixture.seller_farm_id, fixture.order_id)
+ .expect("load seller committed validation receipt detail")
+ .expect("seller committed validation receipt detail");
+
+ assert_eq!(buyer_detail.status, BuyerOrderStatus::Scheduled);
+ assert_eq!(seller_detail.status, OrderStatus::Scheduled);
+ assert_eq!(
+ buyer_detail.workflow.agreement,
+ TradeAgreementStatus::Committed
+ );
+ assert_eq!(
+ seller_detail.workflow.agreement,
+ TradeAgreementStatus::Committed
+ );
+ assert_eq!(
+ buyer_detail.workflow.inventory,
+ TradeInventoryStatus::Reserved
+ );
+ assert_eq!(
+ buyer_detail.workflow.provenance.last_event_id.as_deref(),
+ Some(fixture.decision_event_id.as_str())
+ );
+ assert_eq!(buyer_detail.validation_receipts.len(), 1);
+ assert_eq!(
+ buyer_detail.validation_receipts[0].result,
+ TradeValidationReceiptResult::Valid
+ );
+ }
+
+ #[test]
+ fn validation_receipts_drive_buyer_and_seller_order_details() {
+ let fixture = validation_receipt_order_fixture("validation-receipt-driven");
let valid_event = validation_receipt_event(
hex_event_id(31).as_str(),
fixture.seller_pubkey.as_str(),
@@ -5419,11 +5590,23 @@ mod tests {
.load_local_interop_records()
.expect("load validation receipt imports");
- assert_eq!(buyer_detail.status, BuyerOrderStatus::Placed);
- assert_eq!(seller_detail.status, OrderStatus::NeedsAction);
+ assert_eq!(buyer_detail.status, BuyerOrderStatus::NeedsReview);
+ assert_eq!(seller_detail.status, OrderStatus::NeedsReview);
assert_eq!(
buyer_detail.workflow.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::Invalid
+ );
+ assert_eq!(
+ seller_detail.workflow.agreement,
+ TradeAgreementStatus::Invalid
+ );
+ assert_eq!(
+ buyer_detail.workflow.inventory,
+ TradeInventoryStatus::NeedsReview
+ );
+ assert_eq!(
+ buyer_detail.workflow.provenance.last_event_id.as_deref(),
+ Some(fixture.decision_event_id.as_str())
);
assert_eq!(seller_detail.primary_action, None);
assert_eq!(buyer_detail.validation_receipts.len(), 2);
@@ -5697,7 +5880,7 @@ mod tests {
assert_eq!(seller_detail.status, OrderStatus::NeedsAction);
assert_eq!(
buyer_detail.workflow.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(seller_detail.primary_action, None);
}
@@ -5867,7 +6050,7 @@ mod tests {
assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsAction);
assert_eq!(
seller_orders.rows[0].workflow.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(
seller_orders.rows[0].workflow.revision,
diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs
@@ -1144,13 +1144,13 @@ mod tests {
workflow_agreement,
workflow_inventory
) VALUES (
- 'order_needs_review',
+ 'order_invalid',
'farm_schema',
'needs review',
'Buyer',
'needs_review',
'2026-01-01T00:00:00Z',
- 'needs_review',
+ 'invalid',
'needs_review'
)",
[],
@@ -1168,13 +1168,13 @@ mod tests {
workflow_agreement,
workflow_inventory
) VALUES (
- 'order_pending_rhi',
+ 'order_agreed_pending_rhi',
'farm_schema',
- 'pending rhi',
+ 'agreed pending rhi',
'Buyer',
'needs_action',
'2026-01-01T00:00:00Z',
- 'pending_rhi',
+ 'agreed_pending_rhi',
'reserved'
)",
[],
@@ -1373,10 +1373,10 @@ mod tests {
assert_eq!(status, "needs_review");
connection
.execute(
- "UPDATE orders SET workflow_agreement = 'pending_rhi' WHERE id = 'order_legacy'",
+ "UPDATE orders SET workflow_agreement = 'agreed_pending_rhi' WHERE id = 'order_legacy'",
[],
)
- .expect("pending rhi agreement should satisfy migrated check");
+ .expect("agreed pending rhi agreement should satisfy migrated check");
drop(store);
remove_database_artifacts(&path);
diff --git a/crates/store/src/migrations.rs b/crates/store/src/migrations.rs
@@ -120,7 +120,7 @@ const MIGRATIONS: &[Migration] = &[
},
Migration {
version: 29,
- sql: include_str!("../migrations/0029_order_workflow_pending_rhi_agreement.sql"),
+ sql: include_str!("../migrations/0029_order_workflow_validation_receipt_agreement.sql"),
},
];
diff --git a/crates/store/src/repo/buyer.rs b/crates/store/src/repo/buyer.rs
@@ -3823,7 +3823,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("agreement-event-1"),
@@ -3839,7 +3839,7 @@ mod tests {
let row = &list.rows[0];
assert_eq!(list.rows.len(), 1);
- assert_eq!(row.workflow.agreement, TradeAgreementStatus::Confirmed);
+ assert_eq!(row.workflow.agreement, TradeAgreementStatus::Committed);
assert_eq!(row.workflow.inventory, TradeInventoryStatus::Reserved);
assert_eq!(
row.workflow.provenance.primary_source,
@@ -3877,7 +3877,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("buyer-workflow-event"),
@@ -3894,7 +3894,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("buyer-workflow-event"),
diff --git a/crates/store/src/repo/orders.rs b/crates/store/src/repo/orders.rs
@@ -1177,7 +1177,11 @@ fn primary_action_for_order(
workflow: &TradeWorkflowProjection,
) -> Option<OrderPrimaryAction> {
match status {
- OrderStatus::NeedsAction if workflow.agreement == TradeAgreementStatus::PendingRhi => None,
+ OrderStatus::NeedsAction
+ if workflow.agreement == TradeAgreementStatus::AgreedPendingRhi =>
+ {
+ None
+ }
OrderStatus::NeedsAction => Some(OrderPrimaryAction::Review),
OrderStatus::Scheduled
| OrderStatus::Packed
@@ -1602,7 +1606,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("seller-workflow-event"),
@@ -1623,7 +1627,7 @@ mod tests {
.expect("seller detail should exist");
let workflow = &list.rows[0].workflow;
- assert_eq!(workflow.agreement, TradeAgreementStatus::Confirmed);
+ assert_eq!(workflow.agreement, TradeAgreementStatus::Committed);
assert_eq!(workflow.revision, TradeRevisionStatus::Updated);
assert_eq!(workflow.inventory, TradeInventoryStatus::Reserved);
assert_eq!(
@@ -1668,7 +1672,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("seller-workflow-event"),
@@ -1685,7 +1689,7 @@ mod tests {
set_order_workflow_display_projection(
connection,
order_id,
- "confirmed",
+ "committed",
"reserved",
"local_events",
Some("seller-workflow-event"),
diff --git a/crates/store/src/repo/workflow.rs b/crates/store/src/repo/workflow.rs
@@ -38,12 +38,13 @@ fn parse_trade_agreement_status(
value: String,
) -> Result<TradeAgreementStatus, AppSqliteError> {
match value.as_str() {
- "ordered" => Ok(TradeAgreementStatus::Ordered),
- "pending_rhi" => Ok(TradeAgreementStatus::PendingRhi),
- "confirmed" => Ok(TradeAgreementStatus::Confirmed),
+ "requested" => Ok(TradeAgreementStatus::Requested),
+ "revision_proposed" => Ok(TradeAgreementStatus::RevisionProposed),
+ "agreed_pending_rhi" => Ok(TradeAgreementStatus::AgreedPendingRhi),
+ "committed" => Ok(TradeAgreementStatus::Committed),
"declined" => Ok(TradeAgreementStatus::Declined),
"cancelled" => Ok(TradeAgreementStatus::Cancelled),
- "needs_review" => Ok(TradeAgreementStatus::NeedsReview),
+ "invalid" => Ok(TradeAgreementStatus::Invalid),
_ => Err(AppSqliteError::DecodeEnum { field, value }),
}
}
diff --git a/crates/view/src/lib.rs b/crates/view/src/lib.rs
@@ -1072,47 +1072,50 @@ pub struct BuyerOrderReviewProjection {
#[serde(rename_all = "snake_case")]
pub enum TradeAgreementStatus {
#[default]
- Ordered,
- PendingRhi,
- Confirmed,
+ Requested,
+ RevisionProposed,
+ AgreedPendingRhi,
+ Committed,
Declined,
Cancelled,
- NeedsReview,
+ Invalid,
}
impl TradeAgreementStatus {
pub const fn storage_key(self) -> &'static str {
match self {
- Self::Ordered => "ordered",
- Self::PendingRhi => "pending_rhi",
- Self::Confirmed => "confirmed",
+ Self::Requested => "requested",
+ Self::RevisionProposed => "revision_proposed",
+ Self::AgreedPendingRhi => "agreed_pending_rhi",
+ Self::Committed => "committed",
Self::Declined => "declined",
Self::Cancelled => "cancelled",
- Self::NeedsReview => "needs_review",
+ Self::Invalid => "invalid",
}
}
pub const fn label_key_id(self) -> &'static str {
match self {
- Self::Ordered => "messages.trade.workflow.agreement.ordered",
- Self::PendingRhi => "messages.trade.workflow.agreement.pending_rhi",
- Self::Confirmed => "messages.trade.workflow.agreement.confirmed",
+ Self::Requested => "messages.trade.workflow.agreement.requested",
+ Self::RevisionProposed => "messages.trade.workflow.agreement.revision_proposed",
+ Self::AgreedPendingRhi => "messages.trade.workflow.agreement.agreed_pending_rhi",
+ Self::Committed => "messages.trade.workflow.agreement.committed",
Self::Declined => "messages.trade.workflow.agreement.declined",
Self::Cancelled => "messages.trade.workflow.agreement.cancelled",
- Self::NeedsReview => "messages.trade.workflow.agreement.needs_review",
+ Self::Invalid => "messages.trade.workflow.agreement.invalid",
}
}
pub const fn from_active_order_status(status: &RadrootsTradeWorkflowState) -> Self {
match status {
- RadrootsTradeWorkflowState::Missing => Self::NeedsReview,
- RadrootsTradeWorkflowState::Requested
- | RadrootsTradeWorkflowState::RevisionProposed => Self::Ordered,
- RadrootsTradeWorkflowState::AgreedPendingRhi => Self::PendingRhi,
- RadrootsTradeWorkflowState::Committed => Self::Confirmed,
+ RadrootsTradeWorkflowState::Missing => Self::Invalid,
+ RadrootsTradeWorkflowState::Requested => Self::Requested,
+ RadrootsTradeWorkflowState::RevisionProposed => Self::RevisionProposed,
+ RadrootsTradeWorkflowState::AgreedPendingRhi => Self::AgreedPendingRhi,
+ RadrootsTradeWorkflowState::Committed => Self::Committed,
RadrootsTradeWorkflowState::Declined => Self::Declined,
RadrootsTradeWorkflowState::Cancelled => Self::Cancelled,
- RadrootsTradeWorkflowState::Invalid => Self::NeedsReview,
+ RadrootsTradeWorkflowState::Invalid => Self::Invalid,
}
}
}
@@ -1504,12 +1507,12 @@ impl TradeWorkflowProjection {
pub fn from_order_status(order_id: OrderId, status: OrderStatus) -> Self {
let mut projection = match status {
- OrderStatus::NeedsAction => Self::new(order_id, TradeAgreementStatus::Ordered),
- OrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Confirmed),
- OrderStatus::Packed => Self::new(order_id, TradeAgreementStatus::Confirmed),
- OrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Confirmed),
+ OrderStatus::NeedsAction => Self::new(order_id, TradeAgreementStatus::Requested),
+ OrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Committed),
+ OrderStatus::Packed => Self::new(order_id, TradeAgreementStatus::Committed),
+ OrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Committed),
OrderStatus::Declined => Self::new(order_id, TradeAgreementStatus::Declined),
- OrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::NeedsReview),
+ OrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::Invalid),
};
match status {
@@ -1534,12 +1537,12 @@ impl TradeWorkflowProjection {
pub fn from_buyer_order_status(order_id: OrderId, status: BuyerOrderStatus) -> Self {
let mut projection = match status {
- BuyerOrderStatus::Placed => Self::new(order_id, TradeAgreementStatus::Ordered),
- BuyerOrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Confirmed),
- BuyerOrderStatus::Ready => Self::new(order_id, TradeAgreementStatus::Confirmed),
- BuyerOrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Confirmed),
+ BuyerOrderStatus::Placed => Self::new(order_id, TradeAgreementStatus::Requested),
+ BuyerOrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Committed),
+ BuyerOrderStatus::Ready => Self::new(order_id, TradeAgreementStatus::Committed),
+ BuyerOrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Committed),
BuyerOrderStatus::Declined => Self::new(order_id, TradeAgreementStatus::Declined),
- BuyerOrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::NeedsReview),
+ BuyerOrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::Invalid),
};
match status {
@@ -2875,21 +2878,27 @@ mod tests {
fn trade_workflow_projection_maps_shared_active_order_projection_to_product_axes() {
assert_eq!(
TradeAgreementStatus::from_active_order_status(&RadrootsTradeWorkflowState::Requested),
- TradeAgreementStatus::Ordered
+ TradeAgreementStatus::Requested
+ );
+ assert_eq!(
+ TradeAgreementStatus::from_active_order_status(
+ &RadrootsTradeWorkflowState::RevisionProposed
+ ),
+ TradeAgreementStatus::RevisionProposed
);
assert_eq!(
TradeAgreementStatus::from_active_order_status(
&RadrootsTradeWorkflowState::AgreedPendingRhi
),
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(
TradeAgreementStatus::from_active_order_status(&RadrootsTradeWorkflowState::Committed),
- TradeAgreementStatus::Confirmed
+ TradeAgreementStatus::Committed
);
assert_eq!(
TradeAgreementStatus::from_active_order_status(&RadrootsTradeWorkflowState::Invalid),
- TradeAgreementStatus::NeedsReview
+ TradeAgreementStatus::Invalid
);
assert_eq!(
TradeRevisionStatus::try_from_storage_key("none"),
@@ -2927,7 +2936,7 @@ mod tests {
TradeProvenanceProjection::from_primary_source(TradeWorkflowSource::LocalEvents),
);
assert_eq!(projection.order_id, order_id);
- assert_eq!(projection.agreement, TradeAgreementStatus::Confirmed);
+ assert_eq!(projection.agreement, TradeAgreementStatus::Committed);
assert_eq!(projection.revision, TradeRevisionStatus::Updated);
assert_eq!(projection.inventory, TradeInventoryStatus::Reserved);
assert_eq!(projection.economics.total_minor_units, Some(1234));
@@ -2954,7 +2963,7 @@ mod tests {
);
assert_eq!(
pending_rhi_projection.agreement,
- TradeAgreementStatus::PendingRhi
+ TradeAgreementStatus::AgreedPendingRhi
);
assert_eq!(
pending_rhi_projection.inventory,
@@ -2974,7 +2983,7 @@ mod tests {
);
assert_eq!(
requested_projection.agreement,
- TradeAgreementStatus::Ordered
+ TradeAgreementStatus::Requested
);
assert_eq!(
requested_projection.inventory,
@@ -3040,13 +3049,19 @@ mod tests {
assert_eq!(
TradeAgreementStatus::from_active_order_status(&RadrootsTradeWorkflowState::Requested)
.storage_key(),
- "ordered"
+ "requested"
+ );
+ assert_eq!(TradeAgreementStatus::Requested.storage_key(), "requested");
+ assert_eq!(
+ TradeAgreementStatus::RevisionProposed.storage_key(),
+ "revision_proposed"
);
- assert_eq!(TradeAgreementStatus::Ordered.storage_key(), "ordered");
assert_eq!(
- TradeAgreementStatus::PendingRhi.storage_key(),
- "pending_rhi"
+ TradeAgreementStatus::AgreedPendingRhi.storage_key(),
+ "agreed_pending_rhi"
);
+ assert_eq!(TradeAgreementStatus::Committed.storage_key(), "committed");
+ assert_eq!(TradeAgreementStatus::Invalid.storage_key(), "invalid");
assert_eq!(
TradeRevisionStatus::KeptAsPlaced.storage_key(),
"kept_as_placed"
@@ -3058,16 +3073,20 @@ mod tests {
);
assert_eq!(
- TradeAgreementStatus::Ordered.label_key_id(),
- "messages.trade.workflow.agreement.ordered"
+ TradeAgreementStatus::Requested.label_key_id(),
+ "messages.trade.workflow.agreement.requested"
+ );
+ assert_eq!(
+ TradeAgreementStatus::RevisionProposed.label_key_id(),
+ "messages.trade.workflow.agreement.revision_proposed"
);
assert_eq!(
- TradeAgreementStatus::PendingRhi.label_key_id(),
- "messages.trade.workflow.agreement.pending_rhi"
+ TradeAgreementStatus::AgreedPendingRhi.label_key_id(),
+ "messages.trade.workflow.agreement.agreed_pending_rhi"
);
assert_eq!(
- TradeAgreementStatus::NeedsReview.label_key_id(),
- "messages.trade.workflow.agreement.needs_review"
+ TradeAgreementStatus::Invalid.label_key_id(),
+ "messages.trade.workflow.agreement.invalid"
);
assert_eq!(
TradeRevisionStatus::ChangeProposed.label_key_id(),
@@ -3491,7 +3510,7 @@ mod tests {
assert_eq!(orders_list.rows[0].primary_action, None);
assert_eq!(
orders_list.rows[0].workflow.agreement,
- TradeAgreementStatus::Confirmed
+ TradeAgreementStatus::Committed
);
assert_eq!(order_detail.items[0].quantity_display, "2 bags");
assert_eq!(
@@ -3629,7 +3648,7 @@ mod tests {
assert_eq!(order_detail.status, BuyerOrderStatus::Scheduled);
assert_eq!(
order_detail.workflow.agreement,
- TradeAgreementStatus::Confirmed
+ TradeAgreementStatus::Committed
);
}
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -411,12 +411,13 @@
"trade.workflow.axis.revision": "Change",
"trade.workflow.axis.inventory": "Stock",
"trade.workflow.axis.source": "Source",
- "trade.workflow.agreement.ordered": "Ordered",
- "trade.workflow.agreement.pending_rhi": "Pending validation",
- "trade.workflow.agreement.confirmed": "Confirmed",
+ "trade.workflow.agreement.requested": "Requested",
+ "trade.workflow.agreement.revision_proposed": "Revision proposed",
+ "trade.workflow.agreement.agreed_pending_rhi": "Pending validation",
+ "trade.workflow.agreement.committed": "Committed",
"trade.workflow.agreement.declined": "Declined",
"trade.workflow.agreement.cancelled": "Cancelled",
- "trade.workflow.agreement.needs_review": "Needs review",
+ "trade.workflow.agreement.invalid": "Invalid",
"trade.workflow.revision.none": "No change",
"trade.workflow.revision.change_proposed": "Change proposed",
"trade.workflow.revision.updated": "Updated",