commit 8c8c37ddcb705f0b7b1c5408a2c205d42e4b924e
parent fce2a6d110daa98b7a3e5501f3005c71530ee82c
Author: triesap <tyson@radroots.org>
Date: Sun, 16 Aug 2026 20:33:19 +0000
transport-nostr: require explicit relay endpoints
- Intent: Require every relay URL, destination policy, and access mode to be supplied explicitly.
- Why: Implicit profiles could inject or infer network authority outside the caller-owned configuration boundary.
- Verification: Transport, SDK, and mobile tests, warnings-denied Clippy and Rustdoc, full workspace check, and exact API regeneration passed through extbuild.
- Risk: This is an intentional breaking constructor change; active Rust consumers are migrated while terminal generated consumers remain governed by the later RCLD checkpoint.
Diffstat:
4 files changed, 83 insertions(+), 29 deletions(-)
diff --git a/core/crates/tera_core/src/runtime/builder.rs b/core/crates/tera_core/src/runtime/builder.rs
@@ -20,8 +20,16 @@ impl RuntimeBuilder {
#[cfg(feature = "mobile-social")]
signer: None,
#[cfg(feature = "mobile-social")]
- relay_profile: radroots_sdk::transport::RelayProfile::public(Vec::<String>::new())
- .expect("bundled public relay profile is valid"),
+ relay_profile: radroots_sdk::transport::RelayProfile::explicit(
+ radroots_sdk::transport::RelayProfileKind::Public,
+ [radroots_sdk::transport::RelayEndpoint::new(
+ "wss://radroots.org",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
+ radroots_sdk::transport::RelayAccess::ReadWrite,
+ )
+ .expect("bundled public relay endpoint is valid")],
+ )
+ .expect("bundled public relay profile is valid"),
#[cfg(feature = "mobile-social")]
blossom_config: None,
}
diff --git a/core/crates/tera_core/src/runtime/product_surface/outbox.rs b/core/crates/tera_core/src/runtime/product_surface/outbox.rs
@@ -3420,14 +3420,18 @@ mod tests {
let profile = radroots_sdk::transport::RelayProfile::explicit(
radroots_sdk::transport::RelayProfileKind::Public,
[
- (
+ radroots_sdk::transport::RelayEndpoint::new(
"wss://read.example",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
radroots_sdk::transport::RelayAccess::ReadOnly,
- ),
- (
+ )
+ .unwrap(),
+ radroots_sdk::transport::RelayEndpoint::new(
"wss://write.example",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
radroots_sdk::transport::RelayAccess::ReadWrite,
- ),
+ )
+ .unwrap(),
],
)
.unwrap();
@@ -3475,10 +3479,12 @@ mod tests {
async fn profile_metadata_uses_the_durable_outbox_with_stable_operation_identity() {
let profile = radroots_sdk::transport::RelayProfile::explicit(
radroots_sdk::transport::RelayProfileKind::Public,
- [(
+ [radroots_sdk::transport::RelayEndpoint::new(
"wss://write.example",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
radroots_sdk::transport::RelayAccess::ReadWrite,
- )],
+ )
+ .unwrap()],
)
.unwrap();
let runtime = profiled_runtime(profile);
@@ -3531,10 +3537,12 @@ mod tests {
async fn add_queue_intent_fails_closed_without_a_writable_relay() {
let profile = radroots_sdk::transport::RelayProfile::explicit(
radroots_sdk::transport::RelayProfileKind::Public,
- [(
+ [radroots_sdk::transport::RelayEndpoint::new(
"wss://read.example",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
radroots_sdk::transport::RelayAccess::ReadOnly,
- )],
+ )
+ .unwrap()],
)
.unwrap();
let runtime = profiled_runtime(profile);
@@ -3733,10 +3741,12 @@ mod tests {
async fn kind_one_revision_never_creates_retraction_before_replacement_acceptance() {
let profile = radroots_sdk::transport::RelayProfile::explicit(
radroots_sdk::transport::RelayProfileKind::Public,
- [(
+ [radroots_sdk::transport::RelayEndpoint::new(
"wss://offline.example",
+ radroots_sdk::transport::RelayUrlPolicy::Public,
radroots_sdk::transport::RelayAccess::ReadWrite,
- )],
+ )
+ .unwrap()],
)
.unwrap();
let signer = radroots_nostr::signing::LocalSigner::new(
diff --git a/core/crates/tera_core/src/runtime/product_surface/settings.rs b/core/crates/tera_core/src/runtime/product_surface/settings.rs
@@ -15,7 +15,7 @@ use sha2::{Digest, Sha256};
use super::super::RadrootsRuntime;
-pub use radroots_sdk::transport::DEFAULT_PUBLIC_RELAY;
+pub const DEFAULT_PUBLIC_RELAY: &str = "wss://radroots.org";
pub const MOBILE_SETTINGS_SCHEMA_VERSION: u16 = 1;
pub const DEFAULT_PUBLIC_BLOSSOM_ORIGIN: &str = "https://blossom.radroots.org";
@@ -437,9 +437,17 @@ impl RelayPreferences {
radroots_sdk::transport::RelayProfileKind::Device
}
};
- radroots_sdk::transport::RelayProfile::explicit(
- kind,
- self.endpoints.iter().map(|endpoint| {
+ let policy = match self.environment {
+ MobileNetworkEnvironment::Public => radroots_sdk::transport::RelayUrlPolicy::Public,
+ MobileNetworkEnvironment::Simulator => radroots_sdk::transport::RelayUrlPolicy::Local,
+ MobileNetworkEnvironment::PhysicalDevice => {
+ radroots_sdk::transport::RelayUrlPolicy::PrivateNetwork
+ }
+ };
+ let endpoints = self
+ .endpoints
+ .iter()
+ .map(|endpoint| {
let access = match endpoint.access {
RelayAccessPreference::ReadOnly => {
radroots_sdk::transport::RelayAccess::ReadOnly
@@ -448,10 +456,12 @@ impl RelayPreferences {
radroots_sdk::transport::RelayAccess::ReadWrite
}
};
- (endpoint.url.as_str(), access)
- }),
- )
- .map_err(|_| SettingsError::InvalidRelayEndpoint)
+ radroots_sdk::transport::RelayEndpoint::new(endpoint.url.as_str(), policy, access)
+ })
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|_| SettingsError::InvalidRelayEndpoint)?;
+ radroots_sdk::transport::RelayProfile::explicit(kind, endpoints)
+ .map_err(|_| SettingsError::InvalidRelayEndpoint)
}
}
diff --git a/core/crates/tera_core/src/runtime/sdk.rs b/core/crates/tera_core/src/runtime/sdk.rs
@@ -117,9 +117,10 @@ impl RadrootsRuntime {
&self,
writable_relays: Vec<String>,
) -> Result<(), RadrootsAppError> {
- self.configure_relay_profile(
- radroots_sdk::transport::RelayProfile::public(writable_relays)
- .map_err(|error| RadrootsAppError::runtime(error.to_string()))?,
+ self.configure_relay_endpoints(
+ radroots_sdk::transport::RelayProfileKind::Public,
+ radroots_sdk::transport::RelayUrlPolicy::Public,
+ writable_relays,
)
}
@@ -129,9 +130,10 @@ impl RadrootsRuntime {
&self,
loopback_relays: Vec<String>,
) -> Result<(), RadrootsAppError> {
- self.configure_relay_profile(
- radroots_sdk::transport::RelayProfile::simulator(loopback_relays)
- .map_err(|error| RadrootsAppError::runtime(error.to_string()))?,
+ self.configure_relay_endpoints(
+ radroots_sdk::transport::RelayProfileKind::Simulator,
+ radroots_sdk::transport::RelayUrlPolicy::Local,
+ loopback_relays,
)
}
@@ -141,13 +143,37 @@ impl RadrootsRuntime {
&self,
writable_relays: Vec<String>,
) -> Result<(), RadrootsAppError> {
- self.configure_relay_profile(
- radroots_sdk::transport::RelayProfile::device(writable_relays)
- .map_err(|error| RadrootsAppError::runtime(error.to_string()))?,
+ self.configure_relay_endpoints(
+ radroots_sdk::transport::RelayProfileKind::Device,
+ radroots_sdk::transport::RelayUrlPolicy::PrivateNetwork,
+ writable_relays,
)
}
#[cfg(feature = "mobile-social")]
+ fn configure_relay_endpoints(
+ &self,
+ kind: radroots_sdk::transport::RelayProfileKind,
+ policy: radroots_sdk::transport::RelayUrlPolicy,
+ relays: Vec<String>,
+ ) -> Result<(), RadrootsAppError> {
+ let endpoints = relays
+ .into_iter()
+ .map(|relay| {
+ radroots_sdk::transport::RelayEndpoint::new(
+ relay,
+ policy,
+ radroots_sdk::transport::RelayAccess::ReadWrite,
+ )
+ })
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|error| RadrootsAppError::runtime(error.to_string()))?;
+ let profile = radroots_sdk::transport::RelayProfile::explicit(kind, endpoints)
+ .map_err(|error| RadrootsAppError::runtime(error.to_string()))?;
+ self.configure_relay_profile(profile)
+ }
+
+ #[cfg(feature = "mobile-social")]
fn configure_relay_profile(
&self,
profile: radroots_sdk::transport::RelayProfile,