commit aa144b2f2f2afe3e9adc1afb5a41306c698b932c
parent 1665548581a8ee9582ff943ffa625bcc13d59960
Author: triesap <tyson@radroots.org>
Date: Sat, 8 Aug 2026 01:34:58 +0000
feat(mobile): sync Today from bounded relay data
- propagate exact selectors through canonical sync pulls
- fetch and typed-admit Today events before projection
- expose relay sync outcomes through the native boundary
- honor public, simulator, and device context policies
Diffstat:
3 files changed, 131 insertions(+), 30 deletions(-)
diff --git a/core/crates/tera_ffi/src/dto.rs b/core/crates/tera_ffi/src/dto.rs
@@ -19,12 +19,12 @@ use radroots_mobile_core::runtime::{
info::{AppInfo, RuntimeBuildInfo, RuntimeInfo},
product_surface::{
AddCommandType, CardLifecycleState, CreateAsk, CreateEvent, CreateFoodAvailability,
- CreatePhotoUpdate, CreateUpdate, LocalNetwork, MeSnapshot, MediaReference,
- MediaVerificationState, Phase1AddCommand, Phase1CancellationPolicy, Phase1DraftStatus,
- Phase1MediaPrerequisite, Phase1MediaStage, Phase1OutboxState, Phase1QueuePolicy,
- Phase1RelaySatisfaction, ProfileSummary, SearchResult, SearchResultType, SupportingProfile,
- ThreadEntry, TodayCard, TodayCardType, TodayPage, TodayProjectionUpdate,
- TodayRefreshReceipt,
+ CreatePhotoUpdate, CreateUpdate, LocalNetwork, LocalNetworkRelayPolicy, MeSnapshot,
+ MediaReference, MediaVerificationState, Phase1AddCommand, Phase1CancellationPolicy,
+ Phase1DraftStatus, Phase1MediaPrerequisite, Phase1MediaStage, Phase1OutboxState,
+ Phase1QueuePolicy, Phase1RelaySatisfaction, ProfileSummary, SearchResult, SearchResultType,
+ SupportingProfile, ThreadEntry, TodayCard, TodayCardType, TodayPage, TodayProjectionUpdate,
+ TodayRefreshReceipt, TodayRelaySyncState, TodaySyncReceipt,
},
sdk::{
SdkCapabilityRecord, SdkRelayStatusRecord, SdkRelayStatusReportRecord, SdkShutdownRecord,
@@ -210,14 +210,24 @@ impl TryFrom<FfiLocalNetworkRecord> for LocalNetwork {
type Error = RadrootsAppError;
fn try_from(value: FfiLocalNetworkRecord) -> Result<Self, Self::Error> {
- require_schema(value.schema_version)?;
- LocalNetwork::new(
- value.id,
- value.label,
- value.relay_urls,
- value.locality,
- value.followed_authors,
- value.generation,
+ value.try_into_with_relay_policy(LocalNetworkRelayPolicy::Public)
+ }
+}
+
+impl FfiLocalNetworkRecord {
+ pub(crate) fn try_into_with_relay_policy(
+ self,
+ relay_policy: LocalNetworkRelayPolicy,
+ ) -> Result<LocalNetwork, RadrootsAppError> {
+ require_schema(self.schema_version)?;
+ LocalNetwork::new_for_relay_policy(
+ self.id,
+ self.label,
+ self.relay_urls,
+ self.locality,
+ self.followed_authors,
+ self.generation,
+ relay_policy,
)
.map_err(|_| RadrootsAppError::invalid_argument("invalid_local_network"))
}
@@ -494,6 +504,43 @@ pub struct FfiTodayRefreshRecord {
pub changed: bool,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
+pub enum FfiTodayRelaySyncState {
+ Complete,
+ Partial,
+ Offline,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
+pub struct FfiTodaySyncRecord {
+ pub schema_version: u16,
+ pub relay_state: FfiTodayRelaySyncState,
+ pub pages_fetched: u16,
+ pub events_observed: u64,
+ pub events_admitted: u64,
+ pub events_rejected: u64,
+ pub projection: FfiTodayRefreshRecord,
+}
+
+#[cfg_attr(coverage_nightly, coverage(off))]
+impl From<TodaySyncReceipt> for FfiTodaySyncRecord {
+ fn from(value: TodaySyncReceipt) -> Self {
+ Self {
+ schema_version: MOBILE_FFI_SCHEMA_VERSION,
+ relay_state: match value.relay_state {
+ TodayRelaySyncState::Complete => FfiTodayRelaySyncState::Complete,
+ TodayRelaySyncState::Partial => FfiTodayRelaySyncState::Partial,
+ TodayRelaySyncState::Offline => FfiTodayRelaySyncState::Offline,
+ },
+ pages_fetched: value.pages_fetched,
+ events_observed: value.events_observed,
+ events_admitted: value.events_admitted,
+ events_rejected: value.events_rejected,
+ projection: value.projection.into(),
+ }
+ }
+}
+
#[cfg_attr(coverage_nightly, coverage(off))]
impl From<TodayRefreshReceipt> for FfiTodayRefreshRecord {
fn from(value: TodayRefreshReceipt) -> Self {
diff --git a/core/crates/tera_ffi/src/runtime.rs b/core/crates/tera_ffi/src/runtime.rs
@@ -1,5 +1,6 @@
use std::sync::Arc;
+use radroots_mobile_core::runtime::product_surface::LocalNetworkRelayPolicy;
use radroots_mobile_core::runtime::product_surface::TodayPageRequest;
use crate::dto::PreparedMedia;
@@ -11,7 +12,8 @@ use crate::{
FfiMeRecord, FfiQueuePolicyRecord, FfiRelayStatusReportRecord, FfiRuntimeChangeKind,
FfiRuntimeInfoRecord, FfiSearchResultRecord, FfiShutdownRecord, FfiStorageStatusRecord,
FfiSubscriptionHandle, FfiTodayPageRecord, FfiTodayProjectionUpdate, FfiTodayRefreshRecord,
- RadrootsAppError, RadrootsHostSigner, RadrootsRuntimeObserver, add_schemas, decode_id,
+ FfiTodaySyncRecord, RadrootsAppError, RadrootsHostSigner, RadrootsRuntimeObserver, add_schemas,
+ decode_id,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
@@ -253,7 +255,7 @@ impl RadrootsRuntime {
&self,
context: FfiLocalNetworkRecord,
) -> Result<FfiLocalNetworkRecord, RadrootsAppError> {
- LocalNetworkRecordConversion::round_trip(context)
+ self.local_network(context).map(Into::into)
}
pub async fn phase1_today_page(
@@ -268,7 +270,7 @@ impl RadrootsRuntime {
"invalid_today_page_request",
));
}
- let context = context.try_into()?;
+ let context = self.local_network(context)?;
let request = match cursor {
Some(cursor) => TodayPageRequest::after(limit, cursor),
None => TodayPageRequest::first(
@@ -290,7 +292,7 @@ impl RadrootsRuntime {
now_unix_s: u64,
update: FfiTodayProjectionUpdate,
) -> Result<FfiTodayRefreshRecord, RadrootsAppError> {
- let context = context.try_into()?;
+ let context = self.local_network(context)?;
let receipt = self
.inner
.phase1_refresh_today(&context, now_unix_s, update.into())
@@ -300,6 +302,22 @@ impl RadrootsRuntime {
Ok(receipt.into())
}
+ pub async fn phase1_sync_today(
+ &self,
+ context: FfiLocalNetworkRecord,
+ now_unix_s: u64,
+ update: FfiTodayProjectionUpdate,
+ ) -> Result<FfiTodaySyncRecord, RadrootsAppError> {
+ let context = self.local_network(context)?;
+ let receipt = self
+ .inner
+ .phase1_sync_today(&context, now_unix_s, update.into())
+ .await
+ .map_err(RadrootsAppError::from)?;
+ self.subscriptions.notify(FfiRuntimeChangeKind::Today, None);
+ Ok(receipt.into())
+ }
+
pub async fn phase1_search(
&self,
context: FfiLocalNetworkRecord,
@@ -307,7 +325,7 @@ impl RadrootsRuntime {
limit: u16,
as_of_unix_s: u64,
) -> Result<Vec<FfiSearchResultRecord>, RadrootsAppError> {
- let context = context.try_into()?;
+ let context = self.local_network(context)?;
self.inner
.phase1_search(&context, &query, limit, as_of_unix_s)
.await
@@ -320,7 +338,7 @@ impl RadrootsRuntime {
context: FfiLocalNetworkRecord,
as_of_unix_s: u64,
) -> Result<FfiMeRecord, RadrootsAppError> {
- let context = context.try_into()?;
+ let context = self.local_network(context)?;
let public_key = self
.inner
.authenticated_store_public_key_hex()
@@ -514,6 +532,39 @@ impl RadrootsRuntime {
}
}
+impl RadrootsRuntime {
+ fn local_network(
+ &self,
+ context: FfiLocalNetworkRecord,
+ ) -> Result<radroots_mobile_core::runtime::product_surface::LocalNetwork, RadrootsAppError>
+ {
+ let profile = self.inner.sdk_relay_status()?.ok_or_else(|| {
+ RadrootsAppError::failure(
+ "relay_profile_unavailable",
+ "relay",
+ true,
+ &["configure_relay"],
+ "The relay profile is unavailable.",
+ )
+ })?;
+ let relay_policy = match profile.profile.as_str() {
+ "public" => LocalNetworkRelayPolicy::Public,
+ "simulator_local" => LocalNetworkRelayPolicy::Simulator,
+ "device_development" => LocalNetworkRelayPolicy::Device,
+ _ => {
+ return Err(RadrootsAppError::failure(
+ "relay_profile_unsupported",
+ "relay",
+ false,
+ &["configure_relay"],
+ "The relay profile is unsupported.",
+ ));
+ }
+ };
+ context.try_into_with_relay_policy(relay_policy)
+ }
+}
+
async fn build_runtime(
application_support_directory: String,
public_key_hex: String,
@@ -544,13 +595,3 @@ async fn build_runtime(
})
.map_err(Into::into)
}
-
-struct LocalNetworkRecordConversion;
-
-impl LocalNetworkRecordConversion {
- fn round_trip(value: FfiLocalNetworkRecord) -> Result<FfiLocalNetworkRecord, RadrootsAppError> {
- let context: radroots_mobile_core::runtime::product_surface::LocalNetwork =
- value.try_into()?;
- Ok(context.into())
- }
-}
diff --git a/core/crates/tera_ffi/tests/runtime_delegation.rs b/core/crates/tera_ffi/tests/runtime_delegation.rs
@@ -69,6 +69,19 @@ async fn native_boundary_delegates_the_complete_core_surface() {
assert_eq!(simulator.relays[0].access, "read_write");
assert!(
runtime
+ .phase1_local_network(FfiLocalNetworkRecord {
+ schema_version: MOBILE_FFI_SCHEMA_VERSION,
+ id: "simulator".to_owned(),
+ label: "Simulator".to_owned(),
+ relay_urls: vec!["ws://127.0.0.1:7447".to_owned()],
+ locality: None,
+ followed_authors: vec![],
+ generation: 1,
+ })
+ .is_ok()
+ );
+ assert!(
+ runtime
.configure_simulator_relays(vec!["wss://relay.example".to_owned()])
.is_err()
);