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 39a9878b7d4ed939cf2c81214d007acff27ce7ee
parent 0f74c7ebd91668425b5a56cb737d290640758cbe
Author: triesap <tyson@radroots.org>
Date:   Sat,  8 Aug 2026 21:35:30 +0000

blossom: expose passive endpoint evidence

Add a non-mutating bounded endpoint probe, fingerprint-guarded operation evidence, and focused mobile/UniFFI records for configuration, probe, upload, retrieval, and failure states.

Diffstat:
Mcore/crates/tera_ffi/src/dto.rs | 43+++++++++++++++++++++++++++++++++++++++++--
Mcore/crates/tera_ffi/src/runtime.rs | 35++++++++++++++++++++++++++++-------
Mcore/crates/tera_ffi/tests/local_mvp_real_io.rs | 19+++++++++++++++++++
Mcore/crates/tera_ffi/tests/runtime_delegation.rs | 11+++++++++++
4 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/core/crates/tera_ffi/src/dto.rs b/core/crates/tera_ffi/src/dto.rs @@ -28,8 +28,9 @@ use radroots_mobile_core::runtime::{ TodayRefreshReceipt, TodayRelaySyncState, TodaySyncReceipt, }, sdk::{ - SdkBlossomConfigurationRecord, SdkCapabilityRecord, SdkRelayStatusRecord, - SdkRelayStatusReportRecord, SdkShutdownRecord, SdkStorageStatusRecord, + SdkBlossomConfigurationRecord, SdkBlossomEvidenceRecord, SdkCapabilityRecord, + SdkRelayStatusRecord, SdkRelayStatusReportRecord, SdkShutdownRecord, + SdkStorageStatusRecord, }, }; @@ -1862,6 +1863,23 @@ pub struct FfiBlossomConfigurationRecord { pub config_fingerprint: String, } +#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] +pub struct FfiBlossomEvidenceRecord { + pub schema_version: u16, + pub origin: String, + pub config_fingerprint: String, + pub state: String, + pub last_successful_state: String, + pub transport_security: String, + pub observed_at_unix_ms: Option<u64>, + pub http_status: Option<u16>, + pub error_code: Option<String>, + pub error_phase: Option<String>, + pub retryable: bool, + pub possible_orphan: bool, + pub attempts: u8, +} + #[cfg_attr(coverage_nightly, coverage(off))] impl From<SdkBlossomConfigurationRecord> for FfiBlossomConfigurationRecord { fn from(value: SdkBlossomConfigurationRecord) -> Self { @@ -1877,6 +1895,27 @@ impl From<SdkBlossomConfigurationRecord> for FfiBlossomConfigurationRecord { } #[cfg_attr(coverage_nightly, coverage(off))] +impl From<SdkBlossomEvidenceRecord> for FfiBlossomEvidenceRecord { + fn from(value: SdkBlossomEvidenceRecord) -> Self { + Self { + schema_version: value.schema_version, + origin: value.origin, + config_fingerprint: value.config_fingerprint, + state: value.state, + last_successful_state: value.last_successful_state, + transport_security: value.transport_security, + observed_at_unix_ms: value.observed_at_unix_ms, + http_status: value.http_status, + error_code: value.error_code, + error_phase: value.error_phase, + retryable: value.retryable, + possible_orphan: value.possible_orphan, + attempts: value.attempts, + } + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] impl From<SdkRelayStatusReportRecord> for FfiRelayStatusReportRecord { fn from(value: SdkRelayStatusReportRecord) -> Self { Self { diff --git a/core/crates/tera_ffi/src/runtime.rs b/core/crates/tera_ffi/src/runtime.rs @@ -8,13 +8,14 @@ use crate::signer::HostSignerAdapter; use crate::subscription::SubscriptionHub; use crate::{ FfiAddDraftInput, FfiAddSchemaRecord, FfiBlossomConfigurationRecord, - FfiBlossomEndpointAuthority, FfiBlossomHostKind, FfiBlossomUploadInput, FfiCapabilityRecord, - FfiCardAddParityRecord, FfiDraftStatusRecord, FfiIdentityStatusRecord, FfiLocalNetworkRecord, - FfiMeRecord, FfiQueuePolicyRecord, FfiRelayStatusReportRecord, FfiRetractionDraftInput, - FfiRuntimeChangeKind, FfiRuntimeInfoRecord, FfiSearchResultRecord, FfiShutdownRecord, - FfiStorageStatusRecord, FfiSubscriptionHandle, FfiTodayPageRecord, FfiTodayProjectionUpdate, - FfiTodayRefreshRecord, FfiTodaySyncRecord, RadrootsAppError, RadrootsHostSigner, - RadrootsRuntimeObserver, add_schemas, decode_id, + FfiBlossomEndpointAuthority, FfiBlossomEvidenceRecord, FfiBlossomHostKind, + FfiBlossomUploadInput, FfiCapabilityRecord, FfiCardAddParityRecord, FfiDraftStatusRecord, + FfiIdentityStatusRecord, FfiLocalNetworkRecord, FfiMeRecord, FfiQueuePolicyRecord, + FfiRelayStatusReportRecord, FfiRetractionDraftInput, FfiRuntimeChangeKind, + FfiRuntimeInfoRecord, FfiSearchResultRecord, FfiShutdownRecord, FfiStorageStatusRecord, + FfiSubscriptionHandle, FfiTodayPageRecord, FfiTodayProjectionUpdate, FfiTodayRefreshRecord, + FfiTodaySyncRecord, RadrootsAppError, RadrootsHostSigner, RadrootsRuntimeObserver, add_schemas, + decode_id, }; #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)] @@ -174,6 +175,26 @@ impl RadrootsRuntime { .map_err(Into::into) } + pub fn sdk_blossom_evidence( + &self, + ) -> Result<Option<FfiBlossomEvidenceRecord>, RadrootsAppError> { + self.inner + .sdk_blossom_evidence() + .map(|value| value.map(Into::into)) + .map_err(Into::into) + } + + pub async fn probe_blossom(&self) -> Result<FfiBlossomEvidenceRecord, RadrootsAppError> { + let evidence = self + .inner + .probe_blossom() + .await + .map(Into::into) + .map_err(RadrootsAppError::from)?; + self.subscriptions.notify(FfiRuntimeChangeKind::Media, None); + Ok(evidence) + } + pub fn subscribe_changes( &self, observer: Box<dyn RadrootsRuntimeObserver>, diff --git a/core/crates/tera_ffi/tests/local_mvp_real_io.rs b/core/crates/tera_ffi/tests/local_mvp_real_io.rs @@ -233,6 +233,13 @@ async fn public_runtime_completes_the_local_mvp_against_real_protocol_services() .await .expect("upload and re-fetch exact media"); assert_eq!(uploaded.media[0].stage, FfiMediaStage::Verified); + let evidence = publisher + .sdk_blossom_evidence() + .expect("Blossom evidence") + .expect("configured evidence"); + assert_eq!(evidence.state, "retrieval_verified"); + assert_eq!(evidence.last_successful_state, "retrieval_verified"); + assert!(evidence.error_code.is_none()); uploaded } else { saved @@ -812,6 +819,18 @@ async fn prove_corrupted_media_fails( .expect("durable corrupt-media status"); assert_eq!(failed.media[0].stage, FfiMediaStage::Failed); assert!(failed.media[0].possible_orphan); + let evidence = runtime + .sdk_blossom_evidence() + .expect("Blossom evidence") + .expect("configured evidence"); + assert_eq!(evidence.state, "terminal_failure"); + assert_eq!(evidence.last_successful_state, "upload_verified"); + assert_eq!( + evidence.error_code.as_deref(), + Some("blossom_retrieved_bytes_mismatch") + ); + assert_eq!(evidence.error_phase.as_deref(), Some("verification")); + assert!(evidence.possible_orphan); corrupt.finish().await; } diff --git a/core/crates/tera_ffi/tests/runtime_delegation.rs b/core/crates/tera_ffi/tests/runtime_delegation.rs @@ -119,6 +119,17 @@ async fn native_boundary_delegates_the_complete_core_surface() { assert_eq!(blossom.primary_origin, "https://media.example"); assert_eq!(blossom.fallback_origins, ["https://fallback.example"]); assert_eq!(blossom.config_fingerprint.len(), 64); + let evidence = runtime + .sdk_blossom_evidence() + .expect("Blossom evidence") + .expect("configured evidence"); + assert_eq!(evidence.schema_version, 1); + assert_eq!(evidence.origin, "https://media.example"); + assert_eq!(evidence.config_fingerprint, blossom.config_fingerprint); + assert_eq!(evidence.state, "configured_unobserved"); + assert_eq!(evidence.transport_security, "public_webpki"); + assert!(evidence.observed_at_unix_ms.is_none()); + assert!(evidence.error_code.is_none()); runtime .configure_blossom( FfiBlossomHostKind::Simulator,