app

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

commit 5d361b7ea4fb9c80c6e1664acfe17ce26dd8f63d
parent 4504af9d71441f0ddfcb6859489c1540a9d21971
Author: triesap <tyson@radroots.org>
Date:   Sun, 24 May 2026 22:00:42 +0000

app: split order recovery proofs

Diffstat:
Mcrates/launchers/desktop/src/runtime.rs | 344++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
1 file changed, 192 insertions(+), 152 deletions(-)

diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs @@ -8845,114 +8845,9 @@ mod tests { } #[test] - fn runtime_buyer_order_shared_append_failure_is_recoverable_after_restart() { - let (runtime, paths) = bootstrapped_runtime("buyer_order_append_failure"); - assert!( - runtime - .generate_local_account(Some("Buyer".to_owned())) - .expect("account should generate") - ); - let buyer_account_id = runtime - .summary() - .settings_account_projection - .selected_account - .as_ref() - .expect("selected account") - .account - .account_id - .clone(); - assert!( - runtime - .select_active_surface(ActiveSurface::Personal) - .expect("surface should switch into marketplace") - ); - let listing_key = "DDDDDDDDDDDDDDDDDDDDDD"; - append_cli_signed_buyer_listing_record_with( - &paths, - "buyer-order-append-failure-listing", - listing_key, - "Buyer Visible Eggs", - 1100, - ); - let product_id = - deterministic_cli_listing_product_id(Some("buyer-visible-seller-pubkey"), listing_key); - assert!( - runtime - .open_personal_product_detail(PersonalSection::Browse, product_id) - .expect("buyer detail should import before lookup") - ); - assert!( - runtime - .add_personal_product_to_cart(PersonalSection::Browse, false) - .expect("buyer product should add to cart") - ); - assert!( - runtime - .save_personal_checkout_draft(BuyerCheckoutDraft { - name: "Casey Buyer".to_owned(), - email: "casey@example.com".to_owned(), - phone: String::new(), - order_note: String::new(), - }) - .expect("buyer checkout draft should save") - ); - block_shared_local_events_database(&paths); - - let error = runtime - .place_personal_order() - .expect_err("blocked local events should fail order completion"); - - assert!(matches!(error, AppSqliteError::LocalEventsSql { .. })); - let summary = runtime.summary(); - assert_eq!( - summary.shell_projection.selected_section, - ShellSection::Personal(PersonalSection::Orders) - ); - assert!(summary.personal_projection.cart.cart.lines.is_empty()); - assert!(!summary.personal_projection.cart.checkout.can_place_order); - assert_eq!(summary.personal_projection.orders.list.rows.len(), 1); - let (order_id, order_farm_id) = { - let visible_order_id = summary.personal_projection.orders.list.rows[0].order_id; - let order_farm_id = summary - .personal_projection - .orders - .detail - .as_ref() - .expect("buyer order detail should remain visible after coordination failure") - .farm_id; - assert_eq!( - summary - .personal_projection - .orders - .detail - .as_ref() - .expect("buyer order detail should remain visible after coordination failure") - .order_id, - visible_order_id - ); - let state = runtime.lock_state_mut(); - let buyer_context = state.state_store.identity_projection().buyer_context(); - let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store"); - let buyer_orders = sqlite_store - .load_buyer_orders(&buyer_context) - .expect("buyer order should persist after coordination failure"); - assert_eq!(buyer_orders.rows.len(), 1); - let order_id = buyer_orders.rows[0].order_id; - assert_eq!(order_id, visible_order_id); - let coordination = sqlite_store - .load_buyer_order_coordination_record(&buyer_context, order_id) - .expect("buyer order coordination should load") - .expect("buyer order coordination should exist"); - assert_eq!(coordination.state, BuyerOrderCoordinationState::Failed); - assert_eq!(coordination.attempt_count, 1); - assert!(coordination.record_id.is_some()); - assert!(coordination.payload_json.is_some()); - assert!(coordination.last_error_message.is_some()); - (order_id, order_farm_id) - }; - assert!( - pending_order_sync_payloads(&runtime, buyer_account_id.as_str(), order_id).is_empty() - ); + fn runtime_buyer_order_shared_append_failure_is_recoverable_in_same_session() { + let (runtime, paths, buyer_account_id, order_id, order_farm_id) = + blocked_buyer_order_runtime("buyer_order_append_failure_same_session"); let expected_order_sync_payload = super::order_sync_payload( order_id, order_farm_id, @@ -9008,27 +8903,18 @@ mod tests { .status, BuyerOrderStatus::Scheduled ); - let records = shared_local_event_records(&paths); - let order_records = records - .iter() - .filter(|record| { - record.source_runtime == SourceRuntime::App - && record - .local_work_json - .as_ref() - .and_then(|payload| payload["record_kind"].as_str()) - == Some(BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND) - }) - .collect::<Vec<_>>(); - assert_eq!(order_records.len(), 1); assert_eq!( - order_records[0].record_id, - format!("app:local_work:order_request:{order_id}") + app_buyer_order_local_work_record_ids(&paths), + vec![format!("app:local_work:order_request:{order_id}")] ); { let state = runtime.lock_state_mut(); let buyer_context = state.state_store.identity_projection().buyer_context(); let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store"); + let buyer_orders = sqlite_store + .load_buyer_orders(&buyer_context) + .expect("buyer orders should reload"); + assert_eq!(buyer_orders.rows.len(), 1); let coordination = sqlite_store .load_buyer_order_coordination_record(&buyer_context, order_id) .expect("buyer order coordination should reload") @@ -9046,46 +8932,68 @@ mod tests { pending_order_sync_payloads(&runtime, buyer_account_id.as_str(), order_id), vec![expected_order_sync_payload] ); + + cleanup_bootstrapped_runtime_paths(&paths); + } + + #[test] + fn runtime_buyer_order_shared_append_failure_is_recoverable_after_restart() { + let (runtime, paths, buyer_account_id, order_id, order_farm_id) = + blocked_buyer_order_runtime("buyer_order_append_failure_restart"); + let expected_order_sync_payload = super::order_sync_payload( + order_id, + order_farm_id, + "place_personal_order", + Some("needs_action"), + ); + unblock_shared_local_events_database(&paths); drop(runtime); let restarted_runtime = restart_runtime(paths.clone()); - let records = shared_local_event_records(&paths); - let order_records = records - .iter() - .filter(|record| { - record.source_runtime == SourceRuntime::App - && record - .local_work_json - .as_ref() - .and_then(|payload| payload["record_kind"].as_str()) - == Some(BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND) - }) - .collect::<Vec<_>>(); - assert_eq!(order_records.len(), 1); assert_eq!( - order_records[0].record_id, - format!("app:local_work:order_request:{order_id}") + app_buyer_order_local_work_record_ids(&paths), + vec![format!("app:local_work:order_request:{order_id}")] ); - let state = restarted_runtime.lock_state_mut(); - let buyer_context = state.state_store.identity_projection().buyer_context(); - let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store"); - let coordination = sqlite_store - .load_buyer_order_coordination_record(&buyer_context, order_id) - .expect("buyer order coordination should reload") - .expect("buyer order coordination should still exist"); - assert_eq!(coordination.state, BuyerOrderCoordinationState::Synced); - assert_eq!(coordination.attempt_count, 2); - assert_eq!(coordination.last_error_message, None); - drop(state); + let summary = restarted_runtime.summary(); + assert_eq!(summary.personal_projection.orders.list.rows.len(), 1); + assert_eq!( + summary.personal_projection.orders.list.rows[0].order_id, + order_id + ); + assert_eq!( + summary + .personal_projection + .orders + .detail + .as_ref() + .expect("buyer order detail should reload after restart") + .order_id, + order_id + ); + { + let state = restarted_runtime.lock_state_mut(); + let buyer_context = state.state_store.identity_projection().buyer_context(); + let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store"); + let buyer_orders = sqlite_store + .load_buyer_orders(&buyer_context) + .expect("buyer orders should reload"); + assert_eq!(buyer_orders.rows.len(), 1); + let coordination = sqlite_store + .load_buyer_order_coordination_record(&buyer_context, order_id) + .expect("buyer order coordination should reload") + .expect("buyer order coordination should still exist"); + assert_eq!(coordination.state, BuyerOrderCoordinationState::Synced); + assert_eq!(coordination.attempt_count, 2); + assert_eq!(coordination.last_error_message, None); + } assert!( !restarted_runtime .retry_pending_personal_order_coordination() .expect("synced buyer order recovery retry should be idempotent") ); assert_eq!( - pending_order_sync_payloads(&restarted_runtime, buyer_account_id.as_str(), order_id) - .len(), - 1 + pending_order_sync_payloads(&restarted_runtime, buyer_account_id.as_str(), order_id), + vec![expected_order_sync_payload] ); cleanup_bootstrapped_runtime_paths(&paths); @@ -12586,6 +12494,138 @@ mod tests { .collect() } + fn app_buyer_order_local_work_record_ids(paths: &AppDesktopRuntimePaths) -> Vec<String> { + shared_local_event_records(paths) + .into_iter() + .filter(|record| { + record.source_runtime == SourceRuntime::App + && record + .local_work_json + .as_ref() + .and_then(|payload| payload["record_kind"].as_str()) + == Some(BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND) + }) + .map(|record| record.record_id) + .collect() + } + + fn blocked_buyer_order_runtime( + label: &str, + ) -> ( + DesktopAppRuntime, + AppDesktopRuntimePaths, + String, + OrderId, + FarmId, + ) { + let (runtime, paths) = bootstrapped_runtime(label); + assert!( + runtime + .generate_local_account(Some("Buyer".to_owned())) + .expect("account should generate") + ); + let buyer_account_id = runtime + .summary() + .settings_account_projection + .selected_account + .as_ref() + .expect("selected account") + .account + .account_id + .clone(); + assert!( + runtime + .select_active_surface(ActiveSurface::Personal) + .expect("surface should switch into marketplace") + ); + let listing_key = "DDDDDDDDDDDDDDDDDDDDDD"; + append_cli_signed_buyer_listing_record_with( + &paths, + "buyer-order-append-failure-listing", + listing_key, + "Buyer Visible Eggs", + 1100, + ); + let product_id = + deterministic_cli_listing_product_id(Some("buyer-visible-seller-pubkey"), listing_key); + assert!( + runtime + .open_personal_product_detail(PersonalSection::Browse, product_id) + .expect("buyer detail should import before lookup") + ); + assert!( + runtime + .add_personal_product_to_cart(PersonalSection::Browse, false) + .expect("buyer product should add to cart") + ); + assert!( + runtime + .save_personal_checkout_draft(BuyerCheckoutDraft { + name: "Casey Buyer".to_owned(), + email: "casey@example.com".to_owned(), + phone: String::new(), + order_note: String::new(), + }) + .expect("buyer checkout draft should save") + ); + block_shared_local_events_database(&paths); + + let error = runtime + .place_personal_order() + .expect_err("blocked local events should fail order completion"); + + assert!(matches!(error, AppSqliteError::LocalEventsSql { .. })); + let summary = runtime.summary(); + assert_eq!( + summary.shell_projection.selected_section, + ShellSection::Personal(PersonalSection::Orders) + ); + assert!(summary.personal_projection.cart.cart.lines.is_empty()); + assert!(!summary.personal_projection.cart.checkout.can_place_order); + assert_eq!(summary.personal_projection.orders.list.rows.len(), 1); + let visible_order_id = summary.personal_projection.orders.list.rows[0].order_id; + let order_detail = summary + .personal_projection + .orders + .detail + .as_ref() + .expect("buyer order detail should remain visible after coordination failure"); + assert_eq!(order_detail.order_id, visible_order_id); + let order_farm_id = order_detail.farm_id; + { + let state = runtime.lock_state_mut(); + let buyer_context = state.state_store.identity_projection().buyer_context(); + let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store"); + let buyer_orders = sqlite_store + .load_buyer_orders(&buyer_context) + .expect("buyer order should persist after coordination failure"); + assert_eq!(buyer_orders.rows.len(), 1); + let order_id = buyer_orders.rows[0].order_id; + assert_eq!(order_id, visible_order_id); + let coordination = sqlite_store + .load_buyer_order_coordination_record(&buyer_context, order_id) + .expect("buyer order coordination should load") + .expect("buyer order coordination should exist"); + assert_eq!(coordination.state, BuyerOrderCoordinationState::Failed); + assert_eq!(coordination.attempt_count, 1); + assert!(coordination.record_id.is_some()); + assert!(coordination.payload_json.is_some()); + assert!(coordination.last_error_message.is_some()); + } + assert!( + pending_order_sync_payloads(&runtime, buyer_account_id.as_str(), visible_order_id) + .is_empty() + ); + + ( + runtime, + paths, + buyer_account_id, + visible_order_id, + order_farm_id, + ) + } + fn block_shared_local_events_database(paths: &AppDesktopRuntimePaths) { let database_path = paths .shared_local_events_database_path()