radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

source_boundary.rs (24934B)


      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 FORBIDDEN_DAEMON_TRANSPORT_CONCEPTS: &[ForbiddenConcept] = &[
     12     ForbiddenConcept {
     13         pattern: "\"radrootsd_proxy\"",
     14         reason: "proxy-as-transport targets are removed",
     15     },
     16     ForbiddenConcept {
     17         pattern: "radrootsd.publish_proxy.v1",
     18         reason: "daemon publish proxy v1 is removed",
     19     },
     20     ForbiddenConcept {
     21         pattern: "publish.relays.resolve",
     22         reason: "relay-resolution RPC is replaced by transport publish target policy",
     23     },
     24     ForbiddenConcept {
     25         pattern: "\"publish.event\"",
     26         reason: "publish.event is replaced by transport.publish.event",
     27     },
     28     ForbiddenConcept {
     29         pattern: "\"transport_kinds\"",
     30         reason: "capabilities must expose per-transport readiness instead of kind-only lists",
     31     },
     32     ForbiddenConcept {
     33         pattern: "allowed_relay_policy",
     34         reason: "relay policy is Nostr-specific and must not be a generic transport API",
     35     },
     36     ForbiddenConcept {
     37         pattern: "relay_policy",
     38         reason: "relay policy is Nostr-specific and must not be a generic transport API",
     39     },
     40     ForbiddenConcept {
     41         pattern: "PublishRelayPolicy",
     42         reason: "old relay-shaped publish policy names must not return",
     43     },
     44     ForbiddenConcept {
     45         pattern: "PublishRelayOutcome",
     46         reason: "old relay-shaped publish outcome names must not return",
     47     },
     48     ForbiddenConcept {
     49         pattern: "PublishRelaySource",
     50         reason: "old relay-shaped publish source names must not return",
     51     },
     52     ForbiddenConcept {
     53         pattern: "radroots_relay_transport",
     54         reason: "daemon must depend on radroots_transport_nostr directly without a dependency alias",
     55     },
     56 ];
     57 
     58 const FORBIDDEN_FOUNDATION_HARDENING_RETIRED_CONCEPTS: &[ForbiddenConcept] = &[
     59     ForbiddenConcept {
     60         pattern: "WireEventParts",
     61         reason: "event construction must use current Radroots NIP-01 wire part names",
     62     },
     63     ForbiddenConcept {
     64         pattern: "RadrootsFrozenEventDraft",
     65         reason: "event draft surfaces must use the current RadrootsEventDraft name",
     66     },
     67     ForbiddenConcept {
     68         pattern: "RadrootsNostrEvent",
     69         reason: "product-level event surfaces must use protocol-neutral event names",
     70     },
     71     ForbiddenConcept {
     72         pattern: "RadrootsNostrEventRef",
     73         reason: "product-level event references must use RadrootsEventRef",
     74     },
     75     ForbiddenConcept {
     76         pattern: "RadrootsNostrEventPtr",
     77         reason: "product-level event pointers must use RadrootsEventPtr",
     78     },
     79     ForbiddenConcept {
     80         pattern: "SignedNostrEvent",
     81         reason: "generic signed-event surfaces must use product-neutral signed-event names",
     82     },
     83     ForbiddenConcept {
     84         pattern: "RadrootsEventIndexIndexCheckpoint",
     85         reason: "event-index checkpoint names must not duplicate the index noun",
     86     },
     87     ForbiddenConcept {
     88         pattern: "RadrootsEventsIndexed",
     89         reason: "event-indexed APIs must use the singular event-index crate family",
     90     },
     91     ForbiddenConcept {
     92         pattern: "RADROOTS_EVENTS_VERSION",
     93         reason: "event contract version constants must use the current singular event namespace",
     94     },
     95     ForbiddenConcept {
     96         pattern: "radroots_events",
     97         reason: "daemon surfaces must use the current singular event crate names",
     98     },
     99     ForbiddenConcept {
    100         pattern: "radroots_events_codec",
    101         reason: "daemon event codec surfaces must use the current singular event-codec name",
    102     },
    103     ForbiddenConcept {
    104         pattern: "radroots_events_indexed",
    105         reason: "daemon event index surfaces must use the current singular event-index name",
    106     },
    107     ForbiddenConcept {
    108         pattern: "radroots_local_events",
    109         reason: "daemon storage surfaces must not reintroduce retired local-events names",
    110     },
    111     ForbiddenConcept {
    112         pattern: "radroots_local_store",
    113         reason: "daemon storage surfaces must not reintroduce retired local-store names",
    114     },
    115     ForbiddenConcept {
    116         pattern: "radroots_types",
    117         reason: "daemon type surfaces must use current crate ownership instead of retired types crates",
    118     },
    119     ForbiddenConcept {
    120         pattern: "radroots_types_bindings",
    121         reason: "daemon generated surfaces must not reintroduce retired types-binding names",
    122     },
    123     ForbiddenConcept {
    124         pattern: "radroots_nostr_ndb",
    125         reason: "daemon Nostr database surfaces must not reintroduce retired ndb names",
    126     },
    127     ForbiddenConcept {
    128         pattern: "radroots_replica_db",
    129         reason: "daemon replica surfaces must use current replica-store ownership",
    130     },
    131     ForbiddenConcept {
    132         pattern: "radroots_replica_db_schema",
    133         reason: "daemon replica schema surfaces must use current replica-schema ownership",
    134     },
    135     ForbiddenConcept {
    136         pattern: "radroots_sp1_guest_trade",
    137         reason: "daemon trade SP1 surfaces must use the current trade_sp1 crate names",
    138     },
    139     ForbiddenConcept {
    140         pattern: "radroots_sp1_host_trade",
    141         reason: "daemon trade SP1 surfaces must use the current trade_sp1 crate names",
    142     },
    143 ];
    144 
    145 const FORBIDDEN_FOUNDATION_HARDENING_DOC_CONCEPTS: &[ForbiddenConcept] = &[
    146     ForbiddenConcept {
    147         pattern: "Nostr event timestamp",
    148         reason: "daemon docs must describe event-envelope timestamps without generic Nostr wording",
    149     },
    150     ForbiddenConcept {
    151         pattern: "Forwarded satisfies Delivered",
    152         reason: "daemon docs must not imply forwarded evidence is strict delivery",
    153     },
    154     ForbiddenConcept {
    155         pattern: "StoredByGateway satisfies Delivered",
    156         reason: "daemon docs must not imply gateway storage evidence is strict delivery",
    157     },
    158     ForbiddenConcept {
    159         pattern: "Seen satisfies Delivered",
    160         reason: "daemon docs must not imply seen evidence is strict delivery",
    161     },
    162 ];
    163 
    164 #[test]
    165 fn transport_publish_sources_reject_removed_protocol_identifiers() {
    166     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    167     let src_dir = manifest_dir.join("src");
    168     let mut findings = Vec::new();
    169 
    170     for path in rust_source_files(src_dir.as_path()) {
    171         let source = read_source(path.as_path());
    172         let relative_path = relative_path(manifest_dir, path.as_path());
    173 
    174         for concept in FORBIDDEN_DAEMON_TRANSPORT_CONCEPTS {
    175             if contains_forbidden_concept(source.as_str(), concept.pattern) {
    176                 findings.push(format!(
    177                     "{} contains removed daemon transport concept `{}`: {}",
    178                     relative_path, concept.pattern, concept.reason
    179                 ));
    180             }
    181         }
    182 
    183         for line in removed_reticulum_endpoint_alias_lines(source.as_str()) {
    184             findings.push(format!(
    185                 "{relative_path}:{line} contains removed Reticulum endpoint alias"
    186             ));
    187         }
    188     }
    189 
    190     let manifest_source = read_source(manifest_dir.join("Cargo.toml").as_path());
    191     for concept in FORBIDDEN_DAEMON_TRANSPORT_CONCEPTS {
    192         if contains_forbidden_concept(manifest_source.as_str(), concept.pattern) {
    193             findings.push(format!(
    194                 "Cargo.toml contains removed daemon transport concept `{}`: {}",
    195                 concept.pattern, concept.reason
    196             ));
    197         }
    198     }
    199     assert!(
    200         manifest_source.contains(
    201             "radroots_transport_nostr = { git = \"https://github.com/radrootslabs/lib.git\", rev = \"3563f3b5a4331eb2cb3f925cafc9de524d844228\", version = \"=0.1.0-alpha\""
    202         ),
    203         "Cargo.toml must pin radroots_transport_nostr to the approved Lib revision and exact version"
    204     );
    205     assert!(
    206         !manifest_source.contains("package = \"radroots_transport_nostr\""),
    207         "Cargo.toml must not disguise radroots_transport_nostr through a package alias"
    208     );
    209     assert!(
    210         !manifest_source.contains("path = \"../lib"),
    211         "Cargo.toml must not retain sibling source dependencies"
    212     );
    213     assert!(
    214         !manifest_source.contains("radroots_runtime"),
    215         "daemon lifecycle and path policy must remain host-owned"
    216     );
    217 
    218     assert!(
    219         findings.is_empty(),
    220         "daemon transport source-boundary violations:\n{}",
    221         findings.join("\n")
    222     );
    223 }
    224 
    225 #[test]
    226 fn foundation_hardening_sources_reject_retired_names_and_ambiguous_docs() {
    227     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    228     let mut findings = Vec::new();
    229 
    230     for path in foundation_hardening_guard_files(manifest_dir) {
    231         let source_raw = read_source(path.as_path());
    232         let source = if path.extension().and_then(|extension| extension.to_str()) == Some("rs") {
    233             production_source(source_raw.as_str())
    234         } else {
    235             source_raw.as_str()
    236         };
    237         let relative_path = relative_path(manifest_dir, path.as_path());
    238 
    239         for concept in FORBIDDEN_FOUNDATION_HARDENING_RETIRED_CONCEPTS {
    240             if foundation_hardening_retired_concept_allowed(relative_path.as_str(), concept.pattern)
    241             {
    242                 continue;
    243             }
    244             if contains_forbidden_concept(source, concept.pattern) {
    245                 findings.push(format!(
    246                     "{} contains retired Foundation Hardening concept `{}`: {}",
    247                     relative_path, concept.pattern, concept.reason
    248                 ));
    249             }
    250         }
    251 
    252         if is_doc_surface(path.as_path()) {
    253             for concept in FORBIDDEN_FOUNDATION_HARDENING_DOC_CONCEPTS {
    254                 if source.contains(concept.pattern) {
    255                     findings.push(format!(
    256                         "{} contains ambiguous Foundation Hardening wording `{}`: {}",
    257                         relative_path, concept.pattern, concept.reason
    258                     ));
    259                 }
    260             }
    261         }
    262     }
    263 
    264     assert!(
    265         findings.is_empty(),
    266         "daemon Foundation Hardening V1 source-boundary violations:\n{}",
    267         findings.join("\n")
    268     );
    269 }
    270 
    271 #[test]
    272 fn transport_publish_sources_reject_removed_execution_transport_targets() {
    273     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    274     use radroots_protocol::radrootsd::transport_publish::v5::{
    275         DeliveryPolicy, Error, EventRequest, Target, TargetPolicy,
    276     };
    277     let request = EventRequest {
    278         raw_event_json: "{}".to_owned(),
    279         target_policy: TargetPolicy::explicit_targets(vec![Target {
    280             transport_kind: "proxy".to_owned(),
    281             endpoint_uri: "proxy:publish".to_owned(),
    282             target_scope: None,
    283             target_label: None,
    284             reticulum_behavior: None,
    285         }]),
    286         delivery_policy: DeliveryPolicy::Any,
    287         idempotency_key: None,
    288         timeout_ms: None,
    289     };
    290     assert_eq!(
    291         request.validate(20),
    292         Err(Error::InvalidTransportKind { index: 0 })
    293     );
    294 
    295     let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
    296     for required in [
    297         "publish_event_rejects_removed_execution_kind_before_recording_job",
    298         "publish_event_rejects_removed_execution_target_before_recording_job",
    299         "!matches!(canonical.as_str(), \"local\" | \"nostr\" | \"reticulum\")",
    300     ] {
    301         assert!(
    302             daemon_source.contains(required),
    303             "daemon transport publish tests must prove removed execution transport targets are rejected before job recording"
    304         );
    305     }
    306 }
    307 
    308 #[test]
    309 fn transport_publish_sources_require_principal_explicit_kind_scope() {
    310     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    311     let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
    312 
    313     for required in [
    314         "allowed_explicit_transport_kinds_json",
    315         "pub allowed_explicit_transport_kinds: Vec<String>,",
    316         "parse_explicit_transport_kind",
    317         "principal must include at least one allowed explicit transport kind",
    318         "principal is not allowed to use explicit transport target kind",
    319         "publish_event_records_explicit_nostr_target_when_kind_allowed",
    320         "publish_event_rejects_explicit_target_kind_not_allowed_before_recording_job",
    321     ] {
    322         assert!(
    323             daemon_source.contains(required),
    324             "daemon transport publish sources must retain explicit target kind-scope witness `{required}`"
    325         );
    326     }
    327 }
    328 
    329 #[test]
    330 fn transport_publish_sources_reject_runtime_principal_schema_repair() {
    331     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    332     let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
    333 
    334     for required in [
    335         "validate_transport_publish_schema",
    336         "TransportPublishError::Schema",
    337         "transport_store_open_validates_current_principal_schema",
    338         "transport_store_open_rejects_legacy_principal_schema_without_explicit_kind_allowlist",
    339     ] {
    340         assert!(
    341             daemon_source.contains(required),
    342             "daemon transport publish source must retain strict schema validation witness `{required}`"
    343         );
    344     }
    345 
    346     for forbidden in [
    347         concat!("ensure_transport_publish", "_schema"),
    348         concat!("ALTER TABLE ", "transport_publish_principals"),
    349     ] {
    350         assert!(
    351             !daemon_source.contains(forbidden),
    352             "daemon transport publish source must not contain runtime schema repair `{forbidden}`"
    353         );
    354     }
    355 }
    356 
    357 #[test]
    358 fn transport_publish_store_egress_requires_protocol_validation() {
    359     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    360     let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
    361 
    362     for required in [
    363         "fn finalize_job_row_for_egress",
    364         "job.view.targets = self.target_outcomes(job.view.job_id.as_str())?;",
    365         "finalize_job_view(&mut job.view);",
    366         "job.view\n            .validate()",
    367         "TransportPublishError::InvalidPublishJobState",
    368         "target_scope TEXT NOT NULL",
    369         "target_label TEXT",
    370         "PRIMARY KEY(job_id, transport_kind, endpoint_uri, target_scope)",
    371         "storage_target_scope_to_protocol",
    372         "publish_outcomes_from_receipt",
    373         "store_egress_rejects_malformed_target_counts_for_get_list_and_dedupe",
    374         "store_egress_rejects_explicit_target_outcome_drift",
    375         "store_egress_rejects_recovered_explicit_target_snapshot_drift",
    376     ] {
    377         assert!(
    378             daemon_source.contains(required),
    379             "daemon transport publish store must retain validated public egress witness `{required}`"
    380         );
    381     }
    382 }
    383 
    384 #[test]
    385 fn transport_publish_required_targets_stay_fingerprint_exact() {
    386     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    387     let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
    388     for required in [
    389         "validate_delivery_policy_for_resolution",
    390         "let target_fingerprints = resolution.target_fingerprints()?;",
    391         ".any(|actual| actual.as_str() == required.as_str())",
    392         "fn required_outcomes_for_policy",
    393         "target_outcome_fingerprint(outcome, index)",
    394         "fingerprint.as_str() == required.as_str()",
    395         "let required_outcomes = required_outcomes_for_policy(targets, outcomes);",
    396         "required_outcomes.len() == targets.len()",
    397     ] {
    398         assert!(
    399             daemon_source.contains(required),
    400             "daemon transport publish must retain exact required-target witness `{required}`"
    401         );
    402     }
    403 
    404     assert!(
    405         !daemon_source.contains("SatisfactionPolicy"),
    406         "daemon V5 delivery policy must not be duplicated in the transport adapter"
    407     );
    408 
    409     use radroots_protocol::radrootsd::transport_publish::v5::{DeliveryPolicy, TargetFingerprint};
    410     let target = TargetFingerprint::parse("a".repeat(64)).expect("fingerprint");
    411     let policy = DeliveryPolicy::required_targets(vec![target]).expect("required targets");
    412     assert_eq!(policy.required_target_count(9), 1);
    413 }
    414 
    415 #[test]
    416 fn transport_publish_capabilities_expose_per_transport_readiness() {
    417     let methods_source = read_source(
    418         Path::new(env!("CARGO_MANIFEST_DIR"))
    419             .join("src/transport/jsonrpc/methods/mod.rs")
    420             .as_path(),
    421     );
    422     for required in [
    423         "transport.publish.capabilities",
    424         "serde_json::from_str(response.get())",
    425         r#"result["api_version"]"#,
    426         "radrootsd.transport_publish.v5",
    427         r#"result["publish"]["raw_event_json_ingress"]"#,
    428         r#"transport["transport"] == "reticulum""#,
    429         r#"reticulum["configured"]"#,
    430         r#"reticulum["implementation"]"#,
    431         r#"reticulum["usable_for_delivery"]"#,
    432         r#"reticulum["capabilities"]"#,
    433         r#""deliver": false"#,
    434         r#""fetch": false"#,
    435         r#""discovery": false"#,
    436         r#""gateway_forwarding": false"#,
    437         r#""receipt_observation": false"#,
    438         "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE",
    439     ] {
    440         assert!(
    441             methods_source.contains(required),
    442             "daemon capabilities tests must retain transport readiness witness `{required}`"
    443         );
    444     }
    445 
    446     use radroots_protocol::radrootsd::transport_publish::v5::{
    447         Capabilities, Implementation, RETICULUM_UNAVAILABLE_MESSAGE,
    448     };
    449     let capabilities = Capabilities::v5(1024, 20);
    450     let reticulum = capabilities
    451         .publish
    452         .transports
    453         .iter()
    454         .find(|transport| transport.transport == "reticulum")
    455         .expect("reticulum capability");
    456     assert!(reticulum.configured);
    457     assert_eq!(reticulum.implementation, Implementation::Real);
    458     assert!(!reticulum.usable_for_delivery);
    459     assert!(!reticulum.capabilities.deliver);
    460     assert_eq!(reticulum.message, RETICULUM_UNAVAILABLE_MESSAGE);
    461 }
    462 
    463 #[test]
    464 fn transport_publish_reticulum_unavailable_outcomes_use_shared_unavailable_message() {
    465     let daemon_source = read_source(
    466         Path::new(env!("CARGO_MANIFEST_DIR"))
    467             .join("src/core/transport_publish.rs")
    468             .as_path(),
    469     );
    470     for required in [
    471         "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE",
    472         "message: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned())",
    473         "publish_event_records_reticulum_unavailable_as_deferred_until_implemented",
    474     ] {
    475         assert!(
    476             daemon_source.contains(required),
    477             "daemon Reticulum unavailable publish outcomes must retain shared unavailable-message witness `{required}`"
    478         );
    479     }
    480 
    481     let retired_message = [
    482         "reticulum transport is registered for preview",
    483         "not routable by radrootsd",
    484     ]
    485     .join(" but ");
    486     assert!(
    487         !daemon_source.contains(retired_message.as_str()),
    488         "daemon Reticulum unavailable publish outcomes must not revive local unavailable-message copy"
    489     );
    490 }
    491 
    492 fn rust_source_files(root: &Path) -> Vec<PathBuf> {
    493     let mut paths = Vec::new();
    494     collect_rust_source_files(root, &mut paths);
    495     paths.sort();
    496     paths
    497 }
    498 
    499 fn foundation_hardening_guard_files(manifest_dir: &Path) -> Vec<PathBuf> {
    500     let mut paths = Vec::new();
    501 
    502     for path in [
    503         manifest_dir.join("Cargo.toml"),
    504         manifest_dir.join("README"),
    505         manifest_dir.join("README.md"),
    506     ] {
    507         if path.exists() {
    508             paths.push(path);
    509         }
    510     }
    511 
    512     for relative_root in ["src", "docs"] {
    513         let root = manifest_dir.join(relative_root);
    514         if root.exists() {
    515             collect_foundation_hardening_guard_files(root.as_path(), &mut paths);
    516         }
    517     }
    518 
    519     paths.sort();
    520     paths
    521 }
    522 
    523 fn foundation_hardening_retired_concept_allowed(relative_path: &str, pattern: &str) -> bool {
    524     pattern == "RadrootsNostrEvent" && daemon_nostr_protocol_context(relative_path)
    525 }
    526 
    527 fn daemon_nostr_protocol_context(relative_path: &str) -> bool {
    528     relative_path == "src/core/transport_publish.rs"
    529         || relative_path.starts_with("src/transport/nostr/")
    530         || relative_path.contains("/nip46/")
    531 }
    532 
    533 fn collect_foundation_hardening_guard_files(root: &Path, paths: &mut Vec<PathBuf>) {
    534     for entry in fs::read_dir(root)
    535         .unwrap_or_else(|error| panic!("failed to read {}: {error}", root.display()))
    536     {
    537         let path = entry.expect("guard entry").path();
    538         if path.is_dir() {
    539             collect_foundation_hardening_guard_files(path.as_path(), paths);
    540             continue;
    541         }
    542 
    543         let guarded_extension = matches!(
    544             path.extension().and_then(|extension| extension.to_str()),
    545             Some("rs") | Some("toml") | Some("md")
    546         );
    547         if guarded_extension
    548             || matches!(
    549                 path.file_name().and_then(|file_name| file_name.to_str()),
    550                 Some("README") | Some("README.md")
    551             )
    552         {
    553             paths.push(path);
    554         }
    555     }
    556 }
    557 
    558 fn collect_rust_source_files(root: &Path, paths: &mut Vec<PathBuf>) {
    559     for entry in fs::read_dir(root)
    560         .unwrap_or_else(|error| panic!("failed to read {}: {error}", root.display()))
    561     {
    562         let entry = entry.expect("read source entry");
    563         let path = entry.path();
    564         if path.is_dir() {
    565             collect_rust_source_files(path.as_path(), paths);
    566         } else if path.extension().and_then(|extension| extension.to_str()) == Some("rs") {
    567             paths.push(path);
    568         }
    569     }
    570 }
    571 
    572 fn read_source(path: &Path) -> String {
    573     fs::read_to_string(path)
    574         .unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display()))
    575 }
    576 
    577 fn relative_path(root: &Path, path: &Path) -> String {
    578     path.strip_prefix(root)
    579         .expect("source path is under repo root")
    580         .to_string_lossy()
    581         .replace('\\', "/")
    582 }
    583 
    584 fn production_source(source: &str) -> &str {
    585     source
    586         .find("\n#[cfg(test)]")
    587         .map_or(source, |index| &source[..index])
    588 }
    589 
    590 fn is_doc_surface(path: &Path) -> bool {
    591     matches!(
    592         path.file_name().and_then(|file_name| file_name.to_str()),
    593         Some("README") | Some("README.md")
    594     ) || matches!(
    595         path.extension().and_then(|extension| extension.to_str()),
    596         Some("md")
    597     )
    598 }
    599 
    600 fn contains_forbidden_concept(source: &str, pattern: &str) -> bool {
    601     if !pattern.chars().all(is_rust_identifier_character) {
    602         return source.contains(pattern);
    603     }
    604 
    605     source.match_indices(pattern).any(|(index, _)| {
    606         let before = source[..index].chars().next_back();
    607         let after = source[index + pattern.len()..].chars().next();
    608         before.is_none_or(|character| !is_rust_identifier_character(character))
    609             && after.is_none_or(|character| !is_rust_identifier_character(character))
    610     })
    611 }
    612 
    613 fn removed_reticulum_endpoint_alias_lines(source: &str) -> Vec<usize> {
    614     let removed_endpoint = ["reticulum", ":", "preview"].concat();
    615     source
    616         .match_indices(removed_endpoint.as_str())
    617         .filter_map(|(index, _)| {
    618             let after = source[index + removed_endpoint.len()..].chars().next();
    619             (after != Some('-')).then(|| line_number(source, index))
    620         })
    621         .collect()
    622 }
    623 
    624 fn is_rust_identifier_character(character: char) -> bool {
    625     character == '_' || character.is_ascii_alphanumeric()
    626 }
    627 
    628 fn line_number(source: &str, index: usize) -> usize {
    629     source[..index]
    630         .bytes()
    631         .filter(|byte| *byte == b'\n')
    632         .count()
    633         + 1
    634 }
    635 
    636 #[test]
    637 fn nip46_transport_uses_completed_events_and_exact_sign_event_binding() {
    638     let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    639     let protocol = read_source(
    640         manifest_dir
    641             .join("src/transport/nostr/protocol.rs")
    642             .as_path(),
    643     );
    644     let client = read_source(
    645         manifest_dir
    646             .join("src/transport/jsonrpc/nip46/client.rs")
    647             .as_path(),
    648     );
    649     let production_sources = [
    650         "src/transport/nostr/listener.rs",
    651         "src/transport/jsonrpc/nip46/client.rs",
    652         "src/transport/jsonrpc/methods/nip46/connect.rs",
    653         "src/transport/jsonrpc/methods/nip46/session_authorize.rs",
    654     ]
    655     .map(|path| {
    656         let source = read_source(manifest_dir.join(path).as_path());
    657         production_source(source.as_str()).to_owned()
    658     })
    659     .join("\n");
    660 
    661     assert!(protocol.contains("EventBuilder::nostr_connect"));
    662     assert!(protocol.contains(".sign_with_keys(sender_keys)"));
    663     assert!(!production_sources.contains("EventBuilder"));
    664     assert!(!production_sources.contains("radroots_nostr_build_event"));
    665     assert!(!production_sources.contains(".send_event_builder("));
    666     for required in [
    667         "unsigned.verify_id()",
    668         "let expected_event_id = unsigned.id()",
    669         "event.pubkey != expected_public_key",
    670         "event.id != expected_event_id",
    671         ".verify()",
    672     ] {
    673         assert!(
    674             client.contains(required),
    675             "NIP-46 client must retain exact sign_event binding witness `{required}`"
    676         );
    677     }
    678 }