commit 4dce5873dedc247e3dca34352a8ebba5be9cb89f
parent 5a0764a86d774f6227af846fa3dafbadb7d3dd4e
Author: triesap <tyson@radroots.org>
Date: Mon, 13 Jul 2026 06:49:46 +0000
sdk: enforce accepted proxy satisfaction
- reject non-accepted satisfaction classes before daemon proxy publish
- update proxy completion fixtures for accepted delivery protocol semantics
- cover forwarded stored seen delivered and durable proxy rejection
- refresh source-boundary witnesses for the accepted-class gate
Diffstat:
4 files changed, 66 insertions(+), 52 deletions(-)
diff --git a/crates/sdk/src/sync_runtime.rs b/crates/sdk/src/sync_runtime.rs
@@ -1208,7 +1208,7 @@ async fn proxy_delivery_policy(
.iter()
.filter(|target| target.delivery_plan_id == active_delivery_plan_id)
.collect::<Vec<_>>();
- reject_delivered_proxy_satisfaction(&plan.satisfaction_policy)?;
+ reject_non_accepted_proxy_satisfaction(&plan.satisfaction_policy)?;
let ready_target_count = active_targets
.iter()
.filter(|target| target.status.is_ready_for_attempt())
@@ -1249,7 +1249,7 @@ fn proxy_delivery_policy_from_remaining(
required_remaining_targets: Option<&[RadrootsTransportTargetFingerprint]>,
satisfaction_policy: &RadrootsTransportSatisfactionPolicy,
) -> Result<TransportPublishDeliveryPolicy, RadrootsSdkError> {
- reject_delivered_proxy_satisfaction(satisfaction_policy)?;
+ reject_non_accepted_proxy_satisfaction(satisfaction_policy)?;
if required_remaining == 0 {
return Ok(TransportPublishDeliveryPolicy::Any);
}
@@ -1333,11 +1333,12 @@ fn proxy_required_remaining_targets(
}
#[cfg(all(feature = "runtime", feature = "radrootsd-proxy"))]
-fn reject_delivered_proxy_satisfaction(
+fn reject_non_accepted_proxy_satisfaction(
satisfaction_policy: &RadrootsTransportSatisfactionPolicy,
) -> Result<(), RadrootsSdkError> {
- if satisfaction_policy.target_satisfaction_class()
- == Some(RadrootsTransportSatisfactionClass::Delivered)
+ if satisfaction_policy
+ .target_satisfaction_class()
+ .is_some_and(|class| class != RadrootsTransportSatisfactionClass::Accepted)
{
return Err(RadrootsSdkError::InvalidRequest {
message: "radrootsd proxy publish only supports accepted-class satisfaction policies"
@@ -1528,7 +1529,7 @@ async fn complete_proxy_delivery_target(
outcome: &TransportPublishTargetOutcome,
now_ms: i64,
) -> Result<(), RadrootsSdkError> {
- if outcome.outcome_kind.counts_toward_satisfaction() {
+ if outcome.outcome_kind.counts_toward_accepted_delivery() {
sync.sdk
._outbox
.mark_delivery_target_accepted(
diff --git a/crates/sdk/tests/source_boundary.rs b/crates/sdk/tests/source_boundary.rs
@@ -1621,7 +1621,7 @@ fn sdk_proxy_surfaces_reject_removed_daemon_publish_proxy_identifiers() {
"assert!(!stored_before.event_store_ingested)",
"assert!(!stored.event_store_ingested)",
"with_timeout(Duration::from_millis(50))",
- "proxy_delivery_policy_rejects_delivered_satisfaction_before_daemon_publish",
+ "proxy_delivery_policy_rejects_non_accepted_satisfaction_before_daemon_publish",
"proxy_outbox_target_conversion_preserves_nostr_scope_and_label",
"proxy_completion_matches_duplicate_endpoint_targets_by_scope",
"push_proxy_event_receipt_preserves_daemon_target_metadata",
@@ -1657,8 +1657,8 @@ fn sdk_proxy_surfaces_reject_removed_daemon_publish_proxy_identifiers() {
"PushOutboxEventState::PreviewUnavailable",
"PushOutboxTargetOutcomeKind::DeferredUntilImplemented",
"PushOutboxTargetOutcomeKind::PreviewUnavailable",
- "reject_delivered_proxy_satisfaction",
- "RadrootsTransportSatisfactionClass::Delivered",
+ "reject_non_accepted_proxy_satisfaction",
+ "class != RadrootsTransportSatisfactionClass::Accepted",
"target.target_scope.as_ref()",
"outcome.target_scope.as_deref()",
"target_scope: outcome.target_scope",
diff --git a/crates/sdk/tests/unit/adapters_radrootsd_tests.rs b/crates/sdk/tests/unit/adapters_radrootsd_tests.rs
@@ -121,7 +121,7 @@ fn publish_request() -> TransportPublishEventRequest {
}
fn job_status_for_outcome(outcome_kind: TransportPublishOutcomeKind) -> TransportPublishJobStatus {
- if outcome_kind.counts_toward_satisfaction() {
+ if outcome_kind.counts_toward_accepted_delivery() {
TransportPublishJobStatus::DeliverySatisfied
} else if outcome_kind.is_retryable() {
TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
@@ -159,7 +159,7 @@ fn job(outcome_kind: TransportPublishOutcomeKind) -> TransportPublishJobView {
),
delivery_policy: TransportPublishDeliveryPolicy::Any,
target_count: 1,
- acknowledged_count: usize::from(outcome_kind.counts_toward_satisfaction()),
+ acknowledged_count: usize::from(outcome_kind.counts_toward_accepted_delivery()),
retryable_count: usize::from(outcome_kind.is_retryable()),
terminal_count: usize::from(outcome_kind.is_terminal_failure()),
requested_at_ms: 1_700_000_000_000,
diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs
@@ -51,8 +51,8 @@ use radroots_transport::{
RadrootsTransportSatisfactionPolicy,
};
use radroots_transport::{
- RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportTarget,
- RadrootsTransportTargetLabel,
+ RadrootsTransportDeliveryTargetStatus, RadrootsTransportKind, RadrootsTransportMeshScopeId,
+ RadrootsTransportTarget, RadrootsTransportTargetLabel,
};
use radroots_transport_nostr::{
RadrootsNostrTransport, RadrootsOutboxPublishReceipt, RadrootsOutboxPublishTargetReceipt,
@@ -322,7 +322,7 @@ fn delivery_target_record(
#[cfg(feature = "radrootsd-proxy")]
fn proxy_job(event_id: &str, outcome_kind: TransportPublishOutcomeKind) -> TransportPublishJobView {
- let delivery_satisfied = outcome_kind.counts_toward_satisfaction();
+ let delivery_satisfied = outcome_kind.counts_toward_accepted_delivery();
let retryable = outcome_kind.is_retryable();
let terminal_failure = outcome_kind.is_terminal_failure();
let status = if delivery_satisfied {
@@ -929,49 +929,61 @@ async fn proxy_push_empty_queue_and_private_helpers_are_deterministic() {
#[cfg(feature = "radrootsd-proxy")]
#[tokio::test]
-async fn proxy_delivery_policy_rejects_delivered_satisfaction_before_daemon_publish() {
+async fn proxy_delivery_policy_rejects_non_accepted_satisfaction_before_daemon_publish() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind proxy listener");
let endpoint = format!("http://{}/rpc", listener.local_addr().expect("addr"));
- let (sdk, claimed) = claimed_uningested_proxy_event_with_satisfaction(
- "proxy-delivered-rejected",
- endpoint.as_str(),
+ for (index, satisfaction_policy) in [
+ RadrootsTransportSatisfactionPolicy::all_forwarded(),
+ RadrootsTransportSatisfactionPolicy::all_stored(),
+ RadrootsTransportSatisfactionPolicy::all_seen(),
RadrootsTransportSatisfactionPolicy::all_delivered(),
- )
- .await;
- let sync = sdk.sync();
- let adapter = RadrootsdProxyPublishAdapter::new(
- RadrootsdProxyConfig::new(endpoint).with_timeout(Duration::from_millis(50)),
- );
+ RadrootsTransportSatisfactionPolicy::all_durable_or_observed(),
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let d_tag = format!("proxy-non-accepted-rejected-{index}");
+ let (sdk, claimed) = claimed_uningested_proxy_event_with_satisfaction(
+ d_tag.as_str(),
+ endpoint.as_str(),
+ satisfaction_policy,
+ )
+ .await;
+ let sync = sdk.sync();
+ let adapter = RadrootsdProxyPublishAdapter::new(
+ RadrootsdProxyConfig::new(endpoint.clone()).with_timeout(Duration::from_millis(50)),
+ );
- let error =
- push_proxy_claimed_outbox_event(&sync, &adapter, &claimed, 60_000, 1_700_000_000_000)
+ let error =
+ push_proxy_claimed_outbox_event(&sync, &adapter, &claimed, 60_000, 1_700_000_000_000)
+ .await
+ .expect_err("non-accepted-class proxy satisfaction rejected");
+ assert_no_transport_publish_request(&listener);
+
+ assert!(matches!(
+ error,
+ RadrootsSdkError::InvalidRequest { message }
+ if message.contains("radrootsd proxy publish")
+ && message.contains("accepted-class satisfaction")
+ ));
+ let stored = sdk
+ ._outbox
+ .get_event(claimed.outbox_event_id)
.await
- .expect_err("delivered-class proxy satisfaction rejected");
- assert_no_transport_publish_request(&listener);
-
- assert!(matches!(
- error,
- RadrootsSdkError::InvalidRequest { message }
- if message.contains("radrootsd proxy publish")
- && message.contains("accepted-class satisfaction")
- ));
- let stored = sdk
- ._outbox
- .get_event(claimed.outbox_event_id)
- .await
- .expect("stored")
- .expect("stored");
- assert_eq!(stored.state, RadrootsOutboxEventState::FailedTerminal);
- assert!(stored.claim_token.is_none());
- assert!(!stored.event_store_ingested);
- assert_eq!(stored.event_store_ingested_at_ms, None);
- assert!(
- stored
- .last_error
- .as_deref()
- .expect("last error")
- .contains("accepted-class satisfaction")
- );
+ .expect("stored")
+ .expect("stored");
+ assert_eq!(stored.state, RadrootsOutboxEventState::FailedTerminal);
+ assert!(stored.claim_token.is_none());
+ assert!(!stored.event_store_ingested);
+ assert_eq!(stored.event_store_ingested_at_ms, None);
+ assert!(
+ stored
+ .last_error
+ .as_deref()
+ .expect("last error")
+ .contains("accepted-class satisfaction")
+ );
+ }
}
#[cfg(feature = "radrootsd-proxy")]
@@ -1762,6 +1774,7 @@ impl OutboxPublishReceiptFixture for RadrootsOutboxPublishReceipt {
target_scope: Some("farm.local".to_owned()),
target_label: Some("Farm relay".to_owned()),
attempted: true,
+ transport_status: RadrootsTransportDeliveryTargetStatus::Accepted,
outcome: radroots_transport_nostr::RadrootsRelayOutcome::accepted(),
});
self