lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 3c34bbb0129df7803db83e6af92b79f84127d56c
parent e3210f132d41fd82dabad79cecb9634cda9ad09c
Author: triesap <tyson@radroots.org>
Date:   Tue,  7 Jul 2026 05:45:40 +0000

transport: harden publish protocol readiness

- expose per-transport publish capability records with readiness state
- replace preview outcome variants with deferred_until_implemented and preview_unavailable
- add deferred and preview-unavailable publish job statuses
- validate transport publish protocol check and test lanes

Diffstat:
Mcrates/transport_publish_protocol/src/lib.rs | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 98 insertions(+), 10 deletions(-)

diff --git a/crates/transport_publish_protocol/src/lib.rs b/crates/transport_publish_protocol/src/lib.rs @@ -303,6 +303,8 @@ pub enum TransportPublishJobStatus { DeliverySatisfied, DeliveryUnsatisfiedRetryable, DeliveryUnsatisfiedTerminal, + DeliveryDeferred, + DeliveryPreviewUnavailable, Rejected, } @@ -326,8 +328,8 @@ pub enum TransportPublishOutcomeKind { ConnectionFailed, TargetRejected, SkippedAlreadyAccepted, - Deferred, - Unavailable, + DeferredUntilImplemented, + PreviewUnavailable, Unknown, } @@ -362,8 +364,13 @@ impl TransportPublishOutcomeKind { | Self::Unsupported | Self::PaymentRequired | Self::TargetRejected - | Self::Deferred - | Self::Unavailable + ) + } + + pub fn is_deferred_preview(self) -> bool { + matches!( + self, + Self::DeferredUntilImplemented | Self::PreviewUnavailable ) } } @@ -494,7 +501,26 @@ impl TransportPublishCapabilities { TransportPublishTargetPolicyName::ExplicitTargets, TransportPublishTargetPolicyName::Nostr, ], - transport_kinds: vec!["nostr".to_owned(), "reticulum".to_owned()], + transports: vec![ + TransportPublishTransportCapability { + transport_kind: "nostr".to_owned(), + implementation_state: TransportPublishImplementationState::Available, + usable_for_delivery: true, + preview_behavior: None, + message: "Nostr relay publish is available".to_owned(), + }, + TransportPublishTransportCapability { + transport_kind: "reticulum".to_owned(), + implementation_state: + TransportPublishImplementationState::PreviewUnavailable, + usable_for_delivery: false, + preview_behavior: Some( + TransportPublishPreviewBehavior::RejectDeliveryAttempts, + ), + message: "Reticulum preview is explicit and unavailable for real delivery" + .to_owned(), + }, + ], }, } } @@ -517,7 +543,32 @@ pub struct TransportPublishSurfaceCapabilities { pub max_targets_per_request: usize, pub delivery_policies: Vec<TransportPublishDeliveryPolicyName>, pub target_policy_modes: Vec<TransportPublishTargetPolicyName>, - pub transport_kinds: Vec<String>, + pub transports: Vec<TransportPublishTransportCapability>, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TransportPublishImplementationState { + Available, + Disabled, + Misconfigured, + PreviewUnavailable, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransportPublishTransportCapability { + pub transport_kind: String, + pub implementation_state: TransportPublishImplementationState, + pub usable_for_delivery: bool, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + pub preview_behavior: Option<TransportPublishPreviewBehavior>, + pub message: String, } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -609,6 +660,33 @@ mod tests { TransportPublishTargetPolicyName::Nostr ] ); + assert_eq!(capabilities.publish.transports.len(), 2); + let nostr = capabilities + .publish + .transports + .iter() + .find(|transport| transport.transport_kind == "nostr") + .expect("nostr capability"); + assert_eq!( + nostr.implementation_state, + TransportPublishImplementationState::Available + ); + assert!(nostr.usable_for_delivery); + let reticulum = capabilities + .publish + .transports + .iter() + .find(|transport| transport.transport_kind == "reticulum") + .expect("reticulum capability"); + assert_eq!( + reticulum.implementation_state, + TransportPublishImplementationState::PreviewUnavailable + ); + assert!(!reticulum.usable_for_delivery); + assert_eq!( + reticulum.preview_behavior, + Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts) + ); } #[test] @@ -661,8 +739,10 @@ mod tests { assert!(TransportPublishOutcomeKind::Accepted.counts_toward_satisfaction()); assert!(TransportPublishOutcomeKind::SkippedAlreadyAccepted.counts_toward_satisfaction()); assert!(TransportPublishOutcomeKind::Timeout.is_retryable()); - assert!(TransportPublishOutcomeKind::Unavailable.is_terminal_failure()); - assert!(TransportPublishOutcomeKind::Deferred.is_terminal_failure()); + assert!(TransportPublishOutcomeKind::PreviewUnavailable.is_deferred_preview()); + assert!(TransportPublishOutcomeKind::DeferredUntilImplemented.is_deferred_preview()); + assert!(!TransportPublishOutcomeKind::PreviewUnavailable.is_terminal_failure()); + assert!(!TransportPublishOutcomeKind::DeferredUntilImplemented.is_terminal_failure()); } #[test] @@ -924,8 +1004,10 @@ mod tests { TransportPublishOutcomeKind::Unsupported, TransportPublishOutcomeKind::PaymentRequired, TransportPublishOutcomeKind::TargetRejected, - TransportPublishOutcomeKind::Deferred, - TransportPublishOutcomeKind::Unavailable, + ]; + let deferred_preview = [ + TransportPublishOutcomeKind::DeferredUntilImplemented, + TransportPublishOutcomeKind::PreviewUnavailable, ]; for kind in satisfied { @@ -943,6 +1025,12 @@ mod tests { assert!(!kind.is_retryable()); assert!(kind.is_terminal_failure()); } + for kind in deferred_preview { + assert!(!kind.counts_toward_satisfaction()); + assert!(!kind.is_retryable()); + assert!(!kind.is_terminal_failure()); + assert!(kind.is_deferred_preview()); + } } #[test]