lib

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

commit 31311d6b52a7bb7cee12708662ae170c19db0a39
parent 85abda4da9ffd76f4967ac61df519d5095172a81
Author: triesap <tyson@radroots.org>
Date:   Tue, 14 Jul 2026 18:48:44 +0000

event: harden source boundary guard

Diffstat:
Mcrates/event/tests/source_boundary.rs | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 128 insertions(+), 22 deletions(-)

diff --git a/crates/event/tests/source_boundary.rs b/crates/event/tests/source_boundary.rs @@ -1,36 +1,142 @@ use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; + +struct ForbiddenEventName { + pattern: &'static str, + reason: &'static str, +} + +const FORBIDDEN_EVENT_NAMES: &[ForbiddenEventName] = &[ + ForbiddenEventName { + pattern: "WireEventParts", + reason: "event construction must use RadrootsNip01EventWireParts", + }, + ForbiddenEventName { + pattern: "RadrootsFrozenEventDraft", + reason: "event construction must use RadrootsEventDraft", + }, + ForbiddenEventName { + pattern: "RadrootsNostrEvent", + reason: "product-level event surfaces must use protocol-neutral domain names", + }, + ForbiddenEventName { + pattern: "RadrootsNostrEventRef", + reason: "product-level event references must use RadrootsEventRef", + }, + ForbiddenEventName { + pattern: "RadrootsNostrEventPtr", + reason: "product-level event pointers must use RadrootsEventPtr", + }, + ForbiddenEventName { + pattern: "RadrootsSignedNostrEvent", + reason: "signed-event surfaces must use RadrootsSignedEvent", + }, + ForbiddenEventName { + pattern: "RadrootsSignedNostrEventParts", + reason: "signed-event parts must use protocol-neutral domain names", + }, +]; #[test] fn generic_event_source_does_not_reintroduce_old_core_nostr_event_names() { - let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); - let source = read_sources(&src); - - for forbidden in [ - "RadrootsNostrEvent", - "RadrootsNostrEventRef", - "RadrootsNostrEventPtr", - "RadrootsFrozenEventDraft", - "RadrootsSignedNostrEvent", - "RadrootsSignedNostrEventParts", - ] { - assert!( - !source.contains(forbidden), - "radroots_event generic source must not expose old core event name `{forbidden}`" - ); + let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("lib repo root"); + let mut findings = Vec::new(); + + for path in source_boundary_guard_files(repo_root) { + let relative_path = relative_path(repo_root, path.as_path()); + let source = fs::read_to_string(path.as_path()).expect("read source file"); + for forbidden in FORBIDDEN_EVENT_NAMES { + if forbidden_event_name_allowed(relative_path.as_str(), forbidden.pattern) { + continue; + } + if contains_forbidden_event_name(source.as_str(), forbidden.pattern) { + findings.push(format!( + "{} contains retired event concept `{}`: {}", + relative_path, forbidden.pattern, forbidden.reason + )); + } + } + } + + assert!( + findings.is_empty(), + "radroots_event source-boundary violations:\n{}", + findings.join("\n") + ); +} + +fn source_boundary_guard_files(repo_root: &Path) -> Vec<PathBuf> { + let mut paths = Vec::new(); + for relative_root in ["crates", "contracts"] { + let root = repo_root.join(relative_root); + if root.exists() { + collect_source_boundary_guard_files(root.as_path(), &mut paths); + } } + paths.sort(); + paths } -fn read_sources(root: &Path) -> String { - let mut source = String::new(); +fn collect_source_boundary_guard_files(root: &Path, paths: &mut Vec<PathBuf>) { for entry in fs::read_dir(root).expect("read source directory") { let entry = entry.expect("source entry"); let path = entry.path(); if path.is_dir() { - source.push_str(read_sources(&path).as_str()); - } else if path.extension().is_some_and(|extension| extension == "rs") { - source.push_str(fs::read_to_string(path).expect("read source file").as_str()); + if matches!( + path.file_name().and_then(|file_name| file_name.to_str()), + Some("target") + ) { + continue; + } + collect_source_boundary_guard_files(path.as_path(), paths); + continue; + } + + if path.file_name().and_then(|file_name| file_name.to_str()) == Some("source_boundary.rs") { + continue; + } + + if matches!( + path.extension().and_then(|extension| extension.to_str()), + Some("rs") | Some("toml") | Some("md") + ) { + paths.push(path); } } - source +} + +fn forbidden_event_name_allowed(relative_path: &str, pattern: &str) -> bool { + pattern == "RadrootsNostrEvent" && is_nostr_protocol_context(relative_path) +} + +fn is_nostr_protocol_context(relative_path: &str) -> bool { + relative_path.starts_with("crates/nostr/") + || relative_path.starts_with("crates/nostrdb/") + || relative_path.starts_with("crates/nostr_runtime/") + || relative_path.starts_with("crates/nostr_signer/") + || relative_path.starts_with("crates/transport_nostr/") + || relative_path.starts_with("crates/net/src/nostr_client/") +} + +fn contains_forbidden_event_name(source: &str, pattern: &str) -> bool { + source.match_indices(pattern).any(|(index, _)| { + let before = source[..index].chars().next_back(); + let after = source[index + pattern.len()..].chars().next(); + before.is_none_or(|character| !is_identifier_character(character)) + && after.is_none_or(|character| !is_identifier_character(character)) + }) +} + +fn is_identifier_character(character: char) -> bool { + character == '_' || character.is_ascii_alphanumeric() +} + +fn relative_path(root: &Path, path: &Path) -> String { + path.strip_prefix(root) + .expect("source path is under repo root") + .to_string_lossy() + .replace('\\', "/") }