lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 1a6dcde55a0b52a2c36d46b6e34a1d8c456aa3c7
parent 558a8c97318cc8dbe55bf2b691f294e3ae466e5e
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 11:59:31 +0000

sdk: rename public SDK types and remove representation coupling

- lock contextual native type names across supported feature surfaces
- reject public native struct fields in every active SDK module
- document the breaking constructor and accessor migration contract
- prove representation privacy with compile and compile-fail rustdoc examples

Diffstat:
Mcrates/sdk/Cargo.toml | 4++++
Mcrates/sdk/src/lib.rs | 19+++++++++++++++++++
Acrates/sdk/tests/public_api.rs | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 0 deletions(-)

diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -93,5 +93,9 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } name = "package_boundary" path = "tests/package_boundary.rs" +[[test]] +name = "public_api" +path = "tests/public_api.rs" + [lints] workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -3,6 +3,25 @@ //! Advanced hosts compose capabilities through [`ClientBuilder`] and operate //! through a cloneable [`Client`]. All fallible root operations use [`Result`] //! and the SDK-owned [`Error`] boundary. +//! +//! Constructing an empty builder performs no I/O and makes missing composition +//! explicit: +//! +//! ``` +//! use radroots_sdk::ClientBuilder; +//! +//! let result = ClientBuilder::new().build(); +//! assert!(result.is_err()); +//! ``` +//! +//! Native structs are constructor-led and representation-private. Hosts must +//! not depend on field layout: +//! +//! ```compile_fail +//! use radroots_sdk::ClientBuilder; +//! +//! let _ = ClientBuilder { storage: None }; +//! ``` #![forbid(unsafe_code)] diff --git a/crates/sdk/tests/public_api.rs b/crates/sdk/tests/public_api.rs @@ -0,0 +1,136 @@ +use std::{any::type_name, collections::BTreeSet}; + +const ACTIVE_MODULES: &[(&str, &str)] = &[ + ("capability", include_str!("../src/capability.rs")), + ("client", include_str!("../src/client.rs")), + ("diagnostics", include_str!("../src/diagnostics.rs")), + ("error", include_str!("../src/error.rs")), + ("farm", include_str!("../src/farm.rs")), + ("listing", include_str!("../src/listing.rs")), + ("signing", include_str!("../src/signing.rs")), + ("storage", include_str!("../src/storage.rs")), + ("sync", include_str!("../src/sync.rs")), + ("trade", include_str!("../src/trade.rs")), + ("transport", include_str!("../src/transport.rs")), +]; + +#[test] +fn public_native_type_snapshot_uses_contextual_names() { + let actual = BTreeSet::from([ + type_name::<radroots_sdk::Client>(), + type_name::<radroots_sdk::ClientBuilder>(), + type_name::<radroots_sdk::capability::Availability>(), + type_name::<radroots_sdk::capability::CapabilityId>(), + type_name::<radroots_sdk::capability::CapabilityReport>(), + type_name::<radroots_sdk::capability::CapabilityStatus>(), + type_name::<radroots_sdk::capability::Maturity>(), + type_name::<radroots_sdk::diagnostics::Report>(), + type_name::<radroots_sdk::error::Error>(), + type_name::<radroots_sdk::error::ErrorDescriptor>(), + type_name::<radroots_sdk::error::ErrorKind>(), + type_name::<radroots_sdk::farm::Plan>(), + type_name::<radroots_sdk::farm::PrepareError>(), + type_name::<radroots_sdk::farm::PrepareErrorKind>(), + type_name::<radroots_sdk::farm::PrepareRequest>(), + type_name::<radroots_sdk::listing::Action>(), + type_name::<radroots_sdk::listing::Plan>(), + type_name::<radroots_sdk::listing::PrepareError>(), + type_name::<radroots_sdk::listing::PrepareErrorKind>(), + type_name::<radroots_sdk::listing::PrepareRequest>(), + type_name::<radroots_sdk::signing::Mode>(), + type_name::<radroots_sdk::signing::Provider>(), + type_name::<radroots_sdk::storage::Operations<'static>>(), + type_name::<radroots_sdk::trade::Plan>(), + type_name::<radroots_sdk::trade::PrepareError>(), + type_name::<radroots_sdk::trade::PrepareErrorKind>(), + type_name::<radroots_sdk::trade::PrepareRequest>(), + type_name::<radroots_sdk::transport::Profile>(), + ]); + #[cfg(feature = "sync")] + let actual = actual + .into_iter() + .chain([ + type_name::<radroots_sdk::farm::EnqueueRequest>(), + type_name::<radroots_sdk::farm::Operations<'static>>(), + type_name::<radroots_sdk::listing::EnqueueRequest>(), + type_name::<radroots_sdk::listing::Operations<'static>>(), + type_name::<radroots_sdk::sync::Operations<'static>>(), + type_name::<radroots_sdk::trade::EnqueueRequest>(), + type_name::<radroots_sdk::trade::Operations<'static>>(), + type_name::<radroots_sdk::trade::PrivateTermsError>(), + ]) + .collect::<BTreeSet<_>>(); + #[cfg(feature = "radrootsd")] + let actual = actual + .into_iter() + .chain([ + type_name::<radroots_sdk::transport::DaemonAuth>(), + type_name::<radroots_sdk::transport::DaemonConfig>(), + type_name::<radroots_sdk::transport::DaemonDelivery>(), + type_name::<radroots_sdk::transport::DaemonError>(), + type_name::<radroots_sdk::transport::DaemonErrorKind>(), + ]) + .collect::<BTreeSet<_>>(); + + assert!(actual.iter().all(|name| !name.contains("RadrootsSdk"))); + assert!(actual.iter().all(|name| { + name.rsplit("::") + .next() + .is_some_and(|item| !item.starts_with("Sdk")) + })); + assert_eq!(actual.len(), expected_public_type_count()); +} + +#[test] +fn active_native_structs_are_field_layout_independent() { + for (module, source) in ACTIVE_MODULES { + for line in source.lines() { + let line = line.trim_start(); + for declaration in ["pub struct ", "pub enum ", "pub trait ", "pub type "] { + if let Some(item) = line.strip_prefix(declaration) { + let item = item + .split(|character: char| { + character == '<' + || character == '(' + || character == '{' + || character == ';' + || character.is_whitespace() + }) + .next() + .expect("public item name"); + assert!( + !item.starts_with("RadrootsSdk") && !item.starts_with("Sdk"), + "{module} exposes representation-prefixed item {item}" + ); + } + } + + let public_field = line.starts_with("pub ") + && line.contains(':') + && ![ + "pub async fn ", + "pub const ", + "pub enum ", + "pub fn ", + "pub mod ", + "pub static ", + "pub struct ", + "pub trait ", + "pub type ", + "pub use ", + ] + .iter() + .any(|prefix| line.starts_with(prefix)); + assert!(!public_field, "{module} exposes native field `{line}`"); + } + } +} + +const fn expected_public_type_count() -> usize { + let count = 28; + #[cfg(feature = "sync")] + let count = count + 8; + #[cfg(feature = "radrootsd")] + let count = count + 5; + count +}