commit da90092211a6f08618c05dbd2138300f1cd0bb8b
parent 77e41918cd230faea81fa5dda7fa33080f4fde9f
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 21:25:38 +0000
mobile: apply persisted network settings to runtime
Diffstat:
2 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/core/crates/tera_core/src/runtime/product_surface/settings.rs b/core/crates/tera_core/src/runtime/product_surface/settings.rs
@@ -803,6 +803,13 @@ impl MobileSettings {
self.local_storage = local_storage;
self
}
+
+ fn validate(&self) -> Result<(), SettingsError> {
+ if self.relays.environment() != self.blossom.environment() {
+ return Err(SettingsError::NetworkEnvironmentMismatch);
+ }
+ Ok(())
+ }
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -816,6 +823,7 @@ impl ReplaceMobileSettings {
if expected_revision == 0 || settings.revision != expected_revision {
return Err(SettingsError::RevisionConflict);
}
+ settings.validate()?;
Ok(Self {
expected_revision,
settings,
@@ -845,6 +853,8 @@ pub enum SettingsError {
InvalidBlossomEndpoint,
#[error("Blossom endpoint count is invalid")]
InvalidBlossomEndpointCount,
+ #[error("relay and Blossom network environments do not match")]
+ NetworkEnvironmentMismatch,
#[error("media cache byte quota is invalid")]
InvalidMediaCacheBytes,
#[error("media cache artifact quota is invalid")]
@@ -872,6 +882,7 @@ impl SettingsError {
Self::DuplicateRelayEndpoint => "duplicate_relay_endpoint",
Self::InvalidBlossomEndpoint => "invalid_blossom_endpoint",
Self::InvalidBlossomEndpointCount => "invalid_blossom_endpoint_count",
+ Self::NetworkEnvironmentMismatch => "network_environment_mismatch",
Self::InvalidMediaCacheBytes => "invalid_media_cache_bytes",
Self::InvalidMediaCacheArtifacts => "invalid_media_cache_artifacts",
Self::RevisionConflict => "settings_revision_conflict",
@@ -1129,14 +1140,16 @@ impl TryFrom<StoredSettingsV1> for MobileSettings {
if value.schema_version != MOBILE_SETTINGS_SCHEMA_VERSION || value.revision == 0 {
return Err(SettingsError::CorruptDocument);
}
- Ok(Self {
+ let settings = Self {
revision: value.revision,
identity: value.identity.try_into()?,
relays: value.relays.try_into()?,
blossom: value.blossom.try_into()?,
media_network: value.media_network,
local_storage: value.local_storage.try_into()?,
- })
+ };
+ settings.validate()?;
+ Ok(settings)
}
}
@@ -1147,7 +1160,7 @@ impl TryFrom<StoredSettingsV0> for MobileSettings {
if value.schema_version != 0 || value.revision == 0 {
return Err(SettingsError::CorruptDocument);
}
- Ok(Self {
+ let settings = Self {
revision: value.revision,
identity: value.identity.try_into()?,
relays: value.relays.try_into()?,
@@ -1161,7 +1174,9 @@ impl TryFrom<StoredSettingsV0> for MobileSettings {
value.media_cache_bytes,
value.media_cache_artifacts,
)?,
- })
+ };
+ settings.validate()?;
+ Ok(settings)
}
}
@@ -1503,6 +1518,15 @@ mod tests {
assert_eq!(conflict, SettingsError::RevisionConflict);
}
+ #[test]
+ fn settings_reject_mixed_network_environments() {
+ let settings = MobileSettings::default().with_blossom(BlossomPreferences::simulator_default());
+ assert_eq!(
+ ReplaceMobileSettings::new(settings.revision(), settings).unwrap_err(),
+ SettingsError::NetworkEnvironmentMismatch
+ );
+ }
+
#[tokio::test]
async fn atomic_identity_commands_persist_public_state_but_keep_unlock_process_local() {
let runtime = RadrootsRuntime::test_memory().unwrap();
diff --git a/core/crates/tera_core/src/runtime/sdk.rs b/core/crates/tera_core/src/runtime/sdk.rs
@@ -1,6 +1,7 @@
use radroots_sdk::capability::{Availability, Maturity};
use super::RadrootsRuntime;
+use super::product_surface::{BlossomPreferences, RelayPreferences};
use crate::RadrootsAppError;
#[derive(Clone, Debug)]
@@ -155,6 +156,19 @@ impl RadrootsRuntime {
.map_err(RadrootsAppError::from_sdk)
}
+ /// Installs the exact validated relay preferences persisted by the mobile product.
+ #[cfg(feature = "mobile-social")]
+ pub fn configure_relay_preferences(
+ &self,
+ preferences: &RelayPreferences,
+ ) -> Result<(), RadrootsAppError> {
+ self.configure_relay_profile(
+ preferences
+ .sdk_profile()
+ .map_err(|error| RadrootsAppError::runtime(error.code()))?,
+ )
+ }
+
/// Installs one canonical inert Blossom configuration without probing it.
#[cfg(feature = "mobile-social")]
pub fn configure_blossom(
@@ -187,6 +201,19 @@ impl RadrootsRuntime {
.map_err(RadrootsAppError::from_sdk)
}
+ /// Installs the exact validated Blossom preferences persisted by the mobile product.
+ #[cfg(feature = "mobile-social")]
+ pub fn configure_blossom_preferences(
+ &self,
+ preferences: &BlossomPreferences,
+ ) -> Result<(), RadrootsAppError> {
+ self.configure_blossom_profile(
+ preferences
+ .sdk_profile()
+ .map_err(|error| RadrootsAppError::runtime(error.code()))?,
+ )
+ }
+
/// Returns the configured adapter slot for Rust-owned media binding.
#[cfg(feature = "mobile-social")]
pub fn sdk_blossom_slot(