field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

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:
Mcore/crates/tera_core/src/runtime/product_surface/settings.rs | 32++++++++++++++++++++++++++++----
Mcore/crates/tera_core/src/runtime/sdk.rs | 27+++++++++++++++++++++++++++
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(