lib

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

package_boundary.rs (19718B)


      1 use std::collections::BTreeSet;
      2 
      3 const MANIFEST: &str = include_str!("../Cargo.toml");
      4 const ROOT: &str = include_str!("../src/lib.rs");
      5 const CLIENT: &str = include_str!("../src/client.rs");
      6 const FARM: &str = include_str!("../src/farm.rs");
      7 const LISTING: &str = include_str!("../src/listing.rs");
      8 const TRADE: &str = include_str!("../src/trade.rs");
      9 const STORAGE: &str = include_str!("../src/storage.rs");
     10 const DIAGNOSTICS: &str = include_str!("../src/diagnostics.rs");
     11 const ADAPTERS: &str = include_str!("../src/adapters/mod.rs");
     12 const RADROOTSD: &str = include_str!("../src/adapters/radrootsd.rs");
     13 const SYNC: &str = include_str!("../src/sync.rs");
     14 const TRANSPORT: &str = include_str!("../src/transport.rs");
     15 const PUBLICATION: &str = include_str!("fixtures/publication.toml");
     16 
     17 #[test]
     18 fn manifest_has_final_identity_and_dependency_boundary() {
     19     assert!(MANIFEST.contains("name = \"radroots_sdk\""));
     20     assert!(MANIFEST.contains("publish = [\"crates-io\"]"));
     21     assert!(MANIFEST.contains("version.workspace = true"));
     22     assert!(MANIFEST.contains("name = \"package_boundary\""));
     23 
     24     let dependencies = dependency_names(MANIFEST);
     25     let expected = BTreeSet::from([
     26         "radroots_blossom",
     27         "radroots_core",
     28         "radroots_event",
     29         "radroots_event_codec",
     30         "radroots_geonames",
     31         "radroots_identity",
     32         "radroots_nostr",
     33         "radroots_nostr_connect",
     34         "radroots_protocol",
     35         "radroots_secrets",
     36         "radroots_signing",
     37         "radroots_storage",
     38         "radroots_storage_sqlite",
     39         "radroots_sync",
     40         "radroots_trade",
     41         "radroots_transport",
     42         "radroots_transport_nostr",
     43     ]);
     44     assert_eq!(dependencies, expected);
     45     for forbidden in [
     46         "radroots_event_store",
     47         "radroots_nostr_signer",
     48         "radroots_outbox",
     49         "radroots_protected_store",
     50         "radroots_runtime_paths",
     51         "radroots_secret_vault",
     52         "radroots_transport_reticulum",
     53     ] {
     54         assert!(
     55             !dependencies.contains(forbidden),
     56             "SDK depends on private or superseded package `{forbidden}`"
     57         );
     58     }
     59 }
     60 
     61 #[test]
     62 fn manifest_has_exact_feature_vocabulary_and_explicit_optional_activation() {
     63     let features = MANIFEST
     64         .split_once("[features]")
     65         .expect("feature section")
     66         .1
     67         .split_once("[dependencies]")
     68         .expect("dependency section")
     69         .0
     70         .lines()
     71         .filter_map(|line| line.split_once(" = ").map(|(name, _)| name.trim()))
     72         .collect::<BTreeSet<_>>();
     73     assert_eq!(
     74         features,
     75         BTreeSet::from([
     76             "blossom",
     77             "default",
     78             "full",
     79             "geonames",
     80             "knowledge",
     81             "local-signing",
     82             "memory",
     83             "native",
     84             "nip46",
     85             "nostr",
     86             "radrootsd",
     87             "sqlite",
     88             "sync",
     89         ])
     90     );
     91     for activation in [
     92         "dep:radroots_storage_sqlite",
     93         "dep:radroots_sync",
     94         "dep:radroots_blossom",
     95         "dep:radroots_nostr",
     96         "dep:radroots_transport_nostr",
     97         "dep:radroots_nostr_connect",
     98         "dep:radroots_secrets",
     99         "dep:reqwest",
    100         "dep:serde",
    101         "dep:serde_json",
    102         "dep:tokio",
    103         "dep:radroots_geonames",
    104     ] {
    105         assert!(
    106             MANIFEST.contains(activation),
    107             "missing activation `{activation}`"
    108         );
    109     }
    110     for retired in [
    111         "runtime =",
    112         "local-runtime",
    113         "signer-adapters",
    114         "transport-nostr-runtime",
    115         "transport-nostr-client",
    116         "fixtures =",
    117     ] {
    118         assert!(!MANIFEST.contains(retired), "retired feature `{retired}`");
    119     }
    120 }
    121 
    122 #[test]
    123 fn root_declares_exact_final_module_skeleton() {
    124     let actual = ROOT
    125         .lines()
    126         .filter_map(|line| line.trim().strip_prefix("pub mod "))
    127         .map(|module| module.trim_end_matches(';'))
    128         .collect::<BTreeSet<_>>();
    129     let expected = BTreeSet::from([
    130         "capability",
    131         "client",
    132         "diagnostics",
    133         "error",
    134         "farm",
    135         "listing",
    136         "signing",
    137         "storage",
    138         "sync",
    139         "trade",
    140         "transport",
    141     ]);
    142     assert_eq!(actual, expected);
    143     assert!(!ROOT.contains("no_std"));
    144     assert_eq!(ROOT.matches("pub use crate::").count(), 2);
    145     assert!(ROOT.contains("pub use crate::client::{Client, ClientBuilder};"));
    146     assert!(ROOT.contains("pub use crate::error::{Error, Result};"));
    147     assert!(!ROOT.contains("pub use radroots_"));
    148 }
    149 
    150 #[test]
    151 fn package_contains_only_reachable_sources_and_registered_targets() {
    152     let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
    153     assert_eq!(
    154         rust_files(&root.join("src")),
    155         BTreeSet::from([
    156             "adapters/mod.rs".to_owned(),
    157             "adapters/blossom.rs".to_owned(),
    158             "adapters/blossom/native_completion_tests.rs".to_owned(),
    159             "adapters/radrootsd.rs".to_owned(),
    160             "capability.rs".to_owned(),
    161             "client.rs".to_owned(),
    162             "diagnostics.rs".to_owned(),
    163             "error.rs".to_owned(),
    164             "farm.rs".to_owned(),
    165             "lib.rs".to_owned(),
    166             "listing.rs".to_owned(),
    167             "listing/operational_listing/draft.rs".to_owned(),
    168             "listing/operational_listing/mod.rs".to_owned(),
    169             "listing/operational_listing/model.rs".to_owned(),
    170             "listing/operational_listing/mutation.rs".to_owned(),
    171             "listing/operational_listing/price_ext.rs".to_owned(),
    172             "listing/operational_listing/validation.rs".to_owned(),
    173             "signing.rs".to_owned(),
    174             "storage.rs".to_owned(),
    175             "storage/backup_tests.rs".to_owned(),
    176             "storage/restore_tests.rs".to_owned(),
    177             "sync.rs".to_owned(),
    178             "sync/tests/selected.rs".to_owned(),
    179             "trade.rs".to_owned(),
    180             "transport.rs".to_owned(),
    181         ])
    182     );
    183     assert_eq!(
    184         rust_files(&root.join("tests")),
    185         BTreeSet::from([
    186             "lifecycle.rs".to_owned(),
    187             "package_boundary.rs".to_owned(),
    188             "public_api.rs".to_owned(),
    189             "unit/adapters_radrootsd_tests.rs".to_owned(),
    190         ])
    191     );
    192     assert_eq!(
    193         rust_files(&root.join("examples")),
    194         BTreeSet::from([
    195             "safe_memory_client.rs".to_owned(),
    196             "transport_profile.rs".to_owned(),
    197         ])
    198     );
    199 }
    200 
    201 #[test]
    202 fn compatibility_packages_are_removed() {
    203     assert!(PUBLICATION.contains("retired = []"));
    204     let approved = PUBLICATION
    205         .split_once("approved_packages = [")
    206         .expect("approved package list")
    207         .1
    208         .split_once("\n]")
    209         .expect("approved package list terminator")
    210         .0;
    211     assert!(!approved.contains("radroots_runtime_contract_v1"));
    212     let workspace = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    213         .parent()
    214         .and_then(std::path::Path::parent)
    215         .expect("workspace root");
    216     assert!(!workspace.join("crates/runtime_contract_v1").exists());
    217     assert!(!workspace.join("crates/event_index_bindings").exists());
    218 }
    219 
    220 #[test]
    221 fn root_types_have_the_required_std_contracts() {
    222     fn assert_client<T: Clone + Send + Sync>() {}
    223     fn assert_builder<T: Send + Sync>() {}
    224     fn assert_error<T: std::error::Error + Send + Sync + 'static>() {}
    225 
    226     assert_client::<radroots_sdk::Client>();
    227     assert_builder::<radroots_sdk::ClientBuilder>();
    228     assert_error::<radroots_sdk::Error>();
    229     let result: radroots_sdk::Result<()> = Ok(());
    230     assert!(result.is_ok());
    231 }
    232 
    233 #[cfg(feature = "sqlite")]
    234 #[test]
    235 fn sqlite_backend_implements_the_canonical_storage_capability() {
    236     fn assert_storage<T: radroots_storage::Storage>() {}
    237     assert_storage::<radroots_storage_sqlite::SqliteStorage>();
    238 }
    239 
    240 #[cfg(feature = "nostr")]
    241 #[test]
    242 fn nostr_adapter_implements_independent_source_and_sink_capabilities() {
    243     fn assert_source<T: radroots_transport::EventSource>() {}
    244     fn assert_sink<T: radroots_transport::EventSink>() {}
    245     assert_source::<radroots_transport_nostr::NostrTransport>();
    246     assert_sink::<radroots_transport_nostr::NostrTransport>();
    247 }
    248 
    249 #[test]
    250 fn lifecycle_source_owns_no_hidden_worker_runtime_or_blocking_drop() {
    251     for forbidden in [
    252         "tokio::spawn",
    253         "std::thread::spawn",
    254         "Runtime::new",
    255         "block_on(self.close",
    256         "impl Drop for Client",
    257     ] {
    258         assert!(
    259             !CLIENT.contains(forbidden),
    260             "forbidden lifecycle source `{forbidden}`"
    261         );
    262     }
    263     assert!(CLIENT.contains("pub async fn close(&self)"));
    264     assert!(CLIENT.contains("impl Drop for CloseAttempt"));
    265 }
    266 
    267 #[test]
    268 fn sdk_source_contains_no_studio_storage_surface() {
    269     let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
    270     let mut pending = vec![source_root];
    271     while let Some(path) = pending.pop() {
    272         for entry in std::fs::read_dir(path).expect("read SDK source") {
    273             let entry = entry.expect("source entry");
    274             let path = entry.path();
    275             if path.is_dir() {
    276                 pending.push(path);
    277             } else if path.extension().and_then(|value| value.to_str()) == Some("rs") {
    278                 let source = std::fs::read_to_string(&path).expect("read source file");
    279                 for forbidden in [
    280                     "studio.sqlite",
    281                     "SdkStudioStore",
    282                     "sdk_studio_state",
    283                     "studio_store",
    284                 ] {
    285                     assert!(
    286                         !source.contains(forbidden),
    287                         "{} contains retired Studio storage marker {forbidden}",
    288                         path.display()
    289                     );
    290                 }
    291             }
    292         }
    293     }
    294 }
    295 
    296 #[test]
    297 fn transport_profiles_reuse_canonical_types_and_forbid_fallback() {
    298     assert!(TRANSPORT.contains("use radroots_transport::{"));
    299     assert!(TRANSPORT.contains("satisfaction.validate_for(&targets)?"));
    300     assert!(TRANSPORT.contains("Selection::UnavailablePreview"));
    301     for duplicate in [
    302         "pub struct TargetSet",
    303         "pub enum TargetPolicy",
    304         "pub enum SatisfactionPolicy",
    305         "pub struct SourceStatus",
    306         "pub struct SinkStatus",
    307         "DefaultProfile",
    308     ] {
    309         assert!(
    310             !TRANSPORT.contains(duplicate),
    311             "SDK transport source contains forbidden duplicate or fallback `{duplicate}`"
    312         );
    313     }
    314 }
    315 
    316 #[test]
    317 fn sync_operations_only_delegate_to_the_canonical_engine() {
    318     for delegation in [
    319         "self.engine.pull(request, admission).await",
    320         "self.engine.ingest(observed, admission).await",
    321         "self.engine.ingest_batch(observed, admission).await",
    322         "self.engine.refresh_projection(request, reducer).await",
    323         "self.engine.prepare_push(request).await",
    324         "self.engine.push_status(operation_id).await",
    325         "self.engine.sign_prepared(request).await",
    326         "self.engine.admit_signed(operation_id).await",
    327         "self.engine.deliver_push(operation_id).await",
    328         "self.engine.status(projections).await",
    329         "self.engine.retry_decision(plan, now_unix_ms)",
    330     ] {
    331         assert!(
    332             SYNC.contains(delegation),
    333             "missing sync delegation `{delegation}`"
    334         );
    335     }
    336     for duplicate in [
    337         "pub struct SyncTransport",
    338         "pub struct SyncOutbox",
    339         "pub struct SyncProjection",
    340         "pub struct SyncStatus",
    341         "pub struct PushOutbox",
    342     ] {
    343         assert!(
    344             !SYNC.contains(duplicate),
    345             "SDK sync source contains duplicate lower model `{duplicate}`"
    346         );
    347     }
    348     assert!(
    349         !std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    350             .join("src/sync_runtime.rs")
    351             .exists()
    352     );
    353 }
    354 
    355 #[test]
    356 fn authored_submission_is_one_protocol_neutral_boundary_for_all_typed_contracts() {
    357     use radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS;
    358 
    359     assert_eq!(REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS.len(), 16);
    360     assert!(SYNC.contains("pub async fn submit_push"));
    361     assert!(SYNC.contains("request: PushRequest"));
    362     assert!(SYNC.contains("Result<PushStatus, Error>"));
    363     for contract_id in REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS {
    364         assert!(
    365             !SYNC.contains(contract_id),
    366             "SDK submission must not branch on typed contract `{contract_id}`"
    367         );
    368     }
    369 }
    370 
    371 #[test]
    372 fn trade_planning_uses_only_the_typed_mutation_authoring_boundary() {
    373     assert!(TRADE.contains("AuthoredEventPlan::from_trade_mutation"));
    374     for forbidden in ["GenericEventDraft", "trade_mutation_event_build("] {
    375         assert!(
    376             !TRADE.contains(forbidden),
    377             "trade planning retains generic authoring path {forbidden}"
    378         );
    379     }
    380 }
    381 
    382 #[test]
    383 fn trade_evidence_surface_is_curated_and_signature_verified() {
    384     let production = TRADE
    385         .split_once("#[cfg(test)]")
    386         .expect("trade tests remain separated")
    387         .0;
    388     for required in [
    389         "pub use radroots_trade::evidence::{",
    390         "pub fn parse_evidence_manifest(",
    391         "pub fn parse_rhi_evidence_report(",
    392         "pub fn prepare_rhi_evidence_attestation(",
    393         "pub fn validate_rhi_evidence_attestation(",
    394         "Nip01SignatureVerifier",
    395         "rhi_evidence_attestation_from_verified_event",
    396     ] {
    397         assert!(
    398             production.contains(required),
    399             "missing trade evidence boundary `{required}`"
    400         );
    401     }
    402     for forbidden in ["SecretKey", "sign_event", "tokio::spawn", "publish_event("] {
    403         assert!(
    404             !production.contains(forbidden),
    405             "trade evidence surface gained forbidden authority `{forbidden}`"
    406         );
    407     }
    408 }
    409 
    410 #[test]
    411 fn farm_operations_preserve_pure_planning_commit_and_privacy_boundaries() {
    412     for required in [
    413         "encode::farm::to_wire_parts",
    414         "AddressableCoordinate",
    415         "GenericEventDraft",
    416         "AuthoredEventPlan",
    417         "radroots_sync::PushRequest::new",
    418         "self.sync",
    419         ".submit_push(",
    420         "PrivateArtifactStore",
    421     ] {
    422         assert!(
    423             FARM.contains(required),
    424             "missing farm boundary `{required}`"
    425         );
    426     }
    427     for forbidden in [
    428         "SdkExactLocation",
    429         "SdkPublicLocality",
    430         "latitude:",
    431         "longitude:",
    432         "enqueue_signed_workflow",
    433         "local_event_seq",
    434         "outbox_event_id",
    435     ] {
    436         assert!(
    437             !FARM.contains(forbidden),
    438             "farm source contains retired SDK or private representation `{forbidden}`"
    439         );
    440     }
    441     let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
    442     assert!(!source_root.join("farms_runtime.rs").exists());
    443 }
    444 
    445 #[test]
    446 fn listing_operations_reuse_trade_event_sync_and_privacy_boundaries() {
    447     for required in [
    448         "canonicalize_operational_listing_edit",
    449         "RadrootsOperationalListingMutation",
    450         "RadrootsOperationalListingLifecycleState",
    451         "build_operational_listing_mutation_plan",
    452         "radroots_sync::PushRequest::new",
    453         "PrivateArtifactStore",
    454     ] {
    455         assert!(
    456             LISTING.contains(required),
    457             "missing listing boundary `{required}`"
    458         );
    459     }
    460     for forbidden in [
    461         "SdkMutationState",
    462         "ListingEnqueueReceipt",
    463         "enqueue_signed_workflow",
    464         "local_event_seq",
    465         "outbox_event_id",
    466         "latitude:",
    467         "longitude:",
    468     ] {
    469         assert!(
    470             !LISTING.contains(forbidden),
    471             "listing source contains retired duplicate or private representation `{forbidden}`"
    472         );
    473     }
    474     assert!(
    475         !std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    476             .join("src/listings_runtime.rs")
    477             .exists()
    478     );
    479 }
    480 
    481 #[test]
    482 fn trade_operations_use_canonical_workflow_storage_sync_and_projection_types() {
    483     for required in [
    484         "WorkflowPlan",
    485         "TradeMutationEnvelopeV1",
    486         "ReductionInput",
    487         "Projection",
    488         "reduce_trade_records",
    489         "EventQuery",
    490         "EventPage<StoredVisibleEvent>",
    491         "PrivateArtifactMetadata",
    492         "radroots_sync::PushRequest::new",
    493     ] {
    494         assert!(
    495             TRADE.contains(required),
    496             "missing trade boundary `{required}`"
    497         );
    498     }
    499     for forbidden in [
    500         "sqlx::",
    501         "QueryBuilder",
    502         "TradeStatusView",
    503         "SdkPrivateTradeArtifact",
    504         "SdkMutationState",
    505         "TradeCommandReceipt",
    506         "struct Page",
    507         "struct TradeId",
    508     ] {
    509         assert!(
    510             !TRADE.contains(forbidden),
    511             "trade source contains retired duplicate `{forbidden}`"
    512         );
    513     }
    514     assert!(
    515         !std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    516             .join("src/trade_runtime.rs")
    517             .exists()
    518     );
    519 }
    520 
    521 #[test]
    522 fn reliability_and_diagnostics_return_canonical_storage_contracts() {
    523     for required in [
    524         "StorageReliability::begin_backup",
    525         "StorageReliability::transition_backup",
    526         "StorageReliability::begin_restore",
    527         "StorageReliability::transition_restore",
    528         "StorageReliability::integrity",
    529         "StorageReliability::status",
    530     ] {
    531         assert!(
    532             STORAGE.contains(required),
    533             "missing storage delegation `{required}`"
    534         );
    535     }
    536     for forbidden in [
    537         "struct BackupManifest",
    538         "struct IntegrityStatus",
    539         "Studio",
    540         "sqlx::",
    541     ] {
    542         assert!(
    543             !STORAGE.contains(forbidden),
    544             "storage source duplicates `{forbidden}`"
    545         );
    546     }
    547     assert!(DIAGNOSTICS.contains("radroots_storage::StorageStatus"));
    548     for forbidden in ["path:", "SqlitePool", "private_artifact"] {
    549         assert!(
    550             !DIAGNOSTICS.contains(forbidden),
    551             "diagnostics leaks `{forbidden}`"
    552         );
    553     }
    554 }
    555 
    556 #[test]
    557 fn radrootsd_adapter_is_private_explicit_versioned_and_redacted() {
    558     assert!(ROOT.contains("mod adapters;"));
    559     assert!(!ROOT.contains("pub mod adapters"));
    560     assert!(ADAPTERS.contains("cfg(feature = \"radrootsd\")"));
    561     assert!(ADAPTERS.contains("pub(crate) mod radrootsd"));
    562     assert!(RADROOTSD.contains("transport_publish::v5"));
    563     assert!(RADROOTSD.contains("reqwest::Client::builder"));
    564     assert!(RADROOTSD.contains("BearerToken(<redacted>)"));
    565     assert!(!RADROOTSD.contains("tokio::spawn"));
    566 }
    567 
    568 fn dependency_names(manifest: &str) -> BTreeSet<&str> {
    569     let dependencies = manifest
    570         .split_once("[dependencies]")
    571         .expect("dependency section")
    572         .1
    573         .split_once("[[test]]")
    574         .expect("test section")
    575         .0;
    576     dependencies
    577         .lines()
    578         .filter_map(|line| line.split_once(" = ").map(|(name, _)| name.trim()))
    579         .filter(|name| name.starts_with("radroots_"))
    580         .collect()
    581 }
    582 
    583 fn rust_files(root: &std::path::Path) -> BTreeSet<String> {
    584     let mut files = BTreeSet::new();
    585     let mut pending = vec![root.to_path_buf()];
    586     while let Some(directory) = pending.pop() {
    587         for entry in std::fs::read_dir(&directory).expect("read package source directory") {
    588             let path = entry.expect("read package source entry").path();
    589             if path.is_dir() {
    590                 pending.push(path);
    591             } else if path.extension().and_then(|value| value.to_str()) == Some("rs") {
    592                 files.insert(
    593                     path.strip_prefix(root)
    594                         .expect("source remains below root")
    595                         .to_string_lossy()
    596                         .replace(std::path::MAIN_SEPARATOR, "/"),
    597                 );
    598             }
    599         }
    600     }
    601     files
    602 }