app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

focused_availability_projection.rs (46375B)


      1 use harvestcircle_domain::{
      2     AvailabilityEventVersion, AvailabilityHeadState, AvailabilityHeadView,
      3     AvailabilityListingCoordinate, AvailabilityObservation, AvailabilityUnsupportedReason,
      4     AvailabilityVersionView, EventId, Kind0ProfileCandidate, ProfileMetadata, PublicKey, SafeError,
      5     SafeErrorCode, UnixTimestamp,
      6 };
      7 use harvestcircle_nostr::parse_verified_kind0;
      8 use nostr::{Event, EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp};
      9 use radroots_event::envelope::event_head::{
     10     CurrentEventHead, EventHeadCandidate, EventHeadCandidateResult, EventHeadCoordinate,
     11     EventHeadDecision, event_head_candidate_for_nip01_event, select_event_head,
     12 };
     13 use radroots_event::listing::classified::ClassifiedListingPartition;
     14 use radroots_event::wire::{
     15     DEFAULT_EXTRA_MAX_FIELDS, DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES, DEFAULT_RAW_JSON_MAX_BYTES,
     16     DEFAULT_TAG_ELEMENT_MAX_BYTES, Nip01EventWire,
     17 };
     18 use radroots_event_codec::admission::deletion::{
     19     RadrootsAdmittedNip09DeletionRequestEvent, RadrootsNip09SuppressionOutcome,
     20     RadrootsNip09SuppressionReason, evaluate_nip09_suppression_from_borrowed_requests_v1,
     21     verify_and_admit_nip09_deletion_request_event,
     22 };
     23 use radroots_event_codec::admission::food_availability::{
     24     RadrootsFoodAvailabilityAdmissionOutcome, admit_verified_food_availability_event,
     25 };
     26 use radroots_event_codec::decode::food_availability::RadrootsFoodAvailabilityImageDiagnostic;
     27 use radroots_event_codec::verify::{
     28     RadrootsNip01VerificationError, RadrootsSignatureVerifiedEvent, verify_nip01_event,
     29 };
     30 
     31 #[path = "fixtures/availability_admission_v1.rs"]
     32 mod admission;
     33 #[path = "fixtures/availability_lifecycle_v1.rs"]
     34 mod lifecycle;
     35 
     36 const MAX_RECIPE_BYTES: usize = 8 * 1024;
     37 const MAX_RECIPE_TAGS: usize = 32;
     38 const MAX_RECIPE_ELEMENTS: usize = 128;
     39 const MAX_SIGNED_EVENT_BYTES: usize = 16 * 1024;
     40 const MAX_LIFECYCLE_EVENT_BYTES: usize = 4 * 1024;
     41 const MAX_LIFECYCLE_CORPUS_BYTES: usize = 64 * 1024;
     42 const MAX_RETAINED_RECIPE_SOURCE_BYTES: usize = 32 * 1024;
     43 const MAX_SOURCE_BYTES: usize = 2048;
     44 const MAX_DELETION_REQUESTS: usize = 4096;
     45 const OBSERVED_AT: i64 = 1_800_000_500;
     46 const SOURCE: &str = "wss://relay.example.test/observations?scope=public";
     47 
     48 struct PublicFixture {
     49     json: String,
     50     author: String,
     51 }
     52 
     53 fn assert_recipe_source_bounds() {
     54     let sources = [
     55         include_str!("fixtures/availability_admission_v1.rs"),
     56         include_str!("fixtures/availability_lifecycle_v1.rs"),
     57     ];
     58     assert!(
     59         sources.iter().map(|source| source.len()).sum::<usize>()
     60             <= MAX_RETAINED_RECIPE_SOURCE_BYTES
     61     );
     62     for source in sources {
     63         for forbidden in ["nsec1", "secret_key", "private_key", "credential"] {
     64             assert!(
     65                 !source.contains(forbidden),
     66                 "retained recipes contain only public data"
     67             );
     68         }
     69     }
     70 }
     71 
     72 fn sign_bounded_event(
     73     keys: &Keys,
     74     kind: u16,
     75     created_at: u64,
     76     content: &str,
     77     tags: Vec<Vec<String>>,
     78 ) -> Event {
     79     assert_recipe_source_bounds();
     80     assert!(tags.len() <= MAX_RECIPE_TAGS);
     81     assert!(tags.iter().map(Vec::len).sum::<usize>() <= MAX_RECIPE_ELEMENTS);
     82     assert!(
     83         content.len() + tags.iter().flatten().map(String::len).sum::<usize>() <= MAX_RECIPE_BYTES
     84     );
     85     assert!(
     86         tags.iter()
     87             .flatten()
     88             .all(|value| value.len() <= DEFAULT_TAG_ELEMENT_MAX_BYTES)
     89     );
     90     EventBuilder::new(Kind::from(kind), content)
     91         .tags(
     92             tags.into_iter()
     93                 .map(|tag| Tag::parse(tag).expect("bounded public raw tag")),
     94         )
     95         .custom_created_at(Timestamp::from(created_at))
     96         .sign_with_keys(keys)
     97         .expect("isolated public fixture signing")
     98 }
     99 
    100 /// The ephemeral signer is discarded before retained public JSON is returned.
    101 fn public_fixture_at(recipe: admission::Recipe, kind: u16, created_at: u64) -> PublicFixture {
    102     let keys = Keys::generate();
    103     let author = keys.public_key().to_hex();
    104     let event = sign_bounded_event(&keys, kind, created_at, &recipe.content, recipe.tags);
    105     drop(keys);
    106     let json = event.as_json();
    107     assert!(json.len() <= MAX_SIGNED_EVENT_BYTES);
    108     PublicFixture { json, author }
    109 }
    110 
    111 fn public_fixture(recipe: admission::Recipe) -> PublicFixture {
    112     public_fixture_at(recipe, 30402, admission::CREATED_AT)
    113 }
    114 
    115 fn verified(json: &str) -> RadrootsSignatureVerifiedEvent {
    116     assert!(json.len() <= MAX_SIGNED_EVENT_BYTES);
    117     let wire = Nip01EventWire::parse_json_unverified(json).expect("default-bounded public wire");
    118     verify_nip01_event(
    119         wire.into_unverified_envelope()
    120             .expect("structurally valid envelope"),
    121     )
    122     .expect("real shared ID and signature verification")
    123 }
    124 
    125 fn observation() -> AvailabilityObservation {
    126     AvailabilityObservation::parse(
    127         SOURCE,
    128         UnixTimestamp::from_seconds(OBSERVED_AT).expect("local observation time"),
    129     )
    130     .expect("bounded pure source reference")
    131 }
    132 
    133 fn version_view(json: &str) -> AvailabilityVersionView {
    134     AvailabilityVersionView::from_verified(verified(json), json, observation())
    135         .expect("verified version view")
    136 }
    137 
    138 fn candidate(event: &RadrootsSignatureVerifiedEvent) -> EventHeadCandidate {
    139     match event_head_candidate_for_nip01_event(event.event()) {
    140         EventHeadCandidateResult::Candidate(candidate) => candidate,
    141         _ => panic!("verified addressable recipe must retain a raw head"),
    142     }
    143 }
    144 
    145 fn selected(events: &[&RadrootsSignatureVerifiedEvent]) -> CurrentEventHead {
    146     assert!(!events.is_empty() && events.len() <= 3);
    147     let mut current = None;
    148     for event in events {
    149         match select_event_head(candidate(event), current.as_ref()) {
    150             EventHeadDecision::Applied(next) => current = Some(next),
    151             EventHeadDecision::SkippedDuplicate
    152             | EventHeadDecision::SkippedOlder
    153             | EventHeadDecision::SkippedSameTimestampHigherEventId => {}
    154             EventHeadDecision::CoordinateMismatch => {
    155                 panic!("one raw coordinate per ordering fixture")
    156             }
    157         }
    158     }
    159     current.expect("shared selected head")
    160 }
    161 
    162 struct LifecycleCorpus {
    163     author: String,
    164     other_author: String,
    165     events: Vec<(&'static str, String)>,
    166 }
    167 
    168 /// Both temporary signers are discarded before the bounded public corpus escapes.
    169 fn lifecycle_corpus() -> LifecycleCorpus {
    170     let owner = Keys::generate();
    171     let other = Keys::generate();
    172     let author = owner.public_key().to_hex();
    173     let other_author = other.public_key().to_hex();
    174     assert_ne!(author, other_author);
    175     let mut signed = Vec::<(&'static str, Event)>::new();
    176     for version in &lifecycle::VERSIONS {
    177         signed.push((
    178             version.name,
    179             sign_bounded_event(
    180                 if version.other_author { &other } else { &owner },
    181                 30402,
    182                 version.created_at,
    183                 version.content,
    184                 lifecycle::listing_tags(version),
    185             ),
    186         ));
    187     }
    188     let original = &lifecycle::VERSIONS[0];
    189     signed.push((
    190         "other_kind",
    191         sign_bounded_event(
    192             &owner,
    193             lifecycle::OTHER_ADDRESSABLE_KIND,
    194             original.created_at,
    195             original.content,
    196             lifecycle::listing_tags(original),
    197         ),
    198     ));
    199     let address = format!("30402:{author}:{}", lifecycle::IDENTIFIER);
    200     for deletion in &lifecycle::DELETIONS {
    201         let mut tags = Vec::new();
    202         if let lifecycle::Target::Event(name) | lifecycle::Target::EventAndAddress(name) =
    203             deletion.target
    204         {
    205             let (_, target) = signed
    206                 .iter()
    207                 .find(|(target_name, _)| *target_name == name)
    208                 .expect("reused lifecycle target exists");
    209             tags.push(vec!["e".into(), target.id.to_hex()]);
    210         }
    211         if matches!(
    212             deletion.target,
    213             lifecycle::Target::Address | lifecycle::Target::EventAndAddress(_)
    214         ) {
    215             tags.push(vec!["a".into(), address.clone()]);
    216         }
    217         tags.push(vec!["k".into(), "30402".into()]);
    218         signed.push((
    219             deletion.name,
    220             sign_bounded_event(
    221                 if deletion.other_author {
    222                     &other
    223                 } else {
    224                     &owner
    225                 },
    226                 5,
    227                 deletion.created_at,
    228                 "Public deletion conformance.",
    229                 tags,
    230             ),
    231         ));
    232     }
    233     drop(owner);
    234     drop(other);
    235     let events: Vec<_> = signed
    236         .into_iter()
    237         .map(|(name, event)| {
    238             let json = event.as_json();
    239             assert!(json.len() <= MAX_LIFECYCLE_EVENT_BYTES);
    240             (name, json)
    241         })
    242         .collect();
    243     assert_eq!(events.len(), 18);
    244     assert!(events.iter().map(|(_, json)| json.len()).sum::<usize>() <= MAX_LIFECYCLE_CORPUS_BYTES);
    245     LifecycleCorpus {
    246         author,
    247         other_author,
    248         events,
    249     }
    250 }
    251 
    252 fn lifecycle_json<'a>(corpus: &'a LifecycleCorpus, name: &str) -> &'a str {
    253     &corpus
    254         .events
    255         .iter()
    256         .find(|(event_name, _)| *event_name == name)
    257         .expect("named unchanged lifecycle recipe")
    258         .1
    259 }
    260 
    261 fn deletion(corpus: &LifecycleCorpus, name: &str) -> RadrootsAdmittedNip09DeletionRequestEvent {
    262     let wire = Nip01EventWire::parse_json_unverified(lifecycle_json(corpus, name))
    263         .expect("default-bounded deletion wire");
    264     verify_and_admit_nip09_deletion_request_event(
    265         wire.into_unverified_envelope().expect("request envelope"),
    266     )
    267     .expect("real shared signature verification and deletion admission")
    268 }
    269 
    270 fn replace_once(json: &str, original: &str, replacement: &str) -> String {
    271     assert_eq!(
    272         json.matches(original).count(),
    273         1,
    274         "unique public fixture token"
    275     );
    276     let replaced = json.replacen(original, replacement, 1);
    277     assert!(replaced.len() <= DEFAULT_RAW_JSON_MAX_BYTES + 1);
    278     replaced
    279 }
    280 
    281 fn replace_string_field(json: &str, field: &str, original: &str, replacement: &str) -> String {
    282     replace_once(
    283         json,
    284         &format!("\"{field}\":\"{original}\""),
    285         &format!("\"{field}\":\"{replacement}\""),
    286     )
    287 }
    288 
    289 fn add_extras(json: &str, extras: &str) -> String {
    290     assert!(json.ends_with('}'));
    291     assert!(json.len() + extras.len() <= DEFAULT_RAW_JSON_MAX_BYTES);
    292     format!("{},{}{}", &json[..json.len() - 1], extras, '}')
    293 }
    294 
    295 fn text_at_bytes(unit: &str, size: usize) -> String {
    296     unit.repeat(size / unit.len()) + &"x".repeat(size % unit.len())
    297 }
    298 
    299 fn assert_safe(error: SafeError, rejected_input: &str) {
    300     assert_eq!(error.code(), SafeErrorCode::InvalidProfileMetadata);
    301     for rendered in [error.to_string(), format!("{error:?}")] {
    302         assert!(rendered.len() <= 256);
    303         assert!(!rendered.contains(rejected_input));
    304     }
    305 }
    306 
    307 #[test]
    308 fn admitted_view_preserves_exact_version_coordinate_and_signed_times() {
    309     let fixture = public_fixture(admission::focused_recipe());
    310     let event = verified(&fixture.json);
    311     let admitted =
    312         match admit_verified_food_availability_event(event.clone()).expect("real admission") {
    313             RadrootsFoodAvailabilityAdmissionOutcome::Admitted(admitted) => admitted,
    314             _ => panic!("focused recipe"),
    315         };
    316     let view = version_view(&fixture.json);
    317     assert_eq!(view.version().event_id(), *event.event().id());
    318     assert_eq!(view.publisher().public_key().to_hex(), fixture.author);
    319     assert_eq!(view.created_at(), event.event().created_at());
    320     assert_eq!(view.created_at().as_u64(), admission::CREATED_AT);
    321     assert_eq!(view.observation().observed_at().as_seconds(), OBSERVED_AT);
    322     assert_eq!(view.observation().source().as_str(), SOURCE);
    323     assert_eq!(view.original_json(), fixture.json);
    324     assert_eq!(view.raw_coordinate(), &candidate(&event).coordinate);
    325     assert_eq!(
    326         view.listing_coordinate()
    327             .expect("strict app reference")
    328             .identifier(),
    329         "hcav-incoming-v1"
    330     );
    331     assert_eq!(view.focused(), Some(admitted.projection()));
    332     assert_eq!(view.unsupported_reason(), None);
    333     assert_eq!(view.profile_metadata(), None);
    334 
    335     let wide = public_fixture_at(admission::focused_recipe(), 30402, i64::MAX as u64 + 1);
    336     let wide_view = version_view(&wide.json);
    337     assert_eq!(wide_view.created_at().as_u64(), i64::MAX as u64 + 1);
    338     assert_eq!(
    339         wide_view.observation().observed_at().as_seconds(),
    340         OBSERVED_AT
    341     );
    342 }
    343 
    344 #[test]
    345 fn admitted_view_preserves_unknown_quantity_and_exact_normalized_price() {
    346     let fixture = public_fixture(admission::focused_recipe());
    347     let view = version_view(&fixture.json);
    348     let food = view.focused().expect("focused tolerant projection");
    349     assert_eq!(food.quantity(), None);
    350     assert_eq!(food.price().amount(), "3.5");
    351     assert_eq!(food.price().currency().as_str(), "CAD");
    352     assert_eq!(food.price().unit().as_str(), "lb");
    353     assert_eq!(food.status().as_str(), "active");
    354     assert_eq!(food.published_at().as_u64(), 1_800_000_000);
    355     assert_eq!(food.content().as_str(), admission::CONTENT);
    356     assert_eq!(food.title().as_str(), "Incoming availability conformance");
    357     assert_eq!(food.summary().as_str(), "Public incoming protocol fixture");
    358     assert_eq!(food.location().as_str(), "Protocol test location");
    359 }
    360 
    361 #[test]
    362 fn admitted_view_preserves_known_quantity_without_unit_conversion() {
    363     for (amount, unit, expected) in [("0012.5000", "lb", "12.5"), ("0.1250", "kg", "0.125")] {
    364         let mut recipe = admission::focused_recipe();
    365         recipe
    366             .tags
    367             .iter_mut()
    368             .find(|tag| tag[0] == "radroots:price_unit")
    369             .expect("price unit")[1] = unit.into();
    370         recipe
    371             .tags
    372             .push(vec!["radroots:quantity".into(), amount.into(), unit.into()]);
    373         let fixture = public_fixture(recipe);
    374         let view = version_view(&fixture.json);
    375         let food = view.focused().expect("focused quantity");
    376         let quantity = food.quantity().expect("known quantity");
    377         assert_eq!(quantity.amount(), expected);
    378         assert_eq!(quantity.unit().as_str(), unit);
    379         assert_eq!(food.price().unit().as_str(), unit);
    380         assert_eq!(food.price().amount(), "3.5");
    381     }
    382 }
    383 
    384 #[test]
    385 fn admitted_view_retains_optional_image_diagnostics_and_original_wire() {
    386     let fixture = public_fixture(admission::edited_recipe(&[
    387         admission::Edit::Append(&["image", "https://images.example.test/incoming.webp"]),
    388         admission::Edit::Append(&["t", "opaque-public-discovery-tag"]),
    389     ]));
    390     let exact = format!(
    391         " \n{}\t",
    392         add_extras(
    393             &fixture.json,
    394             "\"untrusted_extra\":{\"stock\":999,\"claim\":\"not-signed\"}"
    395         )
    396     );
    397     let view =
    398         AvailabilityVersionView::from_verified(verified(&fixture.json), &exact, observation())
    399             .expect("exact extras and formatting");
    400     assert_eq!(view.original_json().as_bytes(), exact.as_bytes());
    401     let food = view
    402         .focused()
    403         .expect("optional image cannot discard listing");
    404     assert_eq!(food.quantity(), None);
    405     assert_eq!(food.images().len(), 1);
    406     let image = &food.images()[0];
    407     assert_eq!(
    408         image.url(),
    409         Some("https://images.example.test/incoming.webp")
    410     );
    411     assert_eq!(image.dimensions(), None);
    412     assert!(!image.qualifies());
    413     assert_eq!(
    414         image.raw_tag(),
    415         &["image", "https://images.example.test/incoming.webp"]
    416     );
    417     assert!(
    418         image
    419             .diagnostics()
    420             .contains(&RadrootsFoodAvailabilityImageDiagnostic::ShapeInvalid)
    421     );
    422     assert!(
    423         image
    424             .diagnostics()
    425             .contains(&RadrootsFoodAvailabilityImageDiagnostic::DimensionsMissing)
    426     );
    427     let retained =
    428         Nip01EventWire::parse_json_unverified(view.original_json()).expect("retained wire");
    429     assert!(
    430         retained
    431             .tags
    432             .iter()
    433             .any(|tag| tag == &["t", "opaque-public-discovery-tag"])
    434     );
    435     assert_eq!(retained.extra.len(), 1);
    436     assert_eq!(
    437         retained
    438             .into_unverified_envelope()
    439             .expect("extras have no envelope authority"),
    440         *verified(&fixture.json).event()
    441     );
    442 }
    443 
    444 #[test]
    445 fn profile_failure_does_not_remove_an_admitted_listing_view() {
    446     let keys = Keys::generate();
    447     let author = keys.public_key().to_hex();
    448     let recipe = admission::focused_recipe();
    449     let listing = sign_bounded_event(
    450         &keys,
    451         30402,
    452         admission::CREATED_AT,
    453         &recipe.content,
    454         recipe.tags,
    455     );
    456     let malformed_profile =
    457         sign_bounded_event(&keys, 0, admission::CREATED_AT, "not profile JSON", vec![]);
    458     let overlong_profile = sign_bounded_event(
    459         &keys,
    460         0,
    461         admission::CREATED_AT,
    462         &format!("{{\"name\":\"{}\"}}", "p".repeat(129)),
    463         vec![],
    464     );
    465     drop(keys);
    466     let listing_json = listing.as_json();
    467     let view = version_view(&listing_json);
    468     let publisher = PublicKey::from_hex(&author).expect("public profile author");
    469     for event in [malformed_profile, overlong_profile] {
    470         let json = event.as_json();
    471         assert!(json.len() <= MAX_SIGNED_EVENT_BYTES);
    472         assert!(parse_verified_kind0(&json, publisher).is_err());
    473         assert_eq!(view.profile_metadata(), None);
    474         assert_eq!(
    475             view.focused()
    476                 .expect("listing survives optional metadata failure")
    477                 .status()
    478                 .as_str(),
    479             "active"
    480         );
    481         assert_eq!(view.original_json(), listing_json);
    482     }
    483     assert_eq!(
    484         view.with_profile(None)
    485             .expect("absence remains absence")
    486             .profile_metadata(),
    487         None
    488     );
    489 }
    490 
    491 #[test]
    492 fn generic_and_operational_heads_never_become_focused_food() {
    493     let generic = public_fixture(admission::recipe_with_tags(&[]));
    494     let view = version_view(&generic.json);
    495     assert_eq!(view.focused(), None);
    496     assert_eq!(
    497         view.unsupported_reason(),
    498         Some(&AvailabilityUnsupportedReason::Excluded(
    499             ClassifiedListingPartition::GenericNip99
    500         ))
    501     );
    502     for marker in admission::OPERATIONAL_MARKERS {
    503         let fixture = public_fixture(admission::recipe_with_tags(&[&[marker]]));
    504         let view = version_view(&fixture.json);
    505         assert_eq!(view.focused(), None);
    506         assert_eq!(
    507             view.unsupported_reason(),
    508             Some(&AvailabilityUnsupportedReason::Excluded(
    509                 ClassifiedListingPartition::OperationalListing
    510             ))
    511         );
    512         let head =
    513             AvailabilityHeadView::from_selected(selected(&[&verified(&fixture.json)]), view, &[])
    514                 .expect("raw operational head retained");
    515         assert_eq!(head.state(), AvailabilityHeadState::Unsupported);
    516         assert_eq!(head.focused(), None);
    517     }
    518     assert_eq!(
    519         admission::FOCUSED_MARKERS,
    520         ["radroots:price_unit", "radroots:quantity"]
    521     );
    522 }
    523 
    524 #[test]
    525 fn ambiguous_and_malformed_verified_heads_remain_unsupported() {
    526     for marker in admission::OPERATIONAL_MARKERS {
    527         let mut recipe = admission::focused_recipe();
    528         recipe.tags.push(vec![marker.into()]);
    529         let fixture = public_fixture(recipe);
    530         let view = version_view(&fixture.json);
    531         assert_eq!(view.focused(), None);
    532         assert_eq!(
    533             view.unsupported_reason(),
    534             Some(&AvailabilityUnsupportedReason::ProjectionRejected(
    535                 "food_profile_ambiguous"
    536             ))
    537         );
    538     }
    539     for case in &admission::MALFORMED_CASES {
    540         let fixture = public_fixture(admission::edited_recipe(case.edits));
    541         let event = verified(&fixture.json);
    542         assert_eq!(
    543             admit_verified_food_availability_event(event.clone()).expect_err(case.name),
    544             case.expected
    545         );
    546         let view = version_view(&fixture.json);
    547         assert_eq!(view.version().event_id(), *event.event().id());
    548         assert_eq!(view.raw_coordinate(), &candidate(&event).coordinate);
    549         assert_eq!(view.focused(), None, "{}", case.name);
    550         assert_eq!(
    551             view.unsupported_reason(),
    552             Some(&AvailabilityUnsupportedReason::ProjectionRejected(
    553                 case.code
    554             )),
    555             "{}",
    556             case.name
    557         );
    558     }
    559 }
    560 
    561 #[test]
    562 fn unsupported_head_preserves_empty_missing_and_long_identifiers() {
    563     for identifier in [
    564         None,
    565         Some(String::new()),
    566         Some("d".repeat(4025)),
    567         Some("d".repeat(4026)),
    568         Some("é".repeat(2048)),
    569         Some(" opaque:\0:é ".into()),
    570     ] {
    571         let mut recipe = admission::focused_recipe();
    572         recipe.tags.retain(|tag| tag[0] != "d");
    573         if let Some(identifier) = &identifier {
    574             recipe.tags.insert(0, vec!["d".into(), identifier.clone()]);
    575         }
    576         let fixture = public_fixture(recipe);
    577         let view = version_view(&fixture.json);
    578         assert_eq!(view.focused(), None);
    579         assert!(matches!(
    580             view.unsupported_reason(),
    581             Some(AvailabilityUnsupportedReason::ProjectionRejected(_))
    582         ));
    583         match view.raw_coordinate() {
    584             EventHeadCoordinate::Addressable {
    585                 kind,
    586                 pubkey,
    587                 d_tag,
    588             } => {
    589                 assert_eq!(*kind, 30402);
    590                 assert_eq!(pubkey.to_hex(), fixture.author);
    591                 assert_eq!(d_tag, identifier.as_deref().unwrap_or(""));
    592             }
    593             _ => panic!("raw kind-30402 head"),
    594         }
    595         let fits_app = identifier
    596             .as_ref()
    597             .is_some_and(|value| !value.is_empty() && value.len() <= 4025);
    598         assert_eq!(view.listing_coordinate().is_some(), fits_app);
    599         if let Some(coordinate) = view.listing_coordinate() {
    600             assert_eq!(
    601                 coordinate.identifier(),
    602                 identifier.as_deref().expect("nonempty app identifier")
    603             );
    604         }
    605         let head =
    606             AvailabilityHeadView::from_selected(selected(&[&verified(&fixture.json)]), view, &[])
    607                 .expect("unsupported raw head survives");
    608         assert_eq!(head.state(), AvailabilityHeadState::Unsupported);
    609         assert!(head.version().is_some());
    610         assert_eq!(head.focused(), None);
    611     }
    612 }
    613 
    614 #[test]
    615 fn newer_unsupported_head_blocks_older_compatible_view() {
    616     let corpus = lifecycle_corpus();
    617     let old = verified(lifecycle_json(&corpus, "old_active"));
    618     assert_eq!(
    619         version_view(lifecycle_json(&corpus, "old_active"))
    620             .focused()
    621             .expect("older food")
    622             .status()
    623             .as_str(),
    624         "active"
    625     );
    626     for name in ["generic_head", "malformed_head"] {
    627         let newer = verified(lifecycle_json(&corpus, name));
    628         for order in [[&old, &newer, &old], [&newer, &old, &newer]] {
    629             let current = selected(&order);
    630             assert_eq!(current.event_id, *newer.event().id());
    631             let head = AvailabilityHeadView::from_selected(
    632                 current.clone(),
    633                 version_view(lifecycle_json(&corpus, name)),
    634                 &[],
    635             )
    636             .expect("selected unsupported version");
    637             assert_eq!(head.state(), AvailabilityHeadState::Unsupported);
    638             assert_eq!(
    639                 head.version()
    640                     .expect("retained winner")
    641                     .version()
    642                     .event_id(),
    643                 *newer.event().id()
    644             );
    645             assert_eq!(head.focused(), None);
    646             assert!(
    647                 AvailabilityHeadView::from_selected(
    648                     current,
    649                     version_view(lifecycle_json(&corpus, "old_active")),
    650                     &[]
    651                 )
    652                 .is_err()
    653             );
    654         }
    655     }
    656 }
    657 
    658 #[test]
    659 fn missing_unsupported_and_deleted_states_remain_distinct() {
    660     let corpus = lifecycle_corpus();
    661     let focused = version_view(lifecycle_json(&corpus, "newer_sold"));
    662     let missing = AvailabilityHeadView::missing(
    663         focused
    664             .listing_coordinate()
    665             .expect("app coordinate")
    666             .clone(),
    667     );
    668     assert_eq!(missing.state(), AvailabilityHeadState::Missing);
    669     assert_eq!(missing.version(), None);
    670     assert_eq!(missing.suppression(), None);
    671     assert_eq!(missing.focused(), None);
    672     let event = verified(lifecycle_json(&corpus, "newer_sold"));
    673     let visible = AvailabilityHeadView::from_selected(selected(&[&event]), focused.clone(), &[])
    674         .expect("visible selected version");
    675     assert_eq!(visible.state(), AvailabilityHeadState::Focused);
    676     assert_eq!(
    677         visible.focused().expect("visible food").status().as_str(),
    678         "sold"
    679     );
    680     let deleted = AvailabilityHeadView::from_selected(
    681         selected(&[&event]),
    682         focused,
    683         &[deletion(&corpus, "exact_newer")],
    684     )
    685     .expect("shared suppression");
    686     assert_eq!(deleted.state(), AvailabilityHeadState::Deleted);
    687     assert_eq!(deleted.focused(), None);
    688     assert_eq!(
    689         deleted
    690             .version()
    691             .expect("historical food remains")
    692             .focused()
    693             .expect("historical projection")
    694             .status()
    695             .as_str(),
    696         "sold"
    697     );
    698     let generic_json = lifecycle_json(&corpus, "generic_head");
    699     let unsupported = AvailabilityHeadView::from_selected(
    700         selected(&[&verified(generic_json)]),
    701         version_view(generic_json),
    702         &[],
    703     )
    704     .expect("unsupported winner");
    705     assert_eq!(unsupported.state(), AvailabilityHeadState::Unsupported);
    706     assert_eq!(unsupported.focused(), None);
    707 }
    708 
    709 #[test]
    710 fn selected_head_mismatch_rejects_an_older_version() {
    711     let corpus = lifecycle_corpus();
    712     let old_json = lifecycle_json(&corpus, "old_active");
    713     let new = verified(lifecycle_json(&corpus, "newer_sold"));
    714     assert!(
    715         AvailabilityHeadView::from_selected(selected(&[&new]), version_view(old_json), &[])
    716             .is_err()
    717     );
    718     let actual = selected(&[&verified(old_json)]);
    719     let mut wrong_id = actual.clone();
    720     wrong_id.event_id = *new.event().id();
    721     let mut wrong_time = actual.clone();
    722     wrong_time.created_at += 1;
    723     let mut mismatches = vec![wrong_id, wrong_time];
    724     for name in ["other_identifier", "other_author", "other_kind"] {
    725         let mut wrong_coordinate = actual.clone();
    726         wrong_coordinate.coordinate =
    727             candidate(&verified(lifecycle_json(&corpus, name))).coordinate;
    728         assert_eq!(wrong_coordinate.event_id, actual.event_id);
    729         assert_eq!(wrong_coordinate.created_at, actual.created_at);
    730         assert_ne!(wrong_coordinate.coordinate, actual.coordinate);
    731         mismatches.push(wrong_coordinate);
    732     }
    733     assert_eq!(mismatches.len(), 5);
    734     for wrong in mismatches {
    735         let error = AvailabilityHeadView::from_selected(wrong, version_view(old_json), &[])
    736             .expect_err("all selected head fields bind the exact version");
    737         assert_safe(error, "Public lifecycle conformance.");
    738     }
    739     assert!(AvailabilityHeadView::from_selected(actual, version_view(old_json), &[]).is_ok());
    740 }
    741 
    742 #[test]
    743 fn deleted_view_retains_shared_author_and_cutoff_evidence() {
    744     let corpus = lifecycle_corpus();
    745     assert_ne!(corpus.author, corpus.other_author);
    746     let target_json = lifecycle_json(&corpus, "at_cutoff");
    747     let target = verified(target_json);
    748     let exact = deletion(&corpus, "combined_cutoff");
    749     let address = deletion(&corpus, "address_cutoff");
    750     let forged = deletion(&corpus, "forged_address");
    751     assert_eq!(forged.event().author().to_hex(), corpus.other_author);
    752     for permutation in lifecycle::ALL_THREE_PERMUTATIONS {
    753         let source = [&exact, &address, &forged];
    754         let requests: Vec<_> = permutation
    755             .into_iter()
    756             .map(|index| (*source[index]).clone())
    757             .collect();
    758         let expected =
    759             evaluate_nip09_suppression_from_borrowed_requests_v1(&target, requests.iter());
    760         let head = AvailabilityHeadView::from_selected(
    761             selected(&[&target]),
    762             version_view(target_json),
    763             &requests,
    764         )
    765         .expect("shared evidence retained");
    766         assert_eq!(head.state(), AvailabilityHeadState::Deleted);
    767         let decision = head.suppression().expect("canonical decision");
    768         assert_eq!(decision, &expected);
    769         assert_eq!(
    770             decision.outcome(),
    771             RadrootsNip09SuppressionOutcome::Suppressed
    772         );
    773         assert_eq!(
    774             decision.reason(),
    775             RadrootsNip09SuppressionReason::EventIdAndAddressReference
    776         );
    777         assert_eq!(
    778             decision
    779                 .event_reference()
    780                 .expect("exact evidence")
    781                 .request_id(),
    782             exact.event().id()
    783         );
    784         let evidence = decision.address_reference().expect("address evidence");
    785         assert_eq!(
    786             evidence.coordinate().as_str(),
    787             format!("30402:{}:{}", corpus.author, lifecycle::IDENTIFIER)
    788         );
    789         assert_eq!(evidence.inclusive_cutoff(), lifecycle::CUTOFF);
    790         assert_eq!(
    791             evidence.request_id(),
    792             std::cmp::min(exact.event().id(), address.event().id())
    793         );
    794     }
    795     let new_json = lifecycle_json(&corpus, "newer_sold");
    796     let new = verified(new_json);
    797     let forged_only = AvailabilityHeadView::from_selected(
    798         selected(&[&new]),
    799         version_view(new_json),
    800         &[deletion(&corpus, "forged_exact")],
    801     )
    802     .expect("request admission is not author authorization");
    803     assert_eq!(forged_only.state(), AvailabilityHeadState::Focused);
    804     assert_eq!(
    805         forged_only
    806             .suppression()
    807             .expect("author mismatch evidence")
    808             .reason(),
    809         RadrootsNip09SuppressionReason::RequestAuthorMismatch
    810     );
    811     for (name, expected_state, expected_reason) in [
    812         (
    813             "before_cutoff",
    814             AvailabilityHeadState::Deleted,
    815             RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
    816         ),
    817         (
    818             "at_cutoff",
    819             AvailabilityHeadState::Deleted,
    820             RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
    821         ),
    822         (
    823             "after_cutoff",
    824             AvailabilityHeadState::Focused,
    825             RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
    826         ),
    827     ] {
    828         let json = lifecycle_json(&corpus, name);
    829         let head = AvailabilityHeadView::from_selected(
    830             selected(&[&verified(json)]),
    831             version_view(json),
    832             std::slice::from_ref(&address),
    833         )
    834         .expect("inclusive shared address cutoff");
    835         assert_eq!(head.state(), expected_state);
    836         assert_eq!(
    837             head.suppression().expect("cutoff decision").reason(),
    838             expected_reason
    839         );
    840     }
    841     // Dedicated capacity boundary: at most4097 duplicate public requests and16MiB
    842     // of admitted fixture inputs; production must borrow this slice, never clone it.
    843     let request_bytes = lifecycle_json(&corpus, "address_cutoff").len();
    844     assert!(request_bytes * (MAX_DELETION_REQUESTS + 1) <= 16 * 1024 * 1024);
    845     let mut requests = vec![address; MAX_DELETION_REQUESTS];
    846     assert_eq!(requests.len(), 4096);
    847     assert_eq!(
    848         AvailabilityHeadView::from_selected(
    849             selected(&[&target]),
    850             version_view(target_json),
    851             &requests
    852         )
    853         .expect("exact request capacity")
    854         .state(),
    855         AvailabilityHeadState::Deleted
    856     );
    857     requests.push(exact);
    858     assert_eq!(requests.len(), 4097);
    859     assert_safe(
    860         AvailabilityHeadView::from_selected(
    861             selected(&[&target]),
    862             version_view(target_json),
    863             &requests,
    864         )
    865         .expect_err("one request beyond capacity"),
    866         target_json,
    867     );
    868 }
    869 
    870 #[test]
    871 fn invalid_signatures_cannot_create_verified_views() {
    872     let fixture = public_fixture(admission::focused_recipe());
    873     let wire = Nip01EventWire::parse_json_unverified(&fixture.json).expect("public base wire");
    874     let invalid_signature = replace_string_field(&fixture.json, "sig", &wire.sig, &"0".repeat(128));
    875     let unverified = Nip01EventWire::parse_json_unverified(&invalid_signature)
    876         .expect("structurally valid invalid signature")
    877         .into_unverified_envelope()
    878         .expect("unverified envelope");
    879     assert_eq!(
    880         verify_nip01_event(unverified).expect_err("signature gate owns verified typestate"),
    881         RadrootsNip01VerificationError::SignatureInvalid
    882     );
    883     assert_safe(
    884         AvailabilityVersionView::from_verified(
    885             verified(&fixture.json),
    886             &invalid_signature,
    887             observation(),
    888         )
    889         .expect_err("invalid signature wire cannot bind another verified event"),
    890         &invalid_signature,
    891     );
    892     let replacement = if wire.id.starts_with('0') { '1' } else { '0' };
    893     let bad_id = format!("{replacement}{}", &wire.id[1..]);
    894     let invalid_id = replace_string_field(&fixture.json, "id", &wire.id, &bad_id);
    895     let envelope = Nip01EventWire::parse_json_unverified(&invalid_id)
    896         .expect("invalid ID is not structurally trusted")
    897         .into_unverified_envelope()
    898         .expect("unverified envelope");
    899     assert!(matches!(
    900         verify_nip01_event(envelope),
    901         Err(RadrootsNip01VerificationError::IdMismatch { .. })
    902     ));
    903 }
    904 
    905 #[test]
    906 fn original_wire_must_match_the_verified_event() {
    907     let fixture = public_fixture(admission::focused_recipe());
    908     let neighbor = public_fixture(admission::focused_recipe());
    909     let event = verified(&fixture.json);
    910     let wire = Nip01EventWire::parse_json_unverified(&fixture.json).expect("public wire fields");
    911     let neighbor_wire = Nip01EventWire::parse_json_unverified(&neighbor.json)
    912         .expect("distinct valid public fields");
    913     let mismatches = [
    914         replace_string_field(&fixture.json, "id", &wire.id, &neighbor_wire.id),
    915         replace_string_field(&fixture.json, "pubkey", &wire.pubkey, &neighbor.author),
    916         replace_once(
    917             &fixture.json,
    918             &format!("\"created_at\":{}", admission::CREATED_AT),
    919             "\"created_at\":1800000101",
    920         ),
    921         replace_once(&fixture.json, "\"kind\":30402", "\"kind\":30023"),
    922         replace_once(
    923             &fixture.json,
    924             "[\"status\",\"active\"]",
    925             "[\"status\",\"sold\"]",
    926         ),
    927         replace_string_field(
    928             &fixture.json,
    929             "content",
    930             &wire.content,
    931             "Different public content.",
    932         ),
    933         replace_string_field(&fixture.json, "sig", &wire.sig, &neighbor_wire.sig),
    934     ];
    935     for mismatch in &mismatches {
    936         let parsed = Nip01EventWire::parse_json_unverified(mismatch)
    937             .expect("each altered field stays default structurally valid")
    938             .into_unverified_envelope()
    939             .expect("unverified altered envelope");
    940         assert_ne!(&parsed, event.event());
    941         assert_safe(
    942             AvailabilityVersionView::from_verified(event.clone(), mismatch, observation())
    943                 .expect_err("full envelope equality required"),
    944             mismatch,
    945         );
    946     }
    947     assert_eq!(mismatches.len(), 7);
    948     for extras in ["\"untrusted\":true", "\"untrusted\":false"] {
    949         let exact = format!("\n {} \t", add_extras(&fixture.json, extras));
    950         let retained = AvailabilityVersionView::from_verified(event.clone(), &exact, observation())
    951             .expect("unknown extras and formatting remain unauthenticated");
    952         assert_eq!(retained.original_json(), exact);
    953         assert_eq!(retained.version().event_id(), *event.event().id());
    954         assert_eq!(
    955             retained
    956                 .focused()
    957                 .expect("extras cannot change quantity")
    958                 .quantity(),
    959             None
    960         );
    961     }
    962 }
    963 
    964 #[test]
    965 fn view_wire_and_source_retention_obey_shared_byte_limits() {
    966     let mut recipe = admission::focused_recipe();
    967     recipe.content = "Public wire byte boundary é🥕e\u{301}.".into();
    968     let fixture = public_fixture(recipe);
    969     assert!(fixture.json.chars().count() < fixture.json.len());
    970     let event = verified(&fixture.json);
    971     assert_eq!(DEFAULT_RAW_JSON_MAX_BYTES, 256 * 1024);
    972     // Dedicated raw-wire exception to the16KiB signed-fixture bound: whitespace
    973     // padding creates exactly256KiB and256KiB+1 without enlarging signed fields.
    974     let exact = format!(
    975         "{}{}",
    976         fixture.json,
    977         " ".repeat(DEFAULT_RAW_JSON_MAX_BYTES - fixture.json.len())
    978     );
    979     assert_eq!(exact.len(), DEFAULT_RAW_JSON_MAX_BYTES);
    980     assert_eq!(
    981         AvailabilityVersionView::from_verified(event.clone(), &exact, observation())
    982             .expect("exact raw-wire capacity")
    983             .original_json()
    984             .as_bytes(),
    985         exact.as_bytes()
    986     );
    987     let over = exact + " ";
    988     assert_eq!(over.len(), DEFAULT_RAW_JSON_MAX_BYTES + 1);
    989     assert_safe(
    990         AvailabilityVersionView::from_verified(event.clone(), &over, observation())
    991             .expect_err("raw capacity plus one"),
    992         "hcav-incoming-v1",
    993     );
    994     let invalid_tags = replace_once(&fixture.json, "\"tags\":[", "\"tags\":[[0],");
    995     let overlong_tag = replace_once(
    996         &fixture.json,
    997         "[\"status\",\"active\"]",
    998         &format!(
    999             "[\"status\",\"{}\"]",
   1000             "x".repeat(DEFAULT_TAG_ELEMENT_MAX_BYTES + 1)
   1001         ),
   1002     );
   1003     let too_many_fields = (0..=DEFAULT_EXTRA_MAX_FIELDS)
   1004         .map(|index| format!("\"extra_{index}\":0"))
   1005         .collect::<Vec<_>>()
   1006         .join(",");
   1007     let oversized_extra = format!(
   1008         "\"extra\":\"{}\"",
   1009         "x".repeat(DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES + 1)
   1010     );
   1011     for invalid in [
   1012         invalid_tags,
   1013         overlong_tag,
   1014         add_extras(&fixture.json, &too_many_fields),
   1015         add_extras(&fixture.json, &oversized_extra),
   1016     ] {
   1017         assert!(invalid.len() <= DEFAULT_RAW_JSON_MAX_BYTES);
   1018         assert!(
   1019             Nip01EventWire::parse_json_unverified(&invalid).is_err(),
   1020             "shared default structural boundary"
   1021         );
   1022         assert_safe(
   1023             AvailabilityVersionView::from_verified(event.clone(), &invalid, observation())
   1024                 .expect_err("shared limits precede retention"),
   1025             &invalid,
   1026         );
   1027     }
   1028     let other_kind = public_fixture_at(
   1029         admission::focused_recipe(),
   1030         lifecycle::OTHER_ADDRESSABLE_KIND,
   1031         admission::CREATED_AT,
   1032     );
   1033     assert_safe(
   1034         AvailabilityVersionView::from_verified(
   1035             verified(&other_kind.json),
   1036             &other_kind.json,
   1037             observation(),
   1038         )
   1039         .expect_err("view only supports kind30402"),
   1040         &other_kind.json,
   1041     );
   1042     for unit in ["x", "é", "🥕", "e\u{301}"] {
   1043         let prefix = "wss://relay.example.test/source/";
   1044         let source = prefix.to_owned() + &text_at_bytes(unit, MAX_SOURCE_BYTES - prefix.len());
   1045         assert_eq!(source.len(), MAX_SOURCE_BYTES);
   1046         let observed = AvailabilityObservation::parse(&source, UnixTimestamp::UNIX_EPOCH)
   1047             .expect("exact UTF-8 source byte bound");
   1048         assert_eq!(observed.source().as_str().as_bytes(), source.as_bytes());
   1049         let over = source + "x";
   1050         assert_eq!(over.len(), MAX_SOURCE_BYTES + 1);
   1051         assert_safe(
   1052             AvailabilityObservation::parse(&over, UnixTimestamp::UNIX_EPOCH)
   1053                 .expect_err("source byte bound plus one"),
   1054             &over,
   1055         );
   1056     }
   1057 }
   1058 
   1059 #[test]
   1060 fn view_errors_never_echo_untrusted_input() {
   1061     const PAYLOAD: &str = "private-debug-payload-marker";
   1062     let mut recipe = admission::focused_recipe();
   1063     recipe.content = PAYLOAD.into();
   1064     recipe
   1065         .tags
   1066         .iter_mut()
   1067         .find(|tag| tag[0] == "d")
   1068         .expect("identifier")[1] = format!("{PAYLOAD}:\0 ");
   1069     let fixture = public_fixture(recipe);
   1070     let source = format!("wss://relay.example.test/{PAYLOAD}?metadata={PAYLOAD}");
   1071     let observation = AvailabilityObservation::parse(&source, UnixTimestamp::UNIX_EPOCH)
   1072         .expect("public opaque source");
   1073     let version = AvailabilityVersionView::from_verified(
   1074         verified(&fixture.json),
   1075         &fixture.json,
   1076         observation.clone(),
   1077     )
   1078     .expect("unsupported evidence view");
   1079     let metadata = ProfileMetadata::new(
   1080         Some(PAYLOAD.into()),
   1081         None,
   1082         None,
   1083         Some(PAYLOAD.into()),
   1084         Some(format!("https://images.example.test/{PAYLOAD}")),
   1085     )
   1086     .expect("bounded display fields");
   1087     let candidate = Kind0ProfileCandidate::new(
   1088         EventId::from_bytes([7; 32]),
   1089         PublicKey::from_hex(&fixture.author).expect("author"),
   1090         UnixTimestamp::UNIX_EPOCH,
   1091         metadata,
   1092     );
   1093     let attached = version
   1094         .with_profile(Some(&candidate))
   1095         .expect("caller-associated display metadata");
   1096     let head = AvailabilityHeadView::from_selected(
   1097         selected(&[&verified(&fixture.json)]),
   1098         attached.clone(),
   1099         &[],
   1100     )
   1101     .expect("unsupported head");
   1102     let missing = AvailabilityHeadView::missing(
   1103         AvailabilityListingCoordinate::parse(&format!("30402:{}:{PAYLOAD}", fixture.author))
   1104             .expect("opaque missing reference"),
   1105     );
   1106     for debug in [
   1107         format!("{observation:?}"),
   1108         format!("{version:?}"),
   1109         format!("{attached:?}"),
   1110         format!("{head:?}"),
   1111         format!("{missing:?}"),
   1112     ] {
   1113         assert!(debug.len() <= 1024, "bounded safe summary");
   1114         for sensitive in [PAYLOAD, "wss://", "https://", "\"tags\"", "\"sig\""] {
   1115             assert!(
   1116                 !debug.contains(sensitive),
   1117                 "debug cannot expose raw payloads"
   1118             );
   1119         }
   1120     }
   1121     for source in [
   1122         format!("https://relay.example.test/{PAYLOAD}"),
   1123         format!("wss://user:{PAYLOAD}@relay.example.test"),
   1124         format!("wss://relay.example.test/#{PAYLOAD}"),
   1125         format!("wss://relay.example.test/\n{PAYLOAD}"),
   1126     ] {
   1127         assert_safe(
   1128             AvailabilityObservation::parse(&source, UnixTimestamp::UNIX_EPOCH)
   1129                 .expect_err("shared source validation returns static errors"),
   1130             PAYLOAD,
   1131         );
   1132     }
   1133     let malformed = format!("{{{PAYLOAD}");
   1134     assert_safe(
   1135         AvailabilityVersionView::from_verified(verified(&fixture.json), &malformed, observation)
   1136             .expect_err("malformed wire safe error"),
   1137         PAYLOAD,
   1138     );
   1139 }
   1140 
   1141 #[test]
   1142 fn optional_profile_attachment_checks_publisher_and_preserves_missing_fields() {
   1143     let fixture = public_fixture(admission::focused_recipe());
   1144     let view = version_view(&fixture.json);
   1145     assert_eq!(view.profile_metadata(), None);
   1146     let metadata = ProfileMetadata::new(None, Some(" Caller display ".into()), None, None, None)
   1147         .expect("bounded optional display metadata");
   1148     // This constructor has no signature typestate. The association proves only
   1149     // matching author and bounded display data, not verified profile or ownership.
   1150     let candidate = Kind0ProfileCandidate::new(
   1151         EventId::from_bytes([9; 32]),
   1152         PublicKey::from_hex(&fixture.author).expect("listing author"),
   1153         UnixTimestamp::UNIX_EPOCH,
   1154         metadata.clone(),
   1155     );
   1156     let attached = view
   1157         .with_profile(Some(&candidate))
   1158         .expect("same-author caller display metadata");
   1159     assert_eq!(attached.profile_metadata(), Some(&metadata));
   1160     let attached_metadata = attached.profile_metadata().expect("attached fields");
   1161     assert_eq!(attached_metadata.display_name(), Some("Caller display"));
   1162     assert_eq!(attached_metadata.name(), None);
   1163     assert_eq!(attached_metadata.nip05(), None);
   1164     assert_eq!(attached_metadata.about(), None);
   1165     assert_eq!(attached_metadata.picture(), None);
   1166     assert_eq!(view.profile_metadata(), None, "immutable original");
   1167     assert_eq!(attached.version(), view.version());
   1168     assert_eq!(attached.focused(), view.focused());
   1169     assert_eq!(attached.original_json(), view.original_json());
   1170     assert_eq!(
   1171         attached
   1172             .with_profile(None)
   1173             .expect("explicit absent optional metadata")
   1174             .profile_metadata(),
   1175         None
   1176     );
   1177     let other = public_fixture(admission::focused_recipe());
   1178     assert_ne!(other.author, fixture.author);
   1179     let wrong = Kind0ProfileCandidate::new(
   1180         EventId::from_bytes([8; 32]),
   1181         PublicKey::from_hex(&other.author).expect("other public author"),
   1182         UnixTimestamp::UNIX_EPOCH,
   1183         metadata,
   1184     );
   1185     assert_safe(
   1186         view.with_profile(Some(&wrong))
   1187             .expect_err("author mismatch before metadata copy"),
   1188         "Caller display",
   1189     );
   1190     assert_eq!(
   1191         view.focused()
   1192             .expect("failed optional attachment preserves listing")
   1193             .status()
   1194             .as_str(),
   1195         "active"
   1196     );
   1197     assert_eq!(
   1198         view.version(),
   1199         AvailabilityEventVersion::from_canonical(*verified(&fixture.json).event().id())
   1200     );
   1201 }
   1202 
   1203 #[test]
   1204 fn observation_preserves_bounded_source_reference_and_observation_time() {
   1205     let source = "wss://Relay.Example.test:443/path?region=ca-bc";
   1206     let local = UnixTimestamp::from_seconds(i64::MAX).expect("local timestamp upper bound");
   1207     let observation = AvailabilityObservation::parse(source, local)
   1208         .expect("pure shared validation preserves supplied spelling");
   1209     assert_eq!(observation.source().as_str(), source);
   1210     assert_eq!(observation.observed_at(), local);
   1211     assert_ne!(
   1212         observation.source().as_str(),
   1213         "wss://relay.example.test/path?region=ca-bc"
   1214     );
   1215     let empty_time =
   1216         AvailabilityObservation::parse("ws://127.0.0.1:8080/isolated", UnixTimestamp::UNIX_EPOCH)
   1217             .expect("provenance is not endpoint authorization");
   1218     assert_eq!(empty_time.observed_at().as_seconds(), 0);
   1219     for invalid in [
   1220         "",
   1221         "http://relay.example.test",
   1222         "WSS://relay.example.test",
   1223         " wss://relay.example.test",
   1224         "wss://",
   1225         "wss://relay.example.test:0",
   1226     ] {
   1227         let error = AvailabilityObservation::parse(invalid, local)
   1228             .expect_err("delegate selected shared pure source validation");
   1229         assert_eq!(error.code(), SafeErrorCode::InvalidProfileMetadata);
   1230         assert!(error.to_string().len() <= 128);
   1231     }
   1232 }