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:
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");
-}