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 fc52c6acaa08193d30c715500cc20aa3840d106e
parent d4751fff1abb47746701012928957ee8c9af4cf0
Author: triesap <tyson@radroots.org>
Date:   Fri,  7 Aug 2026 17:33:35 +0000

transport: add evidence-based relay profiles

- validate public, simulator, and device relay authority
- bind fetch cursors and retries to exact relay evidence
- expose directional status through SDK and native mobile bindings
- prove loopback I/O, offline outbox behavior, and release gates

Diffstat:
Mcore/crates/tera_ffi/src/remote.rs | 21+++++++++++++++++++++
Mcore/crates/tera_ffi/src/runtime.rs | 35++++++++++++++++++++++++++++++++++-
Mcore/crates/tera_ffi/tests/runtime_delegation.rs | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/core/crates/tera_ffi/src/remote.rs b/core/crates/tera_ffi/src/remote.rs @@ -82,6 +82,27 @@ pub struct SdkStorageStatusRecord { } #[uniffi::remote(Record)] +pub struct SdkRelayStatusRecord { + pub relay_url: String, + pub access: String, + pub read_state: String, + pub write_state: String, + pub read_last_attempt_unix_ms: Option<u64>, + pub write_last_attempt_unix_ms: Option<u64>, + pub read_next_attempt_unix_ms: Option<u64>, + pub write_next_attempt_unix_ms: Option<u64>, +} + +#[uniffi::remote(Record)] +pub struct SdkRelayStatusReportRecord { + pub profile: String, + pub state: String, + pub read_availability: String, + pub write_availability: String, + pub relays: Vec<SdkRelayStatusRecord>, +} + +#[uniffi::remote(Record)] pub struct SdkShutdownRecord { pub state: String, pub already_closed: bool, diff --git a/core/crates/tera_ffi/src/runtime.rs b/core/crates/tera_ffi/src/runtime.rs @@ -1,7 +1,9 @@ use radroots_mobile_core::runtime::{ info::RuntimeInfo, product_surface::{AddCommandType, CardAddParity, LocalNetwork, TodayCardType}, - sdk::{SdkCapabilityRecord, SdkShutdownRecord, SdkStorageStatusRecord}, + sdk::{ + SdkCapabilityRecord, SdkRelayStatusReportRecord, SdkShutdownRecord, SdkStorageStatusRecord, + }, }; use crate::RadrootsAppError; @@ -89,6 +91,37 @@ impl RadrootsRuntime { self.inner.sdk_storage_status().await.map_err(Into::into) } + pub fn sdk_relay_status(&self) -> Result<Option<SdkRelayStatusReportRecord>, RadrootsAppError> { + self.inner.sdk_relay_status().map_err(Into::into) + } + + pub fn configure_public_relays( + &self, + writable_relays: Vec<String>, + ) -> Result<(), RadrootsAppError> { + self.inner + .configure_public_relays(writable_relays) + .map_err(Into::into) + } + + pub fn configure_simulator_relays( + &self, + loopback_relays: Vec<String>, + ) -> Result<(), RadrootsAppError> { + self.inner + .configure_simulator_relays(loopback_relays) + .map_err(Into::into) + } + + pub fn configure_device_relays( + &self, + writable_relays: Vec<String>, + ) -> Result<(), RadrootsAppError> { + self.inner + .configure_device_relays(writable_relays) + .map_err(Into::into) + } + pub fn phase1_card_types(&self) -> Vec<TodayCardType> { self.inner.phase1_card_types() } diff --git a/core/crates/tera_ffi/tests/runtime_delegation.rs b/core/crates/tera_ffi/tests/runtime_delegation.rs @@ -25,6 +25,65 @@ async fn native_boundary_delegates_the_complete_core_surface() { "sqlite" ); + let public = runtime + .sdk_relay_status() + .expect("relay status") + .expect("default public profile"); + assert_eq!(public.profile, "public"); + assert_eq!(public.state, "configured"); + assert_eq!(public.read_availability, "unavailable"); + assert_eq!(public.write_availability, "unavailable"); + assert_eq!(public.relays.len(), 1); + assert_eq!(public.relays[0].access, "read_only"); + assert_eq!(public.relays[0].read_state, "unobserved"); + assert_eq!(public.relays[0].write_state, "unsupported"); + + runtime + .configure_public_relays(vec!["wss://write.example".to_owned()]) + .expect("public relays"); + let public = runtime + .sdk_relay_status() + .expect("relay status") + .expect("public profile"); + assert_eq!(public.relays.len(), 2); + assert_eq!(public.relays[1].access, "read_write"); + assert!( + runtime + .configure_public_relays(vec!["ws://127.0.0.1:7447".to_owned()]) + .is_err() + ); + + runtime + .configure_simulator_relays(vec!["ws://127.0.0.1:7447".to_owned()]) + .expect("simulator relays"); + let simulator = runtime + .sdk_relay_status() + .expect("relay status") + .expect("simulator profile"); + assert_eq!(simulator.profile, "simulator_local"); + assert_eq!(simulator.relays.len(), 1); + assert_eq!(simulator.relays[0].access, "read_write"); + assert!( + runtime + .configure_simulator_relays(vec!["wss://relay.example".to_owned()]) + .is_err() + ); + + runtime + .configure_device_relays(vec!["wss://10.0.0.5:7447".to_owned()]) + .expect("device relays"); + let device = runtime + .sdk_relay_status() + .expect("relay status") + .expect("device profile"); + assert_eq!(device.profile, "device_development"); + assert_eq!(device.relays.len(), 2); + assert!( + runtime + .configure_device_relays(vec!["wss://127.0.0.1:7447".to_owned()]) + .is_err() + ); + assert_eq!( runtime.phase1_card_types(), vec![ @@ -83,4 +142,12 @@ async fn native_boundary_delegates_the_complete_core_surface() { runtime.sdk_storage_status().await, Err(RadrootsAppError::Sdk { .. }) )); + assert!(matches!( + runtime.sdk_relay_status(), + Err(RadrootsAppError::Sdk { .. }) + )); + assert!(matches!( + runtime.configure_public_relays(Vec::new()), + Err(RadrootsAppError::Sdk { .. }) + )); }