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 a459ed6bb67e52777f1ecefbeedc410b7acdfc1b
parent 6373d4698488b161c2e87930e2ea0439be4e36b2
Author: triesap <tyson@radroots.org>
Date:   Thu,  6 Aug 2026 08:10:06 +0000

mobile: isolate native ffi ownership

- remove UniFFI annotations and dependencies from the ordinary Rust mobile core
- own remote DTO converters, errors, exports, logging, and delegation in mobileffi
- require full canonical lib and consumer revisions in runtime provenance
- qualify isolated features, generated bindings, lifecycle ordering, and coverage

Diffstat:
Mcore/crates/tera_core/Cargo.toml | 1-
Mcore/crates/tera_core/build.rs | 29+++++++++++++++++++++--------
Mcore/crates/tera_core/src/error.rs | 4++--
Mcore/crates/tera_core/src/lib.rs | 9++++-----
Acore/crates/tera_core/src/provenance.rs | 26++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/app_info.rs | 2+-
Mcore/crates/tera_core/src/runtime/info.rs | 12+++++++-----
Mcore/crates/tera_core/src/runtime/key_management.rs | 7+++----
Mcore/crates/tera_core/src/runtime/mod.rs | 3---
Mcore/crates/tera_core/src/runtime/nostr.rs | 13++++++-------
Mcore/crates/tera_core/src/runtime/product_surface.rs | 77++++++++++++++++++++++++++++++++++++++---------------------------------------
Mcore/crates/tera_core/src/runtime/sdk.rs | 7+++----
Acore/crates/tera_core/tests/package_boundary.rs | 35+++++++++++++++++++++++++++++++++++
Dcore/crates/tera_core/tests/uniffi_contract.rs | 33---------------------------------
14 files changed, 146 insertions(+), 112 deletions(-)

diff --git a/core/crates/tera_core/Cargo.toml b/core/crates/tera_core/Cargo.toml @@ -32,7 +32,6 @@ chrono = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } -uniffi = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/core/crates/tera_core/build.rs b/core/crates/tera_core/build.rs @@ -1,10 +1,14 @@ use std::{env, process::Command}; +#[path = "src/provenance.rs"] +mod provenance; + fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=RUSTC"); println!("cargo:rerun-if-env-changed=PROFILE"); - println!("cargo:rerun-if-env-changed=RADROOTS_SOURCE_SHA"); + println!("cargo:rerun-if-env-changed=RADROOTS_LIB_REVISION"); + println!("cargo:rerun-if-env-changed=RADROOTS_CONSUMER_REVISION"); println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH"); let rustc = env::var("RUSTC").expect("missing required env var RUSTC"); @@ -15,8 +19,17 @@ fn main() { println!("cargo:rustc-env=RUSTC_VERSION={}", version.trim()); } - if let Some(source_sha) = optional_source_sha() { - println!("cargo:rustc-env=GIT_HASH={source_sha}"); + let lib_revision = optional_full_revision("RADROOTS_LIB_REVISION"); + let consumer_revision = optional_full_revision("RADROOTS_CONSUMER_REVISION"); + assert!( + consumer_revision.is_none() || lib_revision.is_some(), + "RADROOTS_CONSUMER_REVISION requires RADROOTS_LIB_REVISION" + ); + if let Some(revision) = lib_revision { + println!("cargo:rustc-env=RADROOTS_LIB_REVISION={revision}"); + } + if let Some(revision) = consumer_revision { + println!("cargo:rustc-env=RADROOTS_CONSUMER_REVISION={revision}"); } let profile = env::var("PROFILE").expect("missing required env var PROFILE"); @@ -27,13 +40,13 @@ fn main() { } } -fn optional_source_sha() -> Option<String> { - let value = env::var("RADROOTS_SOURCE_SHA").ok()?; +fn optional_full_revision(name: &str) -> Option<String> { + let value = env::var(name).ok()?; assert!( - (7..=64).contains(&value.len()) && value.bytes().all(|byte| byte.is_ascii_hexdigit()), - "RADROOTS_SOURCE_SHA must contain 7 to 64 hexadecimal characters" + provenance::is_full_revision(&value), + "{name} must contain exactly 40 lowercase hexadecimal characters" ); - Some(value.to_ascii_lowercase()) + Some(value) } fn optional_source_date_epoch() -> Option<u64> { diff --git a/core/crates/tera_core/src/error.rs b/core/crates/tera_core/src/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; /// Versioned, secret-safe SDK failure exposed to mobile hosts. -#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SdkErrorRecord { pub schema_version: u16, pub code: String, @@ -13,7 +13,7 @@ pub struct SdkErrorRecord { pub message: String, } -#[derive(Debug, Error, uniffi::Error)] +#[derive(Debug, Error)] pub enum RadrootsAppError { #[error("initialization: {0}")] Initialization(String), diff --git a/core/crates/tera_core/src/lib.rs b/core/crates/tera_core/src/lib.rs @@ -1,12 +1,11 @@ -// UniFFI errors are serialized value contracts. Keeping the complete stable -// SDK report on the error is more important than optimizing the Rust enum's -// in-process size; mobile calls cross this boundary by value in all cases. +// Mobile errors retain the complete stable SDK report. Preserving that typed +// value is more important than optimizing the Rust enum's in-process size. #![allow(clippy::result_large_err)] #![cfg_attr(coverage_nightly, feature(coverage_attribute))] -uniffi::setup_scaffolding!("radroots_mobile_core"); - pub mod error; +#[cfg(test)] +mod provenance; pub mod runtime; pub use error::{RadrootsAppError, SdkErrorRecord}; diff --git a/core/crates/tera_core/src/provenance.rs b/core/crates/tera_core/src/provenance.rs @@ -0,0 +1,26 @@ +pub(crate) fn is_full_revision(value: &str) -> bool { + value.len() == 40 + && value + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) +} + +#[cfg(test)] +mod tests { + use super::is_full_revision; + + #[test] + fn only_full_lowercase_git_revisions_are_accepted() { + assert!(is_full_revision("0123456789abcdef0123456789abcdef01234567")); + assert!(!is_full_revision("0123456")); + assert!(!is_full_revision( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + )); + assert!(!is_full_revision( + "0123456789ABCDEF0123456789abcdef01234567" + )); + assert!(!is_full_revision( + "g123456789abcdef0123456789abcdef01234567" + )); + } +} diff --git a/core/crates/tera_core/src/runtime/app_info.rs b/core/crates/tera_core/src/runtime/app_info.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Default, serde::Serialize, uniffi::Record)] +#[derive(Debug, Clone, Default, serde::Serialize)] pub struct AppInfoPlatform { pub platform: Option<String>, pub bundle_id: Option<String>, diff --git a/core/crates/tera_core/src/runtime/info.rs b/core/crates/tera_core/src/runtime/info.rs @@ -2,17 +2,18 @@ use super::RadrootsRuntime; use chrono::Utc; use serde::Serialize; -#[derive(Debug, Clone, Serialize, Default, uniffi::Record)] +#[derive(Debug, Clone, Serialize, Default)] pub struct RuntimeBuildInfo { pub crate_name: String, pub crate_version: String, pub rustc: Option<String>, pub profile: Option<String>, - pub git_sha: Option<String>, + pub lib_revision: Option<String>, + pub consumer_revision: Option<String>, pub build_time_unix: Option<u64>, } -#[derive(Debug, Clone, Serialize, uniffi::Record)] +#[derive(Debug, Clone, Serialize)] pub struct AppInfo { pub build: RuntimeBuildInfo, pub started_unix_ms: i64, @@ -21,7 +22,7 @@ pub struct AppInfo { pub platform: Option<super::app_info::AppInfoPlatform>, } -#[derive(Debug, Clone, Serialize, uniffi::Record)] +#[derive(Debug, Clone, Serialize)] pub struct RuntimeInfo { pub app: AppInfo, pub sdk: RuntimeBuildInfo, @@ -59,7 +60,8 @@ pub fn app_build_info() -> RuntimeBuildInfo { crate_version: env!("CARGO_PKG_VERSION").to_owned(), rustc: option_env!("RUSTC_VERSION").map(str::to_owned), profile: option_env!("PROFILE").map(str::to_owned), - git_sha: option_env!("GIT_HASH").map(str::to_owned), + lib_revision: option_env!("RADROOTS_LIB_REVISION").map(str::to_owned), + consumer_revision: option_env!("RADROOTS_CONSUMER_REVISION").map(str::to_owned), build_time_unix: option_env!("BUILD_TIME_UNIX").and_then(|value| value.parse().ok()), } } diff --git a/core/crates/tera_core/src/runtime/key_management.rs b/core/crates/tera_core/src/runtime/key_management.rs @@ -3,7 +3,7 @@ use super::RadrootsRuntime; use crate::RadrootsAppError; -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrIdentityRecord { pub id: String, pub public_key_hex: String, @@ -12,7 +12,7 @@ pub struct NostrIdentityRecord { pub is_selected: bool, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrIdentitySnapshot { pub has_selected_signing_identity: bool, pub selected_identity_id: Option<String>, @@ -20,7 +20,7 @@ pub struct NostrIdentitySnapshot { pub identities: Vec<NostrIdentityRecord>, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrHostCustodyIdentity { pub id: String, pub public_key_hex: String, @@ -50,7 +50,6 @@ fn identity_record( } } -#[cfg_attr(not(coverage_nightly), uniffi::export)] impl RadrootsRuntime { pub fn nostr_identity_has_selected_signing_identity(&self) -> bool { self.signing_slot.identity().is_some() diff --git a/core/crates/tera_core/src/runtime/mod.rs b/core/crates/tera_core/src/runtime/mod.rs @@ -21,7 +21,6 @@ use self::{ }; use crate::RadrootsAppError; -#[derive(uniffi::Object)] pub struct RadrootsRuntime { pub(crate) client: Client, #[cfg(feature = "mobile-social")] @@ -35,9 +34,7 @@ pub struct RadrootsRuntime { pub(crate) platform_app: RwLock<Option<AppInfoPlatform>>, } -#[cfg_attr(not(coverage_nightly), uniffi::export)] impl RadrootsRuntime { - #[cfg_attr(not(coverage_nightly), uniffi::constructor)] pub fn new() -> Result<Self, RadrootsAppError> { #[cfg(feature = "mobile-social")] let signing_slot = radroots_sdk::signing::Slot::new(); diff --git a/core/crates/tera_core/src/runtime/nostr.rs b/core/crates/tera_core/src/runtime/nostr.rs @@ -3,14 +3,14 @@ use super::RadrootsRuntime; use crate::RadrootsAppError; -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NostrLight { Red, Yellow, Green, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrConnectionStatus { pub light: NostrLight, pub configured: bool, @@ -19,7 +19,7 @@ pub struct NostrConnectionStatus { pub last_error: Option<String>, } -#[derive(uniffi::Record, Debug, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct NostrProfile { pub name: Option<String>, pub display_name: Option<String>, @@ -33,7 +33,7 @@ pub struct NostrProfile { pub bot: Option<String>, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrProfileEventMetadata { pub id: String, pub author: String, @@ -41,12 +41,12 @@ pub struct NostrProfileEventMetadata { pub profile: NostrProfile, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrPost { pub content: String, } -#[derive(uniffi::Record, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NostrPostEventMetadata { pub id: String, pub author: String, @@ -85,7 +85,6 @@ fn map_post(event: radroots_sdk::client::PostEvent) -> NostrPostEventMetadata { } } -#[cfg_attr(not(coverage_nightly), uniffi::export)] impl RadrootsRuntime { pub fn nostr_set_default_relays(&self, relays: Vec<String>) -> Result<(), RadrootsAppError> { self.nostr_slot diff --git a/core/crates/tera_core/src/runtime/product_surface.rs b/core/crates/tera_core/src/runtime/product_surface.rs @@ -16,7 +16,7 @@ pub const WORKFLOW_ACTOR_COMPATIBILITY_NOTE: &str = "Phase 1 workflow actors are roles such as Farmer, Buyer, Seller, and Service are compatibility roles \ and are not sufficient authority."; -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum ContextType { Regional, @@ -31,7 +31,7 @@ pub enum ContextType { NetworkSteward, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum WorkflowActor { NetworkMember, @@ -47,7 +47,7 @@ pub enum WorkflowActor { NetworkSteward, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum VisibilityClass { LocalDraft, @@ -61,7 +61,7 @@ pub enum VisibilityClass { SecretNeverShared, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum AuthorityDomain { #[serde(rename = "Relay/group access")] RelayGroupAccess, @@ -83,7 +83,7 @@ pub enum AuthorityDomain { NetworkStewardship, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum AuthorityAction { Submit, @@ -98,7 +98,7 @@ pub enum AuthorityAction { NavigateRelatedObject, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum ObjectKind { Region, @@ -127,7 +127,7 @@ pub enum ObjectKind { Correction, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum ObjectPageFamily { Network, @@ -145,7 +145,7 @@ pub enum ObjectPageFamily { Exception, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum TodayCardType { Route, @@ -162,7 +162,7 @@ pub enum TodayCardType { SyncOutbox, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum AddActionType { Photo, @@ -185,7 +185,7 @@ pub enum AddActionType { Correction, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum AddFlowState { NotStarted, @@ -206,7 +206,7 @@ pub enum AddFlowState { Discarded, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum OutboxBehavior { LocalOnly, @@ -216,7 +216,7 @@ pub enum OutboxBehavior { ShareWhenAuthorized, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum PrototypePathKind { ProducerFoodToRoute, @@ -224,7 +224,7 @@ pub enum PrototypePathKind { RouteCoordinatorAssignment, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum RouteExecutionFlowKind { RoutePartnerAssignedStops, @@ -232,7 +232,7 @@ pub enum RouteExecutionFlowKind { ExceptionRecovery, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum RouteExecutionStepKind { AssignedRoute, @@ -243,7 +243,7 @@ pub enum RouteExecutionStepKind { RecoveryAction, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum ProofProvenanceArtifactKind { ProofCompleteness, @@ -252,7 +252,7 @@ pub enum ProofProvenanceArtifactKind { PublicProvenancePreview, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum ProofProvenanceReviewState { MissingProof, @@ -265,7 +265,7 @@ pub enum ProofProvenanceReviewState { Published, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum StewardshipAccessItemKind { AccessRequestReview, @@ -279,7 +279,7 @@ pub enum StewardshipAccessItemKind { GroupManagementDeferred, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum OutboxState { NotQueued, @@ -294,7 +294,7 @@ pub enum OutboxState { Discarded, } -#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub enum SyncState { Unknown, @@ -306,7 +306,7 @@ pub enum SyncState { Failed, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ObjectRef { pub object_type: ObjectKind, @@ -314,7 +314,7 @@ pub struct ObjectRef { pub display_label: String, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EventRef { pub event_id: String, @@ -322,7 +322,7 @@ pub struct EventRef { pub kind: Option<u32>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ActiveContext { pub context_type: ContextType, @@ -332,7 +332,7 @@ pub struct ActiveContext { pub visibility_scope: VisibilityClass, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthorityGate { pub domain: AuthorityDomain, @@ -344,7 +344,7 @@ pub struct AuthorityGate { pub reason: Option<String>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ObjectPageSummary { pub object_ref: ObjectRef, @@ -358,7 +358,7 @@ pub struct ObjectPageSummary { pub sync_state: SyncState, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TodayCardAction { pub id: String, @@ -367,7 +367,7 @@ pub struct TodayCardAction { pub target_object: Option<ObjectRef>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TodayCard { pub id: String, @@ -392,7 +392,7 @@ pub struct TodayCard { pub is_offline: bool, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RelatedObjectRequirement { pub object_type: ObjectKind, @@ -400,7 +400,7 @@ pub struct RelatedObjectRequirement { pub is_required: bool, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ValidationRequirement { pub id: String, @@ -408,7 +408,7 @@ pub struct ValidationRequirement { pub is_blocking: bool, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddAction { pub action_type: AddActionType, @@ -427,7 +427,7 @@ pub struct AddAction { pub completion_state: AddFlowState, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OutboxItem { pub id: String, @@ -446,7 +446,7 @@ pub struct OutboxItem { pub last_error: Option<String>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OutboxRetryDecision { pub item_id: String, @@ -455,7 +455,7 @@ pub struct OutboxRetryDecision { pub reason: Option<String>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SearchResultSummary { pub id: String, @@ -469,7 +469,7 @@ pub struct SearchResultSummary { pub sync_state: SyncState, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PrototypePathStep { pub id: String, @@ -483,7 +483,7 @@ pub struct PrototypePathStep { pub sync_state: SyncState, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PrototypePath { pub id: String, @@ -494,7 +494,7 @@ pub struct PrototypePath { pub steps: Vec<PrototypePathStep>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RouteExecutionStep { pub id: String, @@ -514,7 +514,7 @@ pub struct RouteExecutionStep { pub detail_lines: Vec<String>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RouteExecutionFlow { pub id: String, @@ -526,7 +526,7 @@ pub struct RouteExecutionFlow { pub steps: Vec<RouteExecutionStep>, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProofProvenanceArtifact { pub id: String, @@ -548,7 +548,7 @@ pub struct ProofProvenanceArtifact { pub sync_state: SyncState, } -#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StewardshipAccessItem { pub id: String, @@ -2559,7 +2559,6 @@ pub fn fixture_outbox_retry_decision(item: OutboxItem) -> OutboxRetryDecision { } } -#[cfg_attr(not(coverage_nightly), uniffi::export)] impl RadrootsRuntime { pub fn phase1_active_contexts(&self) -> Vec<ActiveContext> { let _ = self; diff --git a/core/crates/tera_core/src/runtime/sdk.rs b/core/crates/tera_core/src/runtime/sdk.rs @@ -3,7 +3,7 @@ use radroots_sdk::capability::{Availability, Maturity}; use super::RadrootsRuntime; use crate::RadrootsAppError; -#[derive(Clone, Debug, uniffi::Record)] +#[derive(Clone, Debug)] pub struct SdkCapabilityRecord { pub id: String, pub compiled: bool, @@ -12,7 +12,7 @@ pub struct SdkCapabilityRecord { pub maturity: String, } -#[derive(Clone, Debug, uniffi::Record)] +#[derive(Clone, Debug)] pub struct SdkStorageStatusRecord { pub backend: String, pub open_mode: String, @@ -20,13 +20,12 @@ pub struct SdkStorageStatusRecord { pub integrity: String, } -#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SdkShutdownRecord { pub state: String, pub already_closed: bool, } -#[cfg_attr(not(coverage_nightly), uniffi::export)] impl RadrootsRuntime { pub fn sdk_capabilities(&self) -> Vec<SdkCapabilityRecord> { self.client diff --git a/core/crates/tera_core/tests/package_boundary.rs b/core/crates/tera_core/tests/package_boundary.rs @@ -0,0 +1,35 @@ +const MANIFEST: &str = include_str!("../Cargo.toml"); +const LIB: &str = include_str!("../src/lib.rs"); +const ERROR: &str = include_str!("../src/error.rs"); +const RUNTIME: &str = include_str!("../src/runtime/mod.rs"); +const APP_INFO: &str = include_str!("../src/runtime/app_info.rs"); +const INFO: &str = include_str!("../src/runtime/info.rs"); +const KEY_MANAGEMENT: &str = include_str!("../src/runtime/key_management.rs"); +const NOSTR: &str = include_str!("../src/runtime/nostr.rs"); +const PRODUCT_SURFACE: &str = include_str!("../src/runtime/product_surface.rs"); +const SDK: &str = include_str!("../src/runtime/sdk.rs"); + +#[test] +fn core_owns_no_uniffi_or_process_global_logging_policy() { + for (name, source) in [ + ("Cargo.toml", MANIFEST), + ("src/lib.rs", LIB), + ("src/error.rs", ERROR), + ("src/runtime/mod.rs", RUNTIME), + ("src/runtime/app_info.rs", APP_INFO), + ("src/runtime/info.rs", INFO), + ("src/runtime/key_management.rs", KEY_MANAGEMENT), + ("src/runtime/nostr.rs", NOSTR), + ("src/runtime/product_surface.rs", PRODUCT_SURFACE), + ("src/runtime/sdk.rs", SDK), + ] { + assert!( + !source.to_ascii_lowercase().contains("uniffi"), + "{name} contains UniFFI boundary policy" + ); + assert!( + !source.contains("tracing_subscriber") && !source.contains("set_global_default"), + "{name} contains process-global logging policy" + ); + } +} diff --git a/core/crates/tera_core/tests/uniffi_contract.rs b/core/crates/tera_core/tests/uniffi_contract.rs @@ -1,33 +0,0 @@ -use radroots_mobile_core::{RadrootsAppError, RadrootsRuntime, SdkErrorRecord}; - -#[tokio::test] -async fn final_mobile_abi_uses_async_sdk_dtos_and_versioned_errors() { - let runtime = RadrootsRuntime::new().expect("runtime"); - let storage = runtime.sdk_storage_status().await.expect("storage status"); - assert_eq!(storage.backend, "memory"); - - runtime.shutdown().await.expect("shutdown"); - let error = runtime - .sdk_storage_status() - .await - .expect_err("closed client must reject operations"); - let RadrootsAppError::Sdk { - report: - SdkErrorRecord { - schema_version, - code, - class, - retryable, - message, - .. - }, - } = error - else { - panic!("expected versioned SDK error record"); - }; - assert_eq!(schema_version, 1); - assert_eq!(code, "client_closed"); - assert_eq!(class, "runtime"); - assert!(!retryable); - assert_eq!(message, "SDK client is closed"); -}