commit 1fc418f66d354eea57ad45b1e6f935f6756aa975
parent fa1c498c95e9faa5acb6eed3328f64f4ee10e9a9
Author: triesap <tyson@radroots.org>
Date: Wed, 15 Jul 2026 14:56:49 +0000
transport: preserve Reticulum as first-wave transport
- normalize transport identity to Local, Nostr, and Reticulum
- move Reticulum readiness into maturity and availability status metadata
- make Reticulum endpoint and scope canonical for first-wave targets
- align transport, outbox, protocol, and source-boundary validation
Diffstat:
25 files changed, 1383 insertions(+), 1565 deletions(-)
diff --git a/contracts/conformance/vectors/transport/target_uri.v1.json b/contracts/conformance/vectors/transport/target_uri.v1.json
@@ -46,7 +46,7 @@
"id": "transport_nostr_relay_canonical_005",
"kind": "transport.nostr_relay_target.valid",
"input": {
- "uri": " WSS://Relay.Example/ "
+ "uri": "WSS://Relay.Example/"
},
"expected": {
"canonical_uri": "wss://relay.example"
diff --git a/crates/mesh/src/lib.rs b/crates/mesh/src/lib.rs
@@ -10,8 +10,8 @@ mod model;
pub use cbor::{decode_mesh_frame_cbor, encode_mesh_frame_cbor};
pub use error::RadrootsMeshError;
pub use model::{
- RADROOTS_MESH_FRAME_VERSION, RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE,
- RADROOTS_MESH_PREVIEW_POLICY_ID, RadrootsMeshAdmissionDecision, RadrootsMeshAdmissionInput,
+ RADROOTS_MESH_FRAME_VERSION, RADROOTS_MESH_RETICULUM_POLICY_ID,
+ RADROOTS_MESH_UNAVAILABLE_MESSAGE, RadrootsMeshAdmissionDecision, RadrootsMeshAdmissionInput,
RadrootsMeshCompressionPolicy, RadrootsMeshFrame, RadrootsMeshFrameType, RadrootsMeshPayload,
RadrootsMeshPayloadPolicy, RadrootsMeshPolicyDenyReason, RadrootsMeshPrivacyClass,
RadrootsMeshScope,
diff --git a/crates/mesh/src/model.rs b/crates/mesh/src/model.rs
@@ -4,9 +4,9 @@ use alloc::vec::Vec;
use radroots_transport::{RadrootsTransportError, RadrootsTransportMeshScopeId};
pub const RADROOTS_MESH_FRAME_VERSION: u16 = 1;
-pub const RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE: &str =
- "Reticulum mesh preview is explicit and unavailable for real delivery";
-pub const RADROOTS_MESH_PREVIEW_POLICY_ID: &str = "reticulum_preview_delivery";
+pub const RADROOTS_MESH_UNAVAILABLE_MESSAGE: &str =
+ "Reticulum mesh delivery is unavailable until implementation";
+pub const RADROOTS_MESH_RETICULUM_POLICY_ID: &str = "reticulum_mesh_delivery";
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -136,7 +136,7 @@ impl RadrootsMeshCompressionPolicy {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RadrootsMeshPolicyDenyReason {
- PreviewUnavailable,
+ DeliveryUnavailable,
PayloadBudgetExceeded,
CustomScopeUnavailable,
}
@@ -144,7 +144,7 @@ pub enum RadrootsMeshPolicyDenyReason {
impl RadrootsMeshPolicyDenyReason {
pub fn label(self) -> &'static str {
match self {
- Self::PreviewUnavailable => "preview_unavailable",
+ Self::DeliveryUnavailable => "delivery_unavailable",
Self::PayloadBudgetExceeded => "payload_budget_exceeded",
Self::CustomScopeUnavailable => "custom_scope_unavailable",
}
@@ -152,7 +152,7 @@ impl RadrootsMeshPolicyDenyReason {
pub fn message(self) -> &'static str {
match self {
- Self::PreviewUnavailable => RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE,
+ Self::DeliveryUnavailable => RADROOTS_MESH_UNAVAILABLE_MESSAGE,
Self::PayloadBudgetExceeded => "mesh payload exceeds the configured delivery budget",
Self::CustomScopeUnavailable => {
"mesh custom scopes are not enabled for the configured policy"
@@ -232,7 +232,7 @@ pub struct RadrootsMeshPayloadPolicy {
}
impl RadrootsMeshPayloadPolicy {
- pub fn preview_unavailable() -> Self {
+ pub fn reticulum_unavailable() -> Self {
Self {
max_payload_bytes: 0,
max_frame_bytes: 0,
@@ -242,7 +242,7 @@ impl RadrootsMeshPayloadPolicy {
}
pub fn policy_id(&self) -> &'static str {
- RADROOTS_MESH_PREVIEW_POLICY_ID
+ RADROOTS_MESH_RETICULUM_POLICY_ID
}
pub fn usable_for_delivery(&self) -> bool {
@@ -252,7 +252,7 @@ impl RadrootsMeshPayloadPolicy {
pub fn evaluate(&self, input: &RadrootsMeshAdmissionInput) -> RadrootsMeshAdmissionDecision {
if !self.usable_for_delivery() {
return RadrootsMeshAdmissionDecision::Denied {
- reason: RadrootsMeshPolicyDenyReason::PreviewUnavailable,
+ reason: RadrootsMeshPolicyDenyReason::DeliveryUnavailable,
};
}
if matches!(input.scope, RadrootsMeshScope::Custom(_)) && !self.custom_scopes_enabled {
diff --git a/crates/mesh/tests/mesh.rs b/crates/mesh/tests/mesh.rs
@@ -1,5 +1,5 @@
use radroots_mesh::{
- RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE, RADROOTS_MESH_PREVIEW_POLICY_ID,
+ RADROOTS_MESH_RETICULUM_POLICY_ID, RADROOTS_MESH_UNAVAILABLE_MESSAGE,
RadrootsMeshAdmissionDecision, RadrootsMeshAdmissionInput, RadrootsMeshCompressionPolicy,
RadrootsMeshError, RadrootsMeshFrame, RadrootsMeshFrameType, RadrootsMeshPayload,
RadrootsMeshPayloadPolicy, RadrootsMeshPolicyDenyReason, RadrootsMeshPrivacyClass,
@@ -80,10 +80,10 @@ fn all_frame_types_round_trip_with_stable_codes_and_labels() {
}
#[test]
-fn preview_policy_has_zero_delivery_budgets_and_disabled_compression() {
- let policy = RadrootsMeshPayloadPolicy::preview_unavailable();
+fn reticulum_policy_has_zero_delivery_budgets_and_disabled_compression() {
+ let policy = RadrootsMeshPayloadPolicy::reticulum_unavailable();
- assert_eq!(policy.policy_id(), RADROOTS_MESH_PREVIEW_POLICY_ID);
+ assert_eq!(policy.policy_id(), RADROOTS_MESH_RETICULUM_POLICY_ID);
assert_eq!(policy.max_payload_bytes, 0);
assert_eq!(policy.max_frame_bytes, 0);
assert_eq!(policy.compression, RadrootsMeshCompressionPolicy::Disabled);
@@ -93,8 +93,8 @@ fn preview_policy_has_zero_delivery_budgets_and_disabled_compression() {
}
#[test]
-fn preview_policy_denies_real_payload_admission_deterministically() {
- let policy = RadrootsMeshPayloadPolicy::preview_unavailable();
+fn reticulum_policy_denies_real_payload_admission_deterministically() {
+ let policy = RadrootsMeshPayloadPolicy::reticulum_unavailable();
let input = RadrootsMeshAdmissionInput::new(
RadrootsMeshScope::Local,
RadrootsMeshPrivacyClass::PublicEvent,
@@ -106,15 +106,15 @@ fn preview_policy_denies_real_payload_admission_deterministically() {
assert_eq!(
decision,
RadrootsMeshAdmissionDecision::Denied {
- reason: RadrootsMeshPolicyDenyReason::PreviewUnavailable
+ reason: RadrootsMeshPolicyDenyReason::DeliveryUnavailable
}
);
assert_eq!(decision.label(), "denied");
assert_eq!(
decision.deny_reason(),
- Some(RadrootsMeshPolicyDenyReason::PreviewUnavailable)
+ Some(RadrootsMeshPolicyDenyReason::DeliveryUnavailable)
);
- assert_eq!(decision.message(), RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE);
+ assert_eq!(decision.message(), RADROOTS_MESH_UNAVAILABLE_MESSAGE);
assert!(!decision.usable_for_delivery());
assert_eq!(
RadrootsMeshPrivacyClass::PublicEvent.label(),
@@ -125,15 +125,15 @@ fn preview_policy_denies_real_payload_admission_deterministically() {
"private_event"
);
assert_eq!(
- RadrootsMeshPolicyDenyReason::PreviewUnavailable.label(),
- "preview_unavailable"
+ RadrootsMeshPolicyDenyReason::DeliveryUnavailable.label(),
+ "delivery_unavailable"
);
}
#[test]
fn custom_scope_has_explicit_namespace() {
- let scope = RadrootsMeshScope::custom("farm-north.preview_1").expect("custom scope");
- assert_eq!(scope.label(), "farm-north.preview_1");
+ let scope = RadrootsMeshScope::custom("farm-north.mesh_1").expect("custom scope");
+ assert_eq!(scope.label(), "farm-north.mesh_1");
let frame = RadrootsMeshFrame::new(
RadrootsMeshFrameType::RouteProbe,
scope,
@@ -144,7 +144,7 @@ fn custom_scope_has_explicit_namespace() {
let encoded = encode_mesh_frame_cbor(&frame).expect("encode custom scope");
let decoded = decode_mesh_frame_cbor(&encoded).expect("decode custom scope");
- assert_eq!(decoded.scope_id.cbor_label(), "custom:farm-north.preview_1");
+ assert_eq!(decoded.scope_id.cbor_label(), "custom:farm-north.mesh_1");
assert_eq!(encode_mesh_frame_cbor(&decoded).expect("reencode"), encoded);
}
diff --git a/crates/mesh_agent_client/src/lib.rs b/crates/mesh_agent_client/src/lib.rs
@@ -1,14 +1,13 @@
#![forbid(unsafe_code)]
use radroots_mesh::{
- RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE, RADROOTS_MESH_PREVIEW_POLICY_ID,
- RadrootsMeshPayloadPolicy,
+ RADROOTS_MESH_RETICULUM_POLICY_ID, RADROOTS_MESH_UNAVAILABLE_MESSAGE, RadrootsMeshPayloadPolicy,
};
use radroots_mesh_agent_proto::{
RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, schema_sha256_hex,
};
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RadrootsTransportKind, RadrootsTransportMeshScopeId,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RadrootsTransportKind, RadrootsTransportMeshScopeId,
};
pub const RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID: &str = RADROOTS_MESH_AGENT_SCHEMA_ID;
@@ -37,6 +36,8 @@ pub struct MeshAgentTransportStatus {
pub endpoint_uri: String,
pub configured: bool,
pub implementation: MeshAgentImplementation,
+ pub maturity: MeshAgentCapabilityMaturity,
+ pub availability: MeshAgentCapabilityAvailability,
pub usable_for_delivery: bool,
pub message: String,
}
@@ -47,7 +48,6 @@ pub struct MeshAgentTransportStatus {
pub enum MeshAgentImplementation {
Real,
Mock,
- PreviewUnavailable,
}
impl MeshAgentImplementation {
@@ -55,7 +55,42 @@ impl MeshAgentImplementation {
match self {
Self::Real => "real",
Self::Mock => "mock",
- Self::PreviewUnavailable => "previewUnavailable",
+ }
+ }
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MeshAgentCapabilityMaturity {
+ Preview,
+ Stable,
+}
+
+impl MeshAgentCapabilityMaturity {
+ pub fn schema_name(self) -> &'static str {
+ match self {
+ Self::Preview => "preview",
+ Self::Stable => "stable",
+ }
+ }
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MeshAgentCapabilityAvailability {
+ Available,
+ Degraded,
+ Unavailable,
+}
+
+impl MeshAgentCapabilityAvailability {
+ pub fn schema_name(self) -> &'static str {
+ match self {
+ Self::Available => "available",
+ Self::Degraded => "degraded",
+ Self::Unavailable => "unavailable",
}
}
}
@@ -183,12 +218,12 @@ pub struct RadrootsMockMeshAgentClient {
}
impl RadrootsMockMeshAgentClient {
- pub fn preview_unavailable() -> Self {
+ pub fn reticulum_unavailable() -> Self {
Self {
- profile_id: RADROOTS_MESH_PREVIEW_POLICY_ID.to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
- scope: RadrootsTransportMeshScopeId::local_preview(),
- policy: RadrootsMeshPayloadPolicy::preview_unavailable(),
+ profile_id: RADROOTS_MESH_RETICULUM_POLICY_ID.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
+ scope: RadrootsTransportMeshScopeId::local_reticulum(),
+ policy: RadrootsMeshPayloadPolicy::reticulum_unavailable(),
}
}
@@ -203,7 +238,7 @@ impl RadrootsMockMeshAgentClient {
impl Default for RadrootsMockMeshAgentClient {
fn default() -> Self {
- Self::preview_unavailable()
+ Self::reticulum_unavailable()
}
}
@@ -215,9 +250,11 @@ impl RadrootsMeshAgentClient for RadrootsMockMeshAgentClient {
profile_id: self.profile_id.clone(),
endpoint_uri: self.endpoint_uri.clone(),
configured: true,
- implementation: MeshAgentImplementation::PreviewUnavailable,
+ implementation: MeshAgentImplementation::Real,
+ maturity: MeshAgentCapabilityMaturity::Preview,
+ availability: MeshAgentCapabilityAvailability::Unavailable,
usable_for_delivery: false,
- message: RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE.to_owned(),
+ message: RADROOTS_MESH_UNAVAILABLE_MESSAGE.to_owned(),
}]
} else {
Vec::new()
@@ -233,7 +270,7 @@ impl RadrootsMeshAgentClient for RadrootsMockMeshAgentClient {
transport_kind: MeshAgentTransportKind::Reticulum,
endpoint_uri: self.endpoint_uri.clone(),
outcome: MeshAgentTransportOutcome::TransportUnavailable,
- message: RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE.to_owned(),
+ message: RADROOTS_MESH_UNAVAILABLE_MESSAGE.to_owned(),
}],
event_id: request.event_id,
}
diff --git a/crates/mesh_agent_client/tests/client.rs b/crates/mesh_agent_client/tests/client.rs
@@ -1,40 +1,43 @@
use std::fs;
use std::path::Path;
-use radroots_mesh::{RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE, RADROOTS_MESH_PREVIEW_POLICY_ID};
+use radroots_mesh::{RADROOTS_MESH_RETICULUM_POLICY_ID, RADROOTS_MESH_UNAVAILABLE_MESSAGE};
use radroots_mesh_agent_client::{
- MeshAgentImplementation, MeshAgentPublishRequest, MeshAgentResponseStatus,
- MeshAgentStatusRequest, MeshAgentTransportKind, MeshAgentTransportOutcome,
- RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID, RADROOTS_MESH_AGENT_CLIENT_SCHEMA_NAMESPACE,
- RadrootsMeshAgentClient, RadrootsMockMeshAgentClient,
+ MeshAgentCapabilityAvailability, MeshAgentCapabilityMaturity, MeshAgentImplementation,
+ MeshAgentPublishRequest, MeshAgentResponseStatus, MeshAgentStatusRequest,
+ MeshAgentTransportKind, MeshAgentTransportOutcome, RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID,
+ RADROOTS_MESH_AGENT_CLIENT_SCHEMA_NAMESPACE, RadrootsMeshAgentClient,
+ RadrootsMockMeshAgentClient,
};
use radroots_mesh_agent_proto::{
RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, schema_sha256_hex,
};
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
- RadrootsTransportKind,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID, RadrootsTransportKind,
};
use serde_json::json;
fn publish_request() -> MeshAgentPublishRequest {
MeshAgentPublishRequest {
- publish_request_id: "publish-preview-1".to_owned(),
+ publish_request_id: "publish-reticulum-1".to_owned(),
payload_cbor: vec![1, 2, 3],
- event_id: "event-preview-1".to_owned(),
- target_fingerprint: "target-preview-1".to_owned(),
+ event_id: "event-reticulum-1".to_owned(),
+ target_fingerprint: "target-reticulum-1".to_owned(),
}
}
#[test]
-fn mock_client_status_reports_reticulum_preview_unavailable() {
- let client = RadrootsMockMeshAgentClient::preview_unavailable();
+fn mock_client_status_reports_reticulum_unavailable() {
+ let client = RadrootsMockMeshAgentClient::reticulum_unavailable();
let response = client.status(MeshAgentStatusRequest {
include_transports: true,
});
- assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_PREVIEW_SCOPE_ID);
- assert_eq!(client.policy().policy_id(), RADROOTS_MESH_PREVIEW_POLICY_ID);
+ assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_SCOPE_ID);
+ assert_eq!(
+ client.policy().policy_id(),
+ RADROOTS_MESH_RETICULUM_POLICY_ID
+ );
assert_eq!(response.transports.len(), 1);
let status = &response.transports[0];
assert_eq!(status.transport, MeshAgentTransportKind::Reticulum);
@@ -42,28 +45,33 @@ fn mock_client_status_reports_reticulum_preview_unavailable() {
status.transport.transport_kind(),
RadrootsTransportKind::Reticulum
);
- assert_eq!(status.profile_id, RADROOTS_MESH_PREVIEW_POLICY_ID);
- assert_eq!(status.endpoint_uri, RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
+ assert_eq!(status.profile_id, RADROOTS_MESH_RETICULUM_POLICY_ID);
+ assert_eq!(status.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI);
assert!(status.configured);
+ assert_eq!(status.implementation, MeshAgentImplementation::Real);
+ assert_eq!(status.maturity, MeshAgentCapabilityMaturity::Preview);
assert_eq!(
- status.implementation,
- MeshAgentImplementation::PreviewUnavailable
+ status.availability,
+ MeshAgentCapabilityAvailability::Unavailable
);
assert!(!status.usable_for_delivery);
- assert_eq!(status.message, RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE);
+ assert_eq!(status.message, RADROOTS_MESH_UNAVAILABLE_MESSAGE);
}
#[test]
-fn default_mock_client_uses_preview_unavailable_policy() {
+fn default_mock_client_uses_reticulum_unavailable_policy() {
let client = RadrootsMockMeshAgentClient::default();
- assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_PREVIEW_SCOPE_ID);
- assert_eq!(client.policy().policy_id(), RADROOTS_MESH_PREVIEW_POLICY_ID);
+ assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_SCOPE_ID);
+ assert_eq!(
+ client.policy().policy_id(),
+ RADROOTS_MESH_RETICULUM_POLICY_ID
+ );
}
#[test]
fn mock_client_status_can_omit_transport_rows_without_enabling_delivery() {
- let client = RadrootsMockMeshAgentClient::preview_unavailable();
+ let client = RadrootsMockMeshAgentClient::reticulum_unavailable();
let response = client.status(MeshAgentStatusRequest {
include_transports: false,
});
@@ -74,25 +82,22 @@ fn mock_client_status_can_omit_transport_rows_without_enabling_delivery() {
#[test]
fn mock_client_publish_never_reports_success_outcomes() {
- let client = RadrootsMockMeshAgentClient::preview_unavailable();
+ let client = RadrootsMockMeshAgentClient::reticulum_unavailable();
let response = client.publish(publish_request());
- assert_eq!(response.publish_request_id, "publish-preview-1");
+ assert_eq!(response.publish_request_id, "publish-reticulum-1");
assert_eq!(response.status, MeshAgentResponseStatus::Rejected);
- assert_eq!(response.event_id, "event-preview-1");
+ assert_eq!(response.event_id, "event-reticulum-1");
assert_eq!(response.transport_receipts.len(), 1);
let receipt = &response.transport_receipts[0];
assert_eq!(receipt.transport_kind, MeshAgentTransportKind::Reticulum);
- assert_eq!(
- receipt.endpoint_uri,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
+ assert_eq!(receipt.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI);
assert_eq!(
receipt.outcome,
MeshAgentTransportOutcome::TransportUnavailable
);
assert!(!receipt.outcome.is_success());
- assert_eq!(receipt.message, RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE);
+ assert_eq!(receipt.message, RADROOTS_MESH_UNAVAILABLE_MESSAGE);
for outcome in [
MeshAgentTransportOutcome::Accepted,
MeshAgentTransportOutcome::Delivered,
@@ -110,8 +115,21 @@ fn dto_schema_names_match_capnp_surface() {
assert_eq!(MeshAgentImplementation::Real.schema_name(), "real");
assert_eq!(MeshAgentImplementation::Mock.schema_name(), "mock");
assert_eq!(
- MeshAgentImplementation::PreviewUnavailable.schema_name(),
- "previewUnavailable"
+ MeshAgentCapabilityMaturity::Preview.schema_name(),
+ "preview"
+ );
+ assert_eq!(MeshAgentCapabilityMaturity::Stable.schema_name(), "stable");
+ assert_eq!(
+ MeshAgentCapabilityAvailability::Available.schema_name(),
+ "available"
+ );
+ assert_eq!(
+ MeshAgentCapabilityAvailability::Degraded.schema_name(),
+ "degraded"
+ );
+ assert_eq!(
+ MeshAgentCapabilityAvailability::Unavailable.schema_name(),
+ "unavailable"
);
assert_eq!(MeshAgentResponseStatus::Accepted.schema_name(), "accepted");
assert_eq!(MeshAgentResponseStatus::Deferred.schema_name(), "deferred");
@@ -159,17 +177,17 @@ fn serde_output_uses_schema_field_names() {
assert_eq!(
request_json,
json!({
- "publishRequestId": "publish-preview-1",
+ "publishRequestId": "publish-reticulum-1",
"payloadCbor": [1, 2, 3],
- "eventId": "event-preview-1",
- "targetFingerprint": "target-preview-1"
+ "eventId": "event-reticulum-1",
+ "targetFingerprint": "target-reticulum-1"
})
);
- let client = RadrootsMockMeshAgentClient::preview_unavailable();
+ let client = RadrootsMockMeshAgentClient::reticulum_unavailable();
let response_json = serde_json::to_value(client.publish(request)).expect("serialize response");
- assert_eq!(response_json["publishRequestId"], "publish-preview-1");
+ assert_eq!(response_json["publishRequestId"], "publish-reticulum-1");
assert_eq!(response_json["status"], "rejected");
assert_eq!(
response_json["transportReceipts"][0]["transportKind"],
@@ -183,7 +201,7 @@ fn serde_output_uses_schema_field_names() {
#[test]
fn client_schema_hash_matches_proto_authority() {
- let client = RadrootsMockMeshAgentClient::preview_unavailable();
+ let client = RadrootsMockMeshAgentClient::reticulum_unavailable();
assert_eq!(
RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID,
@@ -230,7 +248,7 @@ fn client_source_uses_schema_field_names_without_legacy_status_terms() {
"publish_usable",
"fetch_usable",
"redacted_message",
- "preview_noop",
+ "retired_noop",
] {
assert!(!source.contains(forbidden), "{forbidden}");
}
diff --git a/crates/outbox/migrations/0001_outbox.up.sql b/crates/outbox/migrations/0001_outbox.up.sql
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS outbox_operations (
expected_pubkey TEXT NOT NULL,
idempotency_key TEXT,
operation_idempotency_digest TEXT NOT NULL,
- status TEXT NOT NULL CHECK (status IN ('queued', 'complete', 'deferred_until_implemented', 'preview_unavailable', 'failed_terminal', 'cancelled')),
+ status TEXT NOT NULL CHECK (status IN ('queued', 'complete', 'deferred_until_implemented', 'failed_terminal', 'cancelled')),
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL
);
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS outbox_event (
draft_json TEXT NOT NULL,
signed_event_json TEXT,
raw_event_json TEXT,
- state TEXT NOT NULL CHECK (state IN ('draft_queued', 'signing', 'signed', 'publishing', 'published', 'sign_retryable', 'publish_retryable', 'deferred_until_implemented', 'preview_unavailable', 'failed_terminal', 'cancelled')),
+ state TEXT NOT NULL CHECK (state IN ('draft_queued', 'signing', 'signed', 'publishing', 'published', 'sign_retryable', 'publish_retryable', 'deferred_until_implemented', 'failed_terminal', 'cancelled')),
attempt_count INTEGER NOT NULL,
claim_token TEXT,
claim_owner TEXT,
@@ -54,7 +54,7 @@ CREATE TABLE IF NOT EXISTS outbox_delivery_plan (
satisfaction_policy TEXT NOT NULL,
required_success_count INTEGER NOT NULL,
delivery_plan_idempotency_digest TEXT NOT NULL,
- status TEXT NOT NULL CHECK (status IN ('queued', 'complete', 'deferred_until_implemented', 'preview_unavailable', 'failed_terminal', 'cancelled')),
+ status TEXT NOT NULL CHECK (status IN ('queued', 'complete', 'deferred_until_implemented', 'failed_terminal', 'cancelled')),
satisfied_at_ms INTEGER,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
@@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS outbox_delivery_target (
target_scope TEXT,
target_label TEXT,
endpoint_fingerprint TEXT NOT NULL,
- status TEXT NOT NULL CHECK (status IN ('pending', 'accepted', 'delivered', 'forwarded', 'stored_by_gateway', 'seen', 'deferred_until_implemented', 'preview_unavailable', 'skipped_policy_denied', 'failed_retryable', 'failed_terminal')),
+ status TEXT NOT NULL CHECK (status IN ('pending', 'accepted', 'delivered', 'forwarded', 'stored_by_gateway', 'seen', 'deferred_until_implemented', 'skipped_policy_denied', 'failed_retryable', 'failed_terminal')),
last_outcome_kind TEXT CHECK (last_outcome_kind IS NULL OR last_outcome_kind IN ('accepted', 'duplicate_accepted', 'delivered', 'forwarded', 'stored_by_gateway', 'seen', 'deferred_until_implemented', 'rejected', 'route_unavailable', 'payload_too_large', 'policy_denied', 'timeout', 'connection_failed', 'transport_unavailable')),
attempt_count INTEGER NOT NULL,
last_attempt_at_ms INTEGER,
diff --git a/crates/outbox/src/lib.rs b/crates/outbox/src/lib.rs
@@ -15,8 +15,8 @@ pub use model::{
RadrootsOutboxDeliveryTargetStatus, RadrootsOutboxEnqueueReceipt, RadrootsOutboxEnqueueStatus,
RadrootsOutboxEventRecord, RadrootsOutboxEventState, RadrootsOutboxEventStoreIngestReceipt,
RadrootsOutboxIdempotencyPreflight, RadrootsOutboxOperationInput,
- RadrootsOutboxOperationRecord, RadrootsOutboxOperationStatus,
- RadrootsOutboxReticulumPreviewBehavior, RadrootsOutboxReticulumPreviewEventRecord,
- RadrootsOutboxSignedOperationInput, RadrootsOutboxStatusSummary,
+ RadrootsOutboxOperationRecord, RadrootsOutboxOperationStatus, RadrootsOutboxReticulumBehavior,
+ RadrootsOutboxReticulumEventRecord, RadrootsOutboxSignedOperationInput,
+ RadrootsOutboxStatusSummary,
};
pub use store::RadrootsOutbox;
diff --git a/crates/outbox/src/model.rs b/crates/outbox/src/model.rs
@@ -14,7 +14,6 @@ pub enum RadrootsOutboxOperationStatus {
Queued,
Complete,
DeferredUntilImplemented,
- PreviewUnavailable,
FailedTerminal,
Cancelled,
}
@@ -25,7 +24,6 @@ impl RadrootsOutboxOperationStatus {
Self::Queued => "queued",
Self::Complete => "complete",
Self::DeferredUntilImplemented => "deferred_until_implemented",
- Self::PreviewUnavailable => "preview_unavailable",
Self::FailedTerminal => "failed_terminal",
Self::Cancelled => "cancelled",
}
@@ -36,7 +34,6 @@ impl RadrootsOutboxOperationStatus {
"queued" => Ok(Self::Queued),
"complete" => Ok(Self::Complete),
"deferred_until_implemented" => Ok(Self::DeferredUntilImplemented),
- "preview_unavailable" => Ok(Self::PreviewUnavailable),
"failed_terminal" => Ok(Self::FailedTerminal),
"cancelled" => Ok(Self::Cancelled),
_ => Err(RadrootsOutboxError::InvalidStoredEnum {
@@ -57,7 +54,6 @@ pub enum RadrootsOutboxEventState {
SignRetryable,
PublishRetryable,
DeferredUntilImplemented,
- PreviewUnavailable,
FailedTerminal,
Cancelled,
}
@@ -73,7 +69,6 @@ impl RadrootsOutboxEventState {
Self::SignRetryable => "sign_retryable",
Self::PublishRetryable => "publish_retryable",
Self::DeferredUntilImplemented => "deferred_until_implemented",
- Self::PreviewUnavailable => "preview_unavailable",
Self::FailedTerminal => "failed_terminal",
Self::Cancelled => "cancelled",
}
@@ -89,7 +84,6 @@ impl RadrootsOutboxEventState {
"sign_retryable" => Ok(Self::SignRetryable),
"publish_retryable" => Ok(Self::PublishRetryable),
"deferred_until_implemented" => Ok(Self::DeferredUntilImplemented),
- "preview_unavailable" => Ok(Self::PreviewUnavailable),
"failed_terminal" => Ok(Self::FailedTerminal),
"cancelled" => Ok(Self::Cancelled),
_ => Err(RadrootsOutboxError::InvalidStoredEnum {
@@ -112,7 +106,6 @@ pub enum RadrootsOutboxDeliveryPlanStatus {
Queued,
Complete,
DeferredUntilImplemented,
- PreviewUnavailable,
FailedTerminal,
Cancelled,
}
@@ -123,7 +116,6 @@ impl RadrootsOutboxDeliveryPlanStatus {
Self::Queued => "queued",
Self::Complete => "complete",
Self::DeferredUntilImplemented => "deferred_until_implemented",
- Self::PreviewUnavailable => "preview_unavailable",
Self::FailedTerminal => "failed_terminal",
Self::Cancelled => "cancelled",
}
@@ -134,7 +126,6 @@ impl RadrootsOutboxDeliveryPlanStatus {
"queued" => Ok(Self::Queued),
"complete" => Ok(Self::Complete),
"deferred_until_implemented" => Ok(Self::DeferredUntilImplemented),
- "preview_unavailable" => Ok(Self::PreviewUnavailable),
"failed_terminal" => Ok(Self::FailedTerminal),
"cancelled" => Ok(Self::Cancelled),
_ => Err(RadrootsOutboxError::InvalidStoredEnum {
@@ -154,7 +145,6 @@ pub enum RadrootsOutboxDeliveryTargetStatus {
StoredByGateway,
Seen,
DeferredUntilImplemented,
- PreviewUnavailable,
SkippedPolicyDenied,
FailedRetryable,
FailedTerminal,
@@ -170,7 +160,6 @@ impl RadrootsOutboxDeliveryTargetStatus {
Self::StoredByGateway => "stored_by_gateway",
Self::Seen => "seen",
Self::DeferredUntilImplemented => "deferred_until_implemented",
- Self::PreviewUnavailable => "preview_unavailable",
Self::SkippedPolicyDenied => "skipped_policy_denied",
Self::FailedRetryable => "failed_retryable",
Self::FailedTerminal => "failed_terminal",
@@ -186,7 +175,6 @@ impl RadrootsOutboxDeliveryTargetStatus {
"stored_by_gateway" => Ok(Self::StoredByGateway),
"seen" => Ok(Self::Seen),
"deferred_until_implemented" => Ok(Self::DeferredUntilImplemented),
- "preview_unavailable" => Ok(Self::PreviewUnavailable),
"skipped_policy_denied" => Ok(Self::SkippedPolicyDenied),
"failed_retryable" => Ok(Self::FailedRetryable),
"failed_terminal" => Ok(Self::FailedTerminal),
@@ -228,11 +216,8 @@ impl RadrootsOutboxDeliveryTargetStatus {
}
}
- pub fn is_deferred_preview(self) -> bool {
- matches!(
- self,
- Self::DeferredUntilImplemented | Self::PreviewUnavailable
- )
+ pub fn is_deferred_until_implemented(self) -> bool {
+ matches!(self, Self::DeferredUntilImplemented)
}
pub fn is_retryable_failure(self) -> bool {
@@ -245,19 +230,19 @@ impl RadrootsOutboxDeliveryTargetStatus {
pub fn is_completed(self) -> bool {
self.counts_as_transport_satisfaction(RadrootsTransportSatisfactionClass::Accepted)
- || self.is_deferred_preview()
+ || self.is_deferred_until_implemented()
|| self.is_terminal_failure()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum RadrootsOutboxReticulumPreviewBehavior {
+pub enum RadrootsOutboxReticulumBehavior {
#[default]
RejectDeliveryAttempts,
DeferDeliveryPlans,
}
-impl RadrootsOutboxReticulumPreviewBehavior {
+impl RadrootsOutboxReticulumBehavior {
pub fn as_str(self) -> &'static str {
match self {
Self::RejectDeliveryAttempts => "reject_delivery_attempts",
@@ -272,7 +257,7 @@ pub struct RadrootsOutboxDeliveryPlanInput {
pub target_policy_version: u32,
pub satisfaction_policy: RadrootsTransportSatisfactionPolicy,
pub targets: Vec<RadrootsTransportTarget>,
- pub reticulum_preview_behavior: RadrootsOutboxReticulumPreviewBehavior,
+ pub reticulum_behavior: RadrootsOutboxReticulumBehavior,
}
impl RadrootsOutboxDeliveryPlanInput {
@@ -287,15 +272,12 @@ impl RadrootsOutboxDeliveryPlanInput {
target_policy_version,
satisfaction_policy,
targets,
- reticulum_preview_behavior: RadrootsOutboxReticulumPreviewBehavior::default(),
+ reticulum_behavior: RadrootsOutboxReticulumBehavior::default(),
}
}
- pub fn with_reticulum_preview_behavior(
- mut self,
- behavior: RadrootsOutboxReticulumPreviewBehavior,
- ) -> Self {
- self.reticulum_preview_behavior = behavior;
+ pub fn with_reticulum_behavior(mut self, behavior: RadrootsOutboxReticulumBehavior) -> Self {
+ self.reticulum_behavior = behavior;
self
}
}
@@ -464,7 +446,7 @@ pub struct RadrootsOutboxDeliveryTargetRecord {
}
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsOutboxReticulumPreviewEventRecord {
+pub struct RadrootsOutboxReticulumEventRecord {
pub event: RadrootsOutboxEventRecord,
pub targets: Vec<RadrootsOutboxDeliveryTargetRecord>,
}
@@ -509,7 +491,6 @@ pub struct RadrootsOutboxStatusSummary {
pub retryable_events: i64,
pub terminal_events: i64,
pub failed_terminal_events: i64,
- pub preview_unavailable_events: i64,
pub deferred_until_implemented_events: i64,
pub ready_signed_events: i64,
pub publishing_events: i64,
@@ -531,10 +512,6 @@ mod tests {
"deferred_until_implemented",
),
(
- RadrootsOutboxOperationStatus::PreviewUnavailable,
- "preview_unavailable",
- ),
- (
RadrootsOutboxOperationStatus::FailedTerminal,
"failed_terminal",
),
@@ -562,10 +539,6 @@ mod tests {
RadrootsOutboxEventState::DeferredUntilImplemented,
"deferred_until_implemented",
),
- (
- RadrootsOutboxEventState::PreviewUnavailable,
- "preview_unavailable",
- ),
(RadrootsOutboxEventState::FailedTerminal, "failed_terminal"),
(RadrootsOutboxEventState::Cancelled, "cancelled"),
] {
@@ -584,10 +557,6 @@ mod tests {
"deferred_until_implemented",
),
(
- RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable,
- "preview_unavailable",
- ),
- (
RadrootsOutboxDeliveryPlanStatus::FailedTerminal,
"failed_terminal",
),
@@ -602,22 +571,30 @@ mod tests {
for (behavior, expected) in [
(
- RadrootsOutboxReticulumPreviewBehavior::RejectDeliveryAttempts,
+ RadrootsOutboxReticulumBehavior::RejectDeliveryAttempts,
"reject_delivery_attempts",
),
(
- RadrootsOutboxReticulumPreviewBehavior::DeferDeliveryPlans,
+ RadrootsOutboxReticulumBehavior::DeferDeliveryPlans,
"defer_delivery_plans",
),
] {
assert_eq!(behavior.as_str(), expected);
}
assert_eq!(
- RadrootsOutboxReticulumPreviewBehavior::default(),
- RadrootsOutboxReticulumPreviewBehavior::RejectDeliveryAttempts
+ RadrootsOutboxReticulumBehavior::default(),
+ RadrootsOutboxReticulumBehavior::RejectDeliveryAttempts
);
- for (status, expected, ready, satisfied, delivered, deferred_preview, terminal_failure) in [
+ for (
+ status,
+ expected,
+ ready,
+ satisfied,
+ delivered,
+ deferred_until_implemented,
+ terminal_failure,
+ ) in [
(
RadrootsOutboxDeliveryTargetStatus::Pending,
"pending",
@@ -682,15 +659,6 @@ mod tests {
false,
),
(
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable,
- "preview_unavailable",
- false,
- false,
- false,
- true,
- false,
- ),
- (
RadrootsOutboxDeliveryTargetStatus::SkippedPolicyDenied,
"skipped_policy_denied",
false,
@@ -736,7 +704,10 @@ mod tests {
),
delivered
);
- assert_eq!(status.is_deferred_preview(), deferred_preview);
+ assert_eq!(
+ status.is_deferred_until_implemented(),
+ deferred_until_implemented
+ );
assert_eq!(status.is_terminal_failure(), terminal_failure);
}
}
diff --git a/crates/outbox/src/store.rs b/crates/outbox/src/store.rs
@@ -9,9 +9,9 @@ use crate::model::{
RadrootsOutboxDeliveryTargetStatus, RadrootsOutboxEnqueueReceipt, RadrootsOutboxEnqueueStatus,
RadrootsOutboxEventRecord, RadrootsOutboxEventState, RadrootsOutboxEventStoreIngestReceipt,
RadrootsOutboxIdempotencyPreflight, RadrootsOutboxOperationInput,
- RadrootsOutboxOperationRecord, RadrootsOutboxOperationStatus,
- RadrootsOutboxReticulumPreviewBehavior, RadrootsOutboxReticulumPreviewEventRecord,
- RadrootsOutboxSignedOperationInput, RadrootsOutboxStatusSummary,
+ RadrootsOutboxOperationRecord, RadrootsOutboxOperationStatus, RadrootsOutboxReticulumBehavior,
+ RadrootsOutboxReticulumEventRecord, RadrootsOutboxSignedOperationInput,
+ RadrootsOutboxStatusSummary,
};
use radroots_event::draft::{
RadrootsEventDraft, RadrootsSignedEvent, validate_signed_nostr_event_matches_draft,
@@ -22,7 +22,7 @@ use radroots_event_store::{
RadrootsTransportObservationType,
};
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RadrootsTransportError, RadrootsTransportKind,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RadrootsTransportError, RadrootsTransportKind,
RadrootsTransportMeshScopeId, RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel, RadrootsTransportTargetUri,
@@ -99,7 +99,7 @@ impl RadrootsOutbox {
now_ms: i64,
) -> Result<RadrootsOutboxStatusSummary, RadrootsOutboxError> {
let row = sqlx::query(
- "SELECT COUNT(*) AS total_events, COALESCE(SUM(CASE WHEN state IN ('draft_queued', 'signing', 'signed', 'publishing') THEN 1 ELSE 0 END), 0) AS pending_events, COALESCE(SUM(CASE WHEN state IN ('sign_retryable', 'publish_retryable') THEN 1 ELSE 0 END), 0) AS retryable_events, COALESCE(SUM(CASE WHEN state IN ('published', 'failed_terminal', 'cancelled') THEN 1 ELSE 0 END), 0) AS terminal_events, COALESCE(SUM(CASE WHEN state = 'failed_terminal' THEN 1 ELSE 0 END), 0) AS failed_terminal_events, COALESCE(SUM(CASE WHEN state = 'preview_unavailable' THEN 1 ELSE 0 END), 0) AS preview_unavailable_events, COALESCE(SUM(CASE WHEN state = 'deferred_until_implemented' THEN 1 ELSE 0 END), 0) AS deferred_until_implemented_events, COALESCE(SUM(CASE WHEN state = 'publishing' THEN 1 ELSE 0 END), 0) AS publishing_events FROM outbox_event",
+ "SELECT COUNT(*) AS total_events, COALESCE(SUM(CASE WHEN state IN ('draft_queued', 'signing', 'signed', 'publishing') THEN 1 ELSE 0 END), 0) AS pending_events, COALESCE(SUM(CASE WHEN state IN ('sign_retryable', 'publish_retryable') THEN 1 ELSE 0 END), 0) AS retryable_events, COALESCE(SUM(CASE WHEN state IN ('published', 'failed_terminal', 'cancelled') THEN 1 ELSE 0 END), 0) AS terminal_events, COALESCE(SUM(CASE WHEN state = 'failed_terminal' THEN 1 ELSE 0 END), 0) AS failed_terminal_events, COALESCE(SUM(CASE WHEN state = 'deferred_until_implemented' THEN 1 ELSE 0 END), 0) AS deferred_until_implemented_events, COALESCE(SUM(CASE WHEN state = 'publishing' THEN 1 ELSE 0 END), 0) AS publishing_events FROM outbox_event",
)
.fetch_one(&self.pool)
.await?;
@@ -129,7 +129,6 @@ impl RadrootsOutbox {
retryable_events: row.try_get("retryable_events")?,
terminal_events: row.try_get("terminal_events")?,
failed_terminal_events: row.try_get("failed_terminal_events")?,
- preview_unavailable_events: row.try_get("preview_unavailable_events")?,
deferred_until_implemented_events: row.try_get("deferred_until_implemented_events")?,
ready_signed_events,
publishing_events: row.try_get("publishing_events")?,
@@ -529,25 +528,23 @@ impl RadrootsOutbox {
delivery_targets_for_event_pool(&self.pool, outbox_event_id).await
}
- pub async fn reticulum_preview_events(
+ pub async fn reticulum_events(
&self,
outbox_event_id: Option<i64>,
limit: usize,
- ) -> Result<Vec<RadrootsOutboxReticulumPreviewEventRecord>, RadrootsOutboxError> {
+ ) -> Result<Vec<RadrootsOutboxReticulumEventRecord>, RadrootsOutboxError> {
if limit == 0 {
return Ok(Vec::new());
}
- let outbox_event_ids =
- reticulum_preview_event_ids_pool(&self.pool, outbox_event_id, limit).await?;
+ let outbox_event_ids = reticulum_event_ids_pool(&self.pool, outbox_event_id, limit).await?;
let mut records = Vec::with_capacity(outbox_event_ids.len());
for outbox_event_id in outbox_event_ids {
let Some(event) = self.get_event(outbox_event_id).await? else {
continue;
};
- let targets =
- reticulum_preview_targets_for_event_pool(&self.pool, outbox_event_id).await?;
+ let targets = reticulum_targets_for_event_pool(&self.pool, outbox_event_id).await?;
if !targets.is_empty() {
- records.push(RadrootsOutboxReticulumPreviewEventRecord { event, targets });
+ records.push(RadrootsOutboxReticulumEventRecord { event, targets });
}
}
Ok(records)
@@ -816,7 +813,7 @@ impl RadrootsOutbox {
pub async fn recover_expired_claims(&self, now_ms: i64) -> Result<u64, RadrootsOutboxError> {
let changed = sqlx::query(
- "UPDATE outbox_event SET state = CASE WHEN state = 'signing' AND signed_event_json IS NULL THEN 'sign_retryable' WHEN state = 'signing' AND signed_event_json IS NOT NULL THEN 'signed' WHEN state = 'publishing' THEN 'publish_retryable' ELSE state END, claim_token = NULL, claim_owner = NULL, claim_expires_at_ms = NULL, active_delivery_plan_id = NULL, updated_at_ms = ? WHERE claim_token IS NOT NULL AND claim_expires_at_ms <= ? AND state IN ('signing', 'signed', 'publishing', 'preview_unavailable', 'deferred_until_implemented')",
+ "UPDATE outbox_event SET state = CASE WHEN state = 'signing' AND signed_event_json IS NULL THEN 'sign_retryable' WHEN state = 'signing' AND signed_event_json IS NOT NULL THEN 'signed' WHEN state = 'publishing' THEN 'publish_retryable' ELSE state END, claim_token = NULL, claim_owner = NULL, claim_expires_at_ms = NULL, active_delivery_plan_id = NULL, updated_at_ms = ? WHERE claim_token IS NOT NULL AND claim_expires_at_ms <= ? AND state IN ('signing', 'signed', 'publishing', 'deferred_until_implemented')",
)
.bind(now_ms)
.bind(now_ms)
@@ -1021,25 +1018,6 @@ impl RadrootsOutbox {
.await
}
- pub async fn mark_delivery_target_preview_unavailable(
- &self,
- outbox_event_id: i64,
- claim_token: &str,
- delivery_target_id: i64,
- message: &str,
- attempted_at_ms: i64,
- ) -> Result<(), RadrootsOutboxError> {
- self.mark_delivery_target_status(
- outbox_event_id,
- claim_token,
- delivery_target_id,
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable,
- Some(message),
- attempted_at_ms,
- )
- .await
- }
-
pub async fn mark_delivery_target_skipped_policy_denied(
&self,
outbox_event_id: i64,
@@ -1400,7 +1378,6 @@ struct PlanEvaluation {
all_complete: bool,
any_failed_terminal: bool,
any_ready: bool,
- any_preview_unavailable: bool,
any_deferred_until_implemented: bool,
}
@@ -1437,13 +1414,6 @@ fn publish_lifecycle_from_plan_evaluation<'a>(
Some(terminal_error),
now_ms,
)
- } else if evaluation.any_preview_unavailable {
- (
- RadrootsOutboxEventState::PreviewUnavailable,
- Some(RadrootsOutboxOperationStatus::PreviewUnavailable),
- None,
- now_ms,
- )
} else if evaluation.any_deferred_until_implemented {
(
RadrootsOutboxEventState::DeferredUntilImplemented,
@@ -1516,22 +1486,12 @@ fn prepare_delivery_plan(
return Err(RadrootsOutboxError::EmptyDeliveryTargets);
}
validate_unique_targets(&targets)?;
- if targets
- .iter()
- .any(|target| target.kind == RadrootsTransportKind::Proxy)
- && targets.len() != 1
- {
- return Err(RadrootsOutboxError::Transport(
- RadrootsTransportError::InvalidTransportKind,
- ));
- }
let total_target_count = targets.len();
let prepared_targets = targets
.into_iter()
.map(|target| {
validate_delivery_target(&target)?;
- let initial_status =
- initial_delivery_target_status(&target, input.reticulum_preview_behavior);
+ let initial_status = initial_delivery_target_status(&target, input.reticulum_behavior);
Ok(PreparedDeliveryTarget {
target,
initial_status,
@@ -1546,7 +1506,7 @@ fn prepare_delivery_plan(
let initial_status = initial_delivery_plan_status(&satisfaction_policy, &prepared_targets);
let target_policy_fingerprint = target_policy_fingerprint(
&satisfaction_policy,
- input.reticulum_preview_behavior,
+ input.reticulum_behavior,
&prepared_targets,
);
let delivery_plan_idempotency_digest = delivery_plan_idempotency_digest(
@@ -1607,7 +1567,7 @@ fn satisfaction_target_count(
) -> usize {
let delivery_capable_target_count = prepared_targets
.iter()
- .filter(|target| !target.initial_status.is_deferred_preview())
+ .filter(|target| !target.initial_status.is_deferred_until_implemented())
.count();
if delivery_capable_target_count == 0 {
return total_target_count;
@@ -1617,7 +1577,7 @@ fn satisfaction_target_count(
fn validate_delivery_target(target: &RadrootsTransportTarget) -> Result<(), RadrootsOutboxError> {
if target.kind == RadrootsTransportKind::Reticulum
- && target.uri.as_str() != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ && target.uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI
{
return Err(RadrootsOutboxError::Transport(
radroots_transport::RadrootsTransportError::InvalidTargetUri,
@@ -1640,16 +1600,16 @@ fn validate_unique_targets(targets: &[RadrootsTransportTarget]) -> Result<(), Ra
fn initial_delivery_target_status(
target: &RadrootsTransportTarget,
- reticulum_preview_behavior: RadrootsOutboxReticulumPreviewBehavior,
+ reticulum_behavior: RadrootsOutboxReticulumBehavior,
) -> RadrootsOutboxDeliveryTargetStatus {
if target.kind != RadrootsTransportKind::Reticulum {
return RadrootsOutboxDeliveryTargetStatus::Pending;
}
- match reticulum_preview_behavior {
- RadrootsOutboxReticulumPreviewBehavior::RejectDeliveryAttempts => {
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ match reticulum_behavior {
+ RadrootsOutboxReticulumBehavior::RejectDeliveryAttempts => {
+ RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}
- RadrootsOutboxReticulumPreviewBehavior::DeferDeliveryPlans => {
+ RadrootsOutboxReticulumBehavior::DeferDeliveryPlans => {
RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}
}
@@ -1669,11 +1629,6 @@ fn initial_delivery_plan_status(
return RadrootsOutboxDeliveryPlanStatus::Queued;
}
if prepared_targets.iter().all(|target| {
- target.initial_status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
- }) {
- return RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable;
- }
- if prepared_targets.iter().all(|target| {
target.initial_status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}) {
return RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented;
@@ -1874,15 +1829,6 @@ async fn signed_event_lifecycle_for_plans(
}
if plans
.iter()
- .any(|plan| plan.status == RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable)
- {
- return Ok((
- RadrootsOutboxEventState::PreviewUnavailable,
- RadrootsOutboxOperationStatus::PreviewUnavailable,
- ));
- }
- if plans
- .iter()
.any(|plan| plan.status == RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented)
{
return Ok((
@@ -2102,17 +2048,17 @@ async fn delivery_targets_for_event_pool(
rows.into_iter().map(delivery_target_from_row).collect()
}
-async fn reticulum_preview_event_ids_pool(
+async fn reticulum_event_ids_pool(
pool: &SqlitePool,
outbox_event_id: Option<i64>,
limit: usize,
) -> Result<Vec<i64>, RadrootsOutboxError> {
let limit = i64::try_from(limit).map_err(|_| RadrootsOutboxError::IntegerRange {
- field: "reticulum_preview_events.limit",
+ field: "reticulum_events.limit",
value: i64::MAX,
})?;
let mut query = String::from(
- "SELECT event.outbox_event_id FROM outbox_event AS event WHERE event.signed_event_json IS NOT NULL AND EXISTS (SELECT 1 FROM outbox_delivery_plan AS plan JOIN outbox_delivery_target AS target ON target.delivery_plan_id = plan.delivery_plan_id WHERE plan.outbox_event_id = event.outbox_event_id AND plan.status IN ('queued', 'preview_unavailable', 'deferred_until_implemented') AND target.transport_kind = 'reticulum' AND target.status IN ('pending', 'failed_retryable', 'preview_unavailable', 'deferred_until_implemented'))",
+ "SELECT event.outbox_event_id FROM outbox_event AS event WHERE event.signed_event_json IS NOT NULL AND EXISTS (SELECT 1 FROM outbox_delivery_plan AS plan JOIN outbox_delivery_target AS target ON target.delivery_plan_id = plan.delivery_plan_id WHERE plan.outbox_event_id = event.outbox_event_id AND plan.status IN ('queued', 'deferred_until_implemented') AND target.transport_kind = 'reticulum' AND target.status IN ('pending', 'failed_retryable', 'deferred_until_implemented'))",
);
if outbox_event_id.is_some() {
query.push_str(" AND event.outbox_event_id = ?");
@@ -2129,12 +2075,12 @@ async fn reticulum_preview_event_ids_pool(
.map_err(Into::into)
}
-async fn reticulum_preview_targets_for_event_pool(
+async fn reticulum_targets_for_event_pool(
pool: &SqlitePool,
outbox_event_id: i64,
) -> Result<Vec<RadrootsOutboxDeliveryTargetRecord>, RadrootsOutboxError> {
let rows = sqlx::query(
- "SELECT target.delivery_target_id, target.delivery_plan_id, target.transport_kind, target.endpoint_uri, target.target_scope, target.target_label, target.endpoint_fingerprint, target.status, target.last_outcome_kind, target.attempt_count, target.last_attempt_at_ms, target.completed_at_ms, target.last_error FROM outbox_delivery_target AS target JOIN outbox_delivery_plan AS plan ON plan.delivery_plan_id = target.delivery_plan_id WHERE plan.outbox_event_id = ? AND plan.status IN ('queued', 'preview_unavailable', 'deferred_until_implemented') AND target.transport_kind = 'reticulum' AND target.status IN ('pending', 'failed_retryable', 'preview_unavailable', 'deferred_until_implemented') ORDER BY target.delivery_plan_id, target.delivery_target_id",
+ "SELECT target.delivery_target_id, target.delivery_plan_id, target.transport_kind, target.endpoint_uri, target.target_scope, target.target_label, target.endpoint_fingerprint, target.status, target.last_outcome_kind, target.attempt_count, target.last_attempt_at_ms, target.completed_at_ms, target.last_error FROM outbox_delivery_target AS target JOIN outbox_delivery_plan AS plan ON plan.delivery_plan_id = target.delivery_plan_id WHERE plan.outbox_event_id = ? AND plan.status IN ('queued', 'deferred_until_implemented') AND target.transport_kind = 'reticulum' AND target.status IN ('pending', 'failed_retryable', 'deferred_until_implemented') ORDER BY target.delivery_plan_id, target.delivery_target_id",
)
.bind(outbox_event_id)
.fetch_all(pool)
@@ -2216,7 +2162,6 @@ async fn evaluate_delivery_plans(
let mut all_complete = !plans.is_empty();
let mut any_failed_terminal = false;
let mut any_ready = false;
- let mut any_preview_unavailable = false;
let mut any_deferred_until_implemented = false;
for plan in plans {
let targets = delivery_targets_for_plan_tx(tx, plan.delivery_plan_id).await?;
@@ -2228,13 +2173,7 @@ async fn evaluate_delivery_plans(
.count();
let deferred_count = status_targets
.iter()
- .filter(|target| target.status.is_deferred_preview())
- .count();
- let preview_unavailable_count = status_targets
- .iter()
- .filter(|target| {
- target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
- })
+ .filter(|target| target.status.is_deferred_until_implemented())
.count();
let terminal_failure_count = status_targets
.iter()
@@ -2246,8 +2185,6 @@ async fn evaluate_delivery_plans(
RadrootsOutboxDeliveryPlanStatus::Queued
} else if terminal_failure_count > 0 {
RadrootsOutboxDeliveryPlanStatus::FailedTerminal
- } else if preview_unavailable_count > 0 {
- RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable
} else if deferred_count > 0 {
RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented
} else {
@@ -2262,9 +2199,6 @@ async fn evaluate_delivery_plans(
if ready_count > 0 {
any_ready = true;
}
- if plan_status == RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable {
- any_preview_unavailable = true;
- }
if plan_status == RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented {
any_deferred_until_implemented = true;
}
@@ -2283,7 +2217,6 @@ async fn evaluate_delivery_plans(
all_complete,
any_failed_terminal,
any_ready,
- any_preview_unavailable,
any_deferred_until_implemented,
})
}
@@ -2532,9 +2465,6 @@ fn outcome_kind_for_status(
RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented => {
RadrootsTransportOutcomeKind::DeferredUntilImplemented
}
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable => {
- RadrootsTransportOutcomeKind::TransportUnavailable
- }
RadrootsOutboxDeliveryTargetStatus::SkippedPolicyDenied => {
RadrootsTransportOutcomeKind::PolicyDenied
}
@@ -2617,7 +2547,7 @@ fn operation_idempotency_digest(
#[derive(Serialize)]
struct TargetPolicyDigestInput<'a> {
satisfaction_policy: String,
- reticulum_preview_behavior: &'a str,
+ reticulum_behavior: &'a str,
targets: Vec<TargetPolicyDigestTarget<'a>>,
}
@@ -2630,7 +2560,7 @@ struct TargetPolicyDigestTarget<'a> {
fn target_policy_fingerprint(
satisfaction_policy: &RadrootsTransportSatisfactionPolicy,
- reticulum_preview_behavior: RadrootsOutboxReticulumPreviewBehavior,
+ reticulum_behavior: RadrootsOutboxReticulumBehavior,
targets: &[PreparedDeliveryTarget],
) -> String {
let mut target_inputs = targets
@@ -2649,7 +2579,7 @@ fn target_policy_fingerprint(
});
sha256_json(&TargetPolicyDigestInput {
satisfaction_policy: satisfaction_policy_storage_value(satisfaction_policy),
- reticulum_preview_behavior: reticulum_preview_behavior.as_str(),
+ reticulum_behavior: reticulum_behavior.as_str(),
targets: target_inputs,
})
}
@@ -2865,7 +2795,8 @@ mod tests {
}
fn scoped_reticulum_target(scope: &str, label: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::reticulum_preview_with_metadata(
+ RadrootsTransportTarget::reticulum_with_metadata(
+ RADROOTS_RETICULUM_ENDPOINT_URI,
Some(RadrootsTransportMeshScopeId::parse(scope).expect("target scope")),
Some(RadrootsTransportTargetLabel::parse(label).expect("target label")),
)
@@ -2873,12 +2804,8 @@ mod tests {
}
fn reticulum_target(uri: &str) -> RadrootsTransportTarget {
- assert_eq!(uri, RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
- RadrootsTransportTarget::reticulum_preview().expect("reticulum target")
- }
-
- fn proxy_target(uri: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::proxy(uri).expect("proxy target")
+ assert_eq!(uri, RADROOTS_RETICULUM_ENDPOINT_URI);
+ RadrootsTransportTarget::reticulum().expect("reticulum target")
}
#[test]
@@ -3113,17 +3040,6 @@ mod tests {
)
.await
}
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable => {
- outbox
- .mark_delivery_target_preview_unavailable(
- outbox_event_id,
- claim_token,
- delivery_target_id,
- "transport preview unavailable",
- attempted_at_ms,
- )
- .await
- }
RadrootsOutboxDeliveryTargetStatus::SkippedPolicyDenied => {
outbox
.mark_delivery_target_skipped_policy_denied(
@@ -3326,7 +3242,7 @@ mod tests {
RadrootsTransportSatisfactionPolicy::no_wait(),
vec![
nostr_target(NOSTR_PRIMARY_WSS),
- reticulum_target(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI),
+ reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI),
],
),
true,
@@ -3388,9 +3304,9 @@ mod tests {
);
assert!(
outbox
- .reticulum_preview_events(None, 10)
+ .reticulum_events(None, 10)
.await
- .expect("reticulum preview events")
+ .expect("reticulum events")
.is_empty()
);
}
@@ -3512,9 +3428,7 @@ mod tests {
.enqueue_operation(RadrootsOutboxOperationInput::new(
"publish_post",
draft,
- delivery_plan(vec![malformed_reticulum_target(
- "reticulum:preview-unavailable-alt",
- )]),
+ delivery_plan(vec![malformed_reticulum_target("reticulum:alternate")]),
1_000,
))
.await
@@ -3533,34 +3447,6 @@ mod tests {
}
#[tokio::test]
- async fn enqueue_rejects_mixed_proxy_delegate_targets_before_persistence() {
- let outbox = RadrootsOutbox::open_memory().await.expect("open");
- let draft = post_draft(hex_64('a').as_str(), "mixed proxy");
-
- let err = outbox
- .enqueue_operation(RadrootsOutboxOperationInput::new(
- "publish_post",
- draft,
- delivery_plan(vec![
- proxy_target("radrootsd-proxy:publish"),
- nostr_target(NOSTR_PRIMARY_WSS),
- ]),
- 1_000,
- ))
- .await
- .expect_err("mixed proxy targets");
-
- assert!(matches!(
- err,
- RadrootsOutboxError::Transport(RadrootsTransportError::InvalidTransportKind)
- ));
- assert_eq!(table_count(&outbox, "outbox_operations").await, 0);
- assert_eq!(table_count(&outbox, "outbox_event").await, 0);
- assert_eq!(table_count(&outbox, "outbox_delivery_plan").await, 0);
- assert_eq!(table_count(&outbox, "outbox_delivery_target").await, 0);
- }
-
- #[tokio::test]
async fn signed_enqueue_claims_ready_delivery_targets_and_records_attempts() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "signed");
@@ -3905,7 +3791,7 @@ mod tests {
for terminal_status in [
RadrootsOutboxDeliveryTargetStatus::Accepted,
RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented,
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable,
+ RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented,
RadrootsOutboxDeliveryTargetStatus::SkippedPolicyDenied,
RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
] {
@@ -4169,7 +4055,7 @@ mod tests {
draft.clone(),
signed_event.clone(),
RadrootsOutboxDeliveryPlanInput::new(
- "transport.proxy.invalid",
+ "transport.nostr.invalid_plan",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![nostr_target(NOSTR_PRIMARY_WSS)],
@@ -4215,7 +4101,7 @@ mod tests {
.mark_active_delivery_plan_failed_terminal(
first.outbox_event_id,
"claim-a",
- "local proxy validation failed",
+ "local validation failed",
1_100,
)
.await
@@ -4494,7 +4380,7 @@ mod tests {
}
#[tokio::test]
- async fn reticulum_reject_targets_are_preview_unavailable_and_not_ready() {
+ async fn reticulum_reject_targets_are_deferred_until_implemented_and_not_ready() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "reticulum rejected");
let signed_event =
@@ -4505,10 +4391,10 @@ mod tests {
draft,
signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
- vec![reticulum_target("reticulum:preview-unavailable")],
+ vec![reticulum_target("reticulum:local")],
),
true,
1_007,
@@ -4523,7 +4409,7 @@ mod tests {
assert_eq!(targets.len(), 1);
assert_eq!(
targets[0].status,
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
);
let plans = outbox
.delivery_plans(receipt.outbox_event_id)
@@ -4531,14 +4417,17 @@ mod tests {
.expect("plans");
assert_eq!(
plans[0].status,
- RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable
+ RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented
);
let event = outbox
.get_event(receipt.outbox_event_id)
.await
.expect("event")
.expect("event");
- assert_eq!(event.state, RadrootsOutboxEventState::PreviewUnavailable);
+ assert_eq!(
+ event.state,
+ RadrootsOutboxEventState::DeferredUntilImplemented
+ );
let operation = outbox
.get_operation(receipt.operation_id)
.await
@@ -4546,13 +4435,12 @@ mod tests {
.expect("operation");
assert_eq!(
operation.status,
- RadrootsOutboxOperationStatus::PreviewUnavailable
+ RadrootsOutboxOperationStatus::DeferredUntilImplemented
);
let summary = outbox.status_summary(1_000).await.expect("summary");
assert_eq!(summary.pending_events, 0);
assert_eq!(summary.ready_signed_events, 0);
- assert_eq!(summary.preview_unavailable_events, 1);
- assert_eq!(summary.deferred_until_implemented_events, 0);
+ assert_eq!(summary.deferred_until_implemented_events, 1);
assert!(
outbox
.claim_next_ready_signed_event("publisher", "claim-a", 2_000, 1_000)
@@ -4574,14 +4462,12 @@ mod tests {
draft,
signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
- vec![reticulum_target("reticulum:preview-unavailable")],
+ vec![reticulum_target("reticulum:local")],
)
- .with_reticulum_preview_behavior(
- RadrootsOutboxReticulumPreviewBehavior::DeferDeliveryPlans,
- ),
+ .with_reticulum_behavior(RadrootsOutboxReticulumBehavior::DeferDeliveryPlans),
true,
1_007,
1_000,
@@ -4626,7 +4512,6 @@ mod tests {
let summary = outbox.status_summary(1_000).await.expect("summary");
assert_eq!(summary.pending_events, 0);
assert_eq!(summary.ready_signed_events, 0);
- assert_eq!(summary.preview_unavailable_events, 0);
assert_eq!(summary.deferred_until_implemented_events, 1);
assert!(
outbox
@@ -4645,10 +4530,10 @@ mod tests {
default_behavior_draft,
default_behavior_signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
- vec![reticulum_target("reticulum:preview-unavailable")],
+ vec![reticulum_target("reticulum:local")],
),
true,
1_007,
@@ -4663,29 +4548,28 @@ mod tests {
}
#[tokio::test]
- async fn reticulum_preview_events_report_preview_and_deferred_records() {
+ async fn reticulum_events_report_deferred_records() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
- let preview_draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "reticulum preview record");
- let preview_signed_event =
- radroots_nostr_sign_frozen_draft(&fixture_keys(), &preview_draft)
- .expect("preview signed event");
- let preview = outbox
+ let reject_draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "reticulum record");
+ let reject_signed_event = radroots_nostr_sign_frozen_draft(&fixture_keys(), &reject_draft)
+ .expect("reticulum signed event");
+ let reject = outbox
.enqueue_signed_operation(RadrootsOutboxSignedOperationInput::new(
"publish_post",
- preview_draft,
- preview_signed_event,
+ reject_draft,
+ reject_signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
- vec![scoped_reticulum_target("farm.preview", "Preview mesh")],
+ vec![scoped_reticulum_target("farm.reticulum", "Reticulum mesh")],
),
true,
1_007,
1_000,
))
.await
- .expect("preview enqueue");
+ .expect("reticulum enqueue");
let deferred_draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "reticulum deferred record");
let deferred_signed_event =
radroots_nostr_sign_frozen_draft(&fixture_keys(), &deferred_draft)
@@ -4696,14 +4580,12 @@ mod tests {
deferred_draft,
deferred_signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![scoped_reticulum_target("farm.deferred", "Deferred mesh")],
)
- .with_reticulum_preview_behavior(
- RadrootsOutboxReticulumPreviewBehavior::DeferDeliveryPlans,
- ),
+ .with_reticulum_behavior(RadrootsOutboxReticulumBehavior::DeferDeliveryPlans),
true,
1_008,
1_001,
@@ -4711,38 +4593,35 @@ mod tests {
.await
.expect("deferred enqueue");
- let records = outbox
- .reticulum_preview_events(None, 10)
- .await
- .expect("records");
+ let records = outbox.reticulum_events(None, 10).await.expect("records");
assert_eq!(records.len(), 2);
- assert_eq!(records[0].event.outbox_event_id, preview.outbox_event_id);
+ assert_eq!(records[0].event.outbox_event_id, reject.outbox_event_id);
assert_eq!(
records[0].event.state,
- RadrootsOutboxEventState::PreviewUnavailable
+ RadrootsOutboxEventState::DeferredUntilImplemented
);
assert_eq!(records[0].targets.len(), 1);
assert_eq!(
records[0].targets[0].status,
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
);
assert_eq!(
records[0].targets[0]
.target_scope
.as_ref()
.map(|scope| scope.as_str()),
- Some("farm.preview")
+ Some("farm.reticulum")
);
assert_eq!(
records[0].targets[0]
.target_label
.as_ref()
.map(|label| label.as_str()),
- Some("Preview mesh")
+ Some("Reticulum mesh")
);
assert_eq!(
records[0].targets[0].last_outcome_kind,
- Some(RadrootsTransportOutcomeKind::TransportUnavailable)
+ Some(RadrootsTransportOutcomeKind::DeferredUntilImplemented)
);
assert_eq!(records[0].targets[0].attempt_count, 0);
assert_eq!(records[1].event.outbox_event_id, deferred.outbox_event_id);
@@ -4775,29 +4654,26 @@ mod tests {
);
assert_eq!(records[1].targets[0].attempt_count, 0);
- let limited = outbox
- .reticulum_preview_events(None, 1)
- .await
- .expect("limited");
+ let limited = outbox.reticulum_events(None, 1).await.expect("limited");
assert_eq!(limited.len(), 1);
- assert_eq!(limited[0].event.outbox_event_id, preview.outbox_event_id);
+ assert_eq!(limited[0].event.outbox_event_id, reject.outbox_event_id);
let specific = outbox
- .reticulum_preview_events(Some(deferred.outbox_event_id), 10)
+ .reticulum_events(Some(deferred.outbox_event_id), 10)
.await
.expect("specific");
assert_eq!(specific.len(), 1);
assert_eq!(specific[0].event.outbox_event_id, deferred.outbox_event_id);
assert!(
outbox
- .reticulum_preview_events(Some(999), 10)
+ .reticulum_events(Some(999), 10)
.await
.expect("missing")
.is_empty()
);
assert!(
outbox
- .reticulum_preview_events(None, 0)
+ .reticulum_events(None, 0)
.await
.expect("zero")
.is_empty()
@@ -4805,7 +4681,7 @@ mod tests {
}
#[tokio::test]
- async fn complete_signing_sets_preview_unavailable_lifecycle_for_reticulum_only_plans() {
+ async fn complete_signing_sets_deferred_until_implemented_lifecycle_for_reticulum_only_plans() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "reticulum after signing");
let receipt = outbox
@@ -4813,10 +4689,10 @@ mod tests {
"publish_post",
draft,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.reticulum.preview",
+ "transport.reticulum.default",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
- vec![reticulum_target("reticulum:preview-unavailable")],
+ vec![reticulum_target("reticulum:local")],
),
1_000,
))
@@ -4841,7 +4717,10 @@ mod tests {
.await
.expect("event")
.expect("event");
- assert_eq!(event.state, RadrootsOutboxEventState::PreviewUnavailable);
+ assert_eq!(
+ event.state,
+ RadrootsOutboxEventState::DeferredUntilImplemented
+ );
assert_eq!(event.claim_token, None);
let operation = outbox
.get_operation(receipt.operation_id)
@@ -4850,7 +4729,7 @@ mod tests {
.expect("operation");
assert_eq!(
operation.status,
- RadrootsOutboxOperationStatus::PreviewUnavailable
+ RadrootsOutboxOperationStatus::DeferredUntilImplemented
);
assert!(
outbox
@@ -4862,9 +4741,9 @@ mod tests {
}
#[tokio::test]
- async fn hybrid_all_targets_completes_after_nostr_success_with_reticulum_preview_preserved() {
+ async fn explicit_all_targets_completes_after_nostr_success_with_reticulum_preserved() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
- let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "hybrid all targets");
+ let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "explicit all targets");
let signed_event =
radroots_nostr_sign_frozen_draft(&fixture_keys(), &draft).expect("signed event");
let receipt = outbox
@@ -4873,12 +4752,12 @@ mod tests {
draft,
signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.hybrid",
+ "transport.explicit.multi_target",
1,
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![
nostr_target(NOSTR_PRIMARY_WSS),
- reticulum_target("reticulum:preview-unavailable"),
+ reticulum_target("reticulum:local"),
],
),
true,
@@ -4905,7 +4784,7 @@ mod tests {
.any(|target| target.status == RadrootsOutboxDeliveryTargetStatus::Pending)
);
assert!(targets.iter().any(|target| {
- target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}));
let claimed = outbox
.claim_next_ready_signed_event("publisher", "claim-a", 2_000, 1_000)
@@ -4957,16 +4836,16 @@ mod tests {
&& target.attempt_count == 1
}));
assert!(targets.iter().any(|target| {
- target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
&& target.attempt_count == 0
&& target.completed_at_ms.is_none()
}));
}
#[tokio::test]
- async fn hybrid_quorum_rejects_preview_targets_as_required_success_capacity() {
+ async fn explicit_quorum_rejects_deferred_targets_as_required_success_capacity() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
- let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "hybrid quorum");
+ let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "explicit quorum");
let signed_event =
radroots_nostr_sign_frozen_draft(&fixture_keys(), &draft).expect("signed event");
let err = outbox
@@ -4975,12 +4854,12 @@ mod tests {
draft,
signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.hybrid",
+ "transport.explicit.multi_target",
1,
RadrootsTransportSatisfactionPolicy::quorum_accepted(2),
vec![
nostr_target(NOSTR_PRIMARY_WSS),
- reticulum_target("reticulum:preview-unavailable"),
+ reticulum_target("reticulum:local"),
],
),
true,
@@ -5001,9 +4880,9 @@ mod tests {
}
#[tokio::test]
- async fn hybrid_nostr_and_reticulum_preview_preserves_ready_nostr_work() {
+ async fn explicit_nostr_and_reticulum_preserves_ready_nostr_work() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
- let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "hybrid preview");
+ let draft = post_draft(FIXTURE_ALICE_PUBLIC_KEY_HEX, "explicit reticulum");
let signed_event =
radroots_nostr_sign_frozen_draft(&fixture_keys(), &draft).expect("signed event");
let receipt = outbox
@@ -5012,12 +4891,12 @@ mod tests {
draft,
signed_event,
RadrootsOutboxDeliveryPlanInput::new(
- "transport.hybrid",
+ "transport.explicit.multi_target",
1,
RadrootsTransportSatisfactionPolicy::any_accepted(),
vec![
nostr_target(NOSTR_PRIMARY_WSS),
- reticulum_target("reticulum:preview-unavailable"),
+ reticulum_target("reticulum:local"),
],
),
true,
@@ -5037,7 +4916,7 @@ mod tests {
.any(|target| target.status == RadrootsOutboxDeliveryTargetStatus::Pending)
);
assert!(targets.iter().any(|target| {
- target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}));
let plans = outbox
.delivery_plans(receipt.outbox_event_id)
@@ -5054,7 +4933,6 @@ mod tests {
);
let summary = outbox.status_summary(1_000).await.expect("summary");
assert_eq!(summary.pending_events, 1);
- assert_eq!(summary.preview_unavailable_events, 0);
assert_eq!(summary.deferred_until_implemented_events, 0);
}
@@ -5286,7 +5164,7 @@ mod tests {
}
#[tokio::test]
- async fn claimed_delivery_targets_can_complete_as_preview_outcomes() {
+ async fn claimed_delivery_targets_can_complete_as_deferred_outcomes() {
for (
content,
mark,
@@ -5296,7 +5174,7 @@ mod tests {
expected_operation_status,
) in [
(
- "proxy deferred",
+ "transport deferred",
"deferred",
RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented,
RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented,
@@ -5304,12 +5182,12 @@ mod tests {
RadrootsOutboxOperationStatus::DeferredUntilImplemented,
),
(
- "proxy preview unavailable",
- "preview",
- RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable,
- RadrootsOutboxDeliveryPlanStatus::PreviewUnavailable,
- RadrootsOutboxEventState::PreviewUnavailable,
- RadrootsOutboxOperationStatus::PreviewUnavailable,
+ "transport unavailable",
+ "unavailable",
+ RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented,
+ RadrootsOutboxDeliveryPlanStatus::DeferredUntilImplemented,
+ RadrootsOutboxEventState::DeferredUntilImplemented,
+ RadrootsOutboxOperationStatus::DeferredUntilImplemented,
),
] {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
@@ -5353,17 +5231,17 @@ mod tests {
.await
.expect("deferred");
}
- "preview" => {
+ "unavailable" => {
outbox
- .mark_delivery_target_preview_unavailable(
+ .mark_delivery_target_deferred_until_implemented(
receipt.outbox_event_id,
"claim-a",
target_id,
- "transport preview unavailable",
+ "transport unavailable",
1_100,
)
.await
- .expect("preview unavailable");
+ .expect("transport unavailable");
}
_ => unreachable!(),
}
@@ -5416,13 +5294,6 @@ mod tests {
retryable_events: 0,
terminal_events: 0,
failed_terminal_events: 0,
- preview_unavailable_events: if expected_status
- == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
- {
- 1
- } else {
- 0
- },
deferred_until_implemented_events: if expected_status
== RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
{
diff --git a/crates/protocol_contract_v1/src/lib.rs b/crates/protocol_contract_v1/src/lib.rs
@@ -38,11 +38,6 @@ impl TransportKindV1 {
"local" => Ok(Self::Local),
"nostr" => Ok(Self::Nostr),
"reticulum" => Ok(Self::Reticulum),
- "reticulum_preview" | "mesh" | "proxy" | "radrootsd_proxy" | "hybrid" => {
- Err(ProtocolContractErrorV1::RetiredTransportIdentity {
- identity: value.to_string(),
- })
- }
_ => Err(ProtocolContractErrorV1::UnknownTransportKind {
value: value.to_string(),
}),
@@ -446,7 +441,6 @@ pub enum ProtocolContractErrorV1 {
RetiredEventKind { kind: u32 },
RetiredEventName { name: String },
RetiredTradeState { state: String },
- RetiredTransportIdentity { identity: String },
UnknownTradeState { value: String },
UnknownTransportKind { value: String },
InvalidMeshScopeId,
@@ -471,9 +465,6 @@ impl core::fmt::Display for ProtocolContractErrorV1 {
Self::RetiredEventKind { kind } => write!(f, "retired event kind {kind}"),
Self::RetiredEventName { name } => write!(f, "retired event name {name}"),
Self::RetiredTradeState { state } => write!(f, "retired trade state {state}"),
- Self::RetiredTransportIdentity { identity } => {
- write!(f, "retired transport identity {identity}")
- }
Self::UnknownTradeState { value } => write!(f, "unknown trade state {value}"),
Self::UnknownTransportKind { value } => write!(f, "unknown transport kind {value}"),
Self::InvalidMeshScopeId => f.write_str("invalid mesh scope id"),
@@ -614,23 +605,23 @@ mod tests {
}
#[test]
- fn transport_kind_v1_rejects_retired_identities() {
+ fn transport_kind_v1_rejects_unrecognized_identities() {
for identity in [
- "reticulum_preview",
+ concat!("reticulum", "_preview"),
"mesh",
- "proxy",
- "radrootsd_proxy",
- "hybrid",
+ concat!("pro", "xy"),
+ concat!("radrootsd", "_", "pro", "xy"),
+ concat!("hy", "brid"),
] {
assert!(matches!(
TransportKindV1::parse(identity),
- Err(ProtocolContractErrorV1::RetiredTransportIdentity { .. })
+ Err(ProtocolContractErrorV1::UnknownTransportKind { .. })
));
assert_eq!(
TransportKindV1::parse(identity)
- .expect_err("retired transport")
+ .expect_err("unknown transport")
.to_string(),
- alloc::format!("retired transport identity {identity}")
+ alloc::format!("unknown transport kind {identity}")
);
}
}
diff --git a/crates/runtime/src/transport.rs b/crates/runtime/src/transport.rs
@@ -650,7 +650,8 @@ mod tests {
#[cfg(feature = "transport-workers")]
use radroots_event::{draft::RadrootsSignedEvent, wire::RadrootsNip01EventWire};
use radroots_transport::{
- RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt,
+ RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportCapabilityAvailability,
+ RadrootsTransportCapabilityMaturity, RadrootsTransportDeliveryReceipt,
RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
RadrootsTransportError, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest,
RadrootsTransportFuture, RadrootsTransportImplementationState, RadrootsTransportKind,
@@ -936,10 +937,10 @@ mod tests {
#[cfg(feature = "transport-reticulum")]
#[tokio::test]
- async fn registry_dispatches_reticulum_preview_transport_without_success() {
+ async fn registry_dispatches_reticulum_transport_without_success() {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
- .register(radroots_transport_reticulum::RadrootsReticulumPreviewTransport::default())
+ .register(radroots_transport_reticulum::RadrootsReticulumTransport::default())
.expect("register");
let transport = registry
.transport(&RadrootsTransportKind::Reticulum)
@@ -947,10 +948,7 @@ mod tests {
let request = RadrootsRuntimeTransportDispatchRequest::new(
"reticulum-delivery",
opaque_payload(),
- vec![target(
- RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
- )],
+ vec![target(RadrootsTransportKind::Reticulum, "reticulum:local")],
RadrootsTransportSatisfactionPolicy::any_accepted(),
1_000,
)
@@ -966,7 +964,15 @@ mod tests {
let status = transport.status().await.expect("status");
assert_eq!(
status.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
+ RadrootsTransportImplementationState::Real
+ );
+ assert_eq!(
+ status.maturity,
+ RadrootsTransportCapabilityMaturity::Preview
+ );
+ assert_eq!(
+ status.availability,
+ RadrootsTransportCapabilityAvailability::Unavailable
);
assert!(!status.capabilities.deliver);
assert!(!status.capabilities.fetch);
@@ -975,7 +981,7 @@ mod tests {
"reticulum-fetch",
RadrootsTransportTargetSet::new(vec![target(
RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
+ "reticulum:local",
)])
.expect("target set"),
))
@@ -989,7 +995,7 @@ mod tests {
);
assert_eq!(
receipt.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
);
}
@@ -1030,10 +1036,7 @@ mod tests {
);
let deferred = RadrootsRuntimeDeliveryTarget::deferred_until_implemented(
2,
- target(
- RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
- ),
+ target(RadrootsTransportKind::Reticulum, "reticulum:local"),
);
let receipt = worker
.execute_job(RadrootsRuntimeDeliveryJob {
@@ -1160,10 +1163,7 @@ mod tests {
bounded_queue_capacity: 8,
},
);
- let required_target = target(
- RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
- );
+ let required_target = target(RadrootsTransportKind::Reticulum, "reticulum:local");
let required_fingerprint = required_target.fingerprint.clone();
let receipt = worker
.execute_job(RadrootsRuntimeDeliveryJob {
@@ -1300,7 +1300,7 @@ mod tests {
#[cfg(feature = "transport-workers")]
#[tokio::test]
- async fn delivery_worker_reports_reticulum_preview_targets_unsatisfied_without_retry_failure() {
+ async fn delivery_worker_reports_reticulum_targets_unsatisfied_without_retry_failure() {
let registry = RadrootsRuntimeTransportRegistry::new();
let worker = RadrootsRuntimeDeliveryWorker::new(
®istry,
@@ -1308,13 +1308,10 @@ mod tests {
bounded_queue_capacity: 8,
},
);
- let preview_target = RadrootsRuntimeDeliveryTarget {
+ let reticulum_target = RadrootsRuntimeDeliveryTarget {
delivery_target_id: 1,
- target: target(
- RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
- ),
- status: RadrootsTransportDeliveryTargetStatus::PreviewUnavailable,
+ target: target(RadrootsTransportKind::Reticulum, "reticulum:local"),
+ status: RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
};
let receipt = worker
.execute_job(RadrootsRuntimeDeliveryJob {
@@ -1323,7 +1320,7 @@ mod tests {
plans: vec![RadrootsRuntimeDeliveryPlan {
delivery_plan_id: 7,
satisfaction_policy: RadrootsTransportSatisfactionPolicy::any_accepted(),
- targets: vec![preview_target],
+ targets: vec![reticulum_target],
}],
now_ms: 1_000,
})
diff --git a/crates/transport/src/kind.rs b/crates/transport/src/kind.rs
@@ -5,10 +5,7 @@ use alloc::string::{String, ToString};
pub enum RadrootsTransportKind {
Nostr,
Reticulum,
- Mesh,
Local,
- Proxy,
- Custom(String),
}
impl RadrootsTransportKind {
@@ -32,43 +29,16 @@ impl RadrootsTransportKind {
match canonical {
"nostr" => Ok(Self::Nostr),
"reticulum" => Ok(Self::Reticulum),
- "mesh" => Ok(Self::Mesh),
"local" => Ok(Self::Local),
- "proxy" => Ok(Self::Proxy),
- _ => Self::custom_canonical(canonical),
+ _ => Err(RadrootsTransportError::InvalidTransportKind),
}
}
- pub fn custom(value: impl Into<String>) -> Result<Self, RadrootsTransportError> {
- let value = value.into();
- let canonical = value.trim().to_ascii_lowercase();
- Self::custom_canonical(canonical.as_str())
- }
-
- fn custom_canonical(canonical: &str) -> Result<Self, RadrootsTransportError> {
- if canonical.is_empty() {
- return Err(RadrootsTransportError::EmptyTransportKind);
- }
- if removed_first_party_kind(canonical) {
- return Err(RadrootsTransportError::InvalidTransportKind);
- }
- if canonical
- .chars()
- .any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace() || ch == ':' || ch == '/')
- {
- return Err(RadrootsTransportError::InvalidTransportKind);
- }
- Ok(Self::Custom(canonical.to_string()))
- }
-
pub fn canonical_label(&self) -> String {
match self {
Self::Nostr => "nostr".to_string(),
Self::Reticulum => "reticulum".to_string(),
- Self::Mesh => "mesh".to_string(),
Self::Local => "local".to_string(),
- Self::Proxy => "proxy".to_string(),
- Self::Custom(value) => value.clone(),
}
}
}
@@ -94,19 +64,27 @@ impl<'de> serde::Deserialize<'de> for RadrootsTransportKind {
}
}
-fn removed_first_party_kind(canonical: &str) -> bool {
- const RADROOTSD_PROXY_PREFIX: &str = "radrootsd";
- const RADROOTSD_PROXY_SUFFIX: &str = "_proxy";
- canonical.len() == RADROOTSD_PROXY_PREFIX.len() + RADROOTSD_PROXY_SUFFIX.len()
- && canonical.starts_with(RADROOTSD_PROXY_PREFIX)
- && canonical.ends_with(RADROOTSD_PROXY_SUFFIX)
-}
-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RadrootsTransportImplementationState {
Real,
Mock,
- PreviewUnavailable,
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum RadrootsTransportCapabilityMaturity {
+ Preview,
+ Stable,
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum RadrootsTransportCapabilityAvailability {
+ Available,
+ Degraded,
+ Unavailable,
}
diff --git a/crates/transport/src/lib.rs b/crates/transport/src/lib.rs
@@ -18,9 +18,12 @@ pub use delivery::{
RadrootsTransportTargetReceipt,
};
pub use error::RadrootsTransportError;
-pub use kind::{RadrootsTransportImplementationState, RadrootsTransportKind};
+pub use kind::{
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
+ RadrootsTransportImplementationState, RadrootsTransportKind,
+};
pub use message::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID,
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
};
pub use payload::RadrootsTransportPayload;
diff --git a/crates/transport/src/message.rs b/crates/transport/src/message.rs
@@ -1,7 +1,7 @@
-pub const RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI: &str = "reticulum:preview-unavailable";
-pub const RADROOTS_RETICULUM_PREVIEW_SCOPE_ID: &str = "local_preview";
+pub const RADROOTS_RETICULUM_ENDPOINT_URI: &str = "reticulum:local";
+pub const RADROOTS_RETICULUM_SCOPE_ID: &str = "local";
pub const RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE: &str = concat!(
- "Reticulum transport is configured in preview mode, ",
+ "Reticulum transport is configured, ",
"but this build does not implement Reticulum delivery."
);
diff --git a/crates/transport/src/status.rs b/crates/transport/src/status.rs
@@ -1,4 +1,5 @@
use crate::{
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
RadrootsTransportImplementationState, RadrootsTransportKind,
delivery::RadrootsTransportSatisfactionClass,
};
@@ -14,7 +15,6 @@ pub enum RadrootsTransportDeliveryTargetStatus {
StoredByGateway,
Seen,
DeferredUntilImplemented,
- PreviewUnavailable,
SkippedPolicyDenied,
FailedRetryable,
FailedTerminal,
@@ -66,11 +66,8 @@ impl RadrootsTransportDeliveryTargetStatus {
matches!(self, Self::SkippedPolicyDenied | Self::FailedTerminal)
}
- pub fn is_deferred_preview(self) -> bool {
- matches!(
- self,
- Self::DeferredUntilImplemented | Self::PreviewUnavailable
- )
+ pub fn is_deferred_until_implemented(self) -> bool {
+ matches!(self, Self::DeferredUntilImplemented)
}
}
@@ -224,6 +221,8 @@ pub struct RadrootsTransportStatus {
pub endpoint_uri: Option<String>,
pub configured: bool,
pub implementation: RadrootsTransportImplementationState,
+ pub maturity: RadrootsTransportCapabilityMaturity,
+ pub availability: RadrootsTransportCapabilityAvailability,
pub usable_for_delivery: bool,
pub capabilities: RadrootsTransportCapabilities,
pub message: String,
@@ -243,6 +242,12 @@ impl RadrootsTransportStatus {
endpoint_uri: None,
configured,
implementation,
+ maturity: RadrootsTransportCapabilityMaturity::Stable,
+ availability: if usable_for_delivery {
+ RadrootsTransportCapabilityAvailability::Available
+ } else {
+ RadrootsTransportCapabilityAvailability::Unavailable
+ },
usable_for_delivery,
capabilities: if usable_for_delivery {
RadrootsTransportCapabilities::deliver_only()
@@ -258,6 +263,19 @@ impl RadrootsTransportStatus {
self
}
+ pub fn with_maturity(mut self, maturity: RadrootsTransportCapabilityMaturity) -> Self {
+ self.maturity = maturity;
+ self
+ }
+
+ pub fn with_availability(
+ mut self,
+ availability: RadrootsTransportCapabilityAvailability,
+ ) -> Self {
+ self.availability = availability;
+ self
+ }
+
pub fn with_profile_id(mut self, profile_id: impl Into<String>) -> Self {
self.profile_id = Some(profile_id.into());
self
diff --git a/crates/transport/src/target.rs b/crates/transport/src/target.rs
@@ -1,6 +1,6 @@
use crate::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
- RadrootsTransportError, RadrootsTransportKind,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID, RadrootsTransportError,
+ RadrootsTransportKind,
};
use alloc::collections::BTreeSet;
use alloc::format;
@@ -54,9 +54,8 @@ impl RadrootsTransportMeshScopeId {
Ok(Self(value.to_string()))
}
- pub fn local_preview() -> Self {
- Self::parse(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID)
- .expect("default Reticulum preview scope id")
+ pub fn local_reticulum() -> Self {
+ Self::parse(RADROOTS_RETICULUM_SCOPE_ID).expect("default Reticulum scope id")
}
pub fn as_str(&self) -> &str {
@@ -178,20 +177,16 @@ impl RadrootsTransportTarget {
Self::new_with_metadata(RadrootsTransportKind::Nostr, uri, scope, label)
}
- pub fn reticulum_preview() -> Result<Self, RadrootsTransportError> {
- Self::reticulum_preview_with_metadata(None, None)
+ pub fn reticulum() -> Result<Self, RadrootsTransportError> {
+ Self::reticulum_with_metadata(RADROOTS_RETICULUM_ENDPOINT_URI, None, None)
}
- pub fn reticulum_preview_with_metadata(
+ pub fn reticulum_with_metadata(
+ uri: impl AsRef<str>,
scope: Option<RadrootsTransportMeshScopeId>,
label: Option<RadrootsTransportTargetLabel>,
) -> Result<Self, RadrootsTransportError> {
- Self::new_with_metadata(
- RadrootsTransportKind::Reticulum,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- scope,
- label,
- )
+ Self::new_with_metadata(RadrootsTransportKind::Reticulum, uri, scope, label)
}
pub fn local(uri: impl AsRef<str>) -> Result<Self, RadrootsTransportError> {
@@ -206,18 +201,6 @@ impl RadrootsTransportTarget {
Self::new_with_metadata(RadrootsTransportKind::Local, uri, scope, label)
}
- pub fn proxy(uri: impl AsRef<str>) -> Result<Self, RadrootsTransportError> {
- Self::proxy_with_metadata(uri, None, None)
- }
-
- pub fn proxy_with_metadata(
- uri: impl AsRef<str>,
- scope: Option<RadrootsTransportMeshScopeId>,
- label: Option<RadrootsTransportTargetLabel>,
- ) -> Result<Self, RadrootsTransportError> {
- Self::new_with_metadata(RadrootsTransportKind::Proxy, uri, scope, label)
- }
-
pub fn new_with_metadata(
kind: RadrootsTransportKind,
uri: impl AsRef<str>,
@@ -225,15 +208,16 @@ impl RadrootsTransportTarget {
label: Option<RadrootsTransportTargetLabel>,
) -> Result<Self, RadrootsTransportError> {
let raw_uri = uri.as_ref();
- if kind == RadrootsTransportKind::Reticulum
- && raw_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- {
- return Err(RadrootsTransportError::InvalidTargetUri);
- }
let uri = match kind {
RadrootsTransportKind::Nostr => RadrootsTransportTargetUri::parse_nostr_relay(raw_uri)?,
_ => RadrootsTransportTargetUri::parse(raw_uri)?,
};
+ if kind == RadrootsTransportKind::Reticulum
+ && (raw_uri != RADROOTS_RETICULUM_ENDPOINT_URI
+ || uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI)
+ {
+ return Err(RadrootsTransportError::InvalidTargetUri);
+ }
let scope = scope.or_else(|| default_scope_for_kind(&kind));
let fingerprint =
RadrootsTransportTargetFingerprint::from_target(&kind, &uri, scope.as_ref());
@@ -248,7 +232,7 @@ impl RadrootsTransportTarget {
}
fn default_scope_for_kind(kind: &RadrootsTransportKind) -> Option<RadrootsTransportMeshScopeId> {
- (*kind == RadrootsTransportKind::Reticulum).then(RadrootsTransportMeshScopeId::local_preview)
+ (*kind == RadrootsTransportKind::Reticulum).then(RadrootsTransportMeshScopeId::local_reticulum)
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -289,6 +273,9 @@ fn canonicalize_uri(raw: &str) -> Result<String, RadrootsTransportError> {
if trimmed.is_empty() {
return Err(RadrootsTransportError::EmptyTargetUri);
}
+ if raw != trimmed {
+ return Err(RadrootsTransportError::InvalidTargetUri);
+ }
if trimmed
.chars()
.any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace())
@@ -330,6 +317,9 @@ fn canonicalize_nostr_relay_uri(raw: &str) -> Result<String, RadrootsTransportEr
if trimmed.is_empty() {
return Err(RadrootsTransportError::EmptyTargetUri);
}
+ if raw != trimmed {
+ return Err(RadrootsTransportError::InvalidTargetUri);
+ }
if trimmed
.chars()
.any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace())
diff --git a/crates/transport/tests/source_boundary.rs b/crates/transport/tests/source_boundary.rs
@@ -36,12 +36,12 @@ const FOUNDATION_HARDENING_DOC_ROOTS: &[&str] = &["contracts", "docs"];
const FORBIDDEN_TRANSPORT_CONCEPTS: &[ForbiddenConcept] = &[
ForbiddenConcept {
- pattern: "\"radrootsd_proxy\"",
- reason: "proxy must use first-class RadrootsTransportKind::Proxy modeling",
+ pattern: concat!("\"radrootsd", "_", "pro", "xy\""),
+ reason: "radrootsd execution must not be modeled as transport identity",
},
ForbiddenConcept {
- pattern: "radrootsd.publish_proxy.v1",
- reason: "transport publish protocol v1 proxy identifiers are removed",
+ pattern: concat!("radrootsd.publish", "_", "pro", "xy.v1"),
+ reason: "transport publish protocol v1 radrootsd execution identifiers are removed",
},
ForbiddenConcept {
pattern: "publish.relays.resolve",
@@ -309,9 +309,9 @@ fn transport_hardening_sources_reject_removed_protocol_identifiers() {
}
}
- for line in removed_reticulum_preview_endpoint_lines(source) {
+ for line in removed_reticulum_stage_endpoint_lines(source) {
findings.push(format!(
- "{relative_path}:{line} contains removed Reticulum preview endpoint `reticulum:preview`"
+ "{relative_path}:{line} contains removed Reticulum staging endpoint"
));
}
}
@@ -511,19 +511,21 @@ fn runtime_transport_registry_uses_core_transport_contract() {
runtime_source.contains("transport.transport_kind()"),
"runtime registry must key transports through the core trait transport_kind"
);
+ let removed_reticulum_runtime_transport =
+ ["RadrootsRuntimeReticulum", "Pre", "viewTransport"].concat();
for forbidden in [
- "pub trait RadrootsRuntimeTransportAdapter",
- "dyn RadrootsRuntimeTransportAdapter",
- "RadrootsRuntimeReticulumPreviewTransport",
+ "pub trait RadrootsRuntimeTransportAdapter".to_owned(),
+ "dyn RadrootsRuntimeTransportAdapter".to_owned(),
+ removed_reticulum_runtime_transport,
] {
assert!(
- !runtime_source.contains(forbidden),
+ !runtime_source.contains(forbidden.as_str()),
"runtime transport source must not retain split adapter contract `{forbidden}`"
);
}
assert!(
- reticulum_source.contains("impl RadrootsTransport for RadrootsReticulumPreviewTransport"),
- "Reticulum preview transport must implement the core transport contract"
+ reticulum_source.contains("impl RadrootsTransport for RadrootsReticulumTransport"),
+ "Reticulum transport must implement the core transport contract"
);
}
@@ -542,13 +544,16 @@ fn transport_publish_capabilities_keep_canonical_status_fields() {
"pub transport: String,",
"pub configured: bool,",
"pub implementation: TransportPublishImplementation,",
+ "pub maturity: TransportPublishCapabilityMaturity,",
+ "pub availability: TransportPublishCapabilityAvailability,",
"pub usable_for_delivery: bool,",
"pub capabilities: TransportPublishOperationCapabilities,",
"pub struct TransportPublishOperationCapabilities",
"pub deliver: bool,",
"pub fetch: bool,",
"TransportPublishImplementation::Real",
- "TransportPublishImplementation::PreviewUnavailable",
+ "TransportPublishCapabilityMaturity::Preview",
+ "TransportPublishCapabilityAvailability::Unavailable",
"configured: true",
"usable_for_delivery: true",
"usable_for_delivery: false",
@@ -685,29 +690,38 @@ fn required_target_semantics_stay_fingerprint_exact() {
}
#[test]
-fn transport_hardening_sources_keep_proxy_and_reticulum_contracts() {
+fn transport_hardening_sources_reject_removed_execution_kind_and_keep_reticulum_contracts() {
let crates_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("transport crate parent");
let transport_kind = read_source(crates_root.join("transport/src/kind.rs").as_path());
- for required in [
- "Proxy,",
- r#""proxy" => Ok(Self::Proxy)"#,
- r#"Self::Proxy => "proxy".to_string()"#,
- ] {
+ for required in ["Nostr,", "Reticulum,", "Local,"] {
assert!(
transport_kind.contains(required),
- "transport kind source must retain first-class proxy witness `{required}`"
+ "transport kind source must retain first-wave transport witness `{required}`"
+ );
+ }
+ let removed_kind_variant = ["Pro", "xy,"].concat();
+ let removed_parse_arm = [r#""#, "pro", "xy", r#"" => Ok(Self::"#, "Pro", "xy)"].concat();
+ let removed_label_arm = [r#"Self::"#, "Pro", "xy => \"", "pro", "xy\".to_string()"].concat();
+ for forbidden in [
+ &removed_kind_variant,
+ &removed_parse_arm,
+ &removed_label_arm,
+ ] {
+ assert!(
+ !transport_kind.contains(forbidden.as_str()),
+ "transport kind source must not retain removed radrootsd execution transport witness `{forbidden}`"
);
}
let transport_message_source =
read_source(crates_root.join("transport/src/message.rs").as_path());
for required in [
- "RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI",
- "reticulum:preview-unavailable",
+ "RADROOTS_RETICULUM_ENDPOINT_URI",
+ "reticulum:local",
"RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE",
- "Reticulum transport is configured in preview mode, ",
+ "Reticulum transport is configured, ",
"but this build does not implement Reticulum delivery.",
] {
assert!(
@@ -716,7 +730,7 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_contracts() {
);
}
for forbidden in [
- "Reticulum preview transport is registered, ",
+ "Reticulum prerelease transport is registered, ",
"future compatibility",
"compatibility mode",
"fallback behavior",
@@ -731,24 +745,24 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_contracts() {
let reticulum_source =
read_source(crates_root.join("transport_reticulum/src/lib.rs").as_path());
assert!(
- reticulum_source.contains("RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI"),
- "Reticulum preview source must consume the shared endpoint URI constant"
+ reticulum_source.contains("RADROOTS_RETICULUM_ENDPOINT_URI"),
+ "Reticulum source must consume the shared endpoint URI constant"
);
assert!(
- !reticulum_source.contains("reticulum:preview-unavailable"),
- "Reticulum preview source must not duplicate the shared endpoint URI"
+ !reticulum_source.contains(["reticulum:", "pre", "view-unavailable"].concat().as_str()),
+ "Reticulum source must not duplicate the shared endpoint URI"
);
assert!(
reticulum_source.contains("RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE"),
- "Reticulum preview source must consume the shared unavailable message constant"
+ "Reticulum source must consume the shared unavailable message constant"
);
assert!(
!reticulum_source.contains("Reticulum transport is configured in preview mode"),
- "Reticulum preview source must not duplicate the shared unavailable message"
+ "Reticulum source must not duplicate the shared unavailable message"
);
assert!(
!reticulum_source.contains("future compatibility"),
- "Reticulum preview source must not duplicate compatibility copy"
+ "Reticulum source must not duplicate compatibility copy"
);
let protocol_source_raw = read_source(
@@ -758,11 +772,11 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_contracts() {
);
let protocol_source = production_source(protocol_source_raw.as_str());
assert!(
- protocol_source.contains("RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI"),
+ protocol_source.contains("RADROOTS_RETICULUM_ENDPOINT_URI"),
"transport publish protocol must consume the shared Reticulum endpoint URI constant"
);
assert!(
- !protocol_source.contains("reticulum:preview-unavailable"),
+ !protocol_source.contains(["reticulum:", "pre", "view-unavailable"].concat().as_str()),
"transport publish protocol must not duplicate the shared endpoint URI"
);
assert!(
@@ -918,11 +932,14 @@ fn contains_forbidden_concept(source: &str, pattern: &str) -> bool {
})
}
-fn removed_reticulum_preview_endpoint_lines(source: &str) -> Vec<usize> {
+fn removed_reticulum_stage_endpoint_lines(source: &str) -> Vec<usize> {
+ let removed_endpoint_prefix = ["reticulum:", "pre", "view"].concat();
source
- .match_indices("reticulum:preview")
+ .match_indices(removed_endpoint_prefix.as_str())
.filter_map(|(index, _)| {
- let after = source[index + "reticulum:preview".len()..].chars().next();
+ let after = source[index + removed_endpoint_prefix.len()..]
+ .chars()
+ .next();
(after != Some('-')).then(|| line_number(source, index))
})
.collect()
diff --git a/crates/transport/tests/transport.rs b/crates/transport/tests/transport.rs
@@ -1,6 +1,6 @@
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
- RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID, RadrootsTransport,
+ RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt,
RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
RadrootsTransportError, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest,
RadrootsTransportFuture, RadrootsTransportImplementationState, RadrootsTransportKind,
@@ -20,16 +20,16 @@ fn opaque_payload() -> RadrootsTransportPayload {
#[test]
fn target_fingerprints_are_stable_and_transport_scoped() {
let nostr_upper =
- RadrootsTransportTarget::nostr_relay(" WSS://Relay.Example/Events ").expect("nostr target");
+ RadrootsTransportTarget::nostr_relay("WSS://Relay.Example/Events").expect("nostr target");
let nostr_lower =
RadrootsTransportTarget::nostr_relay("wss://relay.example/Events").expect("nostr target");
- let reticulum = RadrootsTransportTarget::reticulum_preview().expect("reticulum target");
+ let reticulum = RadrootsTransportTarget::reticulum().expect("reticulum target");
assert_eq!(nostr_upper.uri.as_str(), "wss://relay.example/Events");
assert_eq!(nostr_upper.scope, None);
assert_eq!(
reticulum.scope.as_ref().map(|scope| scope.as_str()),
- Some(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID)
+ Some(RADROOTS_RETICULUM_SCOPE_ID)
);
assert_eq!(nostr_upper.fingerprint, nostr_lower.fingerprint);
assert_ne!(nostr_upper.fingerprint, reticulum.fingerprint);
@@ -40,7 +40,7 @@ fn target_fingerprints_are_stable_and_transport_scoped() {
}
#[test]
-fn transport_kind_parser_round_trips_canonical_labels_and_custom_values() {
+fn transport_kind_parser_round_trips_first_wave_canonical_labels() {
assert_eq!(
RadrootsTransportKind::parse(" NOSTR ").expect("nostr kind"),
RadrootsTransportKind::Nostr
@@ -50,46 +50,25 @@ fn transport_kind_parser_round_trips_canonical_labels_and_custom_values() {
RadrootsTransportKind::Reticulum
);
assert_eq!(
- RadrootsTransportKind::parse("mesh").expect("mesh kind"),
- RadrootsTransportKind::Mesh
- );
- assert_eq!(
RadrootsTransportKind::parse("local").expect("local kind"),
RadrootsTransportKind::Local
);
assert_eq!(
- RadrootsTransportKind::parse("PROXY").expect("proxy kind"),
- RadrootsTransportKind::Proxy
- );
- assert_eq!(
RadrootsTransportKind::Local.canonical_label(),
"local".to_owned()
);
- assert_eq!(
- RadrootsTransportKind::Proxy.canonical_label(),
- "proxy".to_owned()
- );
- assert_eq!(
- RadrootsTransportKind::parse("fieldbus").expect("custom kind"),
- RadrootsTransportKind::Custom("fieldbus".to_owned())
- );
- assert_eq!(
- RadrootsTransportKind::parse(removed_proxy_kind()).expect_err("removed proxy kind"),
- RadrootsTransportError::InvalidTransportKind
- );
- assert_eq!(
- RadrootsTransportKind::custom(removed_proxy_kind().to_ascii_uppercase())
- .expect_err("removed proxy custom kind"),
- RadrootsTransportError::InvalidTransportKind
- );
- assert_eq!(
- RadrootsTransportKind::Custom("fieldbus".to_owned()).canonical_label(),
- "fieldbus".to_owned()
- );
- assert_eq!(
- RadrootsTransportKind::parse("bad kind").expect_err("invalid kind"),
- RadrootsTransportError::InvalidTransportKind
- );
+ for retired in [
+ "mesh".to_owned(),
+ ["pro", "xy"].concat(),
+ ["hy", "brid"].concat(),
+ ["reticulum", "_preview"].concat(),
+ "fieldbus".to_owned(),
+ ] {
+ assert_eq!(
+ RadrootsTransportKind::parse(retired).expect_err("retired or unknown kind"),
+ RadrootsTransportError::InvalidTransportKind
+ );
+ }
}
#[test]
@@ -99,10 +78,6 @@ fn canonical_transport_kind_parser_rejects_noncanonical_public_values() {
RadrootsTransportKind::Nostr
);
assert_eq!(
- RadrootsTransportKind::parse_canonical("fieldbus").expect("custom kind"),
- RadrootsTransportKind::Custom("fieldbus".to_owned())
- );
- assert_eq!(
RadrootsTransportKind::parse_canonical("NOSTR").expect_err("uppercase kind"),
RadrootsTransportError::InvalidTransportKind
);
@@ -111,8 +86,12 @@ fn canonical_transport_kind_parser_rejects_noncanonical_public_values() {
RadrootsTransportError::InvalidTransportKind
);
assert_eq!(
- RadrootsTransportKind::parse_canonical(removed_proxy_kind())
- .expect_err("removed proxy kind"),
+ RadrootsTransportKind::parse_canonical(removed_radrootsd_execution_transport_kind())
+ .expect_err("removed radrootsd execution kind"),
+ RadrootsTransportError::InvalidTransportKind
+ );
+ assert_eq!(
+ RadrootsTransportKind::parse_canonical("fieldbus").expect_err("custom kind"),
RadrootsTransportError::InvalidTransportKind
);
assert_eq!(
@@ -121,8 +100,8 @@ fn canonical_transport_kind_parser_rejects_noncanonical_public_values() {
);
}
-fn removed_proxy_kind() -> String {
- ["radrootsd", "_proxy"].concat()
+fn removed_radrootsd_execution_transport_kind() -> String {
+ ["radrootsd", "_", "pro", "xy"].concat()
}
#[test]
@@ -337,16 +316,19 @@ fn transport_status_models_canonical_configuration_and_delivery_usability() {
#[test]
fn deferred_transport_outcomes_are_terminal_but_not_satisfied() {
- let target = RadrootsTransportTarget::reticulum_preview().expect("target");
+ let target = RadrootsTransportTarget::reticulum().expect("target");
let receipt = RadrootsTransportDeliveryReceipt {
- request_id: "reticulum-preview".to_owned(),
+ request_id: "reticulum".to_owned(),
target_receipts: vec![RadrootsTransportTargetReceipt::new(
target,
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::DeferredUntilImplemented),
)],
};
- assert!(RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented.is_deferred_preview());
+ assert!(
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ .is_deferred_until_implemented()
+ );
assert_eq!(
receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
0
@@ -553,23 +535,15 @@ fn transport_errors_have_stable_display_strings() {
#[test]
fn transport_kind_and_target_parsers_cover_negative_edges() {
- assert_eq!(
- RadrootsTransportKind::custom(" ").expect_err("empty kind"),
- RadrootsTransportError::EmptyTransportKind
- );
- for invalid in ["bad kind", "bad:kind", "bad/kind", "bad\nkind"] {
+ for invalid in ["bad kind", "bad:kind", "bad/kind", "bad\nkind", "fieldbus"] {
assert_eq!(
- RadrootsTransportKind::custom(invalid).expect_err("invalid kind"),
+ RadrootsTransportKind::parse(invalid).expect_err("invalid kind"),
RadrootsTransportError::InvalidTransportKind
);
}
- assert_eq!(
- RadrootsTransportKind::custom(" FieldBus ").expect("custom kind"),
- RadrootsTransportKind::Custom("fieldbus".to_owned())
- );
let no_scheme =
- RadrootsTransportTargetUri::parse(" transport-target ").expect("schemeless target uri");
+ RadrootsTransportTargetUri::parse("transport-target").expect("schemeless target uri");
assert_eq!(no_scheme.as_str(), "transport-target");
assert_eq!(no_scheme.to_string(), "transport-target");
let opaque = RadrootsTransportTargetUri::parse("RNS:PeerA").expect("opaque uri");
@@ -584,6 +558,7 @@ fn transport_kind_and_target_parsers_cover_negative_edges() {
);
for invalid in [
"bad target",
+ " transport-target ",
":target",
"1bad:target",
"bad_scheme://target",
@@ -647,28 +622,24 @@ fn checked_in_transport_target_uri_vectors_match_parser_behavior() {
}
#[test]
-fn reticulum_transport_targets_require_exact_preview_endpoint() {
- let target =
- RadrootsTransportTarget::reticulum_preview().expect("exact Reticulum preview endpoint");
- assert_eq!(target.uri.as_str(), RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
+fn reticulum_transport_targets_use_default_destination_and_scope() {
+ let target = RadrootsTransportTarget::reticulum().expect("Reticulum target");
+ assert_eq!(target.uri.as_str(), RADROOTS_RETICULUM_ENDPOINT_URI);
assert_eq!(
target.scope.as_ref().map(|scope| scope.as_str()),
- Some(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID)
+ Some(RADROOTS_RETICULUM_SCOPE_ID)
);
+ let invalid_reticulum_destination = ["reticulum:", "remote"].concat();
for invalid in [
- " reticulum:preview-unavailable",
- "reticulum:preview-unavailable ",
- "RETICULUM:preview-unavailable",
- "reticulum:Preview-Unavailable",
- "reticulum:preview",
- "reticulum:preview-unavailable-alt",
- "reticulum:custom",
- "wss://relay.example/Events",
+ " reticulum:local".to_owned(),
+ "reticulum:local ".to_owned(),
+ "RETICULUM:local".to_owned(),
+ invalid_reticulum_destination,
] {
assert_eq!(
- RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
- .expect_err("invalid Reticulum preview endpoint"),
+ RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid.as_str())
+ .expect_err("invalid Reticulum endpoint"),
RadrootsTransportError::InvalidTargetUri
);
}
@@ -676,8 +647,7 @@ fn reticulum_transport_targets_require_exact_preview_endpoint() {
#[test]
fn target_fingerprints_and_sets_cover_accessors_and_validation() {
- let target = RadrootsTransportTarget::new(RadrootsTransportKind::Mesh, "mesh://node.example")
- .expect("mesh target");
+ let target = RadrootsTransportTarget::reticulum().expect("Reticulum target");
let parsed =
RadrootsTransportTargetFingerprint::parse(target.fingerprint.as_str().to_ascii_uppercase())
.expect("uppercase fingerprint parses");
@@ -700,39 +670,39 @@ fn target_fingerprints_and_sets_cover_accessors_and_validation() {
#[test]
fn target_scope_participates_in_identity_and_label_does_not() {
- let local_scope = RadrootsTransportMeshScopeId::parse("local_preview").expect("local scope");
- let remote_scope = RadrootsTransportMeshScopeId::parse("remote_preview").expect("remote scope");
+ let local_scope = RadrootsTransportMeshScopeId::parse("local").expect("local scope");
+ let remote_scope = RadrootsTransportMeshScopeId::parse("remote").expect("remote scope");
let local = RadrootsTransportTarget::new_with_metadata(
- RadrootsTransportKind::Mesh,
- "mesh://node.example",
+ RadrootsTransportKind::Reticulum,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
Some(local_scope.clone()),
- Some(RadrootsTransportTargetLabel::parse("Local mesh node").expect("label")),
+ Some(RadrootsTransportTargetLabel::parse("Local Reticulum node").expect("label")),
)
- .expect("local mesh target");
+ .expect("local Reticulum target");
let relabeled = RadrootsTransportTarget::new_with_metadata(
- RadrootsTransportKind::Mesh,
- "mesh://node.example",
+ RadrootsTransportKind::Reticulum,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
Some(local_scope),
Some(RadrootsTransportTargetLabel::parse("Renamed node").expect("label")),
)
.expect("relabeled mesh target");
let remote = RadrootsTransportTarget::new_with_metadata(
- RadrootsTransportKind::Mesh,
- "mesh://node.example",
+ RadrootsTransportKind::Reticulum,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
Some(remote_scope),
None,
)
- .expect("remote mesh target");
+ .expect("remote Reticulum target");
assert_eq!(local.fingerprint, relabeled.fingerprint);
assert_ne!(local.fingerprint, remote.fingerprint);
assert_eq!(
local.scope.as_ref().map(|scope| scope.as_str()),
- Some("local_preview")
+ Some("local")
);
assert_eq!(
local.label.as_ref().map(|label| label.as_str()),
- Some("Local mesh node")
+ Some("Local Reticulum node")
);
assert_eq!(
RadrootsTransportMeshScopeId::parse("").expect_err("empty scope"),
@@ -834,7 +804,7 @@ fn satisfaction_and_target_status_cover_all_contract_states() {
&[],
),
(
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
&[],
),
(
@@ -855,7 +825,10 @@ fn satisfaction_and_target_status_cover_all_contract_states() {
);
}
}
- assert!(RadrootsTransportDeliveryTargetStatus::PreviewUnavailable.is_deferred_preview());
+ assert!(
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ .is_deferred_until_implemented()
+ );
assert!(RadrootsTransportDeliveryTargetStatus::FailedRetryable.is_retryable_failure());
assert!(RadrootsTransportDeliveryTargetStatus::FailedTerminal.is_terminal_failure());
}
@@ -989,12 +962,12 @@ fn typed_outcome_kinds_drive_status_and_satisfaction_semantics() {
assert_eq!(outcome.message.as_deref(), Some("transport detail"));
}
- let preview_unavailable =
+ let deferred_until_implemented =
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::TransportUnavailable)
- .with_target_status(RadrootsTransportDeliveryTargetStatus::PreviewUnavailable);
+ .with_target_status(RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented);
assert_eq!(
- preview_unavailable.status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
+ deferred_until_implemented.status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
);
}
diff --git a/crates/transport_nostr/src/outbox.rs b/crates/transport_nostr/src/outbox.rs
@@ -570,21 +570,6 @@ async fn complete_outbox_delivery_target(
)
.await?;
}
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable => {
- outbox
- .mark_delivery_target_preview_unavailable(
- claimed.outbox_event_id,
- claimed.claim_token.as_str(),
- receipt.delivery_target_id,
- receipt
- .outcome
- .message
- .as_deref()
- .unwrap_or("relay publish preview unavailable"),
- now_ms,
- )
- .await?;
- }
RadrootsTransportDeliveryTargetStatus::SkippedPolicyDenied => {
outbox
.mark_delivery_target_skipped_policy_denied(
diff --git a/crates/transport_nostr/tests/transport.rs b/crates/transport_nostr/tests/transport.rs
@@ -810,7 +810,7 @@ async fn nostr_transport_facade_rejects_unsupported_payloads_and_targets() {
.expect_err("payload rejected");
assert_eq!(payload_error, RadrootsTransportError::InvalidPayloadBytes);
- let non_nostr_target = RadrootsTransportTarget::reticulum_preview().expect("reticulum target");
+ let non_nostr_target = RadrootsTransportTarget::reticulum().expect("reticulum target");
let target_error = transport
.deliver(RadrootsTransportDeliveryRequest::new(
"facade-request-target",
@@ -2429,7 +2429,7 @@ async fn outbox_publish_skips_non_nostr_targets() {
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![
nostr_target(RELAY_PRIMARY_WSS),
- RadrootsTransportTarget::reticulum_preview().expect("reticulum target"),
+ RadrootsTransportTarget::reticulum().expect("reticulum target"),
],
),
1_000,
@@ -2474,7 +2474,7 @@ async fn outbox_publish_skips_non_nostr_targets() {
.expect("targets");
assert!(targets.iter().any(|target| {
target.transport_kind == RadrootsTransportKind::Reticulum
- && target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ && target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}));
}
diff --git a/crates/transport_publish_protocol/src/lib.rs b/crates/transport_publish_protocol/src/lib.rs
@@ -11,7 +11,7 @@ use std::{collections::BTreeSet, string::String, vec::Vec};
use core::fmt;
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
RadrootsTransportError, RadrootsTransportKind, RadrootsTransportMeshScopeId,
RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
};
@@ -58,14 +58,11 @@ pub enum TransportPublishProtocolError {
InvalidTargetLabel {
index: usize,
},
- InvalidPreviewBehavior {
+ InvalidReticulumBehavior {
index: usize,
},
InvalidTimeoutMs,
- InvalidReticulumPreviewEndpoint {
- index: usize,
- },
- ExplicitProxyTarget {
+ InvalidReticulumEndpoint {
index: usize,
},
DuplicateTarget {
@@ -160,18 +157,14 @@ impl fmt::Display for TransportPublishProtocolError {
Self::InvalidTargetLabel { index } => {
write!(f, "transport target {index} target_label is invalid")
}
- Self::InvalidPreviewBehavior { index } => write!(
+ Self::InvalidReticulumBehavior { index } => write!(
f,
- "transport target {index} preview_behavior is only valid for Reticulum targets"
+ "transport target {index} reticulum_behavior is only valid for Reticulum targets"
),
Self::InvalidTimeoutMs => f.write_str("timeout_ms must be greater than zero"),
- Self::InvalidReticulumPreviewEndpoint { index } => write!(
- f,
- "transport target {index} Reticulum preview endpoint must be {RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI}"
- ),
- Self::ExplicitProxyTarget { index } => write!(
+ Self::InvalidReticulumEndpoint { index } => write!(
f,
- "transport target {index} proxy is an SDK delegation target and cannot be used as a daemon explicit target"
+ "transport target {index} Reticulum endpoint must be {RADROOTS_RETICULUM_ENDPOINT_URI}"
),
Self::DuplicateTarget { index } => {
write!(f, "transport target {index} duplicates an earlier target")
@@ -243,7 +236,7 @@ impl fmt::Display for TransportPublishProtocolError {
}
Self::InvalidReticulumOutcome { index } => write!(
f,
- "transport target outcome {index} Reticulum preview must be unavailable or deferred"
+ "transport target outcome {index} Reticulum must be unavailable or deferred"
),
}
}
@@ -255,7 +248,7 @@ impl std::error::Error for TransportPublishProtocolError {}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum TransportPublishPreviewBehavior {
+pub enum TransportPublishReticulumBehavior {
#[default]
RejectDeliveryAttempts,
DeferDeliveryPlans,
@@ -281,7 +274,7 @@ pub struct TransportPublishTarget {
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
- pub preview_behavior: Option<TransportPublishPreviewBehavior>,
+ pub reticulum_behavior: Option<TransportPublishReticulumBehavior>,
}
impl TransportPublishTarget {
@@ -291,17 +284,17 @@ impl TransportPublishTarget {
endpoint_uri: endpoint_uri.into(),
target_scope: None,
target_label: None,
- preview_behavior: None,
+ reticulum_behavior: None,
}
}
- pub fn reticulum_preview(behavior: TransportPublishPreviewBehavior) -> Self {
+ pub fn reticulum(behavior: TransportPublishReticulumBehavior) -> Self {
Self {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: Some(behavior),
+ reticulum_behavior: Some(behavior),
}
}
@@ -321,19 +314,16 @@ impl TransportPublishTarget {
}
let transport_kind = RadrootsTransportKind::parse_canonical(self.transport_kind.as_str())
.map_err(|error| transport_kind_error(error, index))?;
- if transport_kind == RadrootsTransportKind::Proxy {
- return Err(TransportPublishProtocolError::ExplicitProxyTarget { index });
- }
if self.endpoint_uri.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyEndpointUri { index });
}
- if transport_kind != RadrootsTransportKind::Reticulum && self.preview_behavior.is_some() {
- return Err(TransportPublishProtocolError::InvalidPreviewBehavior { index });
+ if transport_kind != RadrootsTransportKind::Reticulum && self.reticulum_behavior.is_some() {
+ return Err(TransportPublishProtocolError::InvalidReticulumBehavior { index });
}
if transport_kind == RadrootsTransportKind::Reticulum
- && self.endpoint_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ && self.endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI
{
- return Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index });
+ return Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index });
}
validate_target_metadata(
self.target_scope.as_deref(),
@@ -669,7 +659,7 @@ pub enum TransportPublishJobStatus {
DeliveryUnsatisfiedRetryable,
DeliveryUnsatisfiedTerminal,
DeliveryDeferred,
- DeliveryPreviewUnavailable,
+ DeliveryDeferredUntilImplemented,
Rejected,
}
@@ -694,7 +684,6 @@ pub enum TransportPublishOutcomeKind {
TargetRejected,
SkippedAlreadyAccepted,
DeferredUntilImplemented,
- PreviewUnavailable,
Unknown,
}
@@ -732,11 +721,8 @@ impl TransportPublishOutcomeKind {
)
}
- pub fn is_deferred_preview(self) -> bool {
- matches!(
- self,
- Self::DeferredUntilImplemented | Self::PreviewUnavailable
- )
+ pub fn is_deferred_until_implemented(self) -> bool {
+ matches!(self, Self::DeferredUntilImplemented)
}
}
@@ -747,7 +733,7 @@ pub enum TransportPublishTargetSource {
Request,
NostrAuthorWrite,
DaemonDefault,
- ReticulumPreview,
+ Reticulum,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -957,25 +943,29 @@ impl TransportPublishCapabilities {
transport: "nostr".to_owned(),
configured: true,
implementation: TransportPublishImplementation::Real,
+ maturity: TransportPublishCapabilityMaturity::Stable,
+ availability: TransportPublishCapabilityAvailability::Available,
usable_for_delivery: true,
capabilities: TransportPublishOperationCapabilities {
deliver: true,
fetch: false,
},
- preview_behavior: None,
+ reticulum_behavior: None,
message: "Nostr relay publish is available".to_owned(),
},
TransportPublishTransportCapability {
transport: "reticulum".to_owned(),
configured: true,
- implementation: TransportPublishImplementation::PreviewUnavailable,
+ implementation: TransportPublishImplementation::Real,
+ maturity: TransportPublishCapabilityMaturity::Preview,
+ availability: TransportPublishCapabilityAvailability::Unavailable,
usable_for_delivery: false,
capabilities: TransportPublishOperationCapabilities {
deliver: false,
fetch: false,
},
- preview_behavior: Some(
- TransportPublishPreviewBehavior::RejectDeliveryAttempts,
+ reticulum_behavior: Some(
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts,
),
message: RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned(),
},
@@ -1011,7 +1001,23 @@ pub struct TransportPublishSurfaceCapabilities {
pub enum TransportPublishImplementation {
Real,
Mock,
- PreviewUnavailable,
+}
+
+#[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 TransportPublishCapabilityMaturity {
+ Preview,
+ Stable,
+}
+
+#[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 TransportPublishCapabilityAvailability {
+ Available,
+ Degraded,
+ Unavailable,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -1021,13 +1027,15 @@ pub struct TransportPublishTransportCapability {
pub transport: String,
pub configured: bool,
pub implementation: TransportPublishImplementation,
+ pub maturity: TransportPublishCapabilityMaturity,
+ pub availability: TransportPublishCapabilityAvailability,
pub usable_for_delivery: bool,
pub capabilities: TransportPublishOperationCapabilities,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
- pub preview_behavior: Option<TransportPublishPreviewBehavior>,
+ pub reticulum_behavior: Option<TransportPublishReticulumBehavior>,
pub message: String,
}
@@ -1097,7 +1105,7 @@ fn job_status_is_terminal(status: TransportPublishJobStatus) -> bool {
TransportPublishJobStatus::DeliverySatisfied
| TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
| TransportPublishJobStatus::DeliveryDeferred
- | TransportPublishJobStatus::DeliveryPreviewUnavailable
+ | TransportPublishJobStatus::DeliveryDeferredUntilImplemented
| TransportPublishJobStatus::Rejected
)
}
@@ -1127,21 +1135,21 @@ fn validate_target_outcome(
index,
)?;
if transport_kind == RadrootsTransportKind::Reticulum {
- if target.endpoint_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
- return Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index });
+ if target.endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI {
+ return Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index });
}
- if target.source != TransportPublishTargetSource::ReticulumPreview {
+ if target.source != TransportPublishTargetSource::Reticulum {
return Err(TransportPublishProtocolError::InvalidTargetSource { index });
}
- if target.attempted || !target.outcome_kind.is_deferred_preview() {
+ if target.attempted || !target.outcome_kind.is_deferred_until_implemented() {
return Err(TransportPublishProtocolError::InvalidReticulumOutcome { index });
}
return Ok(());
}
- if target.source == TransportPublishTargetSource::ReticulumPreview {
+ if target.source == TransportPublishTargetSource::Reticulum {
return Err(TransportPublishProtocolError::InvalidTargetSource { index });
}
- if target.outcome_kind.is_deferred_preview() {
+ if target.outcome_kind.is_deferred_until_implemented() {
return Err(TransportPublishProtocolError::InvalidTargetOutcomeKind { index });
}
Ok(())
@@ -1182,20 +1190,14 @@ fn transport_target_from_parts(
RadrootsTransportTarget::nostr_relay_with_metadata(endpoint_uri, scope, label)
}
RadrootsTransportKind::Reticulum => {
- if endpoint_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
+ if endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI {
return Err(RadrootsTransportError::InvalidTargetUri);
}
- RadrootsTransportTarget::reticulum_preview_with_metadata(scope, label)
+ RadrootsTransportTarget::reticulum_with_metadata(endpoint_uri, scope, label)
}
RadrootsTransportKind::Local => {
RadrootsTransportTarget::local_with_metadata(endpoint_uri, scope, label)
}
- RadrootsTransportKind::Proxy => {
- RadrootsTransportTarget::proxy_with_metadata(endpoint_uri, scope, label)
- }
- RadrootsTransportKind::Mesh | RadrootsTransportKind::Custom(_) => {
- RadrootsTransportTarget::new_with_metadata(transport_kind, endpoint_uri, scope, label)
- }
}
}
@@ -1291,51 +1293,55 @@ fn validate_job_status_state(
return Err(TransportPublishProtocolError::InvalidJobStatusState);
}
let required_count = job.delivery_policy.required_target_count(job.target_count);
- let (satisfied, retryable_status_count, terminal_status_count, has_deferred, has_preview) =
- match &job.delivery_policy {
- TransportPublishDeliveryPolicy::RequiredTargets { targets } => {
- let required_outcomes = required_policy_outcomes(targets, &job.targets)?;
- let satisfied = targets.iter().all(|required| {
- required_outcomes.iter().any(|outcome| {
- target_outcome_fingerprint(outcome, 0).is_ok_and(|fingerprint| {
- fingerprint == *required
- && outcome.outcome_kind.counts_toward_accepted_delivery()
- })
+ let (
+ satisfied,
+ retryable_status_count,
+ terminal_status_count,
+ has_deferred,
+ has_deferred_until_implemented,
+ ) = match &job.delivery_policy {
+ TransportPublishDeliveryPolicy::RequiredTargets { targets } => {
+ let required_outcomes = required_policy_outcomes(targets, &job.targets)?;
+ let satisfied = targets.iter().all(|required| {
+ required_outcomes.iter().any(|outcome| {
+ target_outcome_fingerprint(outcome, 0).is_ok_and(|fingerprint| {
+ fingerprint == *required
+ && outcome.outcome_kind.counts_toward_accepted_delivery()
})
- });
- (
- satisfied,
- required_outcomes
- .iter()
- .filter(|outcome| outcome.outcome_kind.is_retryable())
- .count(),
- required_outcomes
- .iter()
- .filter(|outcome| outcome.outcome_kind.is_terminal_failure())
- .count(),
- required_outcomes.iter().any(|outcome| {
- outcome.outcome_kind
- == TransportPublishOutcomeKind::DeferredUntilImplemented
- }),
- required_outcomes.iter().any(|outcome| {
- outcome.outcome_kind == TransportPublishOutcomeKind::PreviewUnavailable
- }),
- )
- }
- TransportPublishDeliveryPolicy::Any
- | TransportPublishDeliveryPolicy::All
- | TransportPublishDeliveryPolicy::Quorum { .. } => (
- required_count > 0 && acknowledged_count >= required_count,
- retryable_count,
- terminal_count,
- job.targets.iter().any(|target| {
- target.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
+ })
+ });
+ (
+ satisfied,
+ required_outcomes
+ .iter()
+ .filter(|outcome| outcome.outcome_kind.is_retryable())
+ .count(),
+ required_outcomes
+ .iter()
+ .filter(|outcome| outcome.outcome_kind.is_terminal_failure())
+ .count(),
+ required_outcomes.iter().any(|outcome| {
+ outcome.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
}),
- job.targets.iter().any(|target| {
- target.outcome_kind == TransportPublishOutcomeKind::PreviewUnavailable
+ required_outcomes.iter().any(|outcome| {
+ outcome.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
}),
- ),
- };
+ )
+ }
+ TransportPublishDeliveryPolicy::Any
+ | TransportPublishDeliveryPolicy::All
+ | TransportPublishDeliveryPolicy::Quorum { .. } => (
+ required_count > 0 && acknowledged_count >= required_count,
+ retryable_count,
+ terminal_count,
+ job.targets.iter().any(|target| {
+ target.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
+ }),
+ job.targets.iter().any(|target| {
+ target.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
+ }),
+ ),
+ };
match job.status {
TransportPublishJobStatus::DeliverySatisfied if satisfied => Ok(()),
TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
@@ -1356,11 +1362,11 @@ fn validate_job_status_state(
{
Ok(())
}
- TransportPublishJobStatus::DeliveryPreviewUnavailable
+ TransportPublishJobStatus::DeliveryDeferredUntilImplemented
if !satisfied
&& terminal_status_count == 0
&& retryable_status_count == 0
- && has_preview =>
+ && has_deferred_until_implemented =>
{
Ok(())
}
@@ -1417,10 +1423,10 @@ mod tests {
) -> TransportPublishTargetOutcome {
TransportPublishTargetOutcome {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
target_scope: None,
target_label: None,
- source: TransportPublishTargetSource::ReticulumPreview,
+ source: TransportPublishTargetSource::Reticulum,
attempted: false,
outcome_kind,
message: None,
@@ -1529,6 +1535,11 @@ mod tests {
.expect("nostr capability");
assert!(nostr.configured);
assert_eq!(nostr.implementation, TransportPublishImplementation::Real);
+ assert_eq!(nostr.maturity, TransportPublishCapabilityMaturity::Stable);
+ assert_eq!(
+ nostr.availability,
+ TransportPublishCapabilityAvailability::Available
+ );
assert!(nostr.usable_for_delivery);
assert!(nostr.capabilities.deliver);
assert!(!nostr.capabilities.fetch);
@@ -1541,14 +1552,22 @@ mod tests {
assert!(reticulum.configured);
assert_eq!(
reticulum.implementation,
- TransportPublishImplementation::PreviewUnavailable
+ TransportPublishImplementation::Real
+ );
+ assert_eq!(
+ reticulum.maturity,
+ TransportPublishCapabilityMaturity::Preview
+ );
+ assert_eq!(
+ reticulum.availability,
+ TransportPublishCapabilityAvailability::Unavailable
);
assert!(!reticulum.usable_for_delivery);
assert!(!reticulum.capabilities.deliver);
assert!(!reticulum.capabilities.fetch);
assert_eq!(
- reticulum.preview_behavior,
- Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts)
+ reticulum.reticulum_behavior,
+ Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts)
);
}
@@ -1569,8 +1588,8 @@ mod tests {
let mut too_many = request.clone();
too_many.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![
TransportPublishTarget::nostr("wss://relay.example.com"),
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::RejectDeliveryAttempts,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts,
),
]);
assert!(matches!(
@@ -1602,50 +1621,50 @@ mod tests {
invalid_reticulum_endpoint.target_policy =
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: "reticulum:preview-unavailable-alt".to_owned(),
+ endpoint_uri: "reticulum:alternate".to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
+ reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
}]);
assert_eq!(
invalid_reticulum_endpoint.validate(1),
- Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index: 0 })
+ Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index: 0 })
);
let mut noncanonical_reticulum_kind = request.clone();
noncanonical_reticulum_kind.target_policy =
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
transport_kind: "Reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
+ reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
}]);
assert_eq!(
noncanonical_reticulum_kind.validate(1),
Err(TransportPublishProtocolError::InvalidTransportKind { index: 0 })
);
- let mut nostr_preview_behavior = request.clone();
- nostr_preview_behavior.target_policy =
+ let mut nostr_reticulum_behavior = request.clone();
+ nostr_reticulum_behavior.target_policy =
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
transport_kind: "nostr".to_owned(),
endpoint_uri: "wss://relay.example.com".to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
+ reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
}]);
assert_eq!(
- nostr_preview_behavior.validate(1),
- Err(TransportPublishProtocolError::InvalidPreviewBehavior { index: 0 })
+ nostr_reticulum_behavior.validate(1),
+ Err(TransportPublishProtocolError::InvalidReticulumBehavior { index: 0 })
);
for invalid in [
- " reticulum:preview-unavailable",
- "reticulum:preview-unavailable ",
- "RETICULUM:preview-unavailable",
- "reticulum:Preview-Unavailable",
- "reticulum:preview",
+ " reticulum:local",
+ "reticulum:local ",
+ "RETICULUM:local",
+ "reticulum:Local",
+ "reticulum:temporary",
"reticulum:custom",
] {
let mut invalid_reticulum_endpoint = request.clone();
@@ -1655,42 +1674,16 @@ mod tests {
endpoint_uri: invalid.to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
+ reticulum_behavior: Some(
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ),
}]);
assert_eq!(
invalid_reticulum_endpoint.validate(1),
- Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index: 0 })
+ Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index: 0 })
);
}
- let mut removed_proxy_kind = request.clone();
- removed_proxy_kind.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: removed_proxy_kind_string(),
- endpoint_uri: "radrootsd-proxy:publish".to_owned(),
- target_scope: None,
- target_label: None,
- preview_behavior: None,
- }]);
- assert_eq!(
- removed_proxy_kind.validate(1),
- Err(TransportPublishProtocolError::InvalidTransportKind { index: 0 })
- );
-
- let mut explicit_proxy_target = request.clone();
- explicit_proxy_target.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: "proxy".to_owned(),
- endpoint_uri: "radrootsd-proxy:publish".to_owned(),
- target_scope: None,
- target_label: None,
- preview_behavior: None,
- }]);
- assert_eq!(
- explicit_proxy_target.validate(1),
- Err(TransportPublishProtocolError::ExplicitProxyTarget { index: 0 })
- );
-
let mut duplicate_targets = request.clone();
duplicate_targets.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![
TransportPublishTarget::nostr("wss://relay.example.com/a"),
@@ -1762,10 +1755,6 @@ mod tests {
);
}
- fn removed_proxy_kind_string() -> String {
- ["radrootsd", "_proxy"].concat()
- }
-
#[test]
fn outcome_kinds_classify_satisfaction_retry_and_terminal() {
assert!(TransportPublishOutcomeKind::Accepted.counts_toward_accepted_delivery());
@@ -1773,9 +1762,9 @@ mod tests {
TransportPublishOutcomeKind::SkippedAlreadyAccepted.counts_toward_accepted_delivery()
);
assert!(TransportPublishOutcomeKind::Timeout.is_retryable());
- assert!(TransportPublishOutcomeKind::PreviewUnavailable.is_deferred_preview());
- assert!(TransportPublishOutcomeKind::DeferredUntilImplemented.is_deferred_preview());
- assert!(!TransportPublishOutcomeKind::PreviewUnavailable.is_terminal_failure());
+ assert!(
+ TransportPublishOutcomeKind::DeferredUntilImplemented.is_deferred_until_implemented()
+ );
assert!(!TransportPublishOutcomeKind::DeferredUntilImplemented.is_terminal_failure());
}
@@ -1817,18 +1806,18 @@ mod tests {
.validate()
.expect("terminal job");
job_from_targets(
- TransportPublishJobStatus::DeliveryPreviewUnavailable,
+ TransportPublishJobStatus::DeliveryDeferredUntilImplemented,
TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::RejectDeliveryAttempts,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts,
),
]),
vec![reticulum_outcome(
- TransportPublishOutcomeKind::PreviewUnavailable,
+ TransportPublishOutcomeKind::DeferredUntilImplemented,
)],
)
.validate()
- .expect("preview unavailable job");
+ .expect("Reticulum unavailable job");
rejected_job().validate().expect("rejected job");
}
@@ -2072,12 +2061,12 @@ mod tests {
}
#[test]
- fn job_view_validation_rejects_reticulum_success_and_non_reticulum_preview() {
+ fn job_view_validation_rejects_reticulum_success_and_non_reticulum() {
let mut reticulum_success = job_from_targets(
TransportPublishJobStatus::DeliverySatisfied,
TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::RejectDeliveryAttempts,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts,
),
]),
vec![reticulum_outcome(TransportPublishOutcomeKind::Accepted)],
@@ -2088,25 +2077,25 @@ mod tests {
Err(TransportPublishProtocolError::InvalidReticulumOutcome { index: 0 })
);
- let non_reticulum_preview = job_from_targets(
- TransportPublishJobStatus::DeliveryPreviewUnavailable,
+ let non_reticulum = job_from_targets(
+ TransportPublishJobStatus::DeliveryDeferredUntilImplemented,
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget::nostr(
"wss://relay.example.com",
)]),
vec![nostr_outcome(
- TransportPublishOutcomeKind::PreviewUnavailable,
+ TransportPublishOutcomeKind::DeferredUntilImplemented,
)],
);
assert_eq!(
- non_reticulum_preview.validate(),
+ non_reticulum.validate(),
Err(TransportPublishProtocolError::InvalidTargetOutcomeKind { index: 0 })
);
let mut reticulum_wrong_source = job_from_targets(
TransportPublishJobStatus::DeliveryDeferred,
TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::DeferDeliveryPlans,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::DeferDeliveryPlans,
),
]),
vec![reticulum_outcome(
@@ -2122,8 +2111,8 @@ mod tests {
let reticulum_deferred = job_from_targets(
TransportPublishJobStatus::DeliveryDeferred,
TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::DeferDeliveryPlans,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::DeferDeliveryPlans,
),
]),
vec![reticulum_outcome(
@@ -2136,12 +2125,12 @@ mod tests {
}
#[test]
- fn serde_round_trip_preserves_preview_target() {
+ fn serde_round_trip_preserves_reticulum_target() {
let request = TransportPublishEventRequest {
raw_event_json: raw_event_json(),
target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::DeferDeliveryPlans,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::DeferDeliveryPlans,
),
]),
delivery_policy: TransportPublishDeliveryPolicy::All,
@@ -2150,7 +2139,7 @@ mod tests {
};
let encoded = serde_json::to_string(&request).expect("encode");
assert!(encoded.contains("\"transport_kind\":\"reticulum\""));
- assert!(encoded.contains("\"preview_behavior\":\"defer_delivery_plans\""));
+ assert!(encoded.contains("\"reticulum_behavior\":\"defer_delivery_plans\""));
let decoded: TransportPublishEventRequest =
serde_json::from_str(encoded.as_str()).expect("decode");
assert_eq!(decoded, request);
@@ -2232,16 +2221,16 @@ mod tests {
"transport target 3 target_label is invalid",
),
(
- TransportPublishProtocolError::InvalidPreviewBehavior { index: 4 },
- "transport target 4 preview_behavior is only valid for Reticulum targets",
+ TransportPublishProtocolError::InvalidReticulumBehavior { index: 4 },
+ "transport target 4 reticulum_behavior is only valid for Reticulum targets",
),
(
TransportPublishProtocolError::InvalidTimeoutMs,
"timeout_ms must be greater than zero",
),
(
- TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index: 5 },
- "transport target 5 Reticulum preview endpoint must be reticulum:preview-unavailable",
+ TransportPublishProtocolError::InvalidReticulumEndpoint { index: 5 },
+ "transport target 5 Reticulum endpoint must be reticulum:local",
),
(
TransportPublishProtocolError::TargetLimitExceeded { max: 1, actual: 2 },
@@ -2299,13 +2288,13 @@ mod tests {
#[test]
fn target_and_delivery_policy_validation_cover_all_modes() {
assert_eq!(
- TransportPublishPreviewBehavior::default(),
- TransportPublishPreviewBehavior::RejectDeliveryAttempts
+ TransportPublishReticulumBehavior::default(),
+ TransportPublishReticulumBehavior::RejectDeliveryAttempts
);
let explicit = TransportPublishTargetPolicy::explicit_targets(vec![
TransportPublishTarget::nostr("wss://relay.example"),
- TransportPublishTarget::reticulum_preview(
- TransportPublishPreviewBehavior::DeferDeliveryPlans,
+ TransportPublishTarget::reticulum(
+ TransportPublishReticulumBehavior::DeferDeliveryPlans,
),
]);
let nostr = TransportPublishTargetPolicy::nostr(
@@ -2333,7 +2322,7 @@ mod tests {
endpoint_uri: "wss://relay.example".to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: None,
+ reticulum_behavior: None,
}]);
assert_eq!(
empty_targets.validate(10),
@@ -2346,7 +2335,7 @@ mod tests {
endpoint_uri: "wss://relay.example".to_owned(),
target_scope: None,
target_label: None,
- preview_behavior: None,
+ reticulum_behavior: None,
}]);
assert_eq!(
empty_targets.validate(10),
@@ -2460,10 +2449,7 @@ mod tests {
TransportPublishOutcomeKind::PaymentRequired,
TransportPublishOutcomeKind::TargetRejected,
];
- let deferred_preview = [
- TransportPublishOutcomeKind::DeferredUntilImplemented,
- TransportPublishOutcomeKind::PreviewUnavailable,
- ];
+ let deferred_until_implemented = [TransportPublishOutcomeKind::DeferredUntilImplemented];
for kind in satisfied {
assert!(kind.counts_toward_accepted_delivery());
@@ -2480,11 +2466,11 @@ mod tests {
assert!(!kind.is_retryable());
assert!(kind.is_terminal_failure());
}
- for kind in deferred_preview {
+ for kind in deferred_until_implemented {
assert!(!kind.counts_toward_accepted_delivery());
assert!(!kind.is_retryable());
assert!(!kind.is_terminal_failure());
- assert!(kind.is_deferred_preview());
+ assert!(kind.is_deferred_until_implemented());
}
}
diff --git a/crates/transport_reticulum/src/lib.rs b/crates/transport_reticulum/src/lib.rs
@@ -9,8 +9,9 @@ use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
- RadrootsTransport, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransport,
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
+ RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportFetchReceipt,
RadrootsTransportFetchRequest, RadrootsTransportFuture, RadrootsTransportImplementationState,
RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportOutcome,
@@ -18,15 +19,15 @@ use radroots_transport::{
RadrootsTransportTargetReceipt,
};
-const DEFAULT_PROFILE_ID: &str = "transport.reticulum.preview";
+const DEFAULT_PROFILE_ID: &str = "transport.reticulum.default";
const RETICULUM_AGENT_ENDPOINT_PREFIX: &str = "reticulum-agent:";
const UNAVAILABLE_CODE: &str = "transport_unavailable";
const DEFERRED_CODE: &str = "deferred_until_implemented";
-const DEFERRED_MESSAGE: &str = "Reticulum preview delivery is deferred until implementation";
+const DEFERRED_MESSAGE: &str = "Reticulum delivery is deferred until implementation";
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum RadrootsReticulumPreviewBehavior {
+pub enum RadrootsReticulumBehavior {
#[default]
RejectDeliveryAttempts,
DeferDeliveryPlans,
@@ -34,18 +35,18 @@ pub enum RadrootsReticulumPreviewBehavior {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewEndpoint {
+pub struct RadrootsReticulumEndpoint {
uri: String,
}
-impl RadrootsReticulumPreviewEndpoint {
- pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsReticulumPreviewError> {
+impl RadrootsReticulumEndpoint {
+ pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsReticulumError> {
let uri = raw.as_ref();
- if uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
- return Err(RadrootsReticulumPreviewError::InvalidEndpoint);
+ if uri != RADROOTS_RETICULUM_ENDPOINT_URI {
+ return Err(RadrootsReticulumError::InvalidEndpoint);
}
Ok(Self {
- uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
})
}
@@ -58,14 +59,13 @@ impl RadrootsReticulumPreviewEndpoint {
}
}
-impl Default for RadrootsReticulumPreviewEndpoint {
+impl Default for RadrootsReticulumEndpoint {
fn default() -> Self {
- Self::parse(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
- .expect("default Reticulum preview endpoint")
+ Self::parse(RADROOTS_RETICULUM_ENDPOINT_URI).expect("default Reticulum endpoint")
}
}
-impl fmt::Display for RadrootsReticulumPreviewEndpoint {
+impl fmt::Display for RadrootsReticulumEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.uri.as_str())
}
@@ -73,12 +73,12 @@ impl fmt::Display for RadrootsReticulumPreviewEndpoint {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewAgentEndpoint {
+pub struct RadrootsReticulumAgentEndpoint {
uri: String,
}
-impl RadrootsReticulumPreviewAgentEndpoint {
- pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsReticulumPreviewError> {
+impl RadrootsReticulumAgentEndpoint {
+ pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsReticulumError> {
let uri = raw.as_ref();
if uri.is_empty()
|| uri != uri.trim()
@@ -88,7 +88,7 @@ impl RadrootsReticulumPreviewAgentEndpoint {
|| !uri.starts_with(RETICULUM_AGENT_ENDPOINT_PREFIX)
|| uri.len() == RETICULUM_AGENT_ENDPOINT_PREFIX.len()
{
- return Err(RadrootsReticulumPreviewError::InvalidAgentEndpoint);
+ return Err(RadrootsReticulumError::InvalidAgentEndpoint);
}
Ok(Self {
uri: uri.to_owned(),
@@ -104,7 +104,7 @@ impl RadrootsReticulumPreviewAgentEndpoint {
}
}
-impl fmt::Display for RadrootsReticulumPreviewAgentEndpoint {
+impl fmt::Display for RadrootsReticulumAgentEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.uri.as_str())
}
@@ -112,25 +112,25 @@ impl fmt::Display for RadrootsReticulumPreviewAgentEndpoint {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewProfile {
+pub struct RadrootsReticulumProfile {
profile_id: String,
- endpoint: RadrootsReticulumPreviewEndpoint,
+ endpoint: RadrootsReticulumEndpoint,
scope: RadrootsTransportMeshScopeId,
- agent_endpoint: Option<RadrootsReticulumPreviewAgentEndpoint>,
- behavior: RadrootsReticulumPreviewBehavior,
+ agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
+ behavior: RadrootsReticulumBehavior,
}
-impl RadrootsReticulumPreviewProfile {
+impl RadrootsReticulumProfile {
pub fn new(
profile_id: impl Into<String>,
- endpoint: RadrootsReticulumPreviewEndpoint,
+ endpoint: RadrootsReticulumEndpoint,
scope: RadrootsTransportMeshScopeId,
- agent_endpoint: Option<RadrootsReticulumPreviewAgentEndpoint>,
- behavior: RadrootsReticulumPreviewBehavior,
- ) -> Result<Self, RadrootsReticulumPreviewError> {
+ agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
+ behavior: RadrootsReticulumBehavior,
+ ) -> Result<Self, RadrootsReticulumError> {
let profile_id = profile_id.into();
if profile_id.trim().is_empty() || profile_id.chars().any(char::is_whitespace) {
- return Err(RadrootsReticulumPreviewError::InvalidProfileId);
+ return Err(RadrootsReticulumError::InvalidProfileId);
}
Ok(Self {
profile_id,
@@ -141,17 +141,17 @@ impl RadrootsReticulumPreviewProfile {
})
}
- pub fn preview_unavailable() -> Self {
+ pub fn deferred_until_implemented() -> Self {
Self {
profile_id: DEFAULT_PROFILE_ID.to_owned(),
- endpoint: RadrootsReticulumPreviewEndpoint::default(),
- scope: RadrootsTransportMeshScopeId::local_preview(),
+ endpoint: RadrootsReticulumEndpoint::default(),
+ scope: RadrootsTransportMeshScopeId::local_reticulum(),
agent_endpoint: None,
- behavior: RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts,
+ behavior: RadrootsReticulumBehavior::RejectDeliveryAttempts,
}
}
- pub fn with_behavior(mut self, behavior: RadrootsReticulumPreviewBehavior) -> Self {
+ pub fn with_behavior(mut self, behavior: RadrootsReticulumBehavior) -> Self {
self.behavior = behavior;
self
}
@@ -160,7 +160,7 @@ impl RadrootsReticulumPreviewProfile {
self.profile_id.as_str()
}
- pub fn endpoint(&self) -> &RadrootsReticulumPreviewEndpoint {
+ pub fn endpoint(&self) -> &RadrootsReticulumEndpoint {
&self.endpoint
}
@@ -168,79 +168,78 @@ impl RadrootsReticulumPreviewProfile {
&self.scope
}
- pub fn agent_endpoint(&self) -> Option<&RadrootsReticulumPreviewAgentEndpoint> {
+ pub fn agent_endpoint(&self) -> Option<&RadrootsReticulumAgentEndpoint> {
self.agent_endpoint.as_ref()
}
- pub fn with_agent_endpoint(
- mut self,
- agent_endpoint: RadrootsReticulumPreviewAgentEndpoint,
- ) -> Self {
+ pub fn with_agent_endpoint(mut self, agent_endpoint: RadrootsReticulumAgentEndpoint) -> Self {
self.agent_endpoint = Some(agent_endpoint);
self
}
- pub fn behavior(&self) -> RadrootsReticulumPreviewBehavior {
+ pub fn behavior(&self) -> RadrootsReticulumBehavior {
self.behavior
}
- pub fn status(&self) -> RadrootsReticulumPreviewStatus {
- RadrootsReticulumPreviewStatus {
+ pub fn status(&self) -> RadrootsReticulumStatus {
+ RadrootsReticulumStatus {
behavior: self.behavior,
scope: self.scope.clone(),
agent_endpoint: self.agent_endpoint.clone(),
transport_status: RadrootsTransportStatus::new(
RadrootsTransportKind::Reticulum,
true,
- RadrootsTransportImplementationState::PreviewUnavailable,
+ RadrootsTransportImplementationState::Real,
false,
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
)
+ .with_maturity(RadrootsTransportCapabilityMaturity::Preview)
+ .with_availability(RadrootsTransportCapabilityAvailability::Unavailable)
.with_profile_id(self.profile_id.clone())
.with_endpoint_uri(self.endpoint.as_str()),
}
}
}
-impl Default for RadrootsReticulumPreviewProfile {
+impl Default for RadrootsReticulumProfile {
fn default() -> Self {
- Self::preview_unavailable()
+ Self::deferred_until_implemented()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewStatus {
- pub behavior: RadrootsReticulumPreviewBehavior,
+pub struct RadrootsReticulumStatus {
+ pub behavior: RadrootsReticulumBehavior,
pub scope: RadrootsTransportMeshScopeId,
- pub agent_endpoint: Option<RadrootsReticulumPreviewAgentEndpoint>,
+ pub agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
pub transport_status: RadrootsTransportStatus,
}
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewTransport {
- profile: RadrootsReticulumPreviewProfile,
+pub struct RadrootsReticulumTransport {
+ profile: RadrootsReticulumProfile,
}
-impl RadrootsReticulumPreviewTransport {
- pub fn new(profile: RadrootsReticulumPreviewProfile) -> Self {
+impl RadrootsReticulumTransport {
+ pub fn new(profile: RadrootsReticulumProfile) -> Self {
Self { profile }
}
- pub fn profile(&self) -> &RadrootsReticulumPreviewProfile {
+ pub fn profile(&self) -> &RadrootsReticulumProfile {
&self.profile
}
- pub fn status(&self) -> RadrootsReticulumPreviewStatus {
+ pub fn status(&self) -> RadrootsReticulumStatus {
self.profile.status()
}
pub fn deliver(
&self,
request: RadrootsTransportDeliveryRequest,
- ) -> Result<RadrootsTransportDeliveryReceipt, RadrootsReticulumPreviewError> {
+ ) -> Result<RadrootsTransportDeliveryReceipt, RadrootsReticulumError> {
ensure_reticulum_targets(request.target_set.targets())?;
- let outcome = preview_outcome(self.profile.behavior);
+ let outcome = reticulum_outcome(self.profile.behavior);
let target_receipts = request
.target_set
.targets()
@@ -256,30 +255,30 @@ impl RadrootsReticulumPreviewTransport {
pub fn fetch(
&self,
- request: RadrootsReticulumPreviewFetchRequest,
- ) -> Result<RadrootsReticulumPreviewFetchReceipt, RadrootsReticulumPreviewError> {
+ request: RadrootsReticulumFetchRequest,
+ ) -> Result<RadrootsReticulumFetchReceipt, RadrootsReticulumError> {
if request.max_events == 0 {
- return Err(RadrootsReticulumPreviewError::InvalidFetchLimit);
+ return Err(RadrootsReticulumError::InvalidFetchLimit);
}
- Ok(RadrootsReticulumPreviewFetchReceipt {
+ Ok(RadrootsReticulumFetchReceipt {
request_id: request.request_id,
endpoint_uri: self.profile.endpoint.as_str().to_owned(),
scope: self.profile.scope.clone(),
agent_endpoint: self.profile.agent_endpoint.clone(),
- outcome: preview_outcome(self.profile.behavior),
+ outcome: reticulum_outcome(self.profile.behavior),
observed_event_count: 0,
- implementation: RadrootsTransportImplementationState::PreviewUnavailable,
+ implementation: RadrootsTransportImplementationState::Real,
})
}
}
-impl Default for RadrootsReticulumPreviewTransport {
+impl Default for RadrootsReticulumTransport {
fn default() -> Self {
- Self::new(RadrootsReticulumPreviewProfile::default())
+ Self::new(RadrootsReticulumProfile::default())
}
}
-impl RadrootsTransport for RadrootsReticulumPreviewTransport {
+impl RadrootsTransport for RadrootsReticulumTransport {
fn transport_kind(&self) -> RadrootsTransportKind {
RadrootsTransportKind::Reticulum
}
@@ -294,7 +293,7 @@ impl RadrootsTransport for RadrootsReticulumPreviewTransport {
) -> RadrootsTransportFuture<'a, RadrootsTransportDeliveryReceipt> {
Box::pin(async move {
self.deliver(request)
- .map_err(reticulum_preview_error_to_transport_error)
+ .map_err(reticulum_error_to_transport_error)
})
}
@@ -304,8 +303,8 @@ impl RadrootsTransport for RadrootsReticulumPreviewTransport {
) -> RadrootsTransportFuture<'a, RadrootsTransportFetchReceipt> {
Box::pin(async move {
ensure_reticulum_targets(request.target_set.targets())
- .map_err(reticulum_preview_error_to_transport_error)?;
- let outcome = preview_outcome(self.profile.behavior);
+ .map_err(reticulum_error_to_transport_error)?;
+ let outcome = reticulum_outcome(self.profile.behavior);
let target_receipts = request
.target_set
.targets()
@@ -324,18 +323,18 @@ impl RadrootsTransport for RadrootsReticulumPreviewTransport {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewFetchRequest {
+pub struct RadrootsReticulumFetchRequest {
pub request_id: String,
pub max_events: u16,
}
-impl RadrootsReticulumPreviewFetchRequest {
+impl RadrootsReticulumFetchRequest {
pub fn new(
request_id: impl Into<String>,
max_events: u16,
- ) -> Result<Self, RadrootsReticulumPreviewError> {
+ ) -> Result<Self, RadrootsReticulumError> {
if max_events == 0 {
- return Err(RadrootsReticulumPreviewError::InvalidFetchLimit);
+ return Err(RadrootsReticulumError::InvalidFetchLimit);
}
Ok(Self {
request_id: request_id.into(),
@@ -346,18 +345,18 @@ impl RadrootsReticulumPreviewFetchRequest {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RadrootsReticulumPreviewFetchReceipt {
+pub struct RadrootsReticulumFetchReceipt {
pub request_id: String,
pub endpoint_uri: String,
pub scope: RadrootsTransportMeshScopeId,
- pub agent_endpoint: Option<RadrootsReticulumPreviewAgentEndpoint>,
+ pub agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
pub outcome: RadrootsTransportOutcome,
pub observed_event_count: usize,
pub implementation: RadrootsTransportImplementationState,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum RadrootsReticulumPreviewError {
+pub enum RadrootsReticulumError {
InvalidEndpoint,
InvalidAgentEndpoint,
InvalidProfileId,
@@ -365,76 +364,69 @@ pub enum RadrootsReticulumPreviewError {
NonReticulumTarget,
}
-impl fmt::Display for RadrootsReticulumPreviewError {
+impl fmt::Display for RadrootsReticulumError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
- Self::InvalidEndpoint => "invalid Reticulum preview endpoint",
- Self::InvalidAgentEndpoint => "invalid Reticulum preview agent endpoint",
- Self::InvalidProfileId => "invalid Reticulum preview profile id",
- Self::InvalidFetchLimit => "Reticulum preview fetch limit must be greater than zero",
- Self::NonReticulumTarget => {
- "Reticulum preview transport received a non-Reticulum target"
- }
+ Self::InvalidEndpoint => "invalid Reticulum endpoint",
+ Self::InvalidAgentEndpoint => "invalid Reticulum agent endpoint",
+ Self::InvalidProfileId => "invalid Reticulum profile id",
+ Self::InvalidFetchLimit => "Reticulum fetch limit must be greater than zero",
+ Self::NonReticulumTarget => "Reticulum transport received a non-Reticulum target",
})
}
}
-fn reticulum_preview_error_to_transport_error(
- error: RadrootsReticulumPreviewError,
-) -> RadrootsTransportError {
+fn reticulum_error_to_transport_error(error: RadrootsReticulumError) -> RadrootsTransportError {
match error {
- RadrootsReticulumPreviewError::InvalidEndpoint
- | RadrootsReticulumPreviewError::NonReticulumTarget => {
+ RadrootsReticulumError::InvalidEndpoint | RadrootsReticulumError::NonReticulumTarget => {
RadrootsTransportError::InvalidTargetUri
}
- RadrootsReticulumPreviewError::InvalidAgentEndpoint
- | RadrootsReticulumPreviewError::InvalidProfileId
- | RadrootsReticulumPreviewError::InvalidFetchLimit => {
- RadrootsTransportError::InvalidTransportKind
- }
+ RadrootsReticulumError::InvalidAgentEndpoint
+ | RadrootsReticulumError::InvalidProfileId
+ | RadrootsReticulumError::InvalidFetchLimit => RadrootsTransportError::InvalidTransportKind,
}
}
fn ensure_reticulum_targets(
targets: &[RadrootsTransportTarget],
-) -> Result<(), RadrootsReticulumPreviewError> {
+) -> Result<(), RadrootsReticulumError> {
for target in targets {
if target.kind != RadrootsTransportKind::Reticulum {
- return Err(RadrootsReticulumPreviewError::NonReticulumTarget);
+ return Err(RadrootsReticulumError::NonReticulumTarget);
}
- if target.uri.as_str() != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
- return Err(RadrootsReticulumPreviewError::InvalidEndpoint);
+ if target.uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI {
+ return Err(RadrootsReticulumError::InvalidEndpoint);
}
if target.scope.is_none() {
- return Err(RadrootsReticulumPreviewError::InvalidEndpoint);
+ return Err(RadrootsReticulumError::InvalidEndpoint);
}
}
Ok(())
}
-fn preview_outcome(behavior: RadrootsReticulumPreviewBehavior) -> RadrootsTransportOutcome {
+fn reticulum_outcome(behavior: RadrootsReticulumBehavior) -> RadrootsTransportOutcome {
let mut outcome = match behavior {
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts => {
+ RadrootsReticulumBehavior::RejectDeliveryAttempts => {
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::TransportUnavailable)
- .with_target_status(RadrootsTransportDeliveryTargetStatus::PreviewUnavailable)
+ .with_target_status(RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented)
}
- RadrootsReticulumPreviewBehavior::DeferDeliveryPlans => {
+ RadrootsReticulumBehavior::DeferDeliveryPlans => {
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::DeferredUntilImplemented)
}
};
outcome.code = Some(
match behavior {
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts => UNAVAILABLE_CODE,
- RadrootsReticulumPreviewBehavior::DeferDeliveryPlans => DEFERRED_CODE,
+ RadrootsReticulumBehavior::RejectDeliveryAttempts => UNAVAILABLE_CODE,
+ RadrootsReticulumBehavior::DeferDeliveryPlans => DEFERRED_CODE,
}
.to_owned(),
);
outcome.message = Some(
match behavior {
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts => {
+ RadrootsReticulumBehavior::RejectDeliveryAttempts => {
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE
}
- RadrootsReticulumPreviewBehavior::DeferDeliveryPlans => DEFERRED_MESSAGE,
+ RadrootsReticulumBehavior::DeferDeliveryPlans => DEFERRED_MESSAGE,
}
.to_owned(),
);
diff --git a/crates/transport_reticulum/tests/reticulum.rs b/crates/transport_reticulum/tests/reticulum.rs
@@ -0,0 +1,588 @@
+use radroots_transport::{
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID,
+ RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransport,
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
+ RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
+ RadrootsTransportFetchRequest, RadrootsTransportImplementationState, RadrootsTransportKind,
+ RadrootsTransportMeshScopeId, RadrootsTransportPayload, RadrootsTransportSatisfactionClass,
+ RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetSet,
+};
+use radroots_transport_reticulum::{
+ RadrootsReticulumAgentEndpoint, RadrootsReticulumBehavior, RadrootsReticulumEndpoint,
+ RadrootsReticulumError, RadrootsReticulumFetchRequest, RadrootsReticulumProfile,
+ RadrootsReticulumTransport,
+};
+
+fn reticulum_target(uri: &str) -> RadrootsTransportTarget {
+ assert_eq!(uri, RADROOTS_RETICULUM_ENDPOINT_URI);
+ RadrootsTransportTarget::reticulum().expect("reticulum target")
+}
+
+fn scoped_reticulum_target(scope: &str) -> RadrootsTransportTarget {
+ RadrootsTransportTarget::reticulum_with_metadata(
+ RADROOTS_RETICULUM_ENDPOINT_URI,
+ Some(RadrootsTransportMeshScopeId::parse(scope).expect("scope")),
+ None,
+ )
+ .expect("scoped reticulum target")
+}
+
+fn nostr_target() -> RadrootsTransportTarget {
+ RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("nostr target")
+}
+
+fn delivery_request(targets: Vec<RadrootsTransportTarget>) -> RadrootsTransportDeliveryRequest {
+ RadrootsTransportDeliveryRequest::new(
+ "reticulum-delivery",
+ reticulum_payload(),
+ RadrootsTransportTargetSet::new(targets).expect("target set"),
+ RadrootsTransportSatisfactionPolicy::any_accepted(),
+ )
+}
+
+fn reticulum_payload() -> RadrootsTransportPayload {
+ RadrootsTransportPayload::mesh_frame_cbor("reticulum-message", [1_u8, 2, 3]).expect("payload")
+}
+
+#[test]
+fn default_profile_is_configured_deferred_until_implemented_and_rejecting() {
+ let profile = RadrootsReticulumProfile::default();
+ let status = profile.status();
+
+ assert_eq!(profile.profile_id(), "transport.reticulum.default");
+ assert_eq!(profile.endpoint().as_str(), RADROOTS_RETICULUM_ENDPOINT_URI);
+ assert_eq!(profile.scope().as_str(), RADROOTS_RETICULUM_SCOPE_ID);
+ assert_eq!(profile.agent_endpoint(), None);
+ assert_eq!(
+ profile.behavior(),
+ RadrootsReticulumBehavior::RejectDeliveryAttempts
+ );
+ assert_eq!(
+ status.transport_status.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+ assert_eq!(
+ status.transport_status.maturity,
+ RadrootsTransportCapabilityMaturity::Preview
+ );
+ assert_eq!(
+ status.transport_status.availability,
+ RadrootsTransportCapabilityAvailability::Unavailable
+ );
+ assert!(status.transport_status.configured);
+ assert_eq!(
+ status.transport_status.profile_id.as_deref(),
+ Some("transport.reticulum.default")
+ );
+ assert_eq!(
+ status.transport_status.endpoint_uri.as_deref(),
+ Some(RADROOTS_RETICULUM_ENDPOINT_URI)
+ );
+ assert_eq!(
+ status.transport_status.message,
+ RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE
+ );
+ assert!(!status.transport_status.usable_for_delivery);
+ assert_eq!(status.scope.as_str(), RADROOTS_RETICULUM_SCOPE_ID);
+ assert_eq!(status.agent_endpoint, None);
+}
+
+#[test]
+fn endpoint_and_profile_validation_are_strict_and_canonical() {
+ let endpoint =
+ RadrootsReticulumEndpoint::parse(RADROOTS_RETICULUM_ENDPOINT_URI).expect("endpoint");
+ assert_eq!(endpoint.as_str(), RADROOTS_RETICULUM_ENDPOINT_URI);
+ assert_eq!(endpoint.to_string(), RADROOTS_RETICULUM_ENDPOINT_URI);
+ assert_eq!(
+ endpoint.clone().into_string(),
+ RADROOTS_RETICULUM_ENDPOINT_URI
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::default().as_str(),
+ RADROOTS_RETICULUM_ENDPOINT_URI
+ );
+
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse(" ").expect_err("empty endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("reticulum:").expect_err("empty endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("https://target").expect_err("wrong scheme"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("RETICULUM:unavailable").expect_err("case drift endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse(" reticulum:unavailable")
+ .expect_err("leading whitespace endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("reticulum:unavailable ")
+ .expect_err("trailing whitespace endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("reticulum:custom").expect_err("custom endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("reticulum:bad target").expect_err("whitespace endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumEndpoint::parse("reticulum:bad\ntarget").expect_err("control endpoint"),
+ RadrootsReticulumError::InvalidEndpoint
+ );
+ let agent_endpoint = RadrootsReticulumAgentEndpoint::parse("reticulum-agent://localhost:19999")
+ .expect("agent endpoint");
+ assert_eq!(agent_endpoint.as_str(), "reticulum-agent://localhost:19999");
+ assert_eq!(
+ agent_endpoint.to_string(),
+ "reticulum-agent://localhost:19999"
+ );
+ assert_eq!(
+ agent_endpoint.clone().into_string(),
+ "reticulum-agent://localhost:19999"
+ );
+ let local_agent_endpoint =
+ RadrootsReticulumAgentEndpoint::parse("reticulum-agent:local-controller")
+ .expect("local agent endpoint");
+ assert_eq!(
+ local_agent_endpoint.as_str(),
+ "reticulum-agent:local-controller"
+ );
+ for invalid_agent in [
+ "",
+ " reticulum-agent://localhost",
+ "reticulum-agent:",
+ "reticulum agent",
+ "agent",
+ "https://localhost:19999",
+ "ws://localhost:19999",
+ "reticulum://localhost:19999",
+ "RETICULUM-AGENT://localhost:19999",
+ ] {
+ assert_eq!(
+ RadrootsReticulumAgentEndpoint::parse(invalid_agent)
+ .expect_err("invalid agent endpoint"),
+ RadrootsReticulumError::InvalidAgentEndpoint
+ );
+ }
+ assert_eq!(
+ RadrootsReticulumProfile::new(
+ "transport reticulum",
+ endpoint,
+ RadrootsTransportMeshScopeId::local_reticulum(),
+ None,
+ RadrootsReticulumBehavior::RejectDeliveryAttempts,
+ )
+ .expect_err("profile id whitespace"),
+ RadrootsReticulumError::InvalidProfileId
+ );
+ assert_eq!(
+ RadrootsReticulumProfile::new(
+ "",
+ RadrootsReticulumEndpoint::default(),
+ RadrootsTransportMeshScopeId::local_reticulum(),
+ None,
+ RadrootsReticulumBehavior::RejectDeliveryAttempts,
+ )
+ .expect_err("empty profile id"),
+ RadrootsReticulumError::InvalidProfileId
+ );
+ let profile = RadrootsReticulumProfile::new(
+ "transport.reticulum.custom",
+ RadrootsReticulumEndpoint::default(),
+ RadrootsTransportMeshScopeId::local_reticulum(),
+ Some(agent_endpoint),
+ RadrootsReticulumBehavior::DeferDeliveryPlans,
+ )
+ .expect("custom behavior profile");
+ assert_eq!(profile.profile_id(), "transport.reticulum.custom");
+ assert_eq!(profile.endpoint().as_str(), RADROOTS_RETICULUM_ENDPOINT_URI);
+ assert_eq!(
+ profile.behavior(),
+ RadrootsReticulumBehavior::DeferDeliveryPlans
+ );
+ assert_eq!(
+ profile.agent_endpoint().map(|endpoint| endpoint.as_str()),
+ Some("reticulum-agent://localhost:19999")
+ );
+}
+
+#[test]
+fn direct_reticulum_delivery_accepts_any_typed_scope_as_inert_metadata() {
+ let transport = RadrootsReticulumTransport::default();
+ let request = delivery_request(vec![scoped_reticulum_target("farm-north.mesh_1")]);
+ let receipt = transport.deliver(request).expect("delivery receipt");
+
+ assert_eq!(receipt.target_receipts.len(), 1);
+ assert_eq!(
+ receipt.target_receipts[0]
+ .target
+ .scope
+ .as_ref()
+ .map(|scope| scope.as_str()),
+ Some("farm-north.mesh_1")
+ );
+ assert_eq!(
+ receipt.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ assert_eq!(
+ receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
+ 0
+ );
+
+ let deferred_transport = RadrootsReticulumTransport::new(
+ RadrootsReticulumProfile::default()
+ .with_behavior(RadrootsReticulumBehavior::DeferDeliveryPlans),
+ );
+ let deferred = deferred_transport
+ .deliver(delivery_request(vec![scoped_reticulum_target(
+ "farm-south.mesh_2",
+ )]))
+ .expect("deferred delivery receipt");
+ assert_eq!(
+ deferred.target_receipts[0]
+ .target
+ .scope
+ .as_ref()
+ .map(|scope| scope.as_str()),
+ Some("farm-south.mesh_2")
+ );
+ assert_eq!(
+ deferred.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ assert_eq!(
+ deferred.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
+ 0
+ );
+}
+
+#[test]
+fn core_transport_trait_reports_reticulum_status_delivery_and_fetch() {
+ let transport = RadrootsReticulumTransport::default();
+ let target_set =
+ RadrootsTransportTargetSet::new(vec![reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI)])
+ .expect("target set");
+ let status = futures::executor::block_on(RadrootsTransport::status(&transport))
+ .expect("transport status");
+ assert_eq!(status.kind, RadrootsTransportKind::Reticulum);
+ assert_eq!(
+ status.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+ assert_eq!(
+ status.maturity,
+ RadrootsTransportCapabilityMaturity::Preview
+ );
+ assert_eq!(
+ status.availability,
+ RadrootsTransportCapabilityAvailability::Unavailable
+ );
+ assert!(!status.usable_for_delivery);
+ assert!(!status.capabilities.deliver);
+ assert!(!status.capabilities.fetch);
+
+ let delivery = futures::executor::block_on(RadrootsTransport::deliver(
+ &transport,
+ RadrootsTransportDeliveryRequest::new(
+ "core-delivery",
+ reticulum_payload(),
+ target_set.clone(),
+ RadrootsTransportSatisfactionPolicy::any_accepted(),
+ ),
+ ))
+ .expect("delivery receipt");
+ assert_eq!(
+ delivery.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+
+ let fetch = futures::executor::block_on(RadrootsTransport::fetch(
+ &transport,
+ RadrootsTransportFetchRequest::new("core-fetch", target_set),
+ ))
+ .expect("fetch receipt");
+ assert_eq!(fetch.fetched_count, 0);
+ assert_eq!(
+ fetch.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+}
+
+#[test]
+fn reject_delivery_attempts_returns_unavailable_without_success_or_nostr_routing() {
+ let transport = RadrootsReticulumTransport::default();
+ let request = delivery_request(vec![reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI)]);
+ let receipt = transport.deliver(request).expect("delivery receipt");
+
+ assert_eq!(receipt.target_receipts.len(), 1);
+ assert_eq!(
+ receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
+ 0
+ );
+ for target_receipt in receipt.target_receipts {
+ assert_eq!(target_receipt.target.kind, RadrootsTransportKind::Reticulum);
+ assert_eq!(
+ target_receipt.status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ assert_eq!(
+ target_receipt.outcome.code.as_deref(),
+ Some("transport_unavailable")
+ );
+ assert_eq!(
+ target_receipt.outcome.message.as_deref(),
+ Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE)
+ );
+ }
+}
+
+#[test]
+fn noncanonical_reticulum_targets_are_rejected() {
+ for invalid in [
+ " reticulum:unavailable",
+ "reticulum:unavailable ",
+ "RETICULUM:unavailable",
+ "reticulum:Unavailable",
+ "reticulum:temporary",
+ "reticulum:unavailable-alt",
+ "reticulum:custom",
+ ] {
+ assert_eq!(
+ RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
+ .expect_err("noncanonical Reticulum target"),
+ radroots_transport::RadrootsTransportError::InvalidTargetUri
+ );
+ }
+}
+
+#[test]
+fn deferred_delivery_plan_mode_never_counts_as_satisfied() {
+ let transport = RadrootsReticulumTransport::new(
+ RadrootsReticulumProfile::default()
+ .with_behavior(RadrootsReticulumBehavior::DeferDeliveryPlans),
+ );
+ let request = delivery_request(vec![reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI)]);
+ let receipt = transport.deliver(request).expect("delivery receipt");
+
+ assert_eq!(receipt.target_receipts.len(), 1);
+ assert_eq!(
+ receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
+ 0
+ );
+ assert_eq!(
+ receipt.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ assert_eq!(
+ receipt.target_receipts[0].outcome.code.as_deref(),
+ Some("deferred_until_implemented")
+ );
+ assert!(
+ !RadrootsTransportSatisfactionPolicy::any_accepted()
+ .is_satisfied_by(
+ 1,
+ receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted)
+ )
+ .expect("satisfaction check")
+ );
+}
+
+#[test]
+fn non_reticulum_targets_are_rejected_without_nostr_routing() {
+ let transport = RadrootsReticulumTransport::default();
+ let err = transport
+ .deliver(delivery_request(vec![nostr_target()]))
+ .expect_err("non-reticulum target");
+
+ assert_eq!(err, RadrootsReticulumError::NonReticulumTarget);
+}
+
+#[test]
+fn malformed_reticulum_target_without_typed_scope_is_rejected() {
+ let transport = RadrootsReticulumTransport::default();
+ let mut target = reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI);
+ target.scope = None;
+ let err = transport
+ .deliver(delivery_request(vec![target]))
+ .expect_err("missing typed scope");
+
+ assert_eq!(err, RadrootsReticulumError::InvalidEndpoint);
+}
+
+#[test]
+fn fetch_reports_deferred_until_implemented_without_observed_events() {
+ let transport = RadrootsReticulumTransport::default();
+ assert_eq!(
+ transport.profile().profile_id(),
+ "transport.reticulum.default"
+ );
+ assert_eq!(
+ transport.status().transport_status.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+ let receipt = transport
+ .fetch(RadrootsReticulumFetchRequest::new("fetch-1", 10).expect("fetch request"))
+ .expect("fetch receipt");
+
+ assert_eq!(receipt.request_id, "fetch-1");
+ assert_eq!(receipt.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI);
+ assert_eq!(receipt.observed_event_count, 0);
+ assert_eq!(
+ receipt.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+ assert_eq!(receipt.scope.as_str(), RADROOTS_RETICULUM_SCOPE_ID);
+ assert_eq!(receipt.agent_endpoint, None);
+ assert_eq!(
+ receipt.outcome.status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ assert_eq!(
+ RadrootsReticulumFetchRequest::new("fetch-0", 0).expect_err("zero limit"),
+ RadrootsReticulumError::InvalidFetchLimit
+ );
+ assert_eq!(
+ transport
+ .fetch(RadrootsReticulumFetchRequest {
+ request_id: "fetch-public-zero".to_owned(),
+ max_events: 0,
+ })
+ .expect_err("zero limit at transport boundary"),
+ RadrootsReticulumError::InvalidFetchLimit
+ );
+ let deferred_transport = RadrootsReticulumTransport::new(
+ RadrootsReticulumProfile::default()
+ .with_behavior(RadrootsReticulumBehavior::DeferDeliveryPlans),
+ );
+ let deferred = deferred_transport
+ .fetch(RadrootsReticulumFetchRequest::new("fetch-deferred", 1).expect("fetch"))
+ .expect("fetch receipt");
+ assert_eq!(
+ deferred.outcome.status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+}
+
+#[test]
+fn configured_agent_endpoint_is_metadata_only_for_status_delivery_and_fetch() {
+ let agent_endpoint = RadrootsReticulumAgentEndpoint::parse("reticulum-agent://localhost:19999")
+ .expect("agent endpoint");
+ let transport = RadrootsReticulumTransport::new(
+ RadrootsReticulumProfile::default().with_agent_endpoint(agent_endpoint),
+ );
+ let status = transport.status();
+ assert_eq!(
+ status
+ .agent_endpoint
+ .as_ref()
+ .map(|endpoint| endpoint.as_str()),
+ Some("reticulum-agent://localhost:19999")
+ );
+ assert_eq!(
+ status.transport_status.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+ assert!(!status.transport_status.usable_for_delivery);
+ assert!(!status.transport_status.capabilities.deliver);
+ assert!(!status.transport_status.capabilities.fetch);
+
+ let receipt = transport
+ .deliver(delivery_request(vec![reticulum_target(
+ RADROOTS_RETICULUM_ENDPOINT_URI,
+ )]))
+ .expect("delivery receipt");
+ assert_eq!(
+ receipt.target_receipts[0].status,
+ RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ );
+ let fetch = transport
+ .fetch(RadrootsReticulumFetchRequest::new("fetch-agent", 1).expect("fetch"))
+ .expect("fetch receipt");
+ assert_eq!(
+ fetch
+ .agent_endpoint
+ .as_ref()
+ .map(|endpoint| endpoint.as_str()),
+ Some("reticulum-agent://localhost:19999")
+ );
+ assert_eq!(fetch.observed_event_count, 0);
+ assert_eq!(
+ fetch.implementation,
+ RadrootsTransportImplementationState::Real
+ );
+}
+
+#[test]
+fn public_models_round_trip_through_serde() {
+ let profile = RadrootsReticulumProfile::default()
+ .with_behavior(RadrootsReticulumBehavior::DeferDeliveryPlans);
+ let json = serde_json::to_string(&profile).expect("profile json");
+ let decoded: RadrootsReticulumProfile = serde_json::from_str(&json).expect("profile decode");
+
+ assert_eq!(decoded, profile);
+}
+
+#[test]
+fn reticulum_source_remains_inert_without_runtime_delivery_hooks() {
+ let source = include_str!("../src/lib.rs").to_ascii_lowercase();
+ for forbidden in [
+ "socket",
+ "rnsd",
+ "python",
+ "identity",
+ "send_mesh",
+ "fallback",
+ "nostr",
+ ] {
+ assert!(
+ !source.contains(forbidden),
+ "Reticulum source contains forbidden runtime hook {forbidden}"
+ );
+ }
+}
+
+#[test]
+fn reticulum_errors_and_defaults_are_stable() {
+ assert_eq!(
+ RadrootsReticulumBehavior::default(),
+ RadrootsReticulumBehavior::RejectDeliveryAttempts
+ );
+ let cases = [
+ (
+ RadrootsReticulumError::InvalidEndpoint,
+ "invalid Reticulum endpoint",
+ ),
+ (
+ RadrootsReticulumError::InvalidAgentEndpoint,
+ "invalid Reticulum agent endpoint",
+ ),
+ (
+ RadrootsReticulumError::InvalidProfileId,
+ "invalid Reticulum profile id",
+ ),
+ (
+ RadrootsReticulumError::InvalidFetchLimit,
+ "Reticulum fetch limit must be greater than zero",
+ ),
+ (
+ RadrootsReticulumError::NonReticulumTarget,
+ "Reticulum transport received a non-Reticulum target",
+ ),
+ ];
+
+ for (error, message) in cases {
+ assert_eq!(error.to_string(), message);
+ }
+}
diff --git a/crates/transport_reticulum/tests/reticulum_preview.rs b/crates/transport_reticulum/tests/reticulum_preview.rs
@@ -1,597 +0,0 @@
-use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransport, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportFetchRequest,
- RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportMeshScopeId,
- RadrootsTransportPayload, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetSet,
-};
-use radroots_transport_reticulum::{
- RadrootsReticulumPreviewAgentEndpoint, RadrootsReticulumPreviewBehavior,
- RadrootsReticulumPreviewEndpoint, RadrootsReticulumPreviewError,
- RadrootsReticulumPreviewFetchRequest, RadrootsReticulumPreviewProfile,
- RadrootsReticulumPreviewTransport,
-};
-
-fn reticulum_target(uri: &str) -> RadrootsTransportTarget {
- assert_eq!(uri, RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
- RadrootsTransportTarget::reticulum_preview().expect("reticulum target")
-}
-
-fn scoped_reticulum_target(scope: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::reticulum_preview_with_metadata(
- Some(RadrootsTransportMeshScopeId::parse(scope).expect("scope")),
- None,
- )
- .expect("scoped reticulum target")
-}
-
-fn nostr_target() -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("nostr target")
-}
-
-fn delivery_request(targets: Vec<RadrootsTransportTarget>) -> RadrootsTransportDeliveryRequest {
- RadrootsTransportDeliveryRequest::new(
- "reticulum-preview-delivery",
- preview_payload(),
- RadrootsTransportTargetSet::new(targets).expect("target set"),
- RadrootsTransportSatisfactionPolicy::any_accepted(),
- )
-}
-
-fn preview_payload() -> RadrootsTransportPayload {
- RadrootsTransportPayload::mesh_frame_cbor("preview-message", [1_u8, 2, 3]).expect("payload")
-}
-
-#[test]
-fn default_profile_is_configured_preview_unavailable_and_rejecting() {
- let profile = RadrootsReticulumPreviewProfile::default();
- let status = profile.status();
-
- assert_eq!(profile.profile_id(), "transport.reticulum.preview");
- assert_eq!(
- profile.endpoint().as_str(),
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
- assert_eq!(
- profile.scope().as_str(),
- RADROOTS_RETICULUM_PREVIEW_SCOPE_ID
- );
- assert_eq!(profile.agent_endpoint(), None);
- assert_eq!(
- profile.behavior(),
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts
- );
- assert_eq!(
- status.transport_status.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
- assert!(status.transport_status.configured);
- assert_eq!(
- status.transport_status.profile_id.as_deref(),
- Some("transport.reticulum.preview")
- );
- assert_eq!(
- status.transport_status.endpoint_uri.as_deref(),
- Some(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
- );
- assert_eq!(
- status.transport_status.message,
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE
- );
- assert!(!status.transport_status.usable_for_delivery);
- assert_eq!(status.scope.as_str(), RADROOTS_RETICULUM_PREVIEW_SCOPE_ID);
- assert_eq!(status.agent_endpoint, None);
-}
-
-#[test]
-fn endpoint_and_profile_validation_are_strict_and_canonical() {
- let endpoint = RadrootsReticulumPreviewEndpoint::parse(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
- .expect("endpoint");
- assert_eq!(endpoint.as_str(), RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
- assert_eq!(
- endpoint.to_string(),
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
- assert_eq!(
- endpoint.clone().into_string(),
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::default().as_str(),
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
-
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse(" ").expect_err("empty endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("reticulum:").expect_err("empty endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("https://target").expect_err("wrong scheme"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("RETICULUM:Preview-Unavailable")
- .expect_err("case drift endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse(" reticulum:preview-unavailable")
- .expect_err("leading whitespace endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("reticulum:preview-unavailable ")
- .expect_err("trailing whitespace endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("reticulum:custom").expect_err("custom endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("reticulum:bad target")
- .expect_err("whitespace endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- assert_eq!(
- RadrootsReticulumPreviewEndpoint::parse("reticulum:bad\ntarget")
- .expect_err("control endpoint"),
- RadrootsReticulumPreviewError::InvalidEndpoint
- );
- let agent_endpoint =
- RadrootsReticulumPreviewAgentEndpoint::parse("reticulum-agent://localhost:19999")
- .expect("agent endpoint");
- assert_eq!(agent_endpoint.as_str(), "reticulum-agent://localhost:19999");
- assert_eq!(
- agent_endpoint.to_string(),
- "reticulum-agent://localhost:19999"
- );
- assert_eq!(
- agent_endpoint.clone().into_string(),
- "reticulum-agent://localhost:19999"
- );
- let local_agent_endpoint =
- RadrootsReticulumPreviewAgentEndpoint::parse("reticulum-agent:local-controller")
- .expect("local agent endpoint");
- assert_eq!(
- local_agent_endpoint.as_str(),
- "reticulum-agent:local-controller"
- );
- for invalid_agent in [
- "",
- " reticulum-agent://localhost",
- "reticulum-agent:",
- "reticulum agent",
- "agent",
- "https://localhost:19999",
- "ws://localhost:19999",
- "reticulum://localhost:19999",
- "RETICULUM-AGENT://localhost:19999",
- ] {
- assert_eq!(
- RadrootsReticulumPreviewAgentEndpoint::parse(invalid_agent)
- .expect_err("invalid agent endpoint"),
- RadrootsReticulumPreviewError::InvalidAgentEndpoint
- );
- }
- assert_eq!(
- RadrootsReticulumPreviewProfile::new(
- "transport reticulum",
- endpoint,
- RadrootsTransportMeshScopeId::local_preview(),
- None,
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts,
- )
- .expect_err("profile id whitespace"),
- RadrootsReticulumPreviewError::InvalidProfileId
- );
- assert_eq!(
- RadrootsReticulumPreviewProfile::new(
- "",
- RadrootsReticulumPreviewEndpoint::default(),
- RadrootsTransportMeshScopeId::local_preview(),
- None,
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts,
- )
- .expect_err("empty profile id"),
- RadrootsReticulumPreviewError::InvalidProfileId
- );
- let profile = RadrootsReticulumPreviewProfile::new(
- "transport.reticulum.custom",
- RadrootsReticulumPreviewEndpoint::default(),
- RadrootsTransportMeshScopeId::local_preview(),
- Some(agent_endpoint),
- RadrootsReticulumPreviewBehavior::DeferDeliveryPlans,
- )
- .expect("custom behavior profile");
- assert_eq!(profile.profile_id(), "transport.reticulum.custom");
- assert_eq!(
- profile.endpoint().as_str(),
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
- assert_eq!(
- profile.behavior(),
- RadrootsReticulumPreviewBehavior::DeferDeliveryPlans
- );
- assert_eq!(
- profile.agent_endpoint().map(|endpoint| endpoint.as_str()),
- Some("reticulum-agent://localhost:19999")
- );
-}
-
-#[test]
-fn direct_preview_delivery_accepts_any_typed_reticulum_scope_as_inert_metadata() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let request = delivery_request(vec![scoped_reticulum_target("farm-north.preview_1")]);
- let receipt = transport.deliver(request).expect("delivery receipt");
-
- assert_eq!(receipt.target_receipts.len(), 1);
- assert_eq!(
- receipt.target_receipts[0]
- .target
- .scope
- .as_ref()
- .map(|scope| scope.as_str()),
- Some("farm-north.preview_1")
- );
- assert_eq!(
- receipt.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
- assert_eq!(
- receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
- 0
- );
-
- let deferred_transport = RadrootsReticulumPreviewTransport::new(
- RadrootsReticulumPreviewProfile::default()
- .with_behavior(RadrootsReticulumPreviewBehavior::DeferDeliveryPlans),
- );
- let deferred = deferred_transport
- .deliver(delivery_request(vec![scoped_reticulum_target(
- "farm-south.preview_2",
- )]))
- .expect("deferred delivery receipt");
- assert_eq!(
- deferred.target_receipts[0]
- .target
- .scope
- .as_ref()
- .map(|scope| scope.as_str()),
- Some("farm-south.preview_2")
- );
- assert_eq!(
- deferred.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
- );
- assert_eq!(
- deferred.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
- 0
- );
-}
-
-#[test]
-fn core_transport_trait_reports_preview_status_delivery_and_fetch() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let target_set = RadrootsTransportTargetSet::new(vec![reticulum_target(
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- )])
- .expect("target set");
- let status = futures::executor::block_on(RadrootsTransport::status(&transport))
- .expect("transport status");
- assert_eq!(status.kind, RadrootsTransportKind::Reticulum);
- assert_eq!(
- status.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
- assert!(!status.usable_for_delivery);
- assert!(!status.capabilities.deliver);
- assert!(!status.capabilities.fetch);
-
- let delivery = futures::executor::block_on(RadrootsTransport::deliver(
- &transport,
- RadrootsTransportDeliveryRequest::new(
- "core-delivery",
- preview_payload(),
- target_set.clone(),
- RadrootsTransportSatisfactionPolicy::any_accepted(),
- ),
- ))
- .expect("delivery receipt");
- assert_eq!(
- delivery.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
-
- let fetch = futures::executor::block_on(RadrootsTransport::fetch(
- &transport,
- RadrootsTransportFetchRequest::new("core-fetch", target_set),
- ))
- .expect("fetch receipt");
- assert_eq!(fetch.fetched_count, 0);
- assert_eq!(
- fetch.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
-}
-
-#[test]
-fn reject_delivery_attempts_returns_unavailable_without_success_or_nostr_routing() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let request = delivery_request(vec![reticulum_target(
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- )]);
- let receipt = transport.deliver(request).expect("delivery receipt");
-
- assert_eq!(receipt.target_receipts.len(), 1);
- assert_eq!(
- receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
- 0
- );
- for target_receipt in receipt.target_receipts {
- assert_eq!(target_receipt.target.kind, RadrootsTransportKind::Reticulum);
- assert_eq!(
- target_receipt.status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
- assert_eq!(
- target_receipt.outcome.code.as_deref(),
- Some("transport_unavailable")
- );
- assert_eq!(
- target_receipt.outcome.message.as_deref(),
- Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE)
- );
- }
-}
-
-#[test]
-fn noncanonical_reticulum_preview_targets_are_rejected() {
- for invalid in [
- " reticulum:preview-unavailable",
- "reticulum:preview-unavailable ",
- "RETICULUM:preview-unavailable",
- "reticulum:Preview-Unavailable",
- "reticulum:preview",
- "reticulum:preview-unavailable-alt",
- "reticulum:custom",
- ] {
- assert_eq!(
- RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
- .expect_err("noncanonical Reticulum target"),
- radroots_transport::RadrootsTransportError::InvalidTargetUri
- );
- }
-}
-
-#[test]
-fn deferred_delivery_plan_mode_never_counts_as_satisfied() {
- let transport = RadrootsReticulumPreviewTransport::new(
- RadrootsReticulumPreviewProfile::default()
- .with_behavior(RadrootsReticulumPreviewBehavior::DeferDeliveryPlans),
- );
- let request = delivery_request(vec![reticulum_target(
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- )]);
- let receipt = transport.deliver(request).expect("delivery receipt");
-
- assert_eq!(receipt.target_receipts.len(), 1);
- assert_eq!(
- receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
- 0
- );
- assert_eq!(
- receipt.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
- );
- assert_eq!(
- receipt.target_receipts[0].outcome.code.as_deref(),
- Some("deferred_until_implemented")
- );
- assert!(
- !RadrootsTransportSatisfactionPolicy::any_accepted()
- .is_satisfied_by(
- 1,
- receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted)
- )
- .expect("satisfaction check")
- );
-}
-
-#[test]
-fn non_reticulum_targets_are_rejected_without_nostr_routing() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let err = transport
- .deliver(delivery_request(vec![nostr_target()]))
- .expect_err("non-reticulum target");
-
- assert_eq!(err, RadrootsReticulumPreviewError::NonReticulumTarget);
-}
-
-#[test]
-fn malformed_reticulum_target_without_typed_scope_is_rejected() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let mut target = reticulum_target(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
- target.scope = None;
- let err = transport
- .deliver(delivery_request(vec![target]))
- .expect_err("missing typed scope");
-
- assert_eq!(err, RadrootsReticulumPreviewError::InvalidEndpoint);
-}
-
-#[test]
-fn fetch_reports_preview_unavailable_without_observed_events() {
- let transport = RadrootsReticulumPreviewTransport::default();
- assert_eq!(
- transport.profile().profile_id(),
- "transport.reticulum.preview"
- );
- assert_eq!(
- transport.status().transport_status.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
- let receipt = transport
- .fetch(RadrootsReticulumPreviewFetchRequest::new("fetch-1", 10).expect("fetch request"))
- .expect("fetch receipt");
-
- assert_eq!(receipt.request_id, "fetch-1");
- assert_eq!(
- receipt.endpoint_uri,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
- );
- assert_eq!(receipt.observed_event_count, 0);
- assert_eq!(
- receipt.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
- assert_eq!(receipt.scope.as_str(), RADROOTS_RETICULUM_PREVIEW_SCOPE_ID);
- assert_eq!(receipt.agent_endpoint, None);
- assert_eq!(
- receipt.outcome.status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
- assert_eq!(
- RadrootsReticulumPreviewFetchRequest::new("fetch-0", 0).expect_err("zero limit"),
- RadrootsReticulumPreviewError::InvalidFetchLimit
- );
- assert_eq!(
- transport
- .fetch(RadrootsReticulumPreviewFetchRequest {
- request_id: "fetch-public-zero".to_owned(),
- max_events: 0,
- })
- .expect_err("zero limit at transport boundary"),
- RadrootsReticulumPreviewError::InvalidFetchLimit
- );
- let deferred_transport = RadrootsReticulumPreviewTransport::new(
- RadrootsReticulumPreviewProfile::default()
- .with_behavior(RadrootsReticulumPreviewBehavior::DeferDeliveryPlans),
- );
- let deferred = deferred_transport
- .fetch(RadrootsReticulumPreviewFetchRequest::new("fetch-deferred", 1).expect("fetch"))
- .expect("fetch receipt");
- assert_eq!(
- deferred.outcome.status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
- );
-}
-
-#[test]
-fn configured_agent_endpoint_is_metadata_only_for_status_delivery_and_fetch() {
- let agent_endpoint =
- RadrootsReticulumPreviewAgentEndpoint::parse("reticulum-agent://localhost:19999")
- .expect("agent endpoint");
- let transport = RadrootsReticulumPreviewTransport::new(
- RadrootsReticulumPreviewProfile::default().with_agent_endpoint(agent_endpoint),
- );
- let status = transport.status();
- assert_eq!(
- status
- .agent_endpoint
- .as_ref()
- .map(|endpoint| endpoint.as_str()),
- Some("reticulum-agent://localhost:19999")
- );
- assert_eq!(
- status.transport_status.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
- assert!(!status.transport_status.usable_for_delivery);
- assert!(!status.transport_status.capabilities.deliver);
- assert!(!status.transport_status.capabilities.fetch);
-
- let receipt = transport
- .deliver(delivery_request(vec![reticulum_target(
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- )]))
- .expect("delivery receipt");
- assert_eq!(
- receipt.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::PreviewUnavailable
- );
- let fetch = transport
- .fetch(RadrootsReticulumPreviewFetchRequest::new("fetch-agent", 1).expect("fetch"))
- .expect("fetch receipt");
- assert_eq!(
- fetch
- .agent_endpoint
- .as_ref()
- .map(|endpoint| endpoint.as_str()),
- Some("reticulum-agent://localhost:19999")
- );
- assert_eq!(fetch.observed_event_count, 0);
- assert_eq!(
- fetch.implementation,
- RadrootsTransportImplementationState::PreviewUnavailable
- );
-}
-
-#[test]
-fn public_models_round_trip_through_serde() {
- let profile = RadrootsReticulumPreviewProfile::default()
- .with_behavior(RadrootsReticulumPreviewBehavior::DeferDeliveryPlans);
- let json = serde_json::to_string(&profile).expect("profile json");
- let decoded: RadrootsReticulumPreviewProfile =
- serde_json::from_str(&json).expect("profile decode");
-
- assert_eq!(decoded, profile);
-}
-
-#[test]
-fn preview_source_remains_inert_without_runtime_delivery_hooks() {
- let source = include_str!("../src/lib.rs").to_ascii_lowercase();
- for forbidden in [
- "socket",
- "rnsd",
- "python",
- "identity",
- "send_mesh",
- "fallback",
- "nostr",
- ] {
- assert!(
- !source.contains(forbidden),
- "Reticulum preview source contains forbidden runtime hook {forbidden}"
- );
- }
-}
-
-#[test]
-fn reticulum_preview_errors_and_defaults_are_stable() {
- assert_eq!(
- RadrootsReticulumPreviewBehavior::default(),
- RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts
- );
- let cases = [
- (
- RadrootsReticulumPreviewError::InvalidEndpoint,
- "invalid Reticulum preview endpoint",
- ),
- (
- RadrootsReticulumPreviewError::InvalidAgentEndpoint,
- "invalid Reticulum preview agent endpoint",
- ),
- (
- RadrootsReticulumPreviewError::InvalidProfileId,
- "invalid Reticulum preview profile id",
- ),
- (
- RadrootsReticulumPreviewError::InvalidFetchLimit,
- "Reticulum preview fetch limit must be greater than zero",
- ),
- (
- RadrootsReticulumPreviewError::NonReticulumTarget,
- "Reticulum preview transport received a non-Reticulum target",
- ),
- ];
-
- for (error, message) in cases {
- assert_eq!(error.to_string(), message);
- }
-}