cli

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

commit 4ec2fbfd6b8e6e59bb7c39cbfe95423733ad2310
parent 0b275639a022405ee3a45103ae27701a18259c68
Author: triesap <tyson@radroots.org>
Date:   Tue, 26 May 2026 09:28:49 +0000

market: require verified primary bin readiness

Diffstat:
Msrc/domain/runtime.rs | 45++++++++++++++++++++++++++++++++++++++++++++-
Msrc/operation_market.rs | 2++
Msrc/runtime/find.rs | 2++
Msrc/runtime/listing.rs | 2++
Msrc/runtime/order.rs | 1+
Mtests/target_cli.rs | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -1058,6 +1058,7 @@ pub struct MarketReadinessView { pub protocol_valid: bool, pub marketplace_eligible: bool, pub checkout_enabled: bool, + pub primary_bin_verified: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub reason_codes: Vec<String>, } @@ -1068,6 +1069,7 @@ impl MarketReadinessView { protocol_valid: false, marketplace_eligible: false, checkout_enabled: false, + primary_bin_verified: false, reason_codes: vec![reason_code.into()], } } @@ -1075,6 +1077,7 @@ impl MarketReadinessView { pub fn from_market_projection( listing_addr: Option<&str>, primary_bin_id: Option<&str>, + verified_primary_bin_id: Option<&str>, title: Option<&str>, category: Option<&str>, available_amount: Option<i64>, @@ -1092,13 +1095,19 @@ impl MarketReadinessView { let inventory_available = available_amount.is_some_and(|amount| amount > 0); let primary_bin_available = primary_bin_id.is_some_and(|primary_bin_id| !primary_bin_id.trim().is_empty()); + let primary_bin_verified = primary_bin_available + && primary_bin_id.is_some_and(|primary_bin_id| { + verified_primary_bin_id.is_some_and(|verified_primary_bin_id| { + verified_primary_bin_id.trim() == primary_bin_id.trim() + }) + }); let price_available = price_amount.is_finite() && price_amount > 0.0 && !price_currency.trim().is_empty() && price_per_amount.is_finite() && price_per_amount > 0.0; let checkout_enabled = - marketplace_eligible && inventory_available && primary_bin_available && price_available; + marketplace_eligible && inventory_available && primary_bin_verified && price_available; let mut reason_codes = Vec::new(); if !protocol_valid { reason_codes.push("listing_protocol_invalid".to_owned()); @@ -1113,6 +1122,8 @@ impl MarketReadinessView { } if !primary_bin_available { reason_codes.push("listing_primary_bin_missing".to_owned()); + } else if !primary_bin_verified { + reason_codes.push("listing_primary_bin_invalid".to_owned()); } if !price_available { reason_codes.push("listing_price_unavailable".to_owned()); @@ -1122,6 +1133,7 @@ impl MarketReadinessView { protocol_valid, marketplace_eligible, checkout_enabled, + primary_bin_verified, reason_codes, } } @@ -1138,6 +1150,7 @@ mod market_readiness_tests { let enabled = MarketReadinessView::from_market_projection( Some(LISTING_ADDR), Some("bin-1"), + Some("bin-1"), Some("Eggs"), Some("eggs"), Some(1), @@ -1148,11 +1161,13 @@ mod market_readiness_tests { assert!(enabled.protocol_valid); assert!(enabled.marketplace_eligible); assert!(enabled.checkout_enabled); + assert!(enabled.primary_bin_verified); assert!(enabled.reason_codes.is_empty()); let invalid = MarketReadinessView::from_market_projection( None, Some("bin-1"), + Some("bin-1"), Some("Eggs"), Some("eggs"), Some(1), @@ -1163,11 +1178,13 @@ mod market_readiness_tests { assert!(!invalid.protocol_valid); assert!(!invalid.marketplace_eligible); assert!(!invalid.checkout_enabled); + assert!(invalid.primary_bin_verified); assert_eq!(invalid.reason_codes, vec!["listing_protocol_invalid"]); let ineligible = MarketReadinessView::from_market_projection( Some(LISTING_ADDR), Some("bin-1"), + Some("bin-1"), Some(" "), Some("eggs"), Some(1), @@ -1178,6 +1195,7 @@ mod market_readiness_tests { assert!(ineligible.protocol_valid); assert!(!ineligible.marketplace_eligible); assert!(!ineligible.checkout_enabled); + assert!(ineligible.primary_bin_verified); assert_eq!( ineligible.reason_codes, vec!["listing_marketplace_ineligible"] @@ -1186,6 +1204,7 @@ mod market_readiness_tests { let checkout_disabled = MarketReadinessView::from_market_projection( Some(LISTING_ADDR), Some("bin-1"), + Some("bin-1"), Some("Eggs"), Some("eggs"), Some(0), @@ -1196,6 +1215,7 @@ mod market_readiness_tests { assert!(checkout_disabled.protocol_valid); assert!(checkout_disabled.marketplace_eligible); assert!(!checkout_disabled.checkout_enabled); + assert!(checkout_disabled.primary_bin_verified); assert_eq!( checkout_disabled.reason_codes, vec!["listing_checkout_disabled", "listing_inventory_unavailable"] @@ -1204,6 +1224,7 @@ mod market_readiness_tests { let primary_bin_missing = MarketReadinessView::from_market_projection( Some(LISTING_ADDR), None, + Some("bin-1"), Some("Eggs"), Some("eggs"), Some(1), @@ -1214,6 +1235,7 @@ mod market_readiness_tests { assert!(primary_bin_missing.protocol_valid); assert!(primary_bin_missing.marketplace_eligible); assert!(!primary_bin_missing.checkout_enabled); + assert!(!primary_bin_missing.primary_bin_verified); assert_eq!( primary_bin_missing.reason_codes, vec!["listing_checkout_disabled", "listing_primary_bin_missing"] @@ -1222,6 +1244,7 @@ mod market_readiness_tests { let primary_bin_blank = MarketReadinessView::from_market_projection( Some(LISTING_ADDR), Some(" "), + Some("bin-1"), Some("Eggs"), Some("eggs"), Some(1), @@ -1233,6 +1256,26 @@ mod market_readiness_tests { primary_bin_blank.reason_codes, vec!["listing_checkout_disabled", "listing_primary_bin_missing"] ); + + let primary_bin_invalid = MarketReadinessView::from_market_projection( + Some(LISTING_ADDR), + Some("missing-bin"), + Some("bin-1"), + Some("Eggs"), + Some("eggs"), + Some(1), + 6.0, + "USD", + 1.0, + ); + assert!(primary_bin_invalid.protocol_valid); + assert!(primary_bin_invalid.marketplace_eligible); + assert!(!primary_bin_invalid.checkout_enabled); + assert!(!primary_bin_invalid.primary_bin_verified); + assert_eq!( + primary_bin_invalid.reason_codes, + vec!["listing_checkout_disabled", "listing_primary_bin_invalid"] + ); } } diff --git a/src/operation_market.rs b/src/operation_market.rs @@ -592,6 +592,7 @@ mod tests { protocol_valid: true, marketplace_eligible: true, checkout_enabled: true, + primary_bin_verified: true, reason_codes: Vec::new(), } } @@ -601,6 +602,7 @@ mod tests { protocol_valid: true, marketplace_eligible: true, checkout_enabled: false, + primary_bin_verified: true, reason_codes: vec![ "listing_checkout_disabled".to_owned(), "listing_inventory_unavailable".to_owned(), diff --git a/src/runtime/find.rs b/src/runtime/find.rs @@ -82,6 +82,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, .map(|row| { let listing_addr = row.listing_addr.and_then(non_empty); let primary_bin_id = row.primary_bin_id.and_then(non_empty); + let verified_primary_bin_id = row.verified_primary_bin_id.and_then(non_empty); let available_amount = row.qty_avail; let price_amount = row.price_amt; let price_currency = row.price_currency; @@ -89,6 +90,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, let readiness = MarketReadinessView::from_market_projection( listing_addr.as_deref(), primary_bin_id.as_deref(), + verified_primary_bin_id.as_deref(), Some(row.title.as_str()), Some(row.category.as_str()), available_amount, diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -1656,6 +1656,7 @@ pub fn get( let listing_addr = row.listing_addr.and_then(non_empty); let primary_bin_id = row.primary_bin_id.and_then(non_empty); + let verified_primary_bin_id = row.verified_primary_bin_id.and_then(non_empty); let available_amount = row.qty_avail; let price_amount = row.price_amt; let price_currency = row.price_currency; @@ -1663,6 +1664,7 @@ pub fn get( let readiness = MarketReadinessView::from_market_projection( listing_addr.as_deref(), primary_bin_id.as_deref(), + verified_primary_bin_id.as_deref(), Some(row.title.as_str()), Some(row.category.as_str()), available_amount, diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -9092,6 +9092,7 @@ fn trade_product_listing_addr_filter(listing_addr: &str) -> ITradeProductFieldsF price_qty_unit: None, listing_addr: Some(listing_addr.to_owned()), primary_bin_id: None, + verified_primary_bin_id: None, notes: None, } } diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -5633,6 +5633,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { assert_eq!(result["protocol_valid"], true); assert_eq!(result["marketplace_eligible"], true); assert_eq!(result["checkout_enabled"], true); + assert_eq!(result["primary_bin_verified"], true); assert!(result.get("reason_codes").is_none()); assert!( search["result"]["actions"] @@ -5654,6 +5655,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { assert_eq!(listing["result"]["protocol_valid"], true); assert_eq!(listing["result"]["marketplace_eligible"], true); assert_eq!(listing["result"]["checkout_enabled"], true); + assert_eq!(listing["result"]["primary_bin_verified"], true); assert!( listing["result"]["actions"] .as_array() @@ -5670,6 +5672,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { assert_eq!(disabled_result["protocol_valid"], true); assert_eq!(disabled_result["marketplace_eligible"], true); assert_eq!(disabled_result["checkout_enabled"], false); + assert_eq!(disabled_result["primary_bin_verified"], true); assert_eq!( disabled_result["reason_codes"][0], "listing_checkout_disabled" @@ -5697,6 +5700,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { assert_eq!(disabled_listing["result"]["protocol_valid"], true); assert_eq!(disabled_listing["result"]["marketplace_eligible"], true); assert_eq!(disabled_listing["result"]["checkout_enabled"], false); + assert_eq!(disabled_listing["result"]["primary_bin_verified"], true); assert_eq!( disabled_listing["result"]["reason_codes"][0], "listing_checkout_disabled" @@ -5716,6 +5720,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { let no_bin_result = &no_bin_search["result"]["results"][0]; assert_eq!(no_bin_result["primary_bin_id"], Value::Null); assert_eq!(no_bin_result["checkout_enabled"], false); + assert_eq!(no_bin_result["primary_bin_verified"], false); assert_eq!( no_bin_result["reason_codes"][0], "listing_checkout_disabled" @@ -5742,6 +5747,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { ]); assert_eq!(no_bin_listing["result"]["primary_bin_id"], Value::Null); assert_eq!(no_bin_listing["result"]["checkout_enabled"], false); + assert_eq!(no_bin_listing["result"]["primary_bin_verified"], false); assert_eq!( no_bin_listing["result"]["reason_codes"][1], "listing_primary_bin_missing" @@ -5753,6 +5759,51 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { .is_none_or(Vec::is_empty) ); + update_orderable_listing_primary_bin_id(&sandbox, LISTING_ADDR, Some("missing-bin")); + + let invalid_bin_search = + sandbox.json_success(&["--format", "json", "market", "product", "search", "eggs"]); + let invalid_bin_result = &invalid_bin_search["result"]["results"][0]; + assert_eq!(invalid_bin_result["primary_bin_id"], "missing-bin"); + assert_eq!(invalid_bin_result["checkout_enabled"], false); + assert_eq!(invalid_bin_result["primary_bin_verified"], false); + assert_eq!( + invalid_bin_result["reason_codes"][1], + "listing_primary_bin_invalid" + ); + assert!( + invalid_bin_search["result"]["actions"] + .as_array() + .expect("invalid-bin search actions") + .iter() + .all(|action| action != "radroots basket create") + ); + + let invalid_bin_listing = sandbox.json_success(&[ + "--format", + "json", + "market", + "listing", + "get", + "pasture-eggs", + ]); + assert_eq!( + invalid_bin_listing["result"]["primary_bin_id"], + "missing-bin" + ); + assert_eq!(invalid_bin_listing["result"]["checkout_enabled"], false); + assert_eq!(invalid_bin_listing["result"]["primary_bin_verified"], false); + assert_eq!( + invalid_bin_listing["result"]["reason_codes"][1], + "listing_primary_bin_invalid" + ); + assert!( + invalid_bin_listing["result"] + .get("actions") + .and_then(Value::as_array) + .is_none_or(Vec::is_empty) + ); + update_orderable_listing_primary_bin_id(&sandbox, LISTING_ADDR, Some("bin-1")); let restored_listing = sandbox.json_success(&[ @@ -5765,6 +5816,7 @@ fn market_checkout_readiness_gates_buyer_intent_actions() { ]); assert_eq!(restored_listing["result"]["primary_bin_id"], "bin-1"); assert_eq!(restored_listing["result"]["checkout_enabled"], true); + assert_eq!(restored_listing["result"]["primary_bin_verified"], true); assert!( restored_listing["result"] .get("reason_codes")