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:
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")