package_boundary.rs (5027B)
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 README: &str = include_str!("../README.md"); 6 const SOURCES: &[(&str, &str)] = &[ 7 ("engine.rs", include_str!("../src/engine.rs")), 8 ("ingest.rs", include_str!("../src/ingest.rs")), 9 ("policy.rs", include_str!("../src/policy.rs")), 10 ("projection.rs", include_str!("../src/projection.rs")), 11 ("pull.rs", include_str!("../src/pull.rs")), 12 ("pull/summary.rs", include_str!("../src/pull/summary.rs")), 13 ("pull/wire.rs", include_str!("../src/pull/wire.rs")), 14 ("push.rs", include_str!("../src/push.rs")), 15 ("status.rs", include_str!("../src/status.rs")), 16 ]; 17 18 #[test] 19 fn pull_summary_contract_retains_existing_bounds_and_final_outcome_compatibility() { 20 let contract: serde_json::Value = serde_json::from_str(include_str!( 21 "../../../contracts/architecture/decisions/pull_target_evidence.v1.json" 22 )) 23 .expect("pull target evidence contract"); 24 assert_eq!(contract["owner"], "radroots_sync"); 25 assert_eq!( 26 contract["max_targets"], 27 radroots_transport::target::TARGET_SET_MAX_ITEMS 28 ); 29 assert_eq!(contract["max_pages"], radroots_sync::pull::PULL_MAX_PAGES); 30 assert_eq!( 31 contract["summary_fields"], 32 serde_json::json!([ 33 "target", 34 "pages_observed", 35 "incomplete_pages", 36 "missing_outcome_pages", 37 "last_incomplete" 38 ]) 39 ); 40 assert!( 41 contract["compatibility"] 42 .as_str() 43 .unwrap() 44 .contains("legacy") 45 ); 46 } 47 48 #[test] 49 fn sync_depends_only_on_final_orchestration_boundaries() { 50 for required in [ 51 "name = \"radroots_sync\"", 52 "version = \"0.1.0-alpha\"", 53 "publish = [\"crates-io\"]", 54 "[lib]\nname = \"radroots_sync\"", 55 "default = [\"serde\"]", 56 ] { 57 assert!( 58 MANIFEST.contains(required), 59 "manifest is missing `{required}`" 60 ); 61 } 62 assert_eq!( 63 dependency_keys(MANIFEST) 64 .into_iter() 65 .filter(|dependency| dependency.starts_with("radroots_")) 66 .collect::<BTreeSet<_>>(), 67 BTreeSet::from([ 68 "radroots_event", 69 "radroots_event_codec", 70 "radroots_protocol", 71 "radroots_signing", 72 "radroots_storage", 73 "radroots_trade", 74 "radroots_transport", 75 ]) 76 ); 77 assert!(MANIFEST.contains("serde = { workspace = true, optional = true }")); 78 for forbidden in [ 79 "radroots_event_store", 80 "radroots_event_index", 81 "radroots_outbox", 82 "radroots_runtime_store", 83 "radroots_transport_nostr", 84 ] { 85 assert!(!MANIFEST.contains(forbidden)); 86 assert!(!ROOT.contains(forbidden)); 87 } 88 assert_eq!( 89 declarations(ROOT, "pub mod "), 90 BTreeSet::from(["ingest", "policy", "projection", "pull", "push", "status"]) 91 ); 92 } 93 94 #[test] 95 fn sync_has_no_runtime_scheduler_or_process_lifecycle_authority() { 96 for forbidden_dependency in [ 97 "tokio", 98 "async-std", 99 "smol", 100 "rayon", 101 "signal-hook", 102 "ctrlc", 103 ] { 104 assert!( 105 !dependency_keys(MANIFEST).contains(forbidden_dependency), 106 "sync must not depend on runtime package `{forbidden_dependency}`" 107 ); 108 } 109 for (path, source) in SOURCES { 110 for forbidden in [ 111 "tokio::", 112 "async_std::", 113 "smol::", 114 "std::thread", 115 "thread::spawn", 116 ".spawn(", 117 "Runtime::new", 118 "new_multi_thread", 119 "new_current_thread", 120 "tokio::time", 121 "SystemTime::now", 122 "Instant::now", 123 "ctrl_c", 124 "signal_hook", 125 "process::exit", 126 ] { 127 assert!( 128 !source.contains(forbidden), 129 "{path} must not own runtime behavior `{forbidden}`" 130 ); 131 } 132 } 133 let readme = README.split_whitespace().collect::<Vec<_>>().join(" "); 134 for explicit_boundary in [ 135 "does not create an executor", 136 "spawn workers", 137 "install timers", 138 "own process lifecycle", 139 ] { 140 assert!(readme.contains(explicit_boundary)); 141 } 142 } 143 144 fn declarations<'a>(source: &'a str, prefix: &str) -> BTreeSet<&'a str> { 145 source 146 .lines() 147 .filter_map(|line| line.trim().strip_prefix(prefix)) 148 .filter_map(|name| name.strip_suffix(';')) 149 .collect() 150 } 151 152 fn dependency_keys(manifest: &str) -> BTreeSet<&str> { 153 manifest 154 .split_once("[dependencies]") 155 .map(|(_, dependencies)| dependencies) 156 .unwrap_or_default() 157 .lines() 158 .skip(1) 159 .take_while(|line| !line.starts_with('[')) 160 .filter_map(|line| line.split_once('=').map(|(key, _)| key.trim())) 161 .filter(|key| !key.is_empty()) 162 .collect() 163 }