source_boundary.rs (38623B)
1 use std::{ 2 fs, 3 path::{Path, PathBuf}, 4 }; 5 6 struct ForbiddenConcept { 7 pattern: &'static str, 8 reason: &'static str, 9 } 10 11 const TRANSPORT_HARDENING_CRATE_SOURCE_ROOTS: &[&str] = &[ 12 "protocol/src/radrootsd/transport_publish", 13 "transport/src", 14 "transport_reticulum/src", 15 "transport_nostr/src", 16 ]; 17 18 const GENERIC_TRANSPORT_STATUS_SOURCE_ROOTS: &[&str] = &[ 19 "mesh_agent_proto/src", 20 "protocol/src/radrootsd/transport_publish", 21 "transport/src", 22 "transport_reticulum/src", 23 ]; 24 25 const CORE_STATUS_CONTRACT_SOURCE_ROOTS: &[&str] = &["transport/src", "transport_reticulum/src"]; 26 27 const CORE_TRANSPORT_CONTRACT_SOURCE_ROOTS: &[&str] = &["transport/src"]; 28 29 const TRANSPORT_CONSUMER_SOURCE_ROOTS: &[&str] = &[ 30 "mesh/src", 31 "mesh_agent_client/src", 32 "protocol/src/radrootsd/transport_publish", 33 "transport_nostr/src", 34 "transport_reticulum/src", 35 ]; 36 37 const RETIRED_TRANSPORT_TYPE_NAMES: &[&str] = &[ 38 "RadrootsTransportKind", 39 "RadrootsTransportMeshScopeId", 40 "RadrootsTransportTarget", 41 "RadrootsTransportTargetFingerprint", 42 "RadrootsTransportTargetLabel", 43 "RadrootsTransportTargetSet", 44 ]; 45 46 const DELIVERY_PAYLOAD_CONTRACT_SOURCE_ROOTS: &[&str] = 47 &["transport/src", "transport_reticulum/src", "sync/src"]; 48 49 const FOUNDATION_HARDENING_DOC_ROOTS: &[&str] = &["contracts", "docs"]; 50 51 const FORBIDDEN_TRANSPORT_CONCEPTS: &[ForbiddenConcept] = &[ 52 ForbiddenConcept { 53 pattern: concat!("\"radrootsd", "_", "pro", "xy\""), 54 reason: "radrootsd execution must not be modeled as transport identity", 55 }, 56 ForbiddenConcept { 57 pattern: concat!("radrootsd.publish", "_", "pro", "xy.v1"), 58 reason: "transport publish protocol v1 radrootsd execution identifiers are removed", 59 }, 60 ForbiddenConcept { 61 pattern: "publish.relays.resolve", 62 reason: "relay-resolution RPC is replaced by transport publish target policy", 63 }, 64 ForbiddenConcept { 65 pattern: "\"publish.event\"", 66 reason: "publish.event is replaced by transport.publish.event", 67 }, 68 ForbiddenConcept { 69 pattern: "transport_kinds", 70 reason: "capabilities must expose per-transport readiness instead of kind-only lists", 71 }, 72 ForbiddenConcept { 73 pattern: "allowed_relay_policy", 74 reason: "relay policy is Nostr-specific and must not be a generic transport API", 75 }, 76 ForbiddenConcept { 77 pattern: "relay_policy", 78 reason: "relay policy is Nostr-specific and must not be a generic transport API", 79 }, 80 ForbiddenConcept { 81 pattern: "PublishRelayPolicy", 82 reason: "old relay-shaped publish policy names must not return", 83 }, 84 ForbiddenConcept { 85 pattern: "PublishRelayOutcome", 86 reason: "old relay-shaped publish outcome names must not return", 87 }, 88 ForbiddenConcept { 89 pattern: "PublishRelaySource", 90 reason: "old relay-shaped publish source names must not return", 91 }, 92 ForbiddenConcept { 93 pattern: concat!("Nostr", "Fetch"), 94 reason: "generic transport observations must use transport-neutral fetch naming", 95 }, 96 ForbiddenConcept { 97 pattern: concat!("Nostr", "Subscription"), 98 reason: "generic transport observations must use transport-neutral subscription naming", 99 }, 100 ForbiddenConcept { 101 pattern: concat!("Nostr", "PublishAck"), 102 reason: "generic transport observations must use transport-neutral publish ack naming", 103 }, 104 ForbiddenConcept { 105 pattern: concat!("nostr", "_fetch"), 106 reason: "generic transport observation storage strings must be transport-neutral", 107 }, 108 ForbiddenConcept { 109 pattern: concat!("nostr", "_subscription"), 110 reason: "generic transport observation storage strings must be transport-neutral", 111 }, 112 ForbiddenConcept { 113 pattern: concat!("nostr", "_publish_ack"), 114 reason: "generic transport observation storage strings must be transport-neutral", 115 }, 116 ]; 117 118 const FORBIDDEN_CORE_STATUS_CONCEPTS: &[ForbiddenConcept] = &[ 119 ForbiddenConcept { 120 pattern: "implementation_state", 121 reason: "public transport status must use implementation", 122 }, 123 ForbiddenConcept { 124 pattern: "readiness", 125 reason: "public transport status must use configured, usable_for_delivery, and message", 126 }, 127 ForbiddenConcept { 128 pattern: "publish_usable", 129 reason: "public transport status must use usable_for_delivery", 130 }, 131 ForbiddenConcept { 132 pattern: "fetch_usable", 133 reason: "public transport status must use usable_for_delivery", 134 }, 135 ForbiddenConcept { 136 pattern: "redacted_message", 137 reason: "public transport status must use message", 138 }, 139 ForbiddenConcept { 140 pattern: "RadrootsTransportReadinessState", 141 reason: "readiness state is no longer a public transport status contract", 142 }, 143 ForbiddenConcept { 144 pattern: "Misconfigured", 145 reason: "configuration is modeled by configured and message", 146 }, 147 ForbiddenConcept { 148 pattern: "Disabled", 149 reason: "disabled state is modeled by configured, usable_for_delivery, and message", 150 }, 151 ]; 152 153 const FORBIDDEN_GENERIC_TRANSPORT_STATUS_CONCEPTS: &[ForbiddenConcept] = &[ 154 ForbiddenConcept { 155 pattern: concat!("configured_nostr", "_relay", "_count"), 156 reason: "generic status surfaces must expose configured transport target counts", 157 }, 158 ForbiddenConcept { 159 pattern: concat!("configured_nostr", "_relays"), 160 reason: "generic status surfaces must expose configured transport targets", 161 }, 162 ForbiddenConcept { 163 pattern: concat!("target", "_relays"), 164 reason: "generic transport target surfaces must use endpoint terminology", 165 }, 166 ForbiddenConcept { 167 pattern: concat!("connected", "_relays"), 168 reason: "generic transport attempt surfaces must use endpoint terminology", 169 }, 170 ForbiddenConcept { 171 pattern: concat!("acknowledged", "_relays"), 172 reason: "generic transport acknowledgement surfaces must use endpoint terminology", 173 }, 174 ForbiddenConcept { 175 pattern: concat!("failed", "_relays"), 176 reason: "generic transport failure surfaces must use target terminology", 177 }, 178 ForbiddenConcept { 179 pattern: concat!("relay", "_count"), 180 reason: "generic transport status counts must use transport target terminology", 181 }, 182 ]; 183 184 const FORBIDDEN_CORE_TRANSPORT_CONCEPTS: &[ForbiddenConcept] = &[ 185 ForbiddenConcept { 186 pattern: concat!("Radroots", "Relay"), 187 reason: "core transport contracts must not expose Nostr relay-shaped APIs", 188 }, 189 ForbiddenConcept { 190 pattern: concat!("Relay", "Transport"), 191 reason: "core transport contracts must use transport-neutral names", 192 }, 193 ForbiddenConcept { 194 pattern: concat!("relay", "_transport"), 195 reason: "core transport contracts must use transport-neutral names", 196 }, 197 ]; 198 199 const FORBIDDEN_DELIVERY_PAYLOAD_CONCEPTS: &[ForbiddenConcept] = &[ 200 ForbiddenConcept { 201 pattern: "payload_digest", 202 reason: "delivery requests must carry DeliveryPayload instead of digest-only fields", 203 }, 204 ForbiddenConcept { 205 pattern: "DigestOnly", 206 reason: "runtime dispatch must not retain a digest-only payload path", 207 }, 208 ForbiddenConcept { 209 pattern: "RadrootsTransportPayload::signed_event_json(", 210 reason: "signed-event payload construction must name unchecked validation explicitly", 211 }, 212 ForbiddenConcept { 213 pattern: "RadrootsTransportPayload::signed_event_json_with_digest(", 214 reason: "signed-event digest validation must name unchecked validation explicitly", 215 }, 216 ]; 217 218 const FORBIDDEN_FOUNDATION_HARDENING_RETIRED_CONCEPTS: &[ForbiddenConcept] = &[ 219 ForbiddenConcept { 220 pattern: "SignedNostrEvent", 221 reason: "generic signed-event surfaces must use product-neutral signed-event names", 222 }, 223 ForbiddenConcept { 224 pattern: "RadrootsEventIndexIndexCheckpoint", 225 reason: "event-index checkpoint names must not duplicate the index noun", 226 }, 227 ForbiddenConcept { 228 pattern: "RadrootsEventsIndexed", 229 reason: "event-indexed APIs must use the singular event-index crate family", 230 }, 231 ForbiddenConcept { 232 pattern: "RADROOTS_EVENTS_VERSION", 233 reason: "event contract version constants must use the current singular event namespace", 234 }, 235 ForbiddenConcept { 236 pattern: "radroots_events", 237 reason: "crate and manifest surfaces must use the current singular event crate names", 238 }, 239 ForbiddenConcept { 240 pattern: "radroots_events_codec", 241 reason: "event codec crate surfaces must use the current singular event-codec name", 242 }, 243 ForbiddenConcept { 244 pattern: "radroots_events_indexed", 245 reason: "event index crate surfaces must use the current singular event-index name", 246 }, 247 ForbiddenConcept { 248 pattern: "radroots_local_events", 249 reason: "local event storage must not reintroduce retired local-events crate names", 250 }, 251 ForbiddenConcept { 252 pattern: "radroots_local_store", 253 reason: "runtime storage must not reintroduce retired local-store crate names", 254 }, 255 ForbiddenConcept { 256 pattern: "radroots_types", 257 reason: "shared type surfaces must use current crate ownership instead of retired types crates", 258 }, 259 ForbiddenConcept { 260 pattern: "radroots_types_bindings", 261 reason: "generated bindings must not reintroduce retired types-binding crate names", 262 }, 263 ForbiddenConcept { 264 pattern: "radroots_nostr_ndb", 265 reason: "Nostr database ownership must not reintroduce retired ndb crate names", 266 }, 267 ForbiddenConcept { 268 pattern: "radroots_replica_db", 269 reason: "replica database surfaces must use current replica-store ownership", 270 }, 271 ForbiddenConcept { 272 pattern: "radroots_replica_db_schema", 273 reason: "replica schema surfaces must use current replica-schema ownership", 274 }, 275 ForbiddenConcept { 276 pattern: "radroots_sp1_guest_trade", 277 reason: "trade SP1 crate surfaces must use the current trade_sp1 crate names", 278 }, 279 ForbiddenConcept { 280 pattern: "radroots_sp1_host_trade", 281 reason: "trade SP1 crate surfaces must use the current trade_sp1 crate names", 282 }, 283 ]; 284 285 const FORBIDDEN_FOUNDATION_HARDENING_DOC_CONCEPTS: &[ForbiddenConcept] = &[ 286 ForbiddenConcept { 287 pattern: "Nostr event timestamp", 288 reason: "generic docs must describe event-envelope timestamps without protocol leakage", 289 }, 290 ForbiddenConcept { 291 pattern: "Forwarded satisfies Delivered", 292 reason: "forwarded evidence must not be documented as strict delivery", 293 }, 294 ForbiddenConcept { 295 pattern: "StoredByGateway satisfies Delivered", 296 reason: "gateway storage evidence must not be documented as strict delivery", 297 }, 298 ForbiddenConcept { 299 pattern: "Seen satisfies Delivered", 300 reason: "seen evidence must not be documented as strict delivery", 301 }, 302 ]; 303 304 #[test] 305 fn transport_hardening_sources_reject_removed_protocol_identifiers() { 306 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 307 .parent() 308 .expect("transport crate parent"); 309 let mut findings = Vec::new(); 310 311 for relative_root in TRANSPORT_HARDENING_CRATE_SOURCE_ROOTS { 312 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 313 let source_raw = read_source(path.as_path()); 314 let source = production_source(source_raw.as_str()); 315 let relative_path = relative_path(crates_root, path.as_path()); 316 317 for concept in FORBIDDEN_TRANSPORT_CONCEPTS { 318 if contains_forbidden_concept(source, concept.pattern) { 319 findings.push(format!( 320 "{} contains removed transport concept `{}`: {}", 321 relative_path, concept.pattern, concept.reason 322 )); 323 } 324 } 325 326 for line in removed_reticulum_stage_endpoint_lines(source) { 327 findings.push(format!( 328 "{relative_path}:{line} contains removed Reticulum staging endpoint" 329 )); 330 } 331 } 332 } 333 334 assert!( 335 findings.is_empty(), 336 "transport hardening source-boundary violations:\n{}", 337 findings.join("\n") 338 ); 339 } 340 341 #[test] 342 fn core_status_contract_sources_reject_retired_public_status_fields() { 343 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 344 .parent() 345 .expect("transport crate parent"); 346 let mut findings = Vec::new(); 347 348 for relative_root in CORE_STATUS_CONTRACT_SOURCE_ROOTS { 349 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 350 let source_raw = read_source(path.as_path()); 351 let source = production_source(source_raw.as_str()); 352 let relative_path = relative_path(crates_root, path.as_path()); 353 354 for concept in FORBIDDEN_CORE_STATUS_CONCEPTS { 355 if contains_forbidden_concept(source, concept.pattern) { 356 findings.push(format!( 357 "{} contains retired core transport status concept `{}`: {}", 358 relative_path, concept.pattern, concept.reason 359 )); 360 } 361 } 362 } 363 } 364 365 assert!( 366 findings.is_empty(), 367 "core transport status source-boundary violations:\n{}", 368 findings.join("\n") 369 ); 370 } 371 372 #[test] 373 fn generic_transport_status_sources_reject_retired_relay_shaped_names() { 374 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 375 .parent() 376 .expect("transport crate parent"); 377 let mut findings = Vec::new(); 378 379 for relative_root in GENERIC_TRANSPORT_STATUS_SOURCE_ROOTS { 380 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 381 let source_raw = read_source(path.as_path()); 382 let source = production_source(source_raw.as_str()); 383 let relative_path = relative_path(crates_root, path.as_path()); 384 385 for concept in FORBIDDEN_GENERIC_TRANSPORT_STATUS_CONCEPTS { 386 if contains_forbidden_concept(source, concept.pattern) { 387 findings.push(format!( 388 "{} contains retired generic transport status concept `{}`: {}", 389 relative_path, concept.pattern, concept.reason 390 )); 391 } 392 } 393 } 394 } 395 396 assert!( 397 findings.is_empty(), 398 "generic transport status source-boundary violations:\n{}", 399 findings.join("\n") 400 ); 401 } 402 403 #[test] 404 fn core_transport_sources_reject_relay_shaped_public_contracts() { 405 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 406 .parent() 407 .expect("transport crate parent"); 408 let mut findings = Vec::new(); 409 410 for relative_root in CORE_TRANSPORT_CONTRACT_SOURCE_ROOTS { 411 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 412 let source_raw = read_source(path.as_path()); 413 let source = production_source(source_raw.as_str()); 414 let relative_path = relative_path(crates_root, path.as_path()); 415 416 for concept in FORBIDDEN_CORE_TRANSPORT_CONCEPTS { 417 if contains_forbidden_concept(source, concept.pattern) { 418 findings.push(format!( 419 "{} contains relay-shaped core transport concept `{}`: {}", 420 relative_path, concept.pattern, concept.reason 421 )); 422 } 423 } 424 } 425 } 426 427 assert!( 428 findings.is_empty(), 429 "core transport public contract source-boundary violations:\n{}", 430 findings.join("\n") 431 ); 432 } 433 434 #[test] 435 fn delivery_request_sources_require_payload_objects() { 436 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 437 .parent() 438 .expect("transport crate parent"); 439 let mut findings = Vec::new(); 440 441 for relative_root in DELIVERY_PAYLOAD_CONTRACT_SOURCE_ROOTS { 442 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 443 let source_raw = read_source(path.as_path()); 444 let source = production_source(source_raw.as_str()); 445 let relative_path = relative_path(crates_root, path.as_path()); 446 447 for concept in FORBIDDEN_DELIVERY_PAYLOAD_CONCEPTS { 448 if contains_forbidden_concept(source, concept.pattern) { 449 findings.push(format!( 450 "{} contains digest-only delivery concept `{}`: {}", 451 relative_path, concept.pattern, concept.reason 452 )); 453 } 454 } 455 } 456 } 457 458 assert!( 459 findings.is_empty(), 460 "delivery payload source-boundary violations:\n{}", 461 findings.join("\n") 462 ); 463 } 464 465 #[test] 466 fn foundation_hardening_repo_sources_reject_retired_names_and_ambiguous_docs() { 467 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 468 .parent() 469 .expect("transport crate parent"); 470 let repo_root = crates_root.parent().expect("repo root"); 471 let mut findings = Vec::new(); 472 473 for path in foundation_hardening_guard_files(repo_root) { 474 let source = read_source(path.as_path()); 475 let relative_path = relative_path(repo_root, path.as_path()); 476 477 for concept in FORBIDDEN_FOUNDATION_HARDENING_RETIRED_CONCEPTS { 478 if contains_forbidden_concept(source.as_str(), concept.pattern) { 479 findings.push(format!( 480 "{} contains retired Foundation Hardening concept `{}`: {}", 481 relative_path, concept.pattern, concept.reason 482 )); 483 } 484 } 485 486 if is_doc_surface(path.as_path()) { 487 for concept in FORBIDDEN_FOUNDATION_HARDENING_DOC_CONCEPTS { 488 if source.contains(concept.pattern) { 489 findings.push(format!( 490 "{} contains ambiguous Foundation Hardening wording `{}`: {}", 491 relative_path, concept.pattern, concept.reason 492 )); 493 } 494 } 495 } 496 } 497 498 assert!( 499 findings.is_empty(), 500 "Foundation Hardening V1 source-boundary violations:\n{}", 501 findings.join("\n") 502 ); 503 } 504 505 #[test] 506 fn workspace_consumers_use_only_the_final_split_transport_spis() { 507 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 508 .parent() 509 .expect("transport crate parent"); 510 assert!( 511 !crates_root.join("runtime").exists(), 512 "the predecessor runtime package must remain retired" 513 ); 514 let reticulum_source_raw = 515 read_source(crates_root.join("transport_reticulum/src/lib.rs").as_path()); 516 let reticulum_source = production_source(reticulum_source_raw.as_str()); 517 for required in [ 518 "impl EventSource for RadrootsReticulumTransport", 519 "impl EventSink for RadrootsReticulumTransport", 520 ] { 521 assert!( 522 reticulum_source.contains(required), 523 "Reticulum preview must implement final split SPI witness `{required}`" 524 ); 525 } 526 assert!(!reticulum_source.contains("RadrootsRuntimeTransportShim")); 527 528 let nostr_sink = read_source(crates_root.join("transport_nostr/src/sink.rs").as_path()); 529 let nostr_source = read_source(crates_root.join("transport_nostr/src/source.rs").as_path()); 530 assert!( 531 nostr_sink.contains("impl EventSink for NostrTransport"), 532 "Nostr adapter must implement the final sink SPI" 533 ); 534 assert!( 535 nostr_source.contains("impl EventSource for NostrTransport"), 536 "Nostr adapter must implement the final source SPI" 537 ); 538 assert!( 539 !nostr_sink.contains("RadrootsRuntimeTransportShim") 540 && !nostr_source.contains("RadrootsRuntimeTransportShim"), 541 "Nostr adapter must not implement the predecessor monolithic SPI" 542 ); 543 } 544 545 #[test] 546 fn canonical_workspace_consumers_reject_retired_transport_type_names() { 547 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 548 .parent() 549 .expect("transport crate parent"); 550 let mut findings = Vec::new(); 551 552 for relative_root in TRANSPORT_CONSUMER_SOURCE_ROOTS { 553 for path in rust_source_files(crates_root.join(relative_root).as_path()) { 554 if path 555 .components() 556 .any(|component| component.as_os_str() == "generated") 557 { 558 continue; 559 } 560 let source_raw = read_source(path.as_path()); 561 let source = production_source(source_raw.as_str()); 562 let relative_path = relative_path(crates_root, path.as_path()); 563 for retired in RETIRED_TRANSPORT_TYPE_NAMES { 564 if contains_forbidden_concept(source, retired) { 565 findings.push(format!( 566 "{relative_path} still consumes retired transport type `{retired}`" 567 )); 568 } 569 } 570 } 571 } 572 573 assert!( 574 findings.is_empty(), 575 "canonical transport consumer migration violations:\n{}", 576 findings.join("\n") 577 ); 578 } 579 580 #[test] 581 fn transport_publish_capabilities_keep_canonical_status_fields() { 582 let source_raw = read_source( 583 Path::new(env!("CARGO_MANIFEST_DIR")) 584 .parent() 585 .expect("transport crate parent") 586 .join("protocol/src/radrootsd/transport_publish/v5.rs") 587 .as_path(), 588 ); 589 let source = production_source(source_raw.as_str()); 590 591 for required in [ 592 "pub transport: String,", 593 "pub configured: bool,", 594 "pub implementation: Implementation,", 595 "pub maturity: CapabilityMaturity,", 596 "pub availability: CapabilityAvailability,", 597 "pub usable_for_delivery: bool,", 598 "pub capabilities: OperationCapabilities,", 599 "pub struct OperationCapabilities", 600 "pub deliver: bool,", 601 "pub fetch: bool,", 602 "pub discovery: bool,", 603 "pub gateway_forwarding: bool,", 604 "pub receipt_observation: bool,", 605 "Implementation::Real", 606 "CapabilityMaturity::Preview", 607 "CapabilityAvailability::Unavailable", 608 "configured: true", 609 "usable_for_delivery: true", 610 "usable_for_delivery: false", 611 "capabilities: OperationCapabilities", 612 "deliver: true", 613 "fetch: false", 614 "discovery: false", 615 "gateway_forwarding: false", 616 "receipt_observation: false", 617 ] { 618 assert!( 619 source.contains(required), 620 "transport publish capabilities must retain canonical status field `{required}`" 621 ); 622 } 623 624 for forbidden in [ 625 "pub implementation_state: TransportPublishImplementationState,", 626 "TransportPublishImplementationState", 627 ] { 628 assert!( 629 !source.contains(forbidden), 630 "transport publish capabilities must not retain retired status field `{forbidden}`" 631 ); 632 } 633 } 634 635 #[test] 636 fn transport_target_identity_sources_reject_silent_dedupe() { 637 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 638 .parent() 639 .expect("transport crate parent"); 640 641 let transport_source = read_source(crates_root.join("transport/src/target.rs").as_path()); 642 for required in [ 643 "let mut fingerprints = BTreeSet::new();", 644 "TransportError::DuplicateTargetFingerprint", 645 "targets.len() > TARGET_SET_MAX_ITEMS", 646 ] { 647 assert!( 648 transport_source.contains(required), 649 "transport target set source must retain duplicate rejection witness `{required}`" 650 ); 651 } 652 let target_struct = source_between( 653 transport_source.as_str(), 654 "pub struct Target {", 655 "impl Target {", 656 ); 657 for forbidden in [ 658 "pub kind:", 659 "pub uri:", 660 "pub scope:", 661 "pub label:", 662 "pub fingerprint:", 663 ] { 664 assert!( 665 !target_struct.contains(forbidden), 666 "transport target identity field must remain sealed: `{forbidden}`" 667 ); 668 } 669 for required in [ 670 "impl<'de> serde::Deserialize<'de> for Target", 671 "impl<'de> serde::Deserialize<'de> for TargetSet", 672 ] { 673 assert!( 674 transport_source.contains(required), 675 "transport target source must retain checked deserialization witness `{required}`" 676 ); 677 } 678 679 let reticulum_source = read_source( 680 crates_root 681 .join("transport_reticulum/src/contract.rs") 682 .as_path(), 683 ); 684 let destination_struct = source_between( 685 reticulum_source.as_str(), 686 "pub struct ReticulumDestinationV1 {", 687 "impl ReticulumDestinationV1 {", 688 ); 689 for forbidden in ["pub uri:", "pub routing:", "pub label:", "pub fingerprint:"] { 690 assert!( 691 !destination_struct.contains(forbidden), 692 "Reticulum destination identity field must remain sealed: `{forbidden}`" 693 ); 694 } 695 assert!( 696 reticulum_source.contains("impl<'de> serde::Deserialize<'de> for ReticulumDestinationV1"), 697 "Reticulum destination source must retain checked deserialization" 698 ); 699 700 let relay_source = read_source(crates_root.join("transport_nostr/src/relay.rs").as_path()); 701 let profile_source = read_source(crates_root.join("transport_nostr/src/profile.rs").as_path()); 702 for required in ["Target::nostr_relay(original)", "Error::DuplicateRelayUrl"] { 703 let source = if required.contains("Duplicate") { 704 profile_source.as_str() 705 } else { 706 relay_source.as_str() 707 }; 708 assert!( 709 source.contains(required), 710 "Nostr relay target source must retain canonical identity witness `{required}`" 711 ); 712 } 713 for forbidden in [ 714 "impl<'de> Deserialize<'de> for RelayUrl", 715 "impl<'de> Deserialize<'de> for RadrootsRelayTargetSet", 716 ] { 717 assert!( 718 !relay_source.contains(forbidden), 719 "policy-free Nostr relay identity must not regain deserialization: `{forbidden}`" 720 ); 721 } 722 723 let protocol_source = read_source( 724 crates_root 725 .join("protocol/src/radrootsd/transport_publish/v5.rs") 726 .as_path(), 727 ); 728 for required in [ 729 "target.validate_structure(index)?;", 730 "return Err(Error::DuplicateTarget { index });", 731 "Err(Error::DuplicateTarget { index: 1 })", 732 ] { 733 assert!( 734 protocol_source.contains(required), 735 "transport publish protocol must retain explicit-target duplicate rejection witness `{required}`" 736 ); 737 } 738 739 assert!( 740 !crates_root.join("outbox").exists(), 741 "the predecessor outbox package must remain retired" 742 ); 743 } 744 745 #[test] 746 fn required_target_semantics_stay_fingerprint_exact() { 747 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 748 .parent() 749 .expect("transport crate parent"); 750 751 let protocol_source = read_source( 752 crates_root 753 .join("protocol/src/radrootsd/transport_publish/v5.rs") 754 .as_path(), 755 ); 756 for required in [ 757 "Self::RequiredTargets { targets } => targets.len()", 758 "validate_required_target_fingerprints(targets.as_slice())", 759 "Error::DuplicateRequiredTargetFingerprint { index }", 760 "Matching fingerprints to native targets is intentionally deferred", 761 ] { 762 assert!( 763 protocol_source.contains(required), 764 "transport publish protocol must retain exact required-target witness `{required}`" 765 ); 766 } 767 768 let nostr_publish_source = 769 read_source(crates_root.join("transport_nostr/src/sink.rs").as_path()); 770 for required in [ 771 "DeliveryReceipt::for_request(&request, receipts)", 772 "DeliveryTargetReceipt::attempted(target, outcome)", 773 "DeliveryTargetReceipt::skipped(", 774 ] { 775 assert!( 776 nostr_publish_source.contains(required), 777 "Nostr sink must delegate exact target satisfaction to the generic receipt contract `{required}`" 778 ); 779 } 780 assert!(!crates_root.join("transport_nostr/src/outbox.rs").exists()); 781 assert!(!crates_root.join("transport_nostr/src/publish.rs").exists()); 782 } 783 784 #[test] 785 fn transport_identity_is_extensible_and_reticulum_contracts_are_preview_owned() { 786 let crates_root = Path::new(env!("CARGO_MANIFEST_DIR")) 787 .parent() 788 .expect("transport crate parent"); 789 let transport_id = read_source(crates_root.join("transport/src/id.rs").as_path()); 790 for required in [ 791 "pub struct TransportId(", 792 "pub const LOCAL:", 793 "pub const NOSTR:", 794 "pub const RETICULUM:", 795 "pub const RADROOTSD:", 796 "ProtocolTransportKind::parse", 797 ] { 798 assert!( 799 transport_id.contains(required), 800 "transport identity source must retain extensible identity witness `{required}`" 801 ); 802 } 803 assert!(!transport_id.contains("pub enum TransportId")); 804 805 let protocol_identity = 806 read_source(crates_root.join("protocol/src/capability/v1.rs").as_path()); 807 assert!(protocol_identity.contains("pub struct TransportKind")); 808 assert!(!protocol_identity.contains("pub enum TransportKind")); 809 assert!(protocol_identity.contains("MAX_TRANSPORT_KIND_BYTES")); 810 811 let transport_root_source = read_source(crates_root.join("transport/src/lib.rs").as_path()); 812 for forbidden in [ 813 "RADROOTS_RETICULUM_ENDPOINT_URI", 814 "RADROOTS_RETICULUM_SCOPE_ID", 815 "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE", 816 "ReticulumCapabilityReportV1", 817 "ReticulumDestinationV1", 818 "ReticulumFragmentPolicyV1", 819 "ReticulumPayloadPolicyV1", 820 "ReticulumRoutingMetadataV1", 821 ] { 822 assert!( 823 !transport_root_source.contains(forbidden), 824 "generic transport root must not expose Reticulum-specific symbol `{forbidden}`" 825 ); 826 } 827 assert!( 828 !crates_root.join("transport/src/message.rs").exists(), 829 "generic transport must not retain the Reticulum message module" 830 ); 831 assert!( 832 !crates_root.join("transport/src/reticulum.rs").exists(), 833 "generic transport must not retain the Reticulum contract module" 834 ); 835 836 let reticulum_message_source = read_source( 837 crates_root 838 .join("transport_reticulum/src/message.rs") 839 .as_path(), 840 ); 841 for required in [ 842 "RADROOTS_RETICULUM_ENDPOINT_URI", 843 "reticulum:local", 844 "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE", 845 "Reticulum transport is configured, ", 846 "but this build does not implement Reticulum delivery.", 847 ] { 848 assert!( 849 reticulum_message_source.contains(required), 850 "private Reticulum message source must retain contract witness `{required}`" 851 ); 852 } 853 for forbidden in [ 854 "Reticulum prerelease transport is registered, ", 855 "future compatibility", 856 "compatibility mode", 857 "fallback behavior", 858 "hidden transport substitution", 859 ] { 860 assert!( 861 !reticulum_message_source.contains(forbidden), 862 "private Reticulum message source must not retain superseded copy `{forbidden}`" 863 ); 864 } 865 866 let reticulum_source = 867 read_source(crates_root.join("transport_reticulum/src/lib.rs").as_path()); 868 assert!( 869 reticulum_source.contains("RADROOTS_RETICULUM_ENDPOINT_URI"), 870 "Reticulum source must consume its preview-owned endpoint URI constant" 871 ); 872 assert!( 873 !reticulum_source.contains(["reticulum:", "pre", "view-unavailable"].concat().as_str()), 874 "Reticulum source must not duplicate its endpoint URI" 875 ); 876 assert!( 877 reticulum_source.contains("RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE"), 878 "Reticulum source must consume its preview-owned unavailable message constant" 879 ); 880 assert!( 881 !reticulum_source.contains("Reticulum transport is configured in preview mode"), 882 "Reticulum source must not duplicate the shared unavailable message" 883 ); 884 assert!( 885 !reticulum_source.contains("future compatibility"), 886 "Reticulum source must not duplicate compatibility copy" 887 ); 888 889 let protocol_source_raw = read_source( 890 crates_root 891 .join("protocol/src/radrootsd/transport_publish/v5.rs") 892 .as_path(), 893 ); 894 let protocol_source = production_source(protocol_source_raw.as_str()); 895 assert!( 896 protocol_source.contains("pub const RETICULUM_ENDPOINT_URI: &str = \"reticulum:local\";"), 897 "versioned transport publish protocol must own its endpoint constant" 898 ); 899 assert!( 900 !protocol_source.contains(["reticulum:", "pre", "view-unavailable"].concat().as_str()), 901 "transport publish protocol must not duplicate the shared endpoint URI" 902 ); 903 assert!( 904 protocol_source.contains("pub const RETICULUM_UNAVAILABLE_MESSAGE: &str = concat!("), 905 "versioned transport publish protocol must own its unavailable message" 906 ); 907 assert!( 908 !protocol_source.contains("Reticulum transport is configured in preview mode"), 909 "transport publish protocol must not duplicate the shared unavailable message" 910 ); 911 assert!( 912 !protocol_source.contains("future compatibility"), 913 "transport publish protocol must not duplicate compatibility copy" 914 ); 915 } 916 917 fn rust_source_files(root: &Path) -> Vec<PathBuf> { 918 let mut paths = Vec::new(); 919 collect_rust_source_files(root, &mut paths); 920 paths.sort(); 921 paths 922 } 923 924 fn foundation_hardening_guard_files(repo_root: &Path) -> Vec<PathBuf> { 925 let mut paths = Vec::new(); 926 927 for path in [ 928 repo_root.join("Cargo.toml"), 929 repo_root.join("README"), 930 repo_root.join("README.md"), 931 ] { 932 if path.exists() { 933 paths.push(path); 934 } 935 } 936 937 let crates_root = repo_root.join("crates"); 938 for entry in fs::read_dir(crates_root.as_path()) 939 .unwrap_or_else(|error| panic!("failed to read {}: {error}", crates_root.display())) 940 { 941 let path = entry.expect("crate entry").path(); 942 if !path.is_dir() { 943 continue; 944 } 945 946 let src = path.join("src"); 947 if src.exists() { 948 paths.extend(rust_source_files(src.as_path())); 949 } 950 951 for file_name in ["Cargo.toml", "README", "README.md"] { 952 let candidate = path.join(file_name); 953 if candidate.exists() { 954 paths.push(candidate); 955 } 956 } 957 } 958 959 for relative_root in FOUNDATION_HARDENING_DOC_ROOTS { 960 let root = repo_root.join(relative_root); 961 if root.exists() { 962 collect_doc_surface_files(root.as_path(), &mut paths); 963 } 964 } 965 966 paths.sort(); 967 paths 968 } 969 970 fn collect_doc_surface_files(root: &Path, paths: &mut Vec<PathBuf>) { 971 for entry in fs::read_dir(root) 972 .unwrap_or_else(|error| panic!("failed to read {}: {error}", root.display())) 973 { 974 let path = entry.expect("doc surface entry").path(); 975 if path.is_dir() { 976 collect_doc_surface_files(path.as_path(), paths); 977 continue; 978 } 979 980 if is_doc_surface(path.as_path()) { 981 paths.push(path); 982 } 983 } 984 } 985 986 fn collect_rust_source_files(root: &Path, paths: &mut Vec<PathBuf>) { 987 for entry in fs::read_dir(root) 988 .unwrap_or_else(|error| panic!("failed to read {}: {error}", root.display())) 989 { 990 let entry = entry.expect("read source entry"); 991 let path = entry.path(); 992 if path.is_dir() { 993 collect_rust_source_files(path.as_path(), paths); 994 } else if path.extension().and_then(|extension| extension.to_str()) == Some("rs") { 995 paths.push(path); 996 } 997 } 998 } 999 1000 fn read_source(path: &Path) -> String { 1001 fs::read_to_string(path) 1002 .unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display())) 1003 } 1004 1005 fn production_source(source: &str) -> &str { 1006 source 1007 .find("\n#[cfg(test)]") 1008 .map_or(source, |index| &source[..index]) 1009 } 1010 1011 fn is_doc_surface(path: &Path) -> bool { 1012 matches!( 1013 path.file_name().and_then(|file_name| file_name.to_str()), 1014 Some("README") | Some("README.md") 1015 ) || matches!( 1016 path.extension().and_then(|extension| extension.to_str()), 1017 Some("md") 1018 ) 1019 } 1020 1021 fn relative_path(root: &Path, path: &Path) -> String { 1022 path.strip_prefix(root) 1023 .expect("source path is under crate root") 1024 .to_string_lossy() 1025 .replace('\\', "/") 1026 } 1027 1028 fn source_between<'source>( 1029 source: &'source str, 1030 start_marker: &str, 1031 end_marker: &str, 1032 ) -> &'source str { 1033 let start = source 1034 .find(start_marker) 1035 .unwrap_or_else(|| panic!("failed to find source marker `{start_marker}`")); 1036 let source_after_start = &source[start..]; 1037 let end = source_after_start 1038 .find(end_marker) 1039 .unwrap_or_else(|| panic!("failed to find source marker `{end_marker}`")); 1040 &source_after_start[..end] 1041 } 1042 1043 fn contains_forbidden_concept(source: &str, pattern: &str) -> bool { 1044 if !pattern.chars().all(is_rust_identifier_character) { 1045 return source.contains(pattern); 1046 } 1047 1048 source.match_indices(pattern).any(|(index, _)| { 1049 let before = source[..index].chars().next_back(); 1050 let after = source[index + pattern.len()..].chars().next(); 1051 before.is_none_or(|character| !is_rust_identifier_character(character)) 1052 && after.is_none_or(|character| !is_rust_identifier_character(character)) 1053 }) 1054 } 1055 1056 fn removed_reticulum_stage_endpoint_lines(source: &str) -> Vec<usize> { 1057 let removed_endpoint_prefix = ["reticulum:", "pre", "view"].concat(); 1058 source 1059 .match_indices(removed_endpoint_prefix.as_str()) 1060 .filter_map(|(index, _)| { 1061 let after = source[index + removed_endpoint_prefix.len()..] 1062 .chars() 1063 .next(); 1064 (after != Some('-')).then(|| line_number(source, index)) 1065 }) 1066 .collect() 1067 } 1068 1069 fn is_rust_identifier_character(character: char) -> bool { 1070 character == '_' || character.is_ascii_alphanumeric() 1071 } 1072 1073 fn line_number(source: &str, index: usize) -> usize { 1074 source[..index] 1075 .bytes() 1076 .filter(|byte| *byte == b'\n') 1077 .count() 1078 + 1 1079 }