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 cef43c597b8dc892d6385b4665266914e96e63e5
parent 38dfa7fe45dc666f8ebc0b35a85cdbfa7b12033b
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_core/src/runtime/builder.rs | 10++++++++++
Mcore/crates/tera_core/src/runtime/sdk.rs | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 125 insertions(+), 0 deletions(-)

diff --git a/core/crates/tera_core/src/runtime/builder.rs b/core/crates/tera_core/src/runtime/builder.rs @@ -181,6 +181,16 @@ mod tests { .host_kind, "simulator" ); + let evidence = runtime + .sdk_blossom_evidence() + .expect("Blossom evidence") + .expect("configured evidence"); + assert_eq!(evidence.schema_version, 1); + assert_eq!(evidence.state, "configured_unobserved"); + assert_eq!(evidence.last_successful_state, "configured_unobserved"); + assert_eq!(evidence.transport_security, "development_cleartext"); + assert!(evidence.observed_at_unix_ms.is_none()); + assert!(evidence.error_code.is_none()); assert!( runtime .configure_blossom( diff --git a/core/crates/tera_core/src/runtime/sdk.rs b/core/crates/tera_core/src/runtime/sdk.rs @@ -51,6 +51,23 @@ pub struct SdkBlossomConfigurationRecord { } #[derive(Clone, Debug, Eq, PartialEq)] +pub struct SdkBlossomEvidenceRecord { + 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, +} + +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SdkShutdownRecord { pub state: String, pub already_closed: bool, @@ -199,6 +216,34 @@ impl RadrootsRuntime { ) } + /// Returns the latest passive Blossom evidence without network I/O. + #[cfg(feature = "mobile-social")] + pub fn sdk_blossom_evidence( + &self, + ) -> Result<Option<SdkBlossomEvidenceRecord>, RadrootsAppError> { + let evidence = self + .client + .blossom() + .map_err(RadrootsAppError::from_sdk)? + .and_then(radroots_sdk::transport::BlossomSlot::evidence); + Ok(evidence.map(sdk_blossom_evidence_record)) + } + + /// Explicitly probes the primary Blossom origin without mutation or authorization. + #[cfg(feature = "mobile-social")] + pub async fn probe_blossom(&self) -> Result<SdkBlossomEvidenceRecord, RadrootsAppError> { + let blossom = self + .client + .blossom() + .map_err(RadrootsAppError::from_sdk)? + .ok_or_else(|| RadrootsAppError::runtime("blossom_endpoint_not_configured"))?; + blossom + .probe(radroots_sdk::transport::BlossomCancellation::default()) + .await + .map(sdk_blossom_evidence_record) + .map_err(|error| RadrootsAppError::runtime(error.code())) + } + /// Returns passive relay evidence without DNS, socket, or probe work. #[cfg(feature = "mobile-social")] pub fn sdk_relay_status(&self) -> Result<Option<SdkRelayStatusReportRecord>, RadrootsAppError> { @@ -236,6 +281,76 @@ impl RadrootsRuntime { } #[cfg(feature = "mobile-social")] +fn sdk_blossom_evidence_record( + value: radroots_sdk::transport::BlossomEndpointEvidence, +) -> SdkBlossomEvidenceRecord { + SdkBlossomEvidenceRecord { + schema_version: value.schema_version(), + origin: value.origin().to_owned(), + config_fingerprint: value.config_fingerprint().to_hex(), + state: blossom_evidence_label(value.state()).to_owned(), + last_successful_state: blossom_evidence_label(value.last_successful_state()).to_owned(), + transport_security: blossom_transport_security_label(value.transport_security()).to_owned(), + observed_at_unix_ms: value.observed_at_unix_ms(), + http_status: value.http_status(), + error_code: value.error_code().map(str::to_owned), + error_phase: value + .error_phase() + .map(blossom_phase_label) + .map(str::to_owned), + retryable: value.retryable(), + possible_orphan: value.possible_orphan(), + attempts: value.attempts(), + } +} + +#[cfg(feature = "mobile-social")] +const fn blossom_evidence_label( + value: radroots_sdk::transport::BlossomEvidenceState, +) -> &'static str { + match value { + radroots_sdk::transport::BlossomEvidenceState::ConfiguredUnobserved => { + "configured_unobserved" + } + radroots_sdk::transport::BlossomEvidenceState::DnsPolicyValidated => "dns_policy_validated", + radroots_sdk::transport::BlossomEvidenceState::TlsHttpObserved => "tls_http_observed", + radroots_sdk::transport::BlossomEvidenceState::UploadVerified => "upload_verified", + radroots_sdk::transport::BlossomEvidenceState::RetrievalVerified => "retrieval_verified", + radroots_sdk::transport::BlossomEvidenceState::RetryableFailure => "retryable_failure", + radroots_sdk::transport::BlossomEvidenceState::TerminalFailure => "terminal_failure", + _ => "unknown", + } +} + +#[cfg(feature = "mobile-social")] +const fn blossom_transport_security_label( + value: radroots_sdk::transport::BlossomTransportSecurity, +) -> &'static str { + match value { + radroots_sdk::transport::BlossomTransportSecurity::PublicWebPki => "public_webpki", + radroots_sdk::transport::BlossomTransportSecurity::DevelopmentTls => "development_tls", + radroots_sdk::transport::BlossomTransportSecurity::DevelopmentCleartext => { + "development_cleartext" + } + _ => "unknown", + } +} + +#[cfg(feature = "mobile-social")] +const fn blossom_phase_label(value: radroots_sdk::transport::BlossomPhase) -> &'static str { + match value { + radroots_sdk::transport::BlossomPhase::Configuration => "configuration", + radroots_sdk::transport::BlossomPhase::Probe => "probe", + radroots_sdk::transport::BlossomPhase::Authorization => "authorization", + radroots_sdk::transport::BlossomPhase::Upload => "upload", + radroots_sdk::transport::BlossomPhase::Descriptor => "descriptor", + radroots_sdk::transport::BlossomPhase::Retrieval => "retrieval", + radroots_sdk::transport::BlossomPhase::Verification => "verification", + _ => "unknown", + } +} + +#[cfg(feature = "mobile-social")] const fn blossom_host_kind_label(value: radroots_sdk::transport::BlossomHostKind) -> &'static str { match value { radroots_sdk::transport::BlossomHostKind::Native => "native",