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:
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 { .. })
+ ));
}