package_boundary.rs (9566B)
1 use std::collections::BTreeSet; 2 3 #[allow(unused_imports)] 4 use radroots_event_codec::{authoring as _, canonical as _, decode as _, encode as _, verify as _}; 5 6 const MANIFEST: &str = include_str!("../Cargo.toml"); 7 const README: &str = include_str!("../README.md"); 8 const ROOT: &str = include_str!("../src/lib.rs"); 9 const RHI: &str = include_str!("../src/rhi.rs"); 10 const VERIFICATION: &str = include_str!("../src/verification/v1.rs"); 11 const EXAMPLE: &str = include_str!("../examples/verify_profile.rs"); 12 const FUZZ_LOCK: &str = include_str!("../../../fuzz/event_codec/Cargo.lock"); 13 const FUZZ_MANIFEST: &str = include_str!("../../../fuzz/event_codec/Cargo.toml"); 14 const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_event_codec.txt"); 15 16 #[test] 17 fn manifest_has_final_identity_and_required_radroots_dependencies() { 18 assert!(MANIFEST.contains("name = \"radroots_event_codec\"")); 19 assert!(MANIFEST.contains("version = \"0.1.0-alpha\"")); 20 assert!(MANIFEST.contains("publish = [\"crates-io\"]")); 21 assert!(MANIFEST.contains("[lib]\nname = \"radroots_event_codec\"")); 22 23 let dependencies = table_keys(MANIFEST, "[dependencies]"); 24 for dependency in ["radroots_blossom", "radroots_event", "radroots_protocol"] { 25 assert!( 26 dependencies.contains(dependency), 27 "missing required Radroots dependency {dependency}" 28 ); 29 } 30 assert!( 31 MANIFEST.contains("radroots_protocol = { workspace = true, default-features = false }") 32 ); 33 } 34 35 #[test] 36 fn consolidated_parser_fuzz_package_uses_the_repository_version_contract() { 37 let package = "name = \"radroots_parser_fuzz\"\npublish = false\nversion = \"0.1.0-alpha\""; 38 let locked_package = "name = \"radroots_parser_fuzz\"\nversion = \"0.1.0-alpha\""; 39 40 assert!(FUZZ_MANIFEST.contains(package)); 41 assert!(FUZZ_LOCK.contains(locked_package)); 42 assert!(!FUZZ_MANIFEST.contains("version = \"0.0.0\"")); 43 } 44 45 #[test] 46 fn crate_root_declares_every_approved_module() { 47 let declared = root_declarations("pub mod "); 48 for module in [ 49 "admission", 50 "authoring", 51 "canonical", 52 "decode", 53 "encode", 54 "manifest", 55 "verify", 56 ] { 57 assert!( 58 declared.contains(module), 59 "missing approved module {module}" 60 ); 61 } 62 } 63 64 #[test] 65 fn canonical_root_exports_are_explicit_and_host_types_do_not_leak() { 66 for export in [ 67 "pub use codec::Codec;", 68 "pub use decode::DecodeError;", 69 "pub use encode::EncodeError;", 70 "pub use verify::VerificationError;", 71 ] { 72 assert!( 73 ROOT.contains(export), 74 "missing canonical root export {export}" 75 ); 76 } 77 78 assert!(ROOT.contains("#![cfg_attr(not(feature = \"std\"), no_std)]")); 79 assert!(!ROOT.contains("pub trait ")); 80 assert!(!ROOT.contains("TEMPORARY COMPATIBILITY QUARANTINE")); 81 for forbidden in [ 82 "nostr::", 83 "nostr_sdk::", 84 "reqwest::", 85 "sqlx::", 86 "tokio::", 87 "std::os::", 88 ] { 89 assert!( 90 !ROOT.contains(forbidden), 91 "crate root must not expose host path {forbidden}" 92 ); 93 } 94 } 95 96 #[test] 97 fn compatibility_surface_is_removed() { 98 assert!(MANIFEST.contains("publish = [\"crates-io\"]")); 99 for module in [ 100 "comment", 101 "deletion", 102 "error", 103 "job", 104 "knowledge", 105 "profile", 106 "verification", 107 "wire", 108 ] { 109 assert!( 110 !ROOT.contains(&format!("pub mod {module};")), 111 "compatibility module {module} remains public" 112 ); 113 } 114 115 for retired_root_export in [ 116 "pub use encode::tag_builders::RadrootsEventTagBuilder;", 117 "pub use verify::{", 118 "pub use manifest::registry_v7::{", 119 "pub use manifest::{", 120 ] { 121 assert!( 122 !ROOT.contains(retired_root_export), 123 "retired prefixed root export remains: {retired_root_export}" 124 ); 125 } 126 } 127 128 #[test] 129 fn rhi_contract_is_private_curated_and_host_free() { 130 assert!(ROOT.contains("#[cfg(feature = \"json\")]\nmod rhi;")); 131 assert!(!ROOT.contains("pub mod rhi;")); 132 133 for forbidden in [ 134 "nostr_sdk::", 135 "reqwest::", 136 "sqlx::", 137 "tokio::", 138 "std::fs", 139 "std::net", 140 "std::process", 141 ] { 142 assert!( 143 !RHI.contains(forbidden), 144 "RHI codec must not acquire host authority through {forbidden}" 145 ); 146 } 147 } 148 149 #[test] 150 fn codec_runtime_is_protocol_neutral_and_host_free() { 151 let features = table_keys(MANIFEST, "[features]"); 152 let dependencies = table_keys(MANIFEST, "[dependencies]"); 153 154 assert!(!features.contains("nostr")); 155 assert!(dependencies.contains("secp256k1")); 156 for forbidden in [ 157 "keyring", 158 "nostr", 159 "nostr-sdk", 160 "reqwest", 161 "sqlx", 162 "tokio", 163 "wasm-bindgen", 164 "web-sys", 165 ] { 166 assert!( 167 !dependencies.contains(forbidden), 168 "codec runtime must not depend on {forbidden}" 169 ); 170 } 171 assert!(!VERIFICATION.contains("nostr::")); 172 assert!(!VERIFICATION.contains("feature = \"nostr\"")); 173 } 174 175 #[test] 176 fn serialization_features_are_explicit_additive_and_final() { 177 let features = table_keys(MANIFEST, "[features]"); 178 assert_eq!( 179 features, 180 BTreeSet::from(["default", "json", "knowledge", "manifests", "serde", "std"]) 181 ); 182 assert!(MANIFEST.contains("default = [\"std\", \"json\"]")); 183 assert!(MANIFEST.contains("json = [\"serde\", \"dep:serde_json\"]")); 184 assert!(MANIFEST.contains("knowledge = [\"json\", \"radroots_event/knowledge\"]")); 185 assert!(MANIFEST.contains("manifests = [\"knowledge\"]")); 186 187 for forbidden in [ 188 "serde_json", 189 "contract-manifest", 190 "knowledge-nip54", 191 "dto-bindgen", 192 "codegen", 193 ] { 194 assert!( 195 !features.contains(forbidden), 196 "retired public feature {forbidden} must remain absent" 197 ); 198 } 199 } 200 201 #[test] 202 fn package_documentation_and_reviewed_api_baseline_are_complete() { 203 for section in [ 204 "## Canonical surface", 205 "## Verification pipeline", 206 "## Features", 207 "## Serialization and canonicalization", 208 "## Security and trust boundaries", 209 "## Side effects, cancellation, and commit points", 210 "## Intended consumers", 211 "## Package charter", 212 ] { 213 assert!(README.contains(section), "README is missing {section}"); 214 } 215 assert!(ROOT.contains("#![doc = include_str!(\"../README.md\")]")); 216 assert!(EXAMPLE.contains("use radroots_event_codec::{admission, decode, verify};")); 217 218 assert!(PUBLIC_API.starts_with("pub mod radroots_event_codec\n")); 219 for item in [ 220 "pub mod radroots_event_codec::admission", 221 "pub mod radroots_event_codec::authoring", 222 "pub mod radroots_event_codec::canonical", 223 "pub mod radroots_event_codec::decode", 224 "pub mod radroots_event_codec::encode", 225 "pub mod radroots_event_codec::manifest", 226 "pub mod radroots_event_codec::verify", 227 "pub use radroots_event_codec::VerificationError", 228 "pub struct radroots_event_codec::authoring::BlossomAuthorizationPlan", 229 "pub const radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 16]", 230 "pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_trade_mutation", 231 "pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_rhi_evidence_attestation", 232 "pub fn radroots_event_codec::decode::rhi::rhi_evidence_attestation_from_verified_event", 233 "pub fn radroots_event_codec::decode::rhi::validate_rhi_evidence_attestation_supersession", 234 "pub fn radroots_event_codec::encode::rhi::rhi_evidence_attestation_event_build_with_extra_tags", 235 "pub fn radroots_event_codec::decode::trade::trade_mutation_from_verified_event", 236 "pub fn radroots_event_codec::encode::trade::trade_mutation_event_build_with_extra_tags", 237 "pub enum radroots_event_codec::decode::trade::RadrootsTradeMutationError", 238 ] { 239 assert!(PUBLIC_API.contains(item), "API baseline is missing {item}"); 240 } 241 for forbidden in ["nostr_sdk::", "reqwest::", "sqlx::", "tokio::", "keyring::"] { 242 assert!( 243 !PUBLIC_API.contains(forbidden), 244 "API baseline exposes forbidden host path {forbidden}" 245 ); 246 } 247 let retired = "RadrootsTradeMutationParseError"; 248 assert!( 249 !PUBLIC_API.contains(retired), 250 "API baseline retains unsafe trade authoring surface {retired}" 251 ); 252 } 253 254 fn table_keys<'a>(manifest: &'a str, heading: &str) -> BTreeSet<&'a str> { 255 let Some((_, table)) = manifest.split_once(heading) else { 256 return BTreeSet::new(); 257 }; 258 table 259 .lines() 260 .skip(1) 261 .take_while(|line| !line.trim_start().starts_with('[')) 262 .filter_map(|line| { 263 let line = line.trim(); 264 (line 265 .bytes() 266 .next() 267 .is_some_and(|byte| byte.is_ascii_lowercase() || byte == b'_') 268 && !line.starts_with('#')) 269 .then(|| line.split_once('=').map(|(key, _)| key.trim())) 270 .flatten() 271 }) 272 .collect() 273 } 274 275 fn root_declarations(prefix: &str) -> BTreeSet<&str> { 276 ROOT.lines() 277 .map(str::trim) 278 .filter_map(|line| line.strip_prefix(prefix)) 279 .filter_map(|name| name.strip_suffix(';')) 280 .collect() 281 }