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:
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,