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 }