commit 6c56aed578ec0ba7cd005a3909f3047a439a7e44
parent 1b54111a98cbd402243420846f77bc29b50bbbb1
Author: triesap <tyson@radroots.org>
Date: Mon, 13 Jul 2026 06:27:38 +0000
transport: implement strict satisfaction classes
- expand transport satisfaction classes beyond accepted and delivered
- make delivered satisfaction require delivered evidence only
- add canonical outbox storage labels for new classes
- cover every status and outcome against every satisfaction class
Diffstat:
5 files changed, 374 insertions(+), 126 deletions(-)
diff --git a/crates/outbox/src/model.rs b/crates/outbox/src/model.rs
@@ -214,10 +214,17 @@ impl RadrootsOutboxDeliveryTargetStatus {
| Self::StoredByGateway
| Self::Seen
),
- RadrootsTransportSatisfactionClass::Delivered => matches!(
- self,
- Self::Delivered | Self::Forwarded | Self::StoredByGateway | Self::Seen
- ),
+ RadrootsTransportSatisfactionClass::Forwarded => {
+ matches!(self, Self::Forwarded | Self::Delivered)
+ }
+ RadrootsTransportSatisfactionClass::Stored => matches!(self, Self::StoredByGateway),
+ RadrootsTransportSatisfactionClass::Seen => {
+ matches!(self, Self::Seen | Self::Delivered)
+ }
+ RadrootsTransportSatisfactionClass::Delivered => matches!(self, Self::Delivered),
+ RadrootsTransportSatisfactionClass::DurableOrObserved => {
+ matches!(self, Self::StoredByGateway | Self::Seen | Self::Delivered)
+ }
}
}
@@ -648,7 +655,7 @@ mod tests {
"forwarded",
false,
true,
- true,
+ false,
false,
false,
),
@@ -657,7 +664,7 @@ mod tests {
"stored_by_gateway",
false,
true,
- true,
+ false,
false,
false,
),
@@ -666,7 +673,7 @@ mod tests {
"seen",
false,
true,
- true,
+ false,
false,
false,
),
diff --git a/crates/outbox/src/store.rs b/crates/outbox/src/store.rs
@@ -2464,7 +2464,25 @@ fn satisfaction_policy_storage_value(policy: &RadrootsTransportSatisfactionPolic
fn satisfaction_class_storage_value(class: RadrootsTransportSatisfactionClass) -> &'static str {
match class {
RadrootsTransportSatisfactionClass::Accepted => "accepted",
+ RadrootsTransportSatisfactionClass::Forwarded => "forwarded",
+ RadrootsTransportSatisfactionClass::Stored => "stored",
+ RadrootsTransportSatisfactionClass::Seen => "seen",
RadrootsTransportSatisfactionClass::Delivered => "delivered",
+ RadrootsTransportSatisfactionClass::DurableOrObserved => "durable_or_observed",
+ }
+}
+
+fn parse_satisfaction_class_storage_value(
+ value: &str,
+) -> Option<RadrootsTransportSatisfactionClass> {
+ match value {
+ "accepted" => Some(RadrootsTransportSatisfactionClass::Accepted),
+ "forwarded" => Some(RadrootsTransportSatisfactionClass::Forwarded),
+ "stored" => Some(RadrootsTransportSatisfactionClass::Stored),
+ "seen" => Some(RadrootsTransportSatisfactionClass::Seen),
+ "delivered" => Some(RadrootsTransportSatisfactionClass::Delivered),
+ "durable_or_observed" => Some(RadrootsTransportSatisfactionClass::DurableOrObserved),
+ _ => None,
}
}
@@ -2472,52 +2490,54 @@ fn parse_satisfaction_policy(
value: &str,
required_success_count: i64,
) -> Result<RadrootsTransportSatisfactionPolicy, RadrootsOutboxError> {
- match value {
- "no_wait" if required_success_count == 0 => {
- Ok(RadrootsTransportSatisfactionPolicy::no_wait())
- }
- "all_accepted" => Ok(RadrootsTransportSatisfactionPolicy::all_accepted()),
- "any_accepted" => Ok(RadrootsTransportSatisfactionPolicy::any_accepted()),
- "all_delivered" => Ok(RadrootsTransportSatisfactionPolicy::all_delivered()),
- "any_delivered" => Ok(RadrootsTransportSatisfactionPolicy::any_delivered()),
- stored if stored == format!("quorum_accepted:{required_success_count}") => {
- Ok(RadrootsTransportSatisfactionPolicy::quorum_accepted(
- required_count_u16(required_success_count)?,
- ))
- }
- stored if stored == format!("quorum_delivered:{required_success_count}") => {
- Ok(RadrootsTransportSatisfactionPolicy::quorum_delivered(
- required_count_u16(required_success_count)?,
- ))
+ if value == "no_wait" && required_success_count == 0 {
+ return Ok(RadrootsTransportSatisfactionPolicy::no_wait());
+ }
+ if let Some(class) = value
+ .strip_prefix("all_")
+ .and_then(parse_satisfaction_class_storage_value)
+ {
+ return Ok(RadrootsTransportSatisfactionPolicy::All { class });
+ }
+ if let Some(class) = value
+ .strip_prefix("any_")
+ .and_then(parse_satisfaction_class_storage_value)
+ {
+ return Ok(RadrootsTransportSatisfactionPolicy::Any { class });
+ }
+ if let Some((class_label, threshold)) = value
+ .strip_prefix("quorum_")
+ .and_then(|stored| stored.split_once(':'))
+ {
+ if threshold == required_success_count.to_string()
+ && let Some(class) = parse_satisfaction_class_storage_value(class_label)
+ {
+ return Ok(RadrootsTransportSatisfactionPolicy::Quorum {
+ class,
+ threshold: required_count_u16(required_success_count)?,
+ });
}
- stored if stored.starts_with("required_accepted:") => parse_required_target_policy(
- stored,
- "required_accepted:",
- RadrootsTransportSatisfactionClass::Accepted,
- required_success_count,
- ),
- stored if stored.starts_with("required_delivered:") => parse_required_target_policy(
- stored,
- "required_delivered:",
- RadrootsTransportSatisfactionClass::Delivered,
- required_success_count,
- ),
- _ => Err(RadrootsOutboxError::InvalidStoredEnum {
- field: "outbox_delivery_plan.satisfaction_policy",
- value: value.to_owned(),
- }),
}
+ if let Some((class_label, fingerprints)) = value
+ .strip_prefix("required_")
+ .and_then(|stored| stored.split_once(':'))
+ && let Some(class) = parse_satisfaction_class_storage_value(class_label)
+ {
+ return parse_required_target_policy(value, fingerprints, class, required_success_count);
+ }
+ Err(RadrootsOutboxError::InvalidStoredEnum {
+ field: "outbox_delivery_plan.satisfaction_policy",
+ value: value.to_owned(),
+ })
}
fn parse_required_target_policy(
stored: &str,
- prefix: &str,
+ fingerprints: &str,
class: RadrootsTransportSatisfactionClass,
required_success_count: i64,
) -> Result<RadrootsTransportSatisfactionPolicy, RadrootsOutboxError> {
- let targets = stored
- .strip_prefix(prefix)
- .expect("stored required target policy prefix")
+ let targets = fingerprints
.split(',')
.map(RadrootsTransportTargetFingerprint::parse)
.collect::<Result<Vec<_>, _>>()?;
@@ -2644,6 +2664,43 @@ mod tests {
}
#[test]
+ fn satisfaction_policy_storage_round_trips_all_transport_classes() {
+ for (policy, stored, required_count) in [
+ (
+ RadrootsTransportSatisfactionPolicy::any_forwarded(),
+ "any_forwarded",
+ 1,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::all_stored(),
+ "all_stored",
+ 2,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::quorum_seen(2),
+ "quorum_seen:2",
+ 2,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::any_durable_or_observed(),
+ "any_durable_or_observed",
+ 1,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::quorum_delivered(2),
+ "quorum_delivered:2",
+ 2,
+ ),
+ ] {
+ assert_eq!(satisfaction_policy_storage_value(&policy), stored);
+ assert_eq!(
+ parse_satisfaction_policy(stored, required_count).expect("parse policy"),
+ policy
+ );
+ }
+ }
+
+ #[test]
fn required_target_policy_idempotency_is_order_independent() {
let first = nostr_target("wss://required-one.example");
let second = nostr_target("wss://required-two.example");
diff --git a/crates/transport/src/delivery.rs b/crates/transport/src/delivery.rs
@@ -11,7 +11,11 @@ use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RadrootsTransportSatisfactionClass {
Accepted,
+ Forwarded,
+ Stored,
+ Seen,
Delivered,
+ DurableOrObserved,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -58,6 +62,63 @@ impl RadrootsTransportSatisfactionPolicy {
}
}
+ pub fn any_forwarded() -> Self {
+ Self::Any {
+ class: RadrootsTransportSatisfactionClass::Forwarded,
+ }
+ }
+
+ pub fn all_forwarded() -> Self {
+ Self::All {
+ class: RadrootsTransportSatisfactionClass::Forwarded,
+ }
+ }
+
+ pub fn quorum_forwarded(threshold: u16) -> Self {
+ Self::Quorum {
+ class: RadrootsTransportSatisfactionClass::Forwarded,
+ threshold,
+ }
+ }
+
+ pub fn any_stored() -> Self {
+ Self::Any {
+ class: RadrootsTransportSatisfactionClass::Stored,
+ }
+ }
+
+ pub fn all_stored() -> Self {
+ Self::All {
+ class: RadrootsTransportSatisfactionClass::Stored,
+ }
+ }
+
+ pub fn quorum_stored(threshold: u16) -> Self {
+ Self::Quorum {
+ class: RadrootsTransportSatisfactionClass::Stored,
+ threshold,
+ }
+ }
+
+ pub fn any_seen() -> Self {
+ Self::Any {
+ class: RadrootsTransportSatisfactionClass::Seen,
+ }
+ }
+
+ pub fn all_seen() -> Self {
+ Self::All {
+ class: RadrootsTransportSatisfactionClass::Seen,
+ }
+ }
+
+ pub fn quorum_seen(threshold: u16) -> Self {
+ Self::Quorum {
+ class: RadrootsTransportSatisfactionClass::Seen,
+ threshold,
+ }
+ }
+
pub fn any_delivered() -> Self {
Self::Any {
class: RadrootsTransportSatisfactionClass::Delivered,
@@ -77,6 +138,25 @@ impl RadrootsTransportSatisfactionPolicy {
}
}
+ pub fn any_durable_or_observed() -> Self {
+ Self::Any {
+ class: RadrootsTransportSatisfactionClass::DurableOrObserved,
+ }
+ }
+
+ pub fn all_durable_or_observed() -> Self {
+ Self::All {
+ class: RadrootsTransportSatisfactionClass::DurableOrObserved,
+ }
+ }
+
+ pub fn quorum_durable_or_observed(threshold: u16) -> Self {
+ Self::Quorum {
+ class: RadrootsTransportSatisfactionClass::DurableOrObserved,
+ threshold,
+ }
+ }
+
pub fn required_targets(
class: RadrootsTransportSatisfactionClass,
mut targets: Vec<RadrootsTransportTargetFingerprint>,
diff --git a/crates/transport/src/status.rs b/crates/transport/src/status.rs
@@ -33,10 +33,7 @@ impl RadrootsTransportDeliveryTargetStatus {
}
pub fn counts_as_delivered_satisfaction(self) -> bool {
- matches!(
- self,
- Self::Delivered | Self::Forwarded | Self::StoredByGateway | Self::Seen
- )
+ matches!(self, Self::Delivered)
}
pub fn counts_as_satisfied(
@@ -45,9 +42,19 @@ impl RadrootsTransportDeliveryTargetStatus {
) -> bool {
match satisfaction_class {
RadrootsTransportSatisfactionClass::Accepted => self.counts_as_accepted_satisfaction(),
+ RadrootsTransportSatisfactionClass::Forwarded => {
+ matches!(self, Self::Forwarded | Self::Delivered)
+ }
+ RadrootsTransportSatisfactionClass::Stored => matches!(self, Self::StoredByGateway),
+ RadrootsTransportSatisfactionClass::Seen => {
+ matches!(self, Self::Seen | Self::Delivered)
+ }
RadrootsTransportSatisfactionClass::Delivered => {
self.counts_as_delivered_satisfaction()
}
+ RadrootsTransportSatisfactionClass::DurableOrObserved => {
+ matches!(self, Self::StoredByGateway | Self::Seen | Self::Delivered)
+ }
}
}
diff --git a/crates/transport/tests/transport.rs b/crates/transport/tests/transport.rs
@@ -150,6 +150,10 @@ fn satisfaction_policy_counts_target_statuses() {
let any = RadrootsTransportSatisfactionPolicy::any_accepted();
let two = RadrootsTransportSatisfactionPolicy::quorum_accepted(2);
let delivered = RadrootsTransportSatisfactionPolicy::quorum_delivered(2);
+ let forwarded = RadrootsTransportSatisfactionPolicy::any_forwarded();
+ let stored = RadrootsTransportSatisfactionPolicy::all_stored();
+ let seen = RadrootsTransportSatisfactionPolicy::quorum_seen(2);
+ let durable_or_observed = RadrootsTransportSatisfactionPolicy::any_durable_or_observed();
assert_eq!(no_wait.required_target_count(0).expect("no wait"), 0);
assert_eq!(no_wait.required_target_count(3).expect("no wait"), 0);
@@ -169,6 +173,58 @@ fn satisfaction_policy_counts_target_statuses() {
Some(RadrootsTransportSatisfactionClass::Delivered)
);
assert_eq!(
+ forwarded.target_satisfaction_class(),
+ Some(RadrootsTransportSatisfactionClass::Forwarded)
+ );
+ assert_eq!(
+ stored.target_satisfaction_class(),
+ Some(RadrootsTransportSatisfactionClass::Stored)
+ );
+ assert_eq!(
+ seen.target_satisfaction_class(),
+ Some(RadrootsTransportSatisfactionClass::Seen)
+ );
+ assert_eq!(
+ durable_or_observed.target_satisfaction_class(),
+ Some(RadrootsTransportSatisfactionClass::DurableOrObserved)
+ );
+ for (policy, class) in [
+ (
+ RadrootsTransportSatisfactionPolicy::all_forwarded(),
+ RadrootsTransportSatisfactionClass::Forwarded,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::quorum_forwarded(2),
+ RadrootsTransportSatisfactionClass::Forwarded,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::any_stored(),
+ RadrootsTransportSatisfactionClass::Stored,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::quorum_stored(2),
+ RadrootsTransportSatisfactionClass::Stored,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::any_seen(),
+ RadrootsTransportSatisfactionClass::Seen,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::all_seen(),
+ RadrootsTransportSatisfactionClass::Seen,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::all_durable_or_observed(),
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ),
+ (
+ RadrootsTransportSatisfactionPolicy::quorum_durable_or_observed(2),
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ),
+ ] {
+ assert_eq!(policy.target_satisfaction_class(), Some(class));
+ }
+ assert_eq!(
any.is_satisfied_by(0, 0).expect_err("zero target set"),
RadrootsTransportError::InvalidSatisfactionPolicy
);
@@ -636,51 +692,82 @@ fn satisfaction_and_target_status_cover_all_contract_states() {
RadrootsTransportError::InvalidSatisfactionPolicy
);
- let statuses = [
- RadrootsTransportDeliveryTargetStatus::Pending,
- RadrootsTransportDeliveryTargetStatus::Accepted,
- RadrootsTransportDeliveryTargetStatus::Delivered,
- RadrootsTransportDeliveryTargetStatus::Forwarded,
- RadrootsTransportDeliveryTargetStatus::StoredByGateway,
- RadrootsTransportDeliveryTargetStatus::Seen,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable,
- RadrootsTransportDeliveryTargetStatus::SkippedPolicyDenied,
- RadrootsTransportDeliveryTargetStatus::FailedRetryable,
- RadrootsTransportDeliveryTargetStatus::FailedTerminal,
+ let classes = [
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ RadrootsTransportSatisfactionClass::Stored,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::Delivered,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ];
+ let cases: &[(
+ RadrootsTransportDeliveryTargetStatus,
+ &[RadrootsTransportSatisfactionClass],
+ )] = &[
+ (RadrootsTransportDeliveryTargetStatus::Pending, &[]),
+ (
+ RadrootsTransportDeliveryTargetStatus::Accepted,
+ &[RadrootsTransportSatisfactionClass::Accepted],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::Delivered,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::Delivered,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::Forwarded,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ ],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::StoredByGateway,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Stored,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::Seen,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
+ &[],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::PreviewUnavailable,
+ &[],
+ ),
+ (
+ RadrootsTransportDeliveryTargetStatus::SkippedPolicyDenied,
+ &[],
+ ),
+ (RadrootsTransportDeliveryTargetStatus::FailedRetryable, &[]),
+ (RadrootsTransportDeliveryTargetStatus::FailedTerminal, &[]),
];
assert!(RadrootsTransportDeliveryTargetStatus::Pending.is_ready_for_attempt());
assert!(RadrootsTransportDeliveryTargetStatus::FailedRetryable.is_ready_for_attempt());
- assert!(
- RadrootsTransportDeliveryTargetStatus::Accepted
- .counts_as_satisfied(RadrootsTransportSatisfactionClass::Accepted)
- );
- assert!(
- !RadrootsTransportDeliveryTargetStatus::Accepted
- .counts_as_satisfied(RadrootsTransportSatisfactionClass::Delivered)
- );
- for status in [
- RadrootsTransportDeliveryTargetStatus::Delivered,
- RadrootsTransportDeliveryTargetStatus::Forwarded,
- RadrootsTransportDeliveryTargetStatus::StoredByGateway,
- RadrootsTransportDeliveryTargetStatus::Seen,
- ] {
- assert!(status.counts_as_satisfied(RadrootsTransportSatisfactionClass::Accepted));
- assert!(status.counts_as_satisfied(RadrootsTransportSatisfactionClass::Delivered));
+ for (status, satisfied_classes) in cases {
+ for class in classes {
+ assert_eq!(
+ status.counts_as_satisfied(class),
+ satisfied_classes.contains(&class),
+ "{status:?} / {class:?}"
+ );
+ }
}
- assert!(
- statuses
- .iter()
- .filter(|status| !matches!(
- status,
- RadrootsTransportDeliveryTargetStatus::Accepted
- | RadrootsTransportDeliveryTargetStatus::Delivered
- | RadrootsTransportDeliveryTargetStatus::Forwarded
- | RadrootsTransportDeliveryTargetStatus::StoredByGateway
- | RadrootsTransportDeliveryTargetStatus::Seen
- ))
- .all(|status| !status.counts_as_satisfied(RadrootsTransportSatisfactionClass::Accepted))
- );
assert!(RadrootsTransportDeliveryTargetStatus::PreviewUnavailable.is_deferred_preview());
assert!(RadrootsTransportDeliveryTargetStatus::FailedRetryable.is_retryable_failure());
assert!(RadrootsTransportDeliveryTargetStatus::FailedTerminal.is_terminal_failure());
@@ -688,120 +775,130 @@ fn satisfaction_and_target_status_cover_all_contract_states() {
#[test]
fn typed_outcome_kinds_drive_status_and_satisfaction_semantics() {
+ let classes = [
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ RadrootsTransportSatisfactionClass::Stored,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::Delivered,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ];
let cases = [
(
RadrootsTransportOutcomeKind::Accepted,
"accepted",
RadrootsTransportDeliveryTargetStatus::Accepted,
- true,
- false,
+ &[RadrootsTransportSatisfactionClass::Accepted] as &[_],
),
(
RadrootsTransportOutcomeKind::DuplicateAccepted,
"duplicate_accepted",
RadrootsTransportDeliveryTargetStatus::Accepted,
- true,
- false,
+ &[RadrootsTransportSatisfactionClass::Accepted],
),
(
RadrootsTransportOutcomeKind::Delivered,
"delivered",
RadrootsTransportDeliveryTargetStatus::Delivered,
- true,
- true,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::Delivered,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
),
(
RadrootsTransportOutcomeKind::Forwarded,
"forwarded",
RadrootsTransportDeliveryTargetStatus::Forwarded,
- true,
- true,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Forwarded,
+ ],
),
(
RadrootsTransportOutcomeKind::StoredByGateway,
"stored_by_gateway",
RadrootsTransportDeliveryTargetStatus::StoredByGateway,
- true,
- true,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Stored,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
),
(
RadrootsTransportOutcomeKind::Seen,
"seen",
RadrootsTransportDeliveryTargetStatus::Seen,
- true,
- true,
+ &[
+ RadrootsTransportSatisfactionClass::Accepted,
+ RadrootsTransportSatisfactionClass::Seen,
+ RadrootsTransportSatisfactionClass::DurableOrObserved,
+ ],
),
(
RadrootsTransportOutcomeKind::DeferredUntilImplemented,
"deferred_until_implemented",
RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::Rejected,
"rejected",
RadrootsTransportDeliveryTargetStatus::FailedTerminal,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::RouteUnavailable,
"route_unavailable",
RadrootsTransportDeliveryTargetStatus::FailedTerminal,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::PayloadTooLarge,
"payload_too_large",
RadrootsTransportDeliveryTargetStatus::FailedTerminal,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::PolicyDenied,
"policy_denied",
RadrootsTransportDeliveryTargetStatus::SkippedPolicyDenied,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::Timeout,
"timeout",
RadrootsTransportDeliveryTargetStatus::FailedRetryable,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::ConnectionFailed,
"connection_failed",
RadrootsTransportDeliveryTargetStatus::FailedRetryable,
- false,
- false,
+ &[],
),
(
RadrootsTransportOutcomeKind::TransportUnavailable,
"transport_unavailable",
RadrootsTransportDeliveryTargetStatus::FailedRetryable,
- false,
- false,
+ &[],
),
];
- for (kind, label, status, accepted, delivered) in cases {
+ for (kind, label, status, satisfied_classes) in cases {
let outcome = RadrootsTransportOutcome::new(kind).with_message("transport detail");
assert_eq!(kind.as_str(), label);
assert_eq!(outcome.kind, kind);
assert_eq!(outcome.status, status);
- assert_eq!(
- kind.counts_as_satisfied(RadrootsTransportSatisfactionClass::Accepted),
- accepted
- );
- assert_eq!(
- kind.counts_as_satisfied(RadrootsTransportSatisfactionClass::Delivered),
- delivered
- );
+ for class in classes {
+ assert_eq!(
+ kind.counts_as_satisfied(class),
+ satisfied_classes.contains(&class),
+ "{kind:?} / {class:?}"
+ );
+ }
assert_eq!(outcome.message.as_deref(), Some("transport detail"));
}