registry_v7.rs (150335B)
1 #![forbid(unsafe_code)] 2 3 //! Frozen event-contract registry-v7 inventory and validation semantics. 4 5 #[cfg(not(feature = "std"))] 6 use alloc::{borrow::ToOwned, string::String, vec::Vec}; 7 8 use crate::{ 9 calendar::{ 10 CalendarDate, CalendarUid, CalendarUri, IanaTimeZoneId, RADROOTS_CALENDAR_MAX_PARTICIPANTS, 11 canonical_calendar_geohash_is_valid, canonical_calendar_tag_text_is_valid, 12 covered_utc_days, 13 }, 14 envelope::EventEnvelope, 15 envelope::kind::*, 16 id::{ 17 AddressableCoordinate, DTag, EventId, MutationId, Nip01Coordinate, parse_public_key, 18 relay_url_is_valid, 19 }, 20 listing::classified::{ 21 ClassifiedListingPartition, TAG_RADROOTS_PRICE_UNIT, TAG_RADROOTS_QUANTITY, 22 }, 23 }; 24 use radroots_blossom::BlobUrl; 25 26 pub const RADROOTS_EVENT_CONTRACT_REGISTRY_VERSION: u32 = 7; 27 28 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 29 pub enum EventClass { 30 Regular, 31 Replaceable, 32 Addressable, 33 Ephemeral, 34 } 35 36 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 37 pub enum NostrStandard { 38 Nip01, 39 Nip09, 40 Nip17, 41 Nip18, 42 Nip22, 43 Nip23, 44 Nip25, 45 Nip28, 46 Nip29, 47 Nip42, 48 Nip51, 49 Nip52, 50 Nip53, 51 Nip54, 52 Nip56, 53 Nip57, 54 Nip78, 55 Nip90, 56 Nip94, 57 Nip98, 58 Nip99, 59 Radroots, 60 } 61 62 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 63 pub enum EventPrivacy { 64 Public, 65 Encrypted, 66 LocalOnly, 67 Secret, 68 } 69 70 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 71 pub enum EventStability { 72 Stable, 73 Experimental, 74 } 75 76 /// Event-contract role required of an author. 77 /// 78 /// This is event authoring policy, not signer provenance, authentication 79 /// state, account persistence, or proof that a host granted the role. 80 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 81 pub enum AuthorRole { 82 Any, 83 Application, 84 Buyer, 85 Farmer, 86 Member, 87 Moderator, 88 Relay, 89 Seller, 90 Service, 91 } 92 93 impl AuthorRole { 94 pub const ALL: [Self; 9] = [ 95 Self::Any, 96 Self::Application, 97 Self::Buyer, 98 Self::Farmer, 99 Self::Member, 100 Self::Moderator, 101 Self::Relay, 102 Self::Seller, 103 Self::Service, 104 ]; 105 106 /// Stable registry label used by versioned manifest encoders. 107 #[must_use] 108 pub const fn as_str(self) -> &'static str { 109 match self { 110 Self::Any => "any", 111 Self::Application => "application", 112 Self::Buyer => "buyer", 113 Self::Farmer => "farmer", 114 Self::Member => "member", 115 Self::Moderator => "moderator", 116 Self::Relay => "relay", 117 Self::Seller => "seller", 118 Self::Service => "service", 119 } 120 } 121 } 122 123 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 124 pub enum Reducer { 125 CalendarProjection, 126 FarmOpsProjection, 127 GroupProjection, 128 KnowledgeProjection, 129 OperationalListingInventoryAccounting, 130 OperationalListingProjection, 131 MarketProjection, 132 OrderProjection, 133 ProfileProjection, 134 NostrRelayPolicyProjection, 135 SocialProjection, 136 TradeProjection, 137 TradeValidation, 138 } 139 140 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 141 pub enum ContentSchema { 142 Empty, 143 JsonObject, 144 PlainText, 145 Markdown, 146 Djot, 147 Encrypted, 148 BinaryReference, 149 } 150 151 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 152 pub enum TagCardinality { 153 RequiredOne, 154 OptionalOne, 155 OptionalMany, 156 RequiredMany, 157 } 158 159 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 160 pub enum TagSemantic { 161 AddressableCoordinate, 162 CalendarEventAuthor, 163 CalendarEventReference, 164 CalendarEventRevision, 165 CalendarInclusionRequest, 166 CalendarEnd, 167 CalendarStart, 168 Category, 169 Citation, 170 Contract, 171 Counterparty, 172 Evidence, 173 EventPointer, 174 FreeBusy, 175 Geohash, 176 GroupId, 177 Identifier, 178 Image, 179 Kind, 180 ClassifiedListingAddress, 181 OperationalListingSnapshot, 182 ListDescription, 183 Location, 184 Nip01Coordinate, 185 Participant, 186 PreviousEvent, 187 Price, 188 PublishedAt, 189 Relay, 190 Reference, 191 ReviewTarget, 192 RootEvent, 193 ServiceInput, 194 ServiceOutput, 195 Source, 196 Status, 197 Summary, 198 Title, 199 Topic, 200 TimeZone, 201 TradeMutation, 202 Url, 203 UtcDayCoverage, 204 } 205 206 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 207 pub enum TagValueType { 208 AddressableCoordinate, 209 CalendarDate, 210 CalendarEventCoordinate, 211 CalendarFreeBusy, 212 CalendarRsvpStatus, 213 CalendarUid, 214 ContractId, 215 DTag, 216 EventId, 217 EventPointer, 218 Geohash, 219 IanaTimeZoneId, 220 Kind, 221 MutationId, 222 Nip01Coordinate, 223 PublicKey, 224 RelayUrl, 225 Sha256, 226 Text, 227 UnixTimestamp, 228 Uri, 229 Url, 230 UtcDayIndex, 231 Uuid, 232 } 233 234 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 235 pub struct TagContract { 236 pub name: &'static str, 237 pub cardinality: TagCardinality, 238 pub semantic: TagSemantic, 239 pub value_type: TagValueType, 240 pub relay_indexed: bool, 241 } 242 243 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 244 pub enum EventDiscriminator { 245 KindOnly, 246 /// Exact profile selection is owned by a verified admission algorithm. 247 AdmissionOnly, 248 /// Exact NIP-99 profile selection uses the central raw marker partition. 249 ClassifiedListingPartition(ClassifiedListingPartition), 250 DTagExact(&'static str), 251 DTagPrefix(&'static str), 252 DTagSuffix(&'static str), 253 TagEquals { 254 name: &'static str, 255 value: &'static str, 256 }, 257 ContentJsonFieldEquals { 258 field: &'static str, 259 value: &'static str, 260 }, 261 EnvelopeType(&'static str), 262 Composite(&'static [EventDiscriminator]), 263 } 264 265 /// Governs whether a contract may enter the generic frozen-draft boundary. 266 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 267 pub enum EventAuthoringPolicy { 268 /// Generic contract validation is sufficient to construct a frozen draft. 269 GenericDraft, 270 /// Authoring requires a sealed typed API instead of generic draft parts. 271 TypedOnly, 272 /// The contract is an inbound/read boundary and cannot be authored. 273 ReadOnly, 274 } 275 276 impl EventAuthoringPolicy { 277 /// Returns whether untyped contract parts may construct a frozen draft. 278 #[must_use] 279 pub const fn permits_generic_draft(self) -> bool { 280 matches!(self, Self::GenericDraft) 281 } 282 283 /// Returns whether a sealed typed authoring API may construct an event. 284 #[must_use] 285 pub const fn permits_typed_authoring(self) -> bool { 286 !matches!(self, Self::ReadOnly) 287 } 288 289 /// Returns whether the contract is exclusively an inbound/read boundary. 290 #[must_use] 291 pub const fn is_read_only(self) -> bool { 292 matches!(self, Self::ReadOnly) 293 } 294 } 295 296 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 297 pub enum ContractMatchError { 298 UnsupportedKind(u32), 299 UnsupportedShape(u32), 300 AmbiguousShape(u32), 301 } 302 303 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 304 pub enum ContractFamily { 305 Account, 306 Application, 307 Calendar, 308 Farm, 309 Group, 310 Http, 311 Job, 312 Knowledge, 313 List, 314 Market, 315 Message, 316 Profile, 317 Relay, 318 Social, 319 Trade, 320 } 321 322 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 323 pub struct ContractFamilyMetadata { 324 pub family: ContractFamily, 325 pub id: &'static str, 326 pub name: &'static str, 327 } 328 329 #[derive(Clone, Debug, PartialEq, Eq)] 330 pub enum ContractValidationError { 331 UnknownContract { 332 contract_id: String, 333 }, 334 AdmissionRequired { 335 contract_id: &'static str, 336 }, 337 ContractMatch { 338 error: ContractMatchError, 339 }, 340 KindMismatch { 341 expected: u32, 342 actual: u32, 343 }, 344 ContentMustBeEmpty { 345 contract_id: &'static str, 346 }, 347 InvalidJsonContent { 348 contract_id: &'static str, 349 }, 350 MissingTag { 351 contract_id: &'static str, 352 name: &'static str, 353 }, 354 TagCardinalityMismatch { 355 contract_id: &'static str, 356 name: &'static str, 357 }, 358 TagValueMismatch { 359 contract_id: &'static str, 360 name: &'static str, 361 expected: String, 362 actual: Option<String>, 363 }, 364 MissingContentField { 365 contract_id: &'static str, 366 field: &'static str, 367 }, 368 ContentFieldMismatch { 369 contract_id: &'static str, 370 field: &'static str, 371 expected: String, 372 }, 373 ForbiddenContentField { 374 contract_id: &'static str, 375 field: &'static str, 376 }, 377 } 378 379 impl ContractValidationError { 380 pub const fn code(&self) -> &'static str { 381 match self { 382 Self::UnknownContract { .. } => "unknown_contract", 383 Self::AdmissionRequired { .. } => "admission_required", 384 Self::ContractMatch { .. } => "contract_match", 385 Self::KindMismatch { .. } => "kind_mismatch", 386 Self::ContentMustBeEmpty { .. } => "content_must_be_empty", 387 Self::InvalidJsonContent { .. } => "invalid_json_content", 388 Self::MissingTag { .. } => "missing_tag", 389 Self::TagCardinalityMismatch { .. } => "tag_cardinality_mismatch", 390 Self::TagValueMismatch { .. } => "tag_value_mismatch", 391 Self::MissingContentField { .. } => "missing_content_field", 392 Self::ContentFieldMismatch { .. } => "content_field_mismatch", 393 Self::ForbiddenContentField { .. } => "forbidden_content_field", 394 } 395 } 396 } 397 398 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 399 pub struct KindContract { 400 pub kind: u32, 401 pub canonical_constant: &'static str, 402 pub name: &'static str, 403 pub class: EventClass, 404 pub standard: NostrStandard, 405 pub accepted_event_contracts: &'static [&'static str], 406 } 407 408 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 409 pub struct EventContract { 410 pub id: &'static str, 411 pub kind: u32, 412 pub name: &'static str, 413 pub payload_type: &'static str, 414 pub class: EventClass, 415 pub stability: EventStability, 416 pub privacy: EventPrivacy, 417 required_author_role: AuthorRole, 418 pub content_schema: ContentSchema, 419 authoring_policy: EventAuthoringPolicy, 420 pub discriminator: EventDiscriminator, 421 pub tags: &'static [TagContract], 422 pub reducers: &'static [Reducer], 423 } 424 425 impl EventContract { 426 /// Returns the event-authoring role required by this contract. 427 /// 428 /// Hosts decide whether a concrete signer or actor has this role; the 429 /// event package only owns the contract requirement. 430 #[must_use] 431 pub const fn required_author_role(&self) -> AuthorRole { 432 self.required_author_role 433 } 434 435 /// Returns the single registry-owned policy governing authoring routes. 436 #[must_use] 437 pub const fn authoring_policy(&self) -> EventAuthoringPolicy { 438 self.authoring_policy 439 } 440 } 441 442 static CONTRACT_FAMILIES: &[ContractFamilyMetadata] = &[ 443 ContractFamilyMetadata { 444 family: ContractFamily::Account, 445 id: "account", 446 name: "Account", 447 }, 448 ContractFamilyMetadata { 449 family: ContractFamily::Application, 450 id: "application", 451 name: "Application", 452 }, 453 ContractFamilyMetadata { 454 family: ContractFamily::Calendar, 455 id: "calendar", 456 name: "Calendar", 457 }, 458 ContractFamilyMetadata { 459 family: ContractFamily::Farm, 460 id: "farm", 461 name: "Farm", 462 }, 463 ContractFamilyMetadata { 464 family: ContractFamily::Group, 465 id: "group", 466 name: "Group", 467 }, 468 ContractFamilyMetadata { 469 family: ContractFamily::Http, 470 id: "http", 471 name: "HTTP", 472 }, 473 ContractFamilyMetadata { 474 family: ContractFamily::Job, 475 id: "job", 476 name: "Job", 477 }, 478 ContractFamilyMetadata { 479 family: ContractFamily::Knowledge, 480 id: "knowledge", 481 name: "Knowledge", 482 }, 483 ContractFamilyMetadata { 484 family: ContractFamily::List, 485 id: "list", 486 name: "List", 487 }, 488 ContractFamilyMetadata { 489 family: ContractFamily::Market, 490 id: "market", 491 name: "Market", 492 }, 493 ContractFamilyMetadata { 494 family: ContractFamily::Message, 495 id: "message", 496 name: "Message", 497 }, 498 ContractFamilyMetadata { 499 family: ContractFamily::Profile, 500 id: "profile", 501 name: "Profile", 502 }, 503 ContractFamilyMetadata { 504 family: ContractFamily::Relay, 505 id: "relay", 506 name: "Relay", 507 }, 508 ContractFamilyMetadata { 509 family: ContractFamily::Social, 510 id: "social", 511 name: "Social", 512 }, 513 ContractFamilyMetadata { 514 family: ContractFamily::Trade, 515 id: "trade", 516 name: "Trade", 517 }, 518 ]; 519 520 const fn tag( 521 name: &'static str, 522 cardinality: TagCardinality, 523 semantic: TagSemantic, 524 value_type: TagValueType, 525 relay_indexed: bool, 526 ) -> TagContract { 527 TagContract { 528 name, 529 cardinality, 530 semantic, 531 value_type, 532 relay_indexed, 533 } 534 } 535 536 const TAG_D: TagContract = tag( 537 "d", 538 TagCardinality::RequiredOne, 539 TagSemantic::Identifier, 540 TagValueType::DTag, 541 true, 542 ); 543 const TAG_P_REQUIRED: TagContract = tag( 544 "p", 545 TagCardinality::RequiredOne, 546 TagSemantic::Counterparty, 547 TagValueType::PublicKey, 548 true, 549 ); 550 const TAG_P_MANY: TagContract = tag( 551 "p", 552 TagCardinality::OptionalMany, 553 TagSemantic::Counterparty, 554 TagValueType::PublicKey, 555 true, 556 ); 557 const TAG_P_TRADE_PARTIES: TagContract = tag( 558 "p", 559 TagCardinality::RequiredMany, 560 TagSemantic::Participant, 561 TagValueType::PublicKey, 562 true, 563 ); 564 const TAG_X_TRADE_MUTATIONS: TagContract = tag( 565 "x", 566 TagCardinality::RequiredMany, 567 TagSemantic::TradeMutation, 568 TagValueType::MutationId, 569 true, 570 ); 571 const TAG_X_RHI_ATTESTATION_REFERENCES: TagContract = tag( 572 "x", 573 TagCardinality::RequiredMany, 574 TagSemantic::Reference, 575 TagValueType::Sha256, 576 true, 577 ); 578 const TAG_T_RHI_ATTESTATION_OUTCOME: TagContract = tag( 579 "t", 580 TagCardinality::RequiredOne, 581 TagSemantic::Status, 582 TagValueType::Text, 583 true, 584 ); 585 const TAG_E_RHI_SUPERSEDES_EVENT: TagContract = tag( 586 "e", 587 TagCardinality::OptionalOne, 588 TagSemantic::PreviousEvent, 589 TagValueType::EventId, 590 true, 591 ); 592 const TAG_CALENDAR_PARTICIPANT: TagContract = tag( 593 "p", 594 TagCardinality::OptionalMany, 595 TagSemantic::Participant, 596 TagValueType::PublicKey, 597 true, 598 ); 599 const TAG_A_ADDRESS_REQUIRED: TagContract = tag( 600 "a", 601 TagCardinality::RequiredOne, 602 TagSemantic::AddressableCoordinate, 603 TagValueType::AddressableCoordinate, 604 true, 605 ); 606 const TAG_A_OPTIONAL: TagContract = tag( 607 "a", 608 TagCardinality::OptionalOne, 609 TagSemantic::AddressableCoordinate, 610 TagValueType::AddressableCoordinate, 611 true, 612 ); 613 const TAG_A_MANY: TagContract = tag( 614 "a", 615 TagCardinality::OptionalMany, 616 TagSemantic::AddressableCoordinate, 617 TagValueType::AddressableCoordinate, 618 true, 619 ); 620 const TAG_CALENDAR_INCLUSION_REQUEST: TagContract = tag( 621 "a", 622 TagCardinality::OptionalMany, 623 TagSemantic::CalendarInclusionRequest, 624 TagValueType::AddressableCoordinate, 625 true, 626 ); 627 const TAG_E_SOURCE_VERSION: TagContract = tag( 628 "e", 629 TagCardinality::RequiredOne, 630 TagSemantic::Source, 631 TagValueType::EventId, 632 true, 633 ); 634 const TAG_E_BASE_VERSION: TagContract = tag( 635 "e", 636 TagCardinality::OptionalOne, 637 TagSemantic::PreviousEvent, 638 TagValueType::EventId, 639 true, 640 ); 641 const TAG_E_MANY: TagContract = tag( 642 "e", 643 TagCardinality::OptionalMany, 644 TagSemantic::EventPointer, 645 TagValueType::EventId, 646 true, 647 ); 648 const TAG_NIP09_E_TARGET: TagContract = tag( 649 "e", 650 TagCardinality::OptionalMany, 651 TagSemantic::EventPointer, 652 TagValueType::EventId, 653 true, 654 ); 655 const TAG_NIP09_A_TARGET: TagContract = tag( 656 "a", 657 TagCardinality::OptionalMany, 658 TagSemantic::Nip01Coordinate, 659 TagValueType::Nip01Coordinate, 660 true, 661 ); 662 const TAG_NIP09_K_ADVISORY: TagContract = tag( 663 "k", 664 TagCardinality::OptionalMany, 665 TagSemantic::Kind, 666 TagValueType::Kind, 667 true, 668 ); 669 const TAG_NIP10_E_REQUIRED: TagContract = tag( 670 "e", 671 TagCardinality::RequiredMany, 672 TagSemantic::EventPointer, 673 TagValueType::EventId, 674 true, 675 ); 676 const TAG_NIP10_P_OPTIONAL: TagContract = tag( 677 "p", 678 TagCardinality::OptionalMany, 679 TagSemantic::Participant, 680 TagValueType::PublicKey, 681 true, 682 ); 683 const TAG_NIP22_E_ROOT: TagContract = tag( 684 "E", 685 TagCardinality::OptionalOne, 686 TagSemantic::RootEvent, 687 TagValueType::EventId, 688 true, 689 ); 690 const TAG_NIP22_A_ROOT: TagContract = tag( 691 "A", 692 TagCardinality::OptionalOne, 693 TagSemantic::AddressableCoordinate, 694 TagValueType::AddressableCoordinate, 695 true, 696 ); 697 const TAG_NIP22_K_ROOT: TagContract = tag( 698 "K", 699 TagCardinality::RequiredOne, 700 TagSemantic::Kind, 701 TagValueType::Kind, 702 true, 703 ); 704 const TAG_NIP22_P_ROOT: TagContract = tag( 705 "P", 706 TagCardinality::RequiredOne, 707 TagSemantic::Participant, 708 TagValueType::PublicKey, 709 true, 710 ); 711 const TAG_NIP22_A_PARENT: TagContract = tag( 712 "a", 713 TagCardinality::OptionalOne, 714 TagSemantic::AddressableCoordinate, 715 TagValueType::AddressableCoordinate, 716 true, 717 ); 718 const TAG_NIP22_E_PARENT: TagContract = tag( 719 "e", 720 TagCardinality::RequiredOne, 721 TagSemantic::EventPointer, 722 TagValueType::EventId, 723 true, 724 ); 725 const TAG_NIP22_K_PARENT: TagContract = tag( 726 "k", 727 TagCardinality::RequiredOne, 728 TagSemantic::Kind, 729 TagValueType::Kind, 730 true, 731 ); 732 const TAG_NIP22_P_PARENT: TagContract = tag( 733 "p", 734 TagCardinality::RequiredMany, 735 TagSemantic::Participant, 736 TagValueType::PublicKey, 737 true, 738 ); 739 const TAG_KIND: TagContract = tag( 740 "k", 741 TagCardinality::OptionalOne, 742 TagSemantic::Kind, 743 TagValueType::Kind, 744 true, 745 ); 746 const TAG_RELAY: TagContract = tag( 747 "relay", 748 TagCardinality::OptionalMany, 749 TagSemantic::Relay, 750 TagValueType::RelayUrl, 751 false, 752 ); 753 const TAG_GROUP: TagContract = tag( 754 "h", 755 TagCardinality::RequiredOne, 756 TagSemantic::GroupId, 757 TagValueType::DTag, 758 true, 759 ); 760 const TAG_TITLE: TagContract = tag( 761 "title", 762 TagCardinality::OptionalOne, 763 TagSemantic::Title, 764 TagValueType::Text, 765 false, 766 ); 767 const TAG_CALENDAR_TITLE: TagContract = tag( 768 "title", 769 TagCardinality::RequiredOne, 770 TagSemantic::Title, 771 TagValueType::Text, 772 false, 773 ); 774 const TAG_CALENDAR_LEGACY_NAME: TagContract = tag( 775 "name", 776 TagCardinality::OptionalOne, 777 TagSemantic::Title, 778 TagValueType::Text, 779 false, 780 ); 781 const TAG_CALENDAR_UID: TagContract = tag( 782 "d", 783 TagCardinality::RequiredOne, 784 TagSemantic::Identifier, 785 TagValueType::CalendarUid, 786 true, 787 ); 788 const TAG_CALENDAR_COLLECTION_DESCRIPTION: TagContract = tag( 789 "description", 790 TagCardinality::OptionalOne, 791 TagSemantic::ListDescription, 792 TagValueType::Text, 793 false, 794 ); 795 const TAG_CALENDAR_COLLECTION_EVENT: TagContract = tag( 796 "a", 797 TagCardinality::OptionalMany, 798 TagSemantic::CalendarEventReference, 799 TagValueType::CalendarEventCoordinate, 800 true, 801 ); 802 const TAG_CALENDAR_RSVP_EVENT: TagContract = tag( 803 "a", 804 TagCardinality::RequiredOne, 805 TagSemantic::CalendarEventReference, 806 TagValueType::CalendarEventCoordinate, 807 true, 808 ); 809 const TAG_CALENDAR_RSVP_REVISION: TagContract = tag( 810 "e", 811 TagCardinality::OptionalOne, 812 TagSemantic::CalendarEventRevision, 813 TagValueType::EventId, 814 true, 815 ); 816 const TAG_CALENDAR_RSVP_STATUS: TagContract = tag( 817 "status", 818 TagCardinality::RequiredOne, 819 TagSemantic::Status, 820 TagValueType::CalendarRsvpStatus, 821 false, 822 ); 823 const TAG_CALENDAR_RSVP_FREE_BUSY: TagContract = tag( 824 "fb", 825 TagCardinality::OptionalOne, 826 TagSemantic::FreeBusy, 827 TagValueType::CalendarFreeBusy, 828 false, 829 ); 830 const TAG_CALENDAR_RSVP_AUTHOR: TagContract = tag( 831 "p", 832 TagCardinality::OptionalOne, 833 TagSemantic::CalendarEventAuthor, 834 TagValueType::PublicKey, 835 true, 836 ); 837 const TAG_CALENDAR_DATE_START: TagContract = tag( 838 "start", 839 TagCardinality::RequiredOne, 840 TagSemantic::CalendarStart, 841 TagValueType::CalendarDate, 842 false, 843 ); 844 const TAG_CALENDAR_DATE_END: TagContract = tag( 845 "end", 846 TagCardinality::OptionalOne, 847 TagSemantic::CalendarEnd, 848 TagValueType::CalendarDate, 849 false, 850 ); 851 const TAG_CALENDAR_TIME_START: TagContract = tag( 852 "start", 853 TagCardinality::RequiredOne, 854 TagSemantic::CalendarStart, 855 TagValueType::UnixTimestamp, 856 false, 857 ); 858 const TAG_CALENDAR_TIME_END: TagContract = tag( 859 "end", 860 TagCardinality::OptionalOne, 861 TagSemantic::CalendarEnd, 862 TagValueType::UnixTimestamp, 863 false, 864 ); 865 const TAG_CALENDAR_COVERED_UTC_DAY: TagContract = tag( 866 "D", 867 TagCardinality::RequiredMany, 868 TagSemantic::UtcDayCoverage, 869 TagValueType::UtcDayIndex, 870 true, 871 ); 872 const TAG_CALENDAR_START_TZID: TagContract = tag( 873 "start_tzid", 874 TagCardinality::OptionalOne, 875 TagSemantic::TimeZone, 876 TagValueType::IanaTimeZoneId, 877 false, 878 ); 879 const TAG_CALENDAR_END_TZID: TagContract = tag( 880 "end_tzid", 881 TagCardinality::OptionalOne, 882 TagSemantic::TimeZone, 883 TagValueType::IanaTimeZoneId, 884 false, 885 ); 886 const TAG_SUMMARY: TagContract = tag( 887 "summary", 888 TagCardinality::OptionalOne, 889 TagSemantic::Summary, 890 TagValueType::Text, 891 false, 892 ); 893 const TAG_PUBLISHED_AT: TagContract = tag( 894 "published_at", 895 TagCardinality::OptionalOne, 896 TagSemantic::PublishedAt, 897 TagValueType::UnixTimestamp, 898 false, 899 ); 900 const TAG_LOCATION: TagContract = tag( 901 "location", 902 TagCardinality::OptionalMany, 903 TagSemantic::Location, 904 TagValueType::Text, 905 false, 906 ); 907 const TAG_CALENDAR_LOCATION: TagContract = tag( 908 "location", 909 TagCardinality::OptionalMany, 910 TagSemantic::Location, 911 TagValueType::Text, 912 false, 913 ); 914 const TAG_PRICE: TagContract = tag( 915 "price", 916 TagCardinality::OptionalMany, 917 TagSemantic::Price, 918 TagValueType::Text, 919 false, 920 ); 921 const TAG_STATUS: TagContract = tag( 922 "status", 923 TagCardinality::OptionalOne, 924 TagSemantic::Status, 925 TagValueType::Text, 926 false, 927 ); 928 const TAG_IMAGE: TagContract = tag( 929 "image", 930 TagCardinality::OptionalMany, 931 TagSemantic::Image, 932 TagValueType::Url, 933 false, 934 ); 935 const TAG_FOOD_TITLE: TagContract = tag( 936 "title", 937 TagCardinality::RequiredOne, 938 TagSemantic::Title, 939 TagValueType::Text, 940 false, 941 ); 942 const TAG_FOOD_SUMMARY: TagContract = tag( 943 "summary", 944 TagCardinality::RequiredOne, 945 TagSemantic::Summary, 946 TagValueType::Text, 947 false, 948 ); 949 const TAG_FOOD_PUBLISHED_AT: TagContract = tag( 950 "published_at", 951 TagCardinality::RequiredOne, 952 TagSemantic::PublishedAt, 953 TagValueType::UnixTimestamp, 954 false, 955 ); 956 const TAG_FOOD_LOCATION: TagContract = tag( 957 "location", 958 TagCardinality::RequiredOne, 959 TagSemantic::Location, 960 TagValueType::Text, 961 false, 962 ); 963 const TAG_FOOD_PRICE: TagContract = tag( 964 "price", 965 TagCardinality::RequiredOne, 966 TagSemantic::Price, 967 TagValueType::Text, 968 false, 969 ); 970 const TAG_FOOD_PRICE_UNIT: TagContract = tag( 971 TAG_RADROOTS_PRICE_UNIT, 972 TagCardinality::RequiredOne, 973 TagSemantic::Price, 974 TagValueType::Text, 975 false, 976 ); 977 const TAG_FOOD_QUANTITY: TagContract = tag( 978 TAG_RADROOTS_QUANTITY, 979 TagCardinality::OptionalOne, 980 TagSemantic::Price, 981 TagValueType::Text, 982 false, 983 ); 984 const TAG_FOOD_STATUS: TagContract = tag( 985 "status", 986 TagCardinality::RequiredOne, 987 TagSemantic::Status, 988 TagValueType::Text, 989 false, 990 ); 991 const TAG_OPERATIONAL_LISTING_FARM: TagContract = tag( 992 "a", 993 TagCardinality::RequiredOne, 994 TagSemantic::AddressableCoordinate, 995 TagValueType::AddressableCoordinate, 996 true, 997 ); 998 const TAG_OPERATIONAL_LISTING_PRODUCT_KEY: TagContract = tag( 999 "key", 1000 TagCardinality::RequiredOne, 1001 TagSemantic::Category, 1002 TagValueType::Text, 1003 false, 1004 ); 1005 const TAG_OPERATIONAL_LISTING_TITLE: TagContract = tag( 1006 "title", 1007 TagCardinality::RequiredOne, 1008 TagSemantic::Title, 1009 TagValueType::Text, 1010 false, 1011 ); 1012 const TAG_OPERATIONAL_LISTING_CATEGORY: TagContract = tag( 1013 "category", 1014 TagCardinality::RequiredOne, 1015 TagSemantic::Category, 1016 TagValueType::Text, 1017 false, 1018 ); 1019 const TAG_OPERATIONAL_LISTING_PRIMARY_BIN: TagContract = tag( 1020 "radroots:primary_bin", 1021 TagCardinality::RequiredOne, 1022 TagSemantic::OperationalListingSnapshot, 1023 TagValueType::Text, 1024 false, 1025 ); 1026 const TAG_OPERATIONAL_LISTING_BIN: TagContract = tag( 1027 "radroots:bin", 1028 TagCardinality::RequiredMany, 1029 TagSemantic::OperationalListingSnapshot, 1030 TagValueType::Text, 1031 false, 1032 ); 1033 const TAG_OPERATIONAL_LISTING_PRICE: TagContract = tag( 1034 "radroots:price", 1035 TagCardinality::RequiredMany, 1036 TagSemantic::Price, 1037 TagValueType::Text, 1038 false, 1039 ); 1040 const TAG_OPERATIONAL_LISTING_DISCOUNT: TagContract = tag( 1041 "radroots:discount", 1042 TagCardinality::OptionalMany, 1043 TagSemantic::Price, 1044 TagValueType::Text, 1045 false, 1046 ); 1047 const TAG_OPERATIONAL_LISTING_RESOURCE_AREA: TagContract = tag( 1048 "radroots:resource_area", 1049 TagCardinality::OptionalOne, 1050 TagSemantic::AddressableCoordinate, 1051 TagValueType::AddressableCoordinate, 1052 false, 1053 ); 1054 const TAG_OPERATIONAL_LISTING_PLOT: TagContract = tag( 1055 "radroots:plot", 1056 TagCardinality::OptionalOne, 1057 TagSemantic::AddressableCoordinate, 1058 TagValueType::AddressableCoordinate, 1059 false, 1060 ); 1061 const TAG_OPERATIONAL_LISTING_INVENTORY: TagContract = tag( 1062 "inventory", 1063 TagCardinality::OptionalOne, 1064 TagSemantic::OperationalListingSnapshot, 1065 TagValueType::Text, 1066 false, 1067 ); 1068 const TAG_OPERATIONAL_LISTING_AVAILABILITY_START: TagContract = tag( 1069 "radroots:availability_start", 1070 TagCardinality::OptionalOne, 1071 TagSemantic::Status, 1072 TagValueType::UnixTimestamp, 1073 false, 1074 ); 1075 const TAG_OPERATIONAL_LISTING_EXPIRES_AT: TagContract = tag( 1076 "expires_at", 1077 TagCardinality::OptionalOne, 1078 TagSemantic::Status, 1079 TagValueType::UnixTimestamp, 1080 false, 1081 ); 1082 const TAG_OPERATIONAL_LISTING_DELIVERY: TagContract = tag( 1083 "delivery", 1084 TagCardinality::OptionalOne, 1085 TagSemantic::Reference, 1086 TagValueType::Text, 1087 false, 1088 ); 1089 const TAG_OPERATIONAL_LISTING_PROCESS: TagContract = tag( 1090 "process", 1091 TagCardinality::OptionalOne, 1092 TagSemantic::Category, 1093 TagValueType::Text, 1094 false, 1095 ); 1096 const TAG_OPERATIONAL_LISTING_LOT: TagContract = tag( 1097 "lot", 1098 TagCardinality::OptionalOne, 1099 TagSemantic::Reference, 1100 TagValueType::Text, 1101 false, 1102 ); 1103 const TAG_OPERATIONAL_LISTING_PROFILE: TagContract = tag( 1104 "profile", 1105 TagCardinality::OptionalOne, 1106 TagSemantic::Category, 1107 TagValueType::Text, 1108 false, 1109 ); 1110 const TAG_OPERATIONAL_LISTING_YEAR: TagContract = tag( 1111 "year", 1112 TagCardinality::OptionalOne, 1113 TagSemantic::Category, 1114 TagValueType::Text, 1115 false, 1116 ); 1117 const TAG_CALENDAR_IMAGE: TagContract = tag( 1118 "image", 1119 TagCardinality::OptionalOne, 1120 TagSemantic::Image, 1121 TagValueType::Url, 1122 false, 1123 ); 1124 const TAG_URL: TagContract = tag( 1125 "url", 1126 TagCardinality::OptionalOne, 1127 TagSemantic::Url, 1128 TagValueType::Url, 1129 false, 1130 ); 1131 const TAG_CONTRACT_REQUIRED: TagContract = tag( 1132 "contract", 1133 TagCardinality::RequiredOne, 1134 TagSemantic::Contract, 1135 TagValueType::ContractId, 1136 false, 1137 ); 1138 const TAG_TOPIC_MANY: TagContract = tag( 1139 "t", 1140 TagCardinality::OptionalMany, 1141 TagSemantic::Topic, 1142 TagValueType::Text, 1143 true, 1144 ); 1145 const TAG_ASK_MARKER: TagContract = tag( 1146 "t", 1147 TagCardinality::RequiredOne, 1148 TagSemantic::Topic, 1149 TagValueType::Text, 1150 true, 1151 ); 1152 const TAG_IMETA_REQUIRED_MANY: TagContract = tag( 1153 "imeta", 1154 TagCardinality::RequiredMany, 1155 TagSemantic::Image, 1156 TagValueType::Text, 1157 false, 1158 ); 1159 const TAG_IMETA_OPTIONAL_MANY: TagContract = tag( 1160 "imeta", 1161 TagCardinality::OptionalMany, 1162 TagSemantic::Image, 1163 TagValueType::Text, 1164 false, 1165 ); 1166 const TAG_CALENDAR_REFERENCE: TagContract = tag( 1167 "r", 1168 TagCardinality::OptionalMany, 1169 TagSemantic::Reference, 1170 TagValueType::Uri, 1171 true, 1172 ); 1173 const TAG_GEOHASH_OPTIONAL: TagContract = tag( 1174 "g", 1175 TagCardinality::OptionalOne, 1176 TagSemantic::Geohash, 1177 TagValueType::Geohash, 1178 true, 1179 ); 1180 const TAG_SOURCE_MANY: TagContract = tag( 1181 "source", 1182 TagCardinality::OptionalMany, 1183 TagSemantic::Source, 1184 TagValueType::EventPointer, 1185 false, 1186 ); 1187 const TAG_CITATION_MANY: TagContract = tag( 1188 "citation", 1189 TagCardinality::OptionalMany, 1190 TagSemantic::Citation, 1191 TagValueType::Sha256, 1192 false, 1193 ); 1194 const TAG_REVIEW_TARGET_REQUIRED: TagContract = tag( 1195 "review_target", 1196 TagCardinality::RequiredOne, 1197 TagSemantic::ReviewTarget, 1198 TagValueType::EventPointer, 1199 false, 1200 ); 1201 const TAG_EVIDENCE_MANY: TagContract = tag( 1202 "evidence", 1203 TagCardinality::OptionalMany, 1204 TagSemantic::Evidence, 1205 TagValueType::EventPointer, 1206 false, 1207 ); 1208 1209 const NO_TAGS: &[TagContract] = &[]; 1210 const D_TAGS: &[TagContract] = &[TAG_D]; 1211 const P_TAGS: &[TagContract] = &[TAG_P_MANY]; 1212 const EVENT_POINTER_TAGS: &[TagContract] = &[TAG_E_MANY, TAG_P_MANY, TAG_KIND]; 1213 const NIP09_DELETION_TAGS: &[TagContract] = 1214 &[TAG_NIP09_E_TARGET, TAG_NIP09_A_TARGET, TAG_NIP09_K_ADVISORY]; 1215 const NIP22_COMMENT_TAGS: &[TagContract] = &[ 1216 TAG_NIP22_E_ROOT, 1217 TAG_NIP22_A_ROOT, 1218 TAG_NIP22_K_ROOT, 1219 TAG_NIP22_P_ROOT, 1220 TAG_NIP22_A_PARENT, 1221 TAG_NIP22_E_PARENT, 1222 TAG_NIP22_K_PARENT, 1223 TAG_NIP22_P_PARENT, 1224 ]; 1225 const LIST_TAGS: &[TagContract] = &[TAG_E_MANY, TAG_A_OPTIONAL, TAG_P_MANY, TAG_RELAY]; 1226 const LIST_SET_TAGS: &[TagContract] = &[TAG_D, TAG_E_MANY, TAG_A_OPTIONAL, TAG_P_MANY]; 1227 const PROFILE_TAGS: &[TagContract] = &[TAG_P_MANY]; 1228 const GROUP_ACTION_TAGS: &[TagContract] = &[TAG_GROUP, TAG_P_MANY, TAG_E_MANY]; 1229 const GROUP_STATE_TAGS: &[TagContract] = &[TAG_D, TAG_P_MANY, TAG_E_MANY]; 1230 const FILE_METADATA_TAGS: &[TagContract] = &[TAG_URL, TAG_IMAGE]; 1231 const ARTICLE_TAGS: &[TagContract] = &[TAG_D, TAG_TITLE, TAG_SUMMARY, TAG_PUBLISHED_AT]; 1232 const WIKI_ARTICLE_TAGS: &[TagContract] = &[ 1233 TAG_D, 1234 TAG_TITLE, 1235 TAG_SUMMARY, 1236 TAG_PUBLISHED_AT, 1237 TAG_TOPIC_MANY, 1238 TAG_SOURCE_MANY, 1239 TAG_A_MANY, 1240 TAG_E_MANY, 1241 ]; 1242 const WIKI_REDIRECT_TAGS: &[TagContract] = &[TAG_D, TAG_A_ADDRESS_REQUIRED]; 1243 const WIKI_MERGE_REQUEST_TAGS: &[TagContract] = &[ 1244 TAG_A_ADDRESS_REQUIRED, 1245 TAG_P_REQUIRED, 1246 TAG_E_SOURCE_VERSION, 1247 TAG_E_BASE_VERSION, 1248 ]; 1249 const CALENDAR_DATE_EVENT_TAGS: &[TagContract] = &[ 1250 TAG_D, 1251 TAG_CALENDAR_TITLE, 1252 TAG_CALENDAR_LEGACY_NAME, 1253 TAG_CALENDAR_DATE_START, 1254 TAG_CALENDAR_DATE_END, 1255 TAG_CALENDAR_LOCATION, 1256 TAG_GEOHASH_OPTIONAL, 1257 TAG_SUMMARY, 1258 TAG_CALENDAR_IMAGE, 1259 TAG_CALENDAR_PARTICIPANT, 1260 TAG_TOPIC_MANY, 1261 TAG_CALENDAR_REFERENCE, 1262 TAG_CALENDAR_INCLUSION_REQUEST, 1263 ]; 1264 const CALENDAR_TIME_EVENT_TAGS: &[TagContract] = &[ 1265 TAG_D, 1266 TAG_CALENDAR_TITLE, 1267 TAG_CALENDAR_LEGACY_NAME, 1268 TAG_CALENDAR_TIME_START, 1269 TAG_CALENDAR_TIME_END, 1270 TAG_CALENDAR_COVERED_UTC_DAY, 1271 TAG_CALENDAR_START_TZID, 1272 TAG_CALENDAR_END_TZID, 1273 TAG_CALENDAR_LOCATION, 1274 TAG_GEOHASH_OPTIONAL, 1275 TAG_SUMMARY, 1276 TAG_CALENDAR_IMAGE, 1277 TAG_CALENDAR_PARTICIPANT, 1278 TAG_TOPIC_MANY, 1279 TAG_CALENDAR_REFERENCE, 1280 TAG_CALENDAR_INCLUSION_REQUEST, 1281 ]; 1282 const CALENDAR_COLLECTION_TAGS: &[TagContract] = &[ 1283 TAG_CALENDAR_UID, 1284 TAG_CALENDAR_TITLE, 1285 TAG_CALENDAR_COLLECTION_DESCRIPTION, 1286 TAG_CALENDAR_IMAGE, 1287 TAG_CALENDAR_COLLECTION_EVENT, 1288 ]; 1289 const CALENDAR_RSVP_TAGS: &[TagContract] = &[ 1290 TAG_CALENDAR_UID, 1291 TAG_CALENDAR_RSVP_EVENT, 1292 TAG_CALENDAR_RSVP_REVISION, 1293 TAG_CALENDAR_RSVP_STATUS, 1294 TAG_CALENDAR_RSVP_FREE_BUSY, 1295 TAG_CALENDAR_RSVP_AUTHOR, 1296 ]; 1297 const FARM_TAGS: &[TagContract] = &[TAG_D, TAG_TITLE, TAG_LOCATION, TAG_IMAGE]; 1298 const FOOD_AVAILABILITY_TAGS: &[TagContract] = &[ 1299 TAG_D, 1300 TAG_FOOD_TITLE, 1301 TAG_FOOD_SUMMARY, 1302 TAG_FOOD_PUBLISHED_AT, 1303 TAG_FOOD_LOCATION, 1304 TAG_FOOD_PRICE, 1305 TAG_FOOD_PRICE_UNIT, 1306 TAG_FOOD_QUANTITY, 1307 TAG_FOOD_STATUS, 1308 TAG_IMAGE, 1309 ]; 1310 const OPERATIONAL_LISTING_TAGS: &[TagContract] = &[ 1311 TAG_D, 1312 TAG_P_REQUIRED, 1313 TAG_OPERATIONAL_LISTING_FARM, 1314 TAG_OPERATIONAL_LISTING_PRODUCT_KEY, 1315 TAG_OPERATIONAL_LISTING_TITLE, 1316 TAG_OPERATIONAL_LISTING_CATEGORY, 1317 TAG_SUMMARY, 1318 TAG_PUBLISHED_AT, 1319 TAG_OPERATIONAL_LISTING_PROCESS, 1320 TAG_OPERATIONAL_LISTING_LOT, 1321 TAG_OPERATIONAL_LISTING_PROFILE, 1322 TAG_OPERATIONAL_LISTING_YEAR, 1323 TAG_LOCATION, 1324 TAG_PRICE, 1325 TAG_STATUS, 1326 TAG_IMAGE, 1327 TAG_GEOHASH_OPTIONAL, 1328 TAG_OPERATIONAL_LISTING_PRIMARY_BIN, 1329 TAG_OPERATIONAL_LISTING_BIN, 1330 TAG_OPERATIONAL_LISTING_PRICE, 1331 TAG_OPERATIONAL_LISTING_DISCOUNT, 1332 TAG_OPERATIONAL_LISTING_RESOURCE_AREA, 1333 TAG_OPERATIONAL_LISTING_PLOT, 1334 TAG_OPERATIONAL_LISTING_INVENTORY, 1335 TAG_OPERATIONAL_LISTING_AVAILABILITY_START, 1336 TAG_OPERATIONAL_LISTING_EXPIRES_AT, 1337 TAG_OPERATIONAL_LISTING_DELIVERY, 1338 ]; 1339 const TRADE_MUTATION_TAGS: &[TagContract] = &[ 1340 TAG_CONTRACT_REQUIRED, 1341 TAG_D, 1342 TAG_X_TRADE_MUTATIONS, 1343 TAG_P_TRADE_PARTIES, 1344 ]; 1345 const RHI_EVIDENCE_ATTESTATION_TAGS: &[TagContract] = &[ 1346 TAG_CONTRACT_REQUIRED, 1347 TAG_D, 1348 TAG_X_RHI_ATTESTATION_REFERENCES, 1349 TAG_T_RHI_ATTESTATION_OUTCOME, 1350 TAG_E_RHI_SUPERSEDES_EVENT, 1351 ]; 1352 const KNOWLEDGE_SOURCE_TAGS: &[TagContract] = &[ 1353 TAG_D, 1354 TAG_CONTRACT_REQUIRED, 1355 TAG_TOPIC_MANY, 1356 TAG_SOURCE_MANY, 1357 ]; 1358 const KNOWLEDGE_CLAIM_TAGS: &[TagContract] = &[ 1359 TAG_CONTRACT_REQUIRED, 1360 TAG_TOPIC_MANY, 1361 TAG_SOURCE_MANY, 1362 TAG_CITATION_MANY, 1363 ]; 1364 const KNOWLEDGE_RELATION_TAGS: &[TagContract] = 1365 &[TAG_CONTRACT_REQUIRED, TAG_TOPIC_MANY, TAG_SOURCE_MANY]; 1366 const KNOWLEDGE_REVIEW_TAGS: &[TagContract] = &[ 1367 TAG_CONTRACT_REQUIRED, 1368 TAG_REVIEW_TARGET_REQUIRED, 1369 TAG_EVIDENCE_MANY, 1370 ]; 1371 const KNOWLEDGE_FIELD_REPORT_TAGS: &[TagContract] = &[ 1372 TAG_CONTRACT_REQUIRED, 1373 TAG_TOPIC_MANY, 1374 TAG_GEOHASH_OPTIONAL, 1375 TAG_EVIDENCE_MANY, 1376 ]; 1377 const KNOWLEDGE_CHANGE_PROPOSAL_TAGS: &[TagContract] = &[TAG_CONTRACT_REQUIRED, TAG_EVIDENCE_MANY]; 1378 const KNOWLEDGE_CONTRIBUTION_TAGS: &[TagContract] = &[TAG_CONTRACT_REQUIRED, TAG_EVIDENCE_MANY]; 1379 const EVIDENCE_BOUNTY_TAGS: &[TagContract] = &[ 1380 TAG_D, 1381 TAG_CONTRACT_REQUIRED, 1382 TAG_TOPIC_MANY, 1383 TAG_EVIDENCE_MANY, 1384 ]; 1385 1386 const SOCIAL_REDUCERS: &[Reducer] = &[Reducer::SocialProjection]; 1387 const PHOTO_UPDATE_TAGS: &[TagContract] = &[TAG_IMETA_REQUIRED_MANY]; 1388 const ASK_TAGS: &[TagContract] = &[TAG_ASK_MARKER, TAG_IMETA_OPTIONAL_MANY]; 1389 const NIP10_REPLY_TAGS: &[TagContract] = &[TAG_NIP10_E_REQUIRED, TAG_NIP10_P_OPTIONAL]; 1390 const PROFILE_REDUCERS: &[Reducer] = &[Reducer::ProfileProjection]; 1391 const FARM_OPS_REDUCERS: &[Reducer] = &[Reducer::FarmOpsProjection]; 1392 const GROUP_REDUCERS: &[Reducer] = &[Reducer::GroupProjection]; 1393 const CALENDAR_REDUCERS: &[Reducer] = &[Reducer::CalendarProjection]; 1394 const OPERATIONAL_LISTING_REDUCERS: &[Reducer] = &[ 1395 Reducer::OperationalListingProjection, 1396 Reducer::MarketProjection, 1397 Reducer::OperationalListingInventoryAccounting, 1398 ]; 1399 const FOOD_AVAILABILITY_REDUCERS: &[Reducer] = &[Reducer::MarketProjection]; 1400 const TRADE_MUTATION_REDUCERS: &[Reducer] = &[ 1401 Reducer::TradeProjection, 1402 Reducer::OperationalListingInventoryAccounting, 1403 ]; 1404 const TRADE_VALIDATION_REDUCERS: &[Reducer] = &[Reducer::TradeValidation]; 1405 const RELAY_REDUCERS: &[Reducer] = &[Reducer::NostrRelayPolicyProjection]; 1406 const KNOWLEDGE_REDUCERS: &[Reducer] = &[Reducer::KnowledgeProjection]; 1407 1408 const FARM_MEMBERS_LIST_DISCRIMINATOR: &[EventDiscriminator] = &[ 1409 EventDiscriminator::DTagPrefix("farm:"), 1410 EventDiscriminator::DTagSuffix(":members"), 1411 ]; 1412 const FARM_OWNERS_LIST_DISCRIMINATOR: &[EventDiscriminator] = &[ 1413 EventDiscriminator::DTagPrefix("farm:"), 1414 EventDiscriminator::DTagSuffix(":members.owners"), 1415 ]; 1416 const FARM_WORKERS_LIST_DISCRIMINATOR: &[EventDiscriminator] = &[ 1417 EventDiscriminator::DTagPrefix("farm:"), 1418 EventDiscriminator::DTagSuffix(":members.workers"), 1419 ]; 1420 const FARM_PLOTS_LIST_DISCRIMINATOR: &[EventDiscriminator] = &[ 1421 EventDiscriminator::DTagPrefix("farm:"), 1422 EventDiscriminator::DTagSuffix(":plots"), 1423 ]; 1424 const FARM_LISTINGS_LIST_DISCRIMINATOR: &[EventDiscriminator] = &[ 1425 EventDiscriminator::DTagPrefix("farm:"), 1426 EventDiscriminator::DTagSuffix(":listings"), 1427 ]; 1428 1429 macro_rules! kind_contract { 1430 ($kind:expr, $constant:literal, $name:literal, $class:expr, $standard:expr, [$($contract:literal),+ $(,)?]) => { 1431 KindContract { 1432 kind: $kind, 1433 canonical_constant: $constant, 1434 name: $name, 1435 class: $class, 1436 standard: $standard, 1437 accepted_event_contracts: &[$($contract),+], 1438 } 1439 }; 1440 } 1441 1442 macro_rules! event_contract_with_stability { 1443 ( 1444 $id:literal, 1445 $kind:expr, 1446 $name:literal, 1447 $payload_type:literal, 1448 $class:expr, 1449 $standard_privacy:expr, 1450 $required_author_role:expr, 1451 $content_schema:expr, 1452 $discriminator:expr, 1453 $tags:expr, 1454 $reducers:expr, 1455 $stability:expr $(,)? 1456 ) => { 1457 EventContract { 1458 id: $id, 1459 kind: $kind, 1460 name: $name, 1461 payload_type: $payload_type, 1462 class: $class, 1463 stability: $stability, 1464 privacy: $standard_privacy, 1465 required_author_role: $required_author_role, 1466 content_schema: $content_schema, 1467 authoring_policy: EventAuthoringPolicy::GenericDraft, 1468 discriminator: $discriminator, 1469 tags: $tags, 1470 reducers: $reducers, 1471 } 1472 }; 1473 } 1474 1475 macro_rules! event_contract_with_authoring_policy { 1476 ( 1477 $id:literal, 1478 $kind:expr, 1479 $name:literal, 1480 $payload_type:literal, 1481 $class:expr, 1482 $standard_privacy:expr, 1483 $required_author_role:expr, 1484 $content_schema:expr, 1485 $authoring_policy:expr, 1486 $discriminator:expr, 1487 $tags:expr, 1488 $reducers:expr $(,)? 1489 ) => { 1490 EventContract { 1491 authoring_policy: $authoring_policy, 1492 ..event_contract!( 1493 $id, 1494 $kind, 1495 $name, 1496 $payload_type, 1497 $class, 1498 $standard_privacy, 1499 $required_author_role, 1500 $content_schema, 1501 $discriminator, 1502 $tags, 1503 $reducers 1504 ) 1505 } 1506 }; 1507 } 1508 1509 macro_rules! event_contract { 1510 ( 1511 $id:literal, 1512 $kind:expr, 1513 $name:literal, 1514 $payload_type:literal, 1515 $class:expr, 1516 $standard_privacy:expr, 1517 $required_author_role:expr, 1518 $content_schema:expr, 1519 $discriminator:expr, 1520 $tags:expr, 1521 $reducers:expr $(,)? 1522 ) => { 1523 event_contract_with_stability!( 1524 $id, 1525 $kind, 1526 $name, 1527 $payload_type, 1528 $class, 1529 $standard_privacy, 1530 $required_author_role, 1531 $content_schema, 1532 $discriminator, 1533 $tags, 1534 $reducers, 1535 EventStability::Stable 1536 ) 1537 }; 1538 } 1539 1540 macro_rules! experimental_event_contract { 1541 ( 1542 $id:literal, 1543 $kind:expr, 1544 $name:literal, 1545 $payload_type:literal, 1546 $class:expr, 1547 $standard_privacy:expr, 1548 $required_author_role:expr, 1549 $content_schema:expr, 1550 $discriminator:expr, 1551 $tags:expr, 1552 $reducers:expr $(,)? 1553 ) => { 1554 event_contract_with_stability!( 1555 $id, 1556 $kind, 1557 $name, 1558 $payload_type, 1559 $class, 1560 $standard_privacy, 1561 $required_author_role, 1562 $content_schema, 1563 $discriminator, 1564 $tags, 1565 $reducers, 1566 EventStability::Experimental 1567 ) 1568 }; 1569 } 1570 1571 static KIND_CONTRACTS_REGISTRY_V7: &[KindContract] = &[ 1572 kind_contract!( 1573 KIND_PROFILE, 1574 "KIND_PROFILE", 1575 "Profile Metadata", 1576 EventClass::Replaceable, 1577 NostrStandard::Nip01, 1578 ["radroots.profile.metadata.v1"] 1579 ), 1580 kind_contract!( 1581 KIND_POST, 1582 "KIND_POST", 1583 "Short Text Note", 1584 EventClass::Regular, 1585 NostrStandard::Nip01, 1586 [ 1587 "radroots.social.post.v1", 1588 "radroots.social.update.v1", 1589 "radroots.social.photo_update.v1", 1590 "radroots.social.ask.v1", 1591 "radroots.social.reply.v1" 1592 ] 1593 ), 1594 kind_contract!( 1595 KIND_FOLLOW, 1596 "KIND_FOLLOW", 1597 "Contact List", 1598 EventClass::Replaceable, 1599 NostrStandard::Nip01, 1600 ["radroots.social.follow_list.v1"] 1601 ), 1602 kind_contract!( 1603 KIND_DELETION_REQUEST, 1604 "KIND_DELETION_REQUEST", 1605 "Deletion Request", 1606 EventClass::Regular, 1607 NostrStandard::Nip09, 1608 ["radroots.social.deletion_request.v1"] 1609 ), 1610 kind_contract!( 1611 KIND_REPOST, 1612 "KIND_REPOST", 1613 "Repost", 1614 EventClass::Regular, 1615 NostrStandard::Nip18, 1616 ["radroots.social.repost.v1"] 1617 ), 1618 kind_contract!( 1619 KIND_REACTION, 1620 "KIND_REACTION", 1621 "Reaction", 1622 EventClass::Regular, 1623 NostrStandard::Nip25, 1624 ["radroots.social.reaction.v1"] 1625 ), 1626 kind_contract!( 1627 KIND_SEAL, 1628 "KIND_SEAL", 1629 "Seal", 1630 EventClass::Regular, 1631 NostrStandard::Nip17, 1632 ["radroots.message.seal.v1"] 1633 ), 1634 kind_contract!( 1635 KIND_MESSAGE, 1636 "KIND_MESSAGE", 1637 "Direct Message", 1638 EventClass::Regular, 1639 NostrStandard::Nip17, 1640 ["radroots.message.private.v1"] 1641 ), 1642 kind_contract!( 1643 KIND_MESSAGE_FILE, 1644 "KIND_MESSAGE_FILE", 1645 "Direct Message File", 1646 EventClass::Regular, 1647 NostrStandard::Nip17, 1648 ["radroots.message.file.v1"] 1649 ), 1650 kind_contract!( 1651 KIND_GENERIC_REPOST, 1652 "KIND_GENERIC_REPOST", 1653 "Generic Repost", 1654 EventClass::Regular, 1655 NostrStandard::Nip18, 1656 ["radroots.social.generic_repost.v1"] 1657 ), 1658 kind_contract!( 1659 KIND_FARM_CRDT_CHANGE, 1660 "KIND_FARM_CRDT_CHANGE", 1661 "Farm CRDT Change", 1662 EventClass::Regular, 1663 NostrStandard::Radroots, 1664 ["radroots.farm.crdt_change.v1"] 1665 ), 1666 kind_contract!( 1667 KIND_GIFT_WRAP, 1668 "KIND_GIFT_WRAP", 1669 "Gift Wrap", 1670 EventClass::Regular, 1671 NostrStandard::Nip17, 1672 ["radroots.message.gift_wrap.v1"] 1673 ), 1674 kind_contract!( 1675 KIND_FILE_METADATA, 1676 "KIND_FILE_METADATA", 1677 "File Metadata", 1678 EventClass::Regular, 1679 NostrStandard::Nip94, 1680 ["radroots.file.metadata.v1"] 1681 ), 1682 kind_contract!( 1683 KIND_COMMENT, 1684 "KIND_COMMENT", 1685 "Comment", 1686 EventClass::Regular, 1687 NostrStandard::Nip22, 1688 ["radroots.social.comment.v1"] 1689 ), 1690 kind_contract!( 1691 KIND_REPORT, 1692 "KIND_REPORT", 1693 "Report", 1694 EventClass::Regular, 1695 NostrStandard::Nip56, 1696 ["radroots.social.report.v1"] 1697 ), 1698 kind_contract!( 1699 KIND_GROUP_PUT_USER, 1700 "KIND_GROUP_PUT_USER", 1701 "Group Put User", 1702 EventClass::Regular, 1703 NostrStandard::Nip29, 1704 ["radroots.group.put_user.v1"] 1705 ), 1706 kind_contract!( 1707 KIND_GROUP_REMOVE_USER, 1708 "KIND_GROUP_REMOVE_USER", 1709 "Group Remove User", 1710 EventClass::Regular, 1711 NostrStandard::Nip29, 1712 ["radroots.group.remove_user.v1"] 1713 ), 1714 kind_contract!( 1715 KIND_GROUP_EDIT_METADATA, 1716 "KIND_GROUP_EDIT_METADATA", 1717 "Group Edit Metadata", 1718 EventClass::Regular, 1719 NostrStandard::Nip29, 1720 ["radroots.group.edit_metadata.v1"] 1721 ), 1722 kind_contract!( 1723 KIND_GROUP_DELETE_EVENT, 1724 "KIND_GROUP_DELETE_EVENT", 1725 "Group Delete Event", 1726 EventClass::Regular, 1727 NostrStandard::Nip29, 1728 ["radroots.group.delete_event.v1"] 1729 ), 1730 kind_contract!( 1731 KIND_GROUP_CREATE_GROUP, 1732 "KIND_GROUP_CREATE_GROUP", 1733 "Group Create Group", 1734 EventClass::Regular, 1735 NostrStandard::Nip29, 1736 ["radroots.group.create_group.v1"] 1737 ), 1738 kind_contract!( 1739 KIND_GROUP_DELETE_GROUP, 1740 "KIND_GROUP_DELETE_GROUP", 1741 "Group Delete Group", 1742 EventClass::Regular, 1743 NostrStandard::Nip29, 1744 ["radroots.group.delete_group.v1"] 1745 ), 1746 kind_contract!( 1747 KIND_GROUP_CREATE_INVITE, 1748 "KIND_GROUP_CREATE_INVITE", 1749 "Group Create Invite", 1750 EventClass::Regular, 1751 NostrStandard::Nip29, 1752 ["radroots.group.create_invite.v1"] 1753 ), 1754 kind_contract!( 1755 KIND_GROUP_JOIN_REQUEST, 1756 "KIND_GROUP_JOIN_REQUEST", 1757 "Group Join Request", 1758 EventClass::Regular, 1759 NostrStandard::Nip29, 1760 ["radroots.group.join_request.v1"] 1761 ), 1762 kind_contract!( 1763 KIND_GROUP_LEAVE_REQUEST, 1764 "KIND_GROUP_LEAVE_REQUEST", 1765 "Group Leave Request", 1766 EventClass::Regular, 1767 NostrStandard::Nip29, 1768 ["radroots.group.leave_request.v1"] 1769 ), 1770 kind_contract!( 1771 KIND_GEOCHAT, 1772 "KIND_GEOCHAT", 1773 "Geochat", 1774 EventClass::Ephemeral, 1775 NostrStandard::Nip28, 1776 ["radroots.social.geochat.v1"] 1777 ), 1778 kind_contract!( 1779 KIND_RELAY_AUTH, 1780 "KIND_RELAY_AUTH", 1781 "Relay Auth", 1782 EventClass::Ephemeral, 1783 NostrStandard::Nip42, 1784 ["radroots.relay.auth.v1"] 1785 ), 1786 kind_contract!( 1787 KIND_HTTP_AUTH, 1788 "KIND_HTTP_AUTH", 1789 "HTTP Auth", 1790 EventClass::Ephemeral, 1791 NostrStandard::Nip98, 1792 ["radroots.http.auth.v1"] 1793 ), 1794 kind_contract!( 1795 KIND_LIST_MUTE, 1796 "KIND_LIST_MUTE", 1797 "Mute List", 1798 EventClass::Replaceable, 1799 NostrStandard::Nip51, 1800 ["radroots.list.mute.v1"] 1801 ), 1802 kind_contract!( 1803 KIND_LIST_PINNED_NOTES, 1804 "KIND_LIST_PINNED_NOTES", 1805 "Pinned Notes List", 1806 EventClass::Replaceable, 1807 NostrStandard::Nip51, 1808 ["radroots.list.pinned_notes.v1"] 1809 ), 1810 kind_contract!( 1811 KIND_LIST_READ_WRITE_RELAYS, 1812 "KIND_LIST_READ_WRITE_RELAYS", 1813 "Read Write Relays List", 1814 EventClass::Replaceable, 1815 NostrStandard::Nip51, 1816 ["radroots.list.read_write_relays.v1"] 1817 ), 1818 kind_contract!( 1819 KIND_LIST_BOOKMARKS, 1820 "KIND_LIST_BOOKMARKS", 1821 "Bookmarks List", 1822 EventClass::Replaceable, 1823 NostrStandard::Nip51, 1824 ["radroots.list.bookmarks.v1"] 1825 ), 1826 kind_contract!( 1827 KIND_LIST_COMMUNITIES, 1828 "KIND_LIST_COMMUNITIES", 1829 "Communities List", 1830 EventClass::Replaceable, 1831 NostrStandard::Nip51, 1832 ["radroots.list.communities.v1"] 1833 ), 1834 kind_contract!( 1835 KIND_LIST_PUBLIC_CHATS, 1836 "KIND_LIST_PUBLIC_CHATS", 1837 "Public Chats List", 1838 EventClass::Replaceable, 1839 NostrStandard::Nip51, 1840 ["radroots.list.public_chats.v1"] 1841 ), 1842 kind_contract!( 1843 KIND_LIST_BLOCKED_RELAYS, 1844 "KIND_LIST_BLOCKED_RELAYS", 1845 "Blocked Relays List", 1846 EventClass::Replaceable, 1847 NostrStandard::Nip51, 1848 ["radroots.list.blocked_relays.v1"] 1849 ), 1850 kind_contract!( 1851 KIND_LIST_SEARCH_RELAYS, 1852 "KIND_LIST_SEARCH_RELAYS", 1853 "Search Relays List", 1854 EventClass::Replaceable, 1855 NostrStandard::Nip51, 1856 ["radroots.list.search_relays.v1"] 1857 ), 1858 kind_contract!( 1859 KIND_LIST_SIMPLE_GROUPS, 1860 "KIND_LIST_SIMPLE_GROUPS", 1861 "Simple Groups List", 1862 EventClass::Replaceable, 1863 NostrStandard::Nip51, 1864 ["radroots.list.simple_groups.v1"] 1865 ), 1866 kind_contract!( 1867 KIND_LIST_RELAY_FEEDS, 1868 "KIND_LIST_RELAY_FEEDS", 1869 "Relay Feeds List", 1870 EventClass::Replaceable, 1871 NostrStandard::Nip51, 1872 ["radroots.list.relay_feeds.v1"] 1873 ), 1874 kind_contract!( 1875 KIND_LIST_INTERESTS, 1876 "KIND_LIST_INTERESTS", 1877 "Interests List", 1878 EventClass::Replaceable, 1879 NostrStandard::Nip51, 1880 ["radroots.list.interests.v1"] 1881 ), 1882 kind_contract!( 1883 KIND_LIST_MEDIA_FOLLOWS, 1884 "KIND_LIST_MEDIA_FOLLOWS", 1885 "Media Follows List", 1886 EventClass::Replaceable, 1887 NostrStandard::Nip51, 1888 ["radroots.list.media_follows.v1"] 1889 ), 1890 kind_contract!( 1891 KIND_LIST_EMOJIS, 1892 "KIND_LIST_EMOJIS", 1893 "Emojis List", 1894 EventClass::Replaceable, 1895 NostrStandard::Nip51, 1896 ["radroots.list.emojis.v1"] 1897 ), 1898 kind_contract!( 1899 KIND_LIST_DM_RELAYS, 1900 "KIND_LIST_DM_RELAYS", 1901 "DM Relays List", 1902 EventClass::Replaceable, 1903 NostrStandard::Nip51, 1904 ["radroots.list.dm_relays.v1"] 1905 ), 1906 kind_contract!( 1907 KIND_LIST_GOOD_WIKI_AUTHORS, 1908 "KIND_LIST_GOOD_WIKI_AUTHORS", 1909 "Good Wiki Authors List", 1910 EventClass::Replaceable, 1911 NostrStandard::Nip51, 1912 ["radroots.list.good_wiki_authors.v1"] 1913 ), 1914 kind_contract!( 1915 KIND_LIST_GOOD_WIKI_RELAYS, 1916 "KIND_LIST_GOOD_WIKI_RELAYS", 1917 "Good Wiki Relays List", 1918 EventClass::Replaceable, 1919 NostrStandard::Nip51, 1920 ["radroots.list.good_wiki_relays.v1"] 1921 ), 1922 kind_contract!( 1923 KIND_LIST_SET_FOLLOW, 1924 "KIND_LIST_SET_FOLLOW", 1925 "Follow Set", 1926 EventClass::Addressable, 1927 NostrStandard::Nip51, 1928 ["radroots.list_set.follow.v1"] 1929 ), 1930 kind_contract!( 1931 KIND_LIST_SET_GENERIC, 1932 "KIND_LIST_SET_GENERIC", 1933 "Generic List Set", 1934 EventClass::Addressable, 1935 NostrStandard::Nip51, 1936 [ 1937 "radroots.list_set.farm.members.v1", 1938 "radroots.list_set.farm.members.owners.v1", 1939 "radroots.list_set.farm.members.workers.v1", 1940 "radroots.list_set.farm.plots.v1", 1941 "radroots.list_set.farm.listings.v1", 1942 "radroots.list_set.member_of.farms.v1" 1943 ] 1944 ), 1945 kind_contract!( 1946 KIND_LIST_SET_RELAY, 1947 "KIND_LIST_SET_RELAY", 1948 "Relay Set", 1949 EventClass::Addressable, 1950 NostrStandard::Nip51, 1951 ["radroots.list_set.relay.v1"] 1952 ), 1953 kind_contract!( 1954 KIND_LIST_SET_BOOKMARK, 1955 "KIND_LIST_SET_BOOKMARK", 1956 "Bookmark Set", 1957 EventClass::Addressable, 1958 NostrStandard::Nip51, 1959 ["radroots.list_set.bookmark.v1"] 1960 ), 1961 kind_contract!( 1962 KIND_LIST_SET_CURATION, 1963 "KIND_LIST_SET_CURATION", 1964 "Curation Set", 1965 EventClass::Addressable, 1966 NostrStandard::Nip51, 1967 ["radroots.list_set.curation.v1"] 1968 ), 1969 kind_contract!( 1970 KIND_LIST_SET_VIDEO, 1971 "KIND_LIST_SET_VIDEO", 1972 "Video Set", 1973 EventClass::Addressable, 1974 NostrStandard::Nip51, 1975 ["radroots.list_set.video.v1"] 1976 ), 1977 kind_contract!( 1978 KIND_LIST_SET_PICTURE, 1979 "KIND_LIST_SET_PICTURE", 1980 "Picture Set", 1981 EventClass::Addressable, 1982 NostrStandard::Nip51, 1983 ["radroots.list_set.picture.v1"] 1984 ), 1985 kind_contract!( 1986 KIND_LIST_SET_KIND_MUTE, 1987 "KIND_LIST_SET_KIND_MUTE", 1988 "Kind Mute Set", 1989 EventClass::Addressable, 1990 NostrStandard::Nip51, 1991 ["radroots.list_set.kind_mute.v1"] 1992 ), 1993 kind_contract!( 1994 KIND_LIST_SET_INTEREST, 1995 "KIND_LIST_SET_INTEREST", 1996 "Interest Set", 1997 EventClass::Addressable, 1998 NostrStandard::Nip51, 1999 ["radroots.list_set.interest.v1"] 2000 ), 2001 kind_contract!( 2002 KIND_LIST_SET_EMOJI, 2003 "KIND_LIST_SET_EMOJI", 2004 "Emoji Set", 2005 EventClass::Addressable, 2006 NostrStandard::Nip51, 2007 ["radroots.list_set.emoji.v1"] 2008 ), 2009 kind_contract!( 2010 KIND_LIST_SET_RELEASE_ARTIFACT, 2011 "KIND_LIST_SET_RELEASE_ARTIFACT", 2012 "Release Artifact Set", 2013 EventClass::Addressable, 2014 NostrStandard::Nip51, 2015 ["radroots.list_set.release_artifact.v1"] 2016 ), 2017 kind_contract!( 2018 KIND_LIST_SET_APP_CURATION, 2019 "KIND_LIST_SET_APP_CURATION", 2020 "App Curation Set", 2021 EventClass::Addressable, 2022 NostrStandard::Nip51, 2023 ["radroots.list_set.app_curation.v1"] 2024 ), 2025 kind_contract!( 2026 KIND_ARTICLE, 2027 "KIND_ARTICLE", 2028 "Long Form Article", 2029 EventClass::Addressable, 2030 NostrStandard::Nip23, 2031 ["radroots.social.article.v1"] 2032 ), 2033 kind_contract!( 2034 KIND_WIKI_MERGE_REQUEST, 2035 "KIND_WIKI_MERGE_REQUEST", 2036 "Wiki Merge Request", 2037 EventClass::Regular, 2038 NostrStandard::Nip54, 2039 ["radroots.wiki.merge_request.v1"] 2040 ), 2041 kind_contract!( 2042 KIND_WIKI_ARTICLE, 2043 "KIND_WIKI_ARTICLE", 2044 "Wiki Article", 2045 EventClass::Addressable, 2046 NostrStandard::Nip54, 2047 ["radroots.wiki.article.v1"] 2048 ), 2049 kind_contract!( 2050 KIND_WIKI_REDIRECT, 2051 "KIND_WIKI_REDIRECT", 2052 "Wiki Redirect", 2053 EventClass::Addressable, 2054 NostrStandard::Nip54, 2055 ["radroots.wiki.redirect.v1"] 2056 ), 2057 kind_contract!( 2058 KIND_CALENDAR_DATE_EVENT, 2059 "KIND_CALENDAR_DATE_EVENT", 2060 "Calendar Date Event", 2061 EventClass::Addressable, 2062 NostrStandard::Nip52, 2063 ["radroots.calendar.date_event.v1"] 2064 ), 2065 kind_contract!( 2066 KIND_CALENDAR_TIME_EVENT, 2067 "KIND_CALENDAR_TIME_EVENT", 2068 "Calendar Time Event", 2069 EventClass::Addressable, 2070 NostrStandard::Nip52, 2071 ["radroots.calendar.time_event.v1"] 2072 ), 2073 kind_contract!( 2074 KIND_CALENDAR, 2075 "KIND_CALENDAR", 2076 "Calendar Collection", 2077 EventClass::Addressable, 2078 NostrStandard::Nip52, 2079 ["radroots.calendar.collection.v1"] 2080 ), 2081 kind_contract!( 2082 KIND_CALENDAR_EVENT_RSVP, 2083 "KIND_CALENDAR_EVENT_RSVP", 2084 "Calendar RSVP", 2085 EventClass::Addressable, 2086 NostrStandard::Nip52, 2087 ["radroots.calendar.rsvp.v1"] 2088 ), 2089 kind_contract!( 2090 KIND_LIST_SET_STARTER_PACK, 2091 "KIND_LIST_SET_STARTER_PACK", 2092 "Starter Pack Set", 2093 EventClass::Addressable, 2094 NostrStandard::Nip51, 2095 ["radroots.list_set.starter_pack.v1"] 2096 ), 2097 kind_contract!( 2098 KIND_LIST_SET_MEDIA_STARTER_PACK, 2099 "KIND_LIST_SET_MEDIA_STARTER_PACK", 2100 "Media Starter Pack Set", 2101 EventClass::Addressable, 2102 NostrStandard::Nip51, 2103 ["radroots.list_set.media_starter_pack.v1"] 2104 ), 2105 kind_contract!( 2106 KIND_FARM, 2107 "KIND_FARM", 2108 "Farm", 2109 EventClass::Addressable, 2110 NostrStandard::Radroots, 2111 ["radroots.farm.profile.v1"] 2112 ), 2113 kind_contract!( 2114 KIND_PLOT, 2115 "KIND_PLOT", 2116 "Plot", 2117 EventClass::Addressable, 2118 NostrStandard::Radroots, 2119 ["radroots.farm.plot.v1"] 2120 ), 2121 kind_contract!( 2122 KIND_COOP, 2123 "KIND_COOP", 2124 "Coop", 2125 EventClass::Addressable, 2126 NostrStandard::Radroots, 2127 ["radroots.farm.coop.v1"] 2128 ), 2129 kind_contract!( 2130 KIND_DOCUMENT, 2131 "KIND_DOCUMENT", 2132 "Document", 2133 EventClass::Addressable, 2134 NostrStandard::Radroots, 2135 ["radroots.farm.document.v1"] 2136 ), 2137 kind_contract!( 2138 KIND_RESOURCE_AREA, 2139 "KIND_RESOURCE_AREA", 2140 "Resource Area", 2141 EventClass::Addressable, 2142 NostrStandard::Radroots, 2143 ["radroots.farm.resource_area.v1"] 2144 ), 2145 kind_contract!( 2146 KIND_RESOURCE_HARVEST_CAP, 2147 "KIND_RESOURCE_HARVEST_CAP", 2148 "Resource Harvest Capacity", 2149 EventClass::Addressable, 2150 NostrStandard::Radroots, 2151 ["radroots.farm.resource_harvest_cap.v1"] 2152 ), 2153 kind_contract!( 2154 KIND_ACCOUNT_CLAIM, 2155 "KIND_ACCOUNT_CLAIM", 2156 "Account Claim", 2157 EventClass::Addressable, 2158 NostrStandard::Radroots, 2159 ["radroots.account.claim.v1"] 2160 ), 2161 kind_contract!( 2162 KIND_FARM_WORKSPACE_MANIFEST, 2163 "KIND_FARM_WORKSPACE_MANIFEST", 2164 "Farm Workspace Manifest", 2165 EventClass::Addressable, 2166 NostrStandard::Nip78, 2167 ["radroots.farm.workspace_manifest.v1"] 2168 ), 2169 kind_contract!( 2170 KIND_CLASSIFIED_LISTING, 2171 "KIND_CLASSIFIED_LISTING", 2172 "Classified Listing", 2173 EventClass::Addressable, 2174 NostrStandard::Nip99, 2175 [ 2176 "radroots.operational_listing.published.v1", 2177 "radroots.food.availability.v1" 2178 ] 2179 ), 2180 kind_contract!( 2181 KIND_KNOWLEDGE_SOURCE, 2182 "KIND_KNOWLEDGE_SOURCE", 2183 "Knowledge Source", 2184 EventClass::Addressable, 2185 NostrStandard::Radroots, 2186 ["radroots.knowledge.source.v1"] 2187 ), 2188 kind_contract!( 2189 KIND_EVIDENCE_BOUNTY, 2190 "KIND_EVIDENCE_BOUNTY", 2191 "Evidence Bounty", 2192 EventClass::Addressable, 2193 NostrStandard::Radroots, 2194 ["radroots.knowledge.evidence_bounty.v1"] 2195 ), 2196 kind_contract!( 2197 KIND_KNOWLEDGE_CLAIM, 2198 "KIND_KNOWLEDGE_CLAIM", 2199 "Knowledge Claim", 2200 EventClass::Regular, 2201 NostrStandard::Radroots, 2202 ["radroots.knowledge.claim.v1"] 2203 ), 2204 kind_contract!( 2205 KIND_KNOWLEDGE_RELATION, 2206 "KIND_KNOWLEDGE_RELATION", 2207 "Knowledge Relation", 2208 EventClass::Regular, 2209 NostrStandard::Radroots, 2210 ["radroots.knowledge.relation.v1"] 2211 ), 2212 kind_contract!( 2213 KIND_KNOWLEDGE_REVIEW, 2214 "KIND_KNOWLEDGE_REVIEW", 2215 "Knowledge Review", 2216 EventClass::Regular, 2217 NostrStandard::Radroots, 2218 ["radroots.knowledge.review.v1"] 2219 ), 2220 kind_contract!( 2221 KIND_KNOWLEDGE_FIELD_REPORT, 2222 "KIND_KNOWLEDGE_FIELD_REPORT", 2223 "Knowledge Field Report", 2224 EventClass::Regular, 2225 NostrStandard::Radroots, 2226 ["radroots.knowledge.field_report.v1"] 2227 ), 2228 kind_contract!( 2229 KIND_KNOWLEDGE_CHANGE_PROPOSAL, 2230 "KIND_KNOWLEDGE_CHANGE_PROPOSAL", 2231 "Knowledge Change Proposal", 2232 EventClass::Regular, 2233 NostrStandard::Radroots, 2234 ["radroots.knowledge.change_proposal.v1"] 2235 ), 2236 kind_contract!( 2237 KIND_CONTRIBUTION_ATTESTATION, 2238 "KIND_CONTRIBUTION_ATTESTATION", 2239 "Contribution Attestation", 2240 EventClass::Regular, 2241 NostrStandard::Radroots, 2242 ["radroots.knowledge.contribution_attestation.v1"] 2243 ), 2244 kind_contract!( 2245 KIND_APPLICATION_HANDLER, 2246 "KIND_APPLICATION_HANDLER", 2247 "Application Handler", 2248 EventClass::Addressable, 2249 NostrStandard::Radroots, 2250 ["radroots.application.handler.v1"] 2251 ), 2252 kind_contract!( 2253 KIND_GROUP_METADATA, 2254 "KIND_GROUP_METADATA", 2255 "Group Metadata", 2256 EventClass::Addressable, 2257 NostrStandard::Nip29, 2258 ["radroots.group.metadata.v1"] 2259 ), 2260 kind_contract!( 2261 KIND_GROUP_ADMINS, 2262 "KIND_GROUP_ADMINS", 2263 "Group Admins", 2264 EventClass::Addressable, 2265 NostrStandard::Nip29, 2266 ["radroots.group.admins.v1"] 2267 ), 2268 kind_contract!( 2269 KIND_GROUP_MEMBERS, 2270 "KIND_GROUP_MEMBERS", 2271 "Group Members", 2272 EventClass::Addressable, 2273 NostrStandard::Nip29, 2274 ["radroots.group.members.v1"] 2275 ), 2276 kind_contract!( 2277 KIND_GROUP_ROLES, 2278 "KIND_GROUP_ROLES", 2279 "Group Roles", 2280 EventClass::Addressable, 2281 NostrStandard::Nip29, 2282 ["radroots.group.roles.v1"] 2283 ), 2284 kind_contract!( 2285 KIND_TRADE_PROPOSAL, 2286 "KIND_TRADE_PROPOSAL", 2287 "Trade Proposal", 2288 EventClass::Regular, 2289 NostrStandard::Radroots, 2290 ["radroots.trade.proposal.v1"] 2291 ), 2292 kind_contract!( 2293 KIND_TRADE_DECISION, 2294 "KIND_TRADE_DECISION", 2295 "Trade Decision", 2296 EventClass::Regular, 2297 NostrStandard::Radroots, 2298 ["radroots.trade.decision.v1"] 2299 ), 2300 kind_contract!( 2301 KIND_TRADE_REVISION_PROPOSAL, 2302 "KIND_TRADE_REVISION_PROPOSAL", 2303 "Trade Revision Proposal", 2304 EventClass::Regular, 2305 NostrStandard::Radroots, 2306 ["radroots.trade.revision_proposal.v1"] 2307 ), 2308 kind_contract!( 2309 KIND_TRADE_REVISION_DECISION, 2310 "KIND_TRADE_REVISION_DECISION", 2311 "Trade Revision Decision", 2312 EventClass::Regular, 2313 NostrStandard::Radroots, 2314 ["radroots.trade.revision_decision.v1"] 2315 ), 2316 kind_contract!( 2317 KIND_TRADE_CANCELLATION, 2318 "KIND_TRADE_CANCELLATION", 2319 "Trade Cancellation", 2320 EventClass::Regular, 2321 NostrStandard::Radroots, 2322 ["radroots.trade.cancellation.v1"] 2323 ), 2324 kind_contract!( 2325 KIND_RHI_EVIDENCE_ATTESTATION, 2326 "KIND_RHI_EVIDENCE_ATTESTATION", 2327 "RHI Evidence Attestation", 2328 EventClass::Regular, 2329 NostrStandard::Radroots, 2330 ["radroots.rhi.evidence_attestation.v1"] 2331 ), 2332 ]; 2333 2334 static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[ 2335 event_contract_with_authoring_policy!( 2336 "radroots.profile.metadata.v1", 2337 KIND_PROFILE, 2338 "Profile Metadata", 2339 "RadrootsAuthoredProfile / RadrootsInboundProfileMetadata", 2340 EventClass::Replaceable, 2341 EventPrivacy::Public, 2342 AuthorRole::Any, 2343 ContentSchema::JsonObject, 2344 EventAuthoringPolicy::TypedOnly, 2345 EventDiscriminator::KindOnly, 2346 PROFILE_TAGS, 2347 PROFILE_REDUCERS 2348 ), 2349 event_contract_with_authoring_policy!( 2350 "radroots.social.post.v1", 2351 KIND_POST, 2352 "Short Text Note", 2353 "RadrootsPost", 2354 EventClass::Regular, 2355 EventPrivacy::Public, 2356 AuthorRole::Any, 2357 ContentSchema::PlainText, 2358 EventAuthoringPolicy::ReadOnly, 2359 EventDiscriminator::KindOnly, 2360 NO_TAGS, 2361 SOCIAL_REDUCERS 2362 ), 2363 event_contract_with_authoring_policy!( 2364 "radroots.social.update.v1", 2365 KIND_POST, 2366 "Root Text Update", 2367 "RadrootsAuthoredUpdate / RadrootsInboundPostProjection", 2368 EventClass::Regular, 2369 EventPrivacy::Public, 2370 AuthorRole::Any, 2371 ContentSchema::PlainText, 2372 EventAuthoringPolicy::TypedOnly, 2373 EventDiscriminator::AdmissionOnly, 2374 NO_TAGS, 2375 SOCIAL_REDUCERS 2376 ), 2377 event_contract_with_authoring_policy!( 2378 "radroots.social.photo_update.v1", 2379 KIND_POST, 2380 "NIP-92 Photo Update", 2381 "RadrootsAuthoredPhotoUpdate / RadrootsInboundPostProjection", 2382 EventClass::Regular, 2383 EventPrivacy::Public, 2384 AuthorRole::Any, 2385 ContentSchema::PlainText, 2386 EventAuthoringPolicy::TypedOnly, 2387 EventDiscriminator::AdmissionOnly, 2388 PHOTO_UPDATE_TAGS, 2389 SOCIAL_REDUCERS 2390 ), 2391 event_contract_with_authoring_policy!( 2392 "radroots.social.ask.v1", 2393 KIND_POST, 2394 "Root Ask", 2395 "RadrootsAuthoredAsk / RadrootsInboundPostProjection", 2396 EventClass::Regular, 2397 EventPrivacy::Public, 2398 AuthorRole::Any, 2399 ContentSchema::PlainText, 2400 EventAuthoringPolicy::TypedOnly, 2401 EventDiscriminator::AdmissionOnly, 2402 ASK_TAGS, 2403 SOCIAL_REDUCERS 2404 ), 2405 event_contract_with_authoring_policy!( 2406 "radroots.social.reply.v1", 2407 KIND_POST, 2408 "NIP-10 Reply", 2409 "RadrootsAuthoredNip10Reply / RadrootsInboundNip10ReplyProjection", 2410 EventClass::Regular, 2411 EventPrivacy::Public, 2412 AuthorRole::Any, 2413 ContentSchema::PlainText, 2414 EventAuthoringPolicy::TypedOnly, 2415 EventDiscriminator::AdmissionOnly, 2416 NIP10_REPLY_TAGS, 2417 SOCIAL_REDUCERS 2418 ), 2419 event_contract!( 2420 "radroots.social.follow_list.v1", 2421 KIND_FOLLOW, 2422 "Contact List", 2423 "RadrootsFollowList", 2424 EventClass::Replaceable, 2425 EventPrivacy::Public, 2426 AuthorRole::Any, 2427 ContentSchema::JsonObject, 2428 EventDiscriminator::KindOnly, 2429 P_TAGS, 2430 PROFILE_REDUCERS 2431 ), 2432 event_contract_with_authoring_policy!( 2433 "radroots.social.deletion_request.v1", 2434 KIND_DELETION_REQUEST, 2435 "Deletion Request", 2436 "RadrootsAuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection", 2437 EventClass::Regular, 2438 EventPrivacy::Public, 2439 AuthorRole::Any, 2440 ContentSchema::PlainText, 2441 EventAuthoringPolicy::TypedOnly, 2442 EventDiscriminator::AdmissionOnly, 2443 NIP09_DELETION_TAGS, 2444 SOCIAL_REDUCERS 2445 ), 2446 event_contract!( 2447 "radroots.social.repost.v1", 2448 KIND_REPOST, 2449 "Repost", 2450 "RadrootsRepost", 2451 EventClass::Regular, 2452 EventPrivacy::Public, 2453 AuthorRole::Any, 2454 ContentSchema::JsonObject, 2455 EventDiscriminator::KindOnly, 2456 EVENT_POINTER_TAGS, 2457 SOCIAL_REDUCERS 2458 ), 2459 event_contract!( 2460 "radroots.social.reaction.v1", 2461 KIND_REACTION, 2462 "Reaction", 2463 "RadrootsReaction", 2464 EventClass::Regular, 2465 EventPrivacy::Public, 2466 AuthorRole::Any, 2467 ContentSchema::PlainText, 2468 EventDiscriminator::KindOnly, 2469 EVENT_POINTER_TAGS, 2470 SOCIAL_REDUCERS 2471 ), 2472 event_contract!( 2473 "radroots.message.seal.v1", 2474 KIND_SEAL, 2475 "Seal", 2476 "RadrootsSeal", 2477 EventClass::Regular, 2478 EventPrivacy::Encrypted, 2479 AuthorRole::Any, 2480 ContentSchema::Encrypted, 2481 EventDiscriminator::KindOnly, 2482 NO_TAGS, 2483 SOCIAL_REDUCERS 2484 ), 2485 event_contract!( 2486 "radroots.message.private.v1", 2487 KIND_MESSAGE, 2488 "Direct Message", 2489 "RadrootsMessage", 2490 EventClass::Regular, 2491 EventPrivacy::Encrypted, 2492 AuthorRole::Any, 2493 ContentSchema::Encrypted, 2494 EventDiscriminator::KindOnly, 2495 P_TAGS, 2496 SOCIAL_REDUCERS 2497 ), 2498 event_contract!( 2499 "radroots.message.file.v1", 2500 KIND_MESSAGE_FILE, 2501 "Direct Message File", 2502 "RadrootsMessageFile", 2503 EventClass::Regular, 2504 EventPrivacy::Encrypted, 2505 AuthorRole::Any, 2506 ContentSchema::Encrypted, 2507 EventDiscriminator::KindOnly, 2508 P_TAGS, 2509 SOCIAL_REDUCERS 2510 ), 2511 event_contract!( 2512 "radroots.social.generic_repost.v1", 2513 KIND_GENERIC_REPOST, 2514 "Generic Repost", 2515 "RadrootsGenericRepost", 2516 EventClass::Regular, 2517 EventPrivacy::Public, 2518 AuthorRole::Any, 2519 ContentSchema::JsonObject, 2520 EventDiscriminator::KindOnly, 2521 EVENT_POINTER_TAGS, 2522 SOCIAL_REDUCERS 2523 ), 2524 event_contract!( 2525 "radroots.farm.crdt_change.v1", 2526 KIND_FARM_CRDT_CHANGE, 2527 "Farm CRDT Change", 2528 "RadrootsFarmCrdtChange", 2529 EventClass::Regular, 2530 EventPrivacy::Encrypted, 2531 AuthorRole::Farmer, 2532 ContentSchema::JsonObject, 2533 EventDiscriminator::KindOnly, 2534 NO_TAGS, 2535 FARM_OPS_REDUCERS 2536 ), 2537 event_contract!( 2538 "radroots.message.gift_wrap.v1", 2539 KIND_GIFT_WRAP, 2540 "Gift Wrap", 2541 "RadrootsGiftWrap", 2542 EventClass::Regular, 2543 EventPrivacy::Encrypted, 2544 AuthorRole::Any, 2545 ContentSchema::Encrypted, 2546 EventDiscriminator::KindOnly, 2547 P_TAGS, 2548 SOCIAL_REDUCERS 2549 ), 2550 event_contract!( 2551 "radroots.file.metadata.v1", 2552 KIND_FILE_METADATA, 2553 "File Metadata", 2554 "RadrootsFileMetadata", 2555 EventClass::Regular, 2556 EventPrivacy::Public, 2557 AuthorRole::Any, 2558 ContentSchema::JsonObject, 2559 EventDiscriminator::KindOnly, 2560 FILE_METADATA_TAGS, 2561 SOCIAL_REDUCERS 2562 ), 2563 event_contract_with_authoring_policy!( 2564 "radroots.social.comment.v1", 2565 KIND_COMMENT, 2566 "Comment", 2567 "RadrootsAuthoredNip22Comment / RadrootsInboundNip22CommentProjection", 2568 EventClass::Regular, 2569 EventPrivacy::Public, 2570 AuthorRole::Any, 2571 ContentSchema::PlainText, 2572 EventAuthoringPolicy::TypedOnly, 2573 EventDiscriminator::AdmissionOnly, 2574 NIP22_COMMENT_TAGS, 2575 SOCIAL_REDUCERS 2576 ), 2577 event_contract!( 2578 "radroots.social.report.v1", 2579 KIND_REPORT, 2580 "Report", 2581 "RadrootsReport", 2582 EventClass::Regular, 2583 EventPrivacy::Public, 2584 AuthorRole::Moderator, 2585 ContentSchema::PlainText, 2586 EventDiscriminator::KindOnly, 2587 EVENT_POINTER_TAGS, 2588 SOCIAL_REDUCERS 2589 ), 2590 event_contract!( 2591 "radroots.group.put_user.v1", 2592 KIND_GROUP_PUT_USER, 2593 "Group Put User", 2594 "RadrootsGroupPutUser", 2595 EventClass::Regular, 2596 EventPrivacy::Public, 2597 AuthorRole::Moderator, 2598 ContentSchema::JsonObject, 2599 EventDiscriminator::KindOnly, 2600 GROUP_ACTION_TAGS, 2601 GROUP_REDUCERS 2602 ), 2603 event_contract!( 2604 "radroots.group.remove_user.v1", 2605 KIND_GROUP_REMOVE_USER, 2606 "Group Remove User", 2607 "RadrootsGroupRemoveUser", 2608 EventClass::Regular, 2609 EventPrivacy::Public, 2610 AuthorRole::Moderator, 2611 ContentSchema::JsonObject, 2612 EventDiscriminator::KindOnly, 2613 GROUP_ACTION_TAGS, 2614 GROUP_REDUCERS 2615 ), 2616 event_contract!( 2617 "radroots.group.edit_metadata.v1", 2618 KIND_GROUP_EDIT_METADATA, 2619 "Group Edit Metadata", 2620 "RadrootsGroupEditMetadata", 2621 EventClass::Regular, 2622 EventPrivacy::Public, 2623 AuthorRole::Moderator, 2624 ContentSchema::JsonObject, 2625 EventDiscriminator::KindOnly, 2626 GROUP_ACTION_TAGS, 2627 GROUP_REDUCERS 2628 ), 2629 event_contract!( 2630 "radroots.group.delete_event.v1", 2631 KIND_GROUP_DELETE_EVENT, 2632 "Group Delete Event", 2633 "RadrootsGroupDeleteEvent", 2634 EventClass::Regular, 2635 EventPrivacy::Public, 2636 AuthorRole::Moderator, 2637 ContentSchema::JsonObject, 2638 EventDiscriminator::KindOnly, 2639 GROUP_ACTION_TAGS, 2640 GROUP_REDUCERS 2641 ), 2642 event_contract!( 2643 "radroots.group.create_group.v1", 2644 KIND_GROUP_CREATE_GROUP, 2645 "Group Create Group", 2646 "RadrootsGroupCreateGroup", 2647 EventClass::Regular, 2648 EventPrivacy::Public, 2649 AuthorRole::Moderator, 2650 ContentSchema::JsonObject, 2651 EventDiscriminator::KindOnly, 2652 GROUP_ACTION_TAGS, 2653 GROUP_REDUCERS 2654 ), 2655 event_contract!( 2656 "radroots.group.delete_group.v1", 2657 KIND_GROUP_DELETE_GROUP, 2658 "Group Delete Group", 2659 "RadrootsGroupDeleteGroup", 2660 EventClass::Regular, 2661 EventPrivacy::Public, 2662 AuthorRole::Moderator, 2663 ContentSchema::JsonObject, 2664 EventDiscriminator::KindOnly, 2665 GROUP_ACTION_TAGS, 2666 GROUP_REDUCERS 2667 ), 2668 event_contract!( 2669 "radroots.group.create_invite.v1", 2670 KIND_GROUP_CREATE_INVITE, 2671 "Group Create Invite", 2672 "RadrootsGroupCreateInvite", 2673 EventClass::Regular, 2674 EventPrivacy::Public, 2675 AuthorRole::Moderator, 2676 ContentSchema::JsonObject, 2677 EventDiscriminator::KindOnly, 2678 GROUP_ACTION_TAGS, 2679 GROUP_REDUCERS 2680 ), 2681 event_contract!( 2682 "radroots.group.join_request.v1", 2683 KIND_GROUP_JOIN_REQUEST, 2684 "Group Join Request", 2685 "RadrootsGroupJoinRequest", 2686 EventClass::Regular, 2687 EventPrivacy::Public, 2688 AuthorRole::Member, 2689 ContentSchema::JsonObject, 2690 EventDiscriminator::KindOnly, 2691 GROUP_ACTION_TAGS, 2692 GROUP_REDUCERS 2693 ), 2694 event_contract!( 2695 "radroots.group.leave_request.v1", 2696 KIND_GROUP_LEAVE_REQUEST, 2697 "Group Leave Request", 2698 "RadrootsGroupLeaveRequest", 2699 EventClass::Regular, 2700 EventPrivacy::Public, 2701 AuthorRole::Member, 2702 ContentSchema::JsonObject, 2703 EventDiscriminator::KindOnly, 2704 GROUP_ACTION_TAGS, 2705 GROUP_REDUCERS 2706 ), 2707 event_contract!( 2708 "radroots.social.geochat.v1", 2709 KIND_GEOCHAT, 2710 "Geochat", 2711 "RadrootsGeochat", 2712 EventClass::Ephemeral, 2713 EventPrivacy::Public, 2714 AuthorRole::Any, 2715 ContentSchema::PlainText, 2716 EventDiscriminator::KindOnly, 2717 NO_TAGS, 2718 SOCIAL_REDUCERS 2719 ), 2720 event_contract!( 2721 "radroots.relay.auth.v1", 2722 KIND_RELAY_AUTH, 2723 "Relay Auth", 2724 "RadrootsRelayAuth", 2725 EventClass::Ephemeral, 2726 EventPrivacy::Public, 2727 AuthorRole::Relay, 2728 ContentSchema::JsonObject, 2729 EventDiscriminator::KindOnly, 2730 NO_TAGS, 2731 RELAY_REDUCERS 2732 ), 2733 event_contract!( 2734 "radroots.http.auth.v1", 2735 KIND_HTTP_AUTH, 2736 "HTTP Auth", 2737 "RadrootsHttpAuth", 2738 EventClass::Ephemeral, 2739 EventPrivacy::Public, 2740 AuthorRole::Application, 2741 ContentSchema::JsonObject, 2742 EventDiscriminator::KindOnly, 2743 NO_TAGS, 2744 RELAY_REDUCERS 2745 ), 2746 event_contract!( 2747 "radroots.list.mute.v1", 2748 KIND_LIST_MUTE, 2749 "Mute List", 2750 "RadrootsList", 2751 EventClass::Replaceable, 2752 EventPrivacy::Public, 2753 AuthorRole::Any, 2754 ContentSchema::JsonObject, 2755 EventDiscriminator::KindOnly, 2756 LIST_TAGS, 2757 SOCIAL_REDUCERS 2758 ), 2759 event_contract!( 2760 "radroots.list.pinned_notes.v1", 2761 KIND_LIST_PINNED_NOTES, 2762 "Pinned Notes List", 2763 "RadrootsList", 2764 EventClass::Replaceable, 2765 EventPrivacy::Public, 2766 AuthorRole::Any, 2767 ContentSchema::JsonObject, 2768 EventDiscriminator::KindOnly, 2769 LIST_TAGS, 2770 SOCIAL_REDUCERS 2771 ), 2772 event_contract!( 2773 "radroots.list.read_write_relays.v1", 2774 KIND_LIST_READ_WRITE_RELAYS, 2775 "Read Write Relays List", 2776 "RadrootsList", 2777 EventClass::Replaceable, 2778 EventPrivacy::Public, 2779 AuthorRole::Any, 2780 ContentSchema::JsonObject, 2781 EventDiscriminator::KindOnly, 2782 LIST_TAGS, 2783 SOCIAL_REDUCERS 2784 ), 2785 event_contract!( 2786 "radroots.list.bookmarks.v1", 2787 KIND_LIST_BOOKMARKS, 2788 "Bookmarks List", 2789 "RadrootsList", 2790 EventClass::Replaceable, 2791 EventPrivacy::Public, 2792 AuthorRole::Any, 2793 ContentSchema::JsonObject, 2794 EventDiscriminator::KindOnly, 2795 LIST_TAGS, 2796 SOCIAL_REDUCERS 2797 ), 2798 event_contract!( 2799 "radroots.list.communities.v1", 2800 KIND_LIST_COMMUNITIES, 2801 "Communities List", 2802 "RadrootsList", 2803 EventClass::Replaceable, 2804 EventPrivacy::Public, 2805 AuthorRole::Any, 2806 ContentSchema::JsonObject, 2807 EventDiscriminator::KindOnly, 2808 LIST_TAGS, 2809 SOCIAL_REDUCERS 2810 ), 2811 event_contract!( 2812 "radroots.list.public_chats.v1", 2813 KIND_LIST_PUBLIC_CHATS, 2814 "Public Chats List", 2815 "RadrootsList", 2816 EventClass::Replaceable, 2817 EventPrivacy::Public, 2818 AuthorRole::Any, 2819 ContentSchema::JsonObject, 2820 EventDiscriminator::KindOnly, 2821 LIST_TAGS, 2822 SOCIAL_REDUCERS 2823 ), 2824 event_contract!( 2825 "radroots.list.blocked_relays.v1", 2826 KIND_LIST_BLOCKED_RELAYS, 2827 "Blocked Relays List", 2828 "RadrootsList", 2829 EventClass::Replaceable, 2830 EventPrivacy::Public, 2831 AuthorRole::Any, 2832 ContentSchema::JsonObject, 2833 EventDiscriminator::KindOnly, 2834 LIST_TAGS, 2835 SOCIAL_REDUCERS 2836 ), 2837 event_contract!( 2838 "radroots.list.search_relays.v1", 2839 KIND_LIST_SEARCH_RELAYS, 2840 "Search Relays List", 2841 "RadrootsList", 2842 EventClass::Replaceable, 2843 EventPrivacy::Public, 2844 AuthorRole::Any, 2845 ContentSchema::JsonObject, 2846 EventDiscriminator::KindOnly, 2847 LIST_TAGS, 2848 SOCIAL_REDUCERS 2849 ), 2850 event_contract!( 2851 "radroots.list.simple_groups.v1", 2852 KIND_LIST_SIMPLE_GROUPS, 2853 "Simple Groups List", 2854 "RadrootsList", 2855 EventClass::Replaceable, 2856 EventPrivacy::Public, 2857 AuthorRole::Any, 2858 ContentSchema::JsonObject, 2859 EventDiscriminator::KindOnly, 2860 LIST_TAGS, 2861 SOCIAL_REDUCERS 2862 ), 2863 event_contract!( 2864 "radroots.list.relay_feeds.v1", 2865 KIND_LIST_RELAY_FEEDS, 2866 "Relay Feeds List", 2867 "RadrootsList", 2868 EventClass::Replaceable, 2869 EventPrivacy::Public, 2870 AuthorRole::Any, 2871 ContentSchema::JsonObject, 2872 EventDiscriminator::KindOnly, 2873 LIST_TAGS, 2874 SOCIAL_REDUCERS 2875 ), 2876 event_contract!( 2877 "radroots.list.interests.v1", 2878 KIND_LIST_INTERESTS, 2879 "Interests List", 2880 "RadrootsList", 2881 EventClass::Replaceable, 2882 EventPrivacy::Public, 2883 AuthorRole::Any, 2884 ContentSchema::JsonObject, 2885 EventDiscriminator::KindOnly, 2886 LIST_TAGS, 2887 SOCIAL_REDUCERS 2888 ), 2889 event_contract!( 2890 "radroots.list.media_follows.v1", 2891 KIND_LIST_MEDIA_FOLLOWS, 2892 "Media Follows List", 2893 "RadrootsList", 2894 EventClass::Replaceable, 2895 EventPrivacy::Public, 2896 AuthorRole::Any, 2897 ContentSchema::JsonObject, 2898 EventDiscriminator::KindOnly, 2899 LIST_TAGS, 2900 SOCIAL_REDUCERS 2901 ), 2902 event_contract!( 2903 "radroots.list.emojis.v1", 2904 KIND_LIST_EMOJIS, 2905 "Emojis List", 2906 "RadrootsList", 2907 EventClass::Replaceable, 2908 EventPrivacy::Public, 2909 AuthorRole::Any, 2910 ContentSchema::JsonObject, 2911 EventDiscriminator::KindOnly, 2912 LIST_TAGS, 2913 SOCIAL_REDUCERS 2914 ), 2915 event_contract!( 2916 "radroots.list.dm_relays.v1", 2917 KIND_LIST_DM_RELAYS, 2918 "DM Relays List", 2919 "RadrootsList", 2920 EventClass::Replaceable, 2921 EventPrivacy::Public, 2922 AuthorRole::Any, 2923 ContentSchema::JsonObject, 2924 EventDiscriminator::KindOnly, 2925 LIST_TAGS, 2926 SOCIAL_REDUCERS 2927 ), 2928 event_contract!( 2929 "radroots.list.good_wiki_authors.v1", 2930 KIND_LIST_GOOD_WIKI_AUTHORS, 2931 "Good Wiki Authors List", 2932 "RadrootsList", 2933 EventClass::Replaceable, 2934 EventPrivacy::Public, 2935 AuthorRole::Any, 2936 ContentSchema::JsonObject, 2937 EventDiscriminator::KindOnly, 2938 LIST_TAGS, 2939 SOCIAL_REDUCERS 2940 ), 2941 event_contract!( 2942 "radroots.list.good_wiki_relays.v1", 2943 KIND_LIST_GOOD_WIKI_RELAYS, 2944 "Good Wiki Relays List", 2945 "RadrootsList", 2946 EventClass::Replaceable, 2947 EventPrivacy::Public, 2948 AuthorRole::Any, 2949 ContentSchema::JsonObject, 2950 EventDiscriminator::KindOnly, 2951 LIST_TAGS, 2952 SOCIAL_REDUCERS 2953 ), 2954 event_contract!( 2955 "radroots.list_set.follow.v1", 2956 KIND_LIST_SET_FOLLOW, 2957 "Follow Set", 2958 "RadrootsListSet", 2959 EventClass::Addressable, 2960 EventPrivacy::Public, 2961 AuthorRole::Any, 2962 ContentSchema::JsonObject, 2963 EventDiscriminator::KindOnly, 2964 LIST_SET_TAGS, 2965 SOCIAL_REDUCERS 2966 ), 2967 event_contract!( 2968 "radroots.list_set.farm.members.v1", 2969 KIND_LIST_SET_GENERIC, 2970 "Farm Members List Set", 2971 "RadrootsListSet", 2972 EventClass::Addressable, 2973 EventPrivacy::Public, 2974 AuthorRole::Farmer, 2975 ContentSchema::JsonObject, 2976 EventDiscriminator::Composite(FARM_MEMBERS_LIST_DISCRIMINATOR), 2977 LIST_SET_TAGS, 2978 FARM_OPS_REDUCERS 2979 ), 2980 event_contract!( 2981 "radroots.list_set.farm.members.owners.v1", 2982 KIND_LIST_SET_GENERIC, 2983 "Farm Owners List Set", 2984 "RadrootsListSet", 2985 EventClass::Addressable, 2986 EventPrivacy::Public, 2987 AuthorRole::Farmer, 2988 ContentSchema::JsonObject, 2989 EventDiscriminator::Composite(FARM_OWNERS_LIST_DISCRIMINATOR), 2990 LIST_SET_TAGS, 2991 FARM_OPS_REDUCERS 2992 ), 2993 event_contract!( 2994 "radroots.list_set.farm.members.workers.v1", 2995 KIND_LIST_SET_GENERIC, 2996 "Farm Workers List Set", 2997 "RadrootsListSet", 2998 EventClass::Addressable, 2999 EventPrivacy::Public, 3000 AuthorRole::Farmer, 3001 ContentSchema::JsonObject, 3002 EventDiscriminator::Composite(FARM_WORKERS_LIST_DISCRIMINATOR), 3003 LIST_SET_TAGS, 3004 FARM_OPS_REDUCERS 3005 ), 3006 event_contract!( 3007 "radroots.list_set.farm.plots.v1", 3008 KIND_LIST_SET_GENERIC, 3009 "Farm Plots List Set", 3010 "RadrootsListSet", 3011 EventClass::Addressable, 3012 EventPrivacy::Public, 3013 AuthorRole::Farmer, 3014 ContentSchema::JsonObject, 3015 EventDiscriminator::Composite(FARM_PLOTS_LIST_DISCRIMINATOR), 3016 LIST_SET_TAGS, 3017 FARM_OPS_REDUCERS 3018 ), 3019 event_contract!( 3020 "radroots.list_set.farm.listings.v1", 3021 KIND_LIST_SET_GENERIC, 3022 "Farm Listings List Set", 3023 "RadrootsListSet", 3024 EventClass::Addressable, 3025 EventPrivacy::Public, 3026 AuthorRole::Farmer, 3027 ContentSchema::JsonObject, 3028 EventDiscriminator::Composite(FARM_LISTINGS_LIST_DISCRIMINATOR), 3029 LIST_SET_TAGS, 3030 FARM_OPS_REDUCERS 3031 ), 3032 event_contract!( 3033 "radroots.list_set.member_of.farms.v1", 3034 KIND_LIST_SET_GENERIC, 3035 "Member Of Farms List Set", 3036 "RadrootsListSet", 3037 EventClass::Addressable, 3038 EventPrivacy::Public, 3039 AuthorRole::Member, 3040 ContentSchema::JsonObject, 3041 EventDiscriminator::DTagExact("member_of.farms"), 3042 LIST_SET_TAGS, 3043 FARM_OPS_REDUCERS 3044 ), 3045 event_contract!( 3046 "radroots.list_set.relay.v1", 3047 KIND_LIST_SET_RELAY, 3048 "Relay Set", 3049 "RadrootsListSet", 3050 EventClass::Addressable, 3051 EventPrivacy::Public, 3052 AuthorRole::Any, 3053 ContentSchema::JsonObject, 3054 EventDiscriminator::KindOnly, 3055 LIST_SET_TAGS, 3056 RELAY_REDUCERS 3057 ), 3058 event_contract!( 3059 "radroots.list_set.bookmark.v1", 3060 KIND_LIST_SET_BOOKMARK, 3061 "Bookmark Set", 3062 "RadrootsListSet", 3063 EventClass::Addressable, 3064 EventPrivacy::Public, 3065 AuthorRole::Any, 3066 ContentSchema::JsonObject, 3067 EventDiscriminator::KindOnly, 3068 LIST_SET_TAGS, 3069 SOCIAL_REDUCERS 3070 ), 3071 event_contract!( 3072 "radroots.list_set.curation.v1", 3073 KIND_LIST_SET_CURATION, 3074 "Curation Set", 3075 "RadrootsListSet", 3076 EventClass::Addressable, 3077 EventPrivacy::Public, 3078 AuthorRole::Any, 3079 ContentSchema::JsonObject, 3080 EventDiscriminator::KindOnly, 3081 LIST_SET_TAGS, 3082 SOCIAL_REDUCERS 3083 ), 3084 event_contract!( 3085 "radroots.list_set.video.v1", 3086 KIND_LIST_SET_VIDEO, 3087 "Video Set", 3088 "RadrootsListSet", 3089 EventClass::Addressable, 3090 EventPrivacy::Public, 3091 AuthorRole::Any, 3092 ContentSchema::JsonObject, 3093 EventDiscriminator::KindOnly, 3094 LIST_SET_TAGS, 3095 SOCIAL_REDUCERS 3096 ), 3097 event_contract!( 3098 "radroots.list_set.picture.v1", 3099 KIND_LIST_SET_PICTURE, 3100 "Picture Set", 3101 "RadrootsListSet", 3102 EventClass::Addressable, 3103 EventPrivacy::Public, 3104 AuthorRole::Any, 3105 ContentSchema::JsonObject, 3106 EventDiscriminator::KindOnly, 3107 LIST_SET_TAGS, 3108 SOCIAL_REDUCERS 3109 ), 3110 event_contract!( 3111 "radroots.list_set.kind_mute.v1", 3112 KIND_LIST_SET_KIND_MUTE, 3113 "Kind Mute Set", 3114 "RadrootsListSet", 3115 EventClass::Addressable, 3116 EventPrivacy::Public, 3117 AuthorRole::Any, 3118 ContentSchema::JsonObject, 3119 EventDiscriminator::KindOnly, 3120 LIST_SET_TAGS, 3121 SOCIAL_REDUCERS 3122 ), 3123 event_contract!( 3124 "radroots.list_set.interest.v1", 3125 KIND_LIST_SET_INTEREST, 3126 "Interest Set", 3127 "RadrootsListSet", 3128 EventClass::Addressable, 3129 EventPrivacy::Public, 3130 AuthorRole::Any, 3131 ContentSchema::JsonObject, 3132 EventDiscriminator::KindOnly, 3133 LIST_SET_TAGS, 3134 SOCIAL_REDUCERS 3135 ), 3136 event_contract!( 3137 "radroots.list_set.emoji.v1", 3138 KIND_LIST_SET_EMOJI, 3139 "Emoji Set", 3140 "RadrootsListSet", 3141 EventClass::Addressable, 3142 EventPrivacy::Public, 3143 AuthorRole::Any, 3144 ContentSchema::JsonObject, 3145 EventDiscriminator::KindOnly, 3146 LIST_SET_TAGS, 3147 SOCIAL_REDUCERS 3148 ), 3149 event_contract!( 3150 "radroots.list_set.release_artifact.v1", 3151 KIND_LIST_SET_RELEASE_ARTIFACT, 3152 "Release Artifact Set", 3153 "RadrootsListSet", 3154 EventClass::Addressable, 3155 EventPrivacy::Public, 3156 AuthorRole::Any, 3157 ContentSchema::JsonObject, 3158 EventDiscriminator::KindOnly, 3159 LIST_SET_TAGS, 3160 SOCIAL_REDUCERS 3161 ), 3162 event_contract!( 3163 "radroots.list_set.app_curation.v1", 3164 KIND_LIST_SET_APP_CURATION, 3165 "App Curation Set", 3166 "RadrootsListSet", 3167 EventClass::Addressable, 3168 EventPrivacy::Public, 3169 AuthorRole::Any, 3170 ContentSchema::JsonObject, 3171 EventDiscriminator::KindOnly, 3172 LIST_SET_TAGS, 3173 SOCIAL_REDUCERS 3174 ), 3175 event_contract!( 3176 "radroots.social.article.v1", 3177 KIND_ARTICLE, 3178 "Long Form Article", 3179 "RadrootsArticle", 3180 EventClass::Addressable, 3181 EventPrivacy::Public, 3182 AuthorRole::Any, 3183 ContentSchema::Markdown, 3184 EventDiscriminator::KindOnly, 3185 ARTICLE_TAGS, 3186 SOCIAL_REDUCERS 3187 ), 3188 experimental_event_contract!( 3189 "radroots.wiki.merge_request.v1", 3190 KIND_WIKI_MERGE_REQUEST, 3191 "Wiki Merge Request", 3192 "RadrootsWikiMergeRequest", 3193 EventClass::Regular, 3194 EventPrivacy::Public, 3195 AuthorRole::Any, 3196 ContentSchema::PlainText, 3197 EventDiscriminator::KindOnly, 3198 WIKI_MERGE_REQUEST_TAGS, 3199 KNOWLEDGE_REDUCERS 3200 ), 3201 experimental_event_contract!( 3202 "radroots.wiki.article.v1", 3203 KIND_WIKI_ARTICLE, 3204 "Wiki Article", 3205 "RadrootsWikiArticle", 3206 EventClass::Addressable, 3207 EventPrivacy::Public, 3208 AuthorRole::Any, 3209 ContentSchema::Djot, 3210 EventDiscriminator::KindOnly, 3211 WIKI_ARTICLE_TAGS, 3212 KNOWLEDGE_REDUCERS 3213 ), 3214 experimental_event_contract!( 3215 "radroots.wiki.redirect.v1", 3216 KIND_WIKI_REDIRECT, 3217 "Wiki Redirect", 3218 "RadrootsWikiRedirect", 3219 EventClass::Addressable, 3220 EventPrivacy::Public, 3221 AuthorRole::Any, 3222 ContentSchema::Empty, 3223 EventDiscriminator::KindOnly, 3224 WIKI_REDIRECT_TAGS, 3225 KNOWLEDGE_REDUCERS 3226 ), 3227 event_contract_with_authoring_policy!( 3228 "radroots.calendar.date_event.v1", 3229 KIND_CALENDAR_DATE_EVENT, 3230 "Calendar Date Event", 3231 "RadrootsAuthoredCalendarDateEvent / RadrootsParsedNip52CalendarDateEvent / RadrootsAdmittedCalendarDateEvent", 3232 EventClass::Addressable, 3233 EventPrivacy::Public, 3234 AuthorRole::Any, 3235 ContentSchema::PlainText, 3236 EventAuthoringPolicy::TypedOnly, 3237 EventDiscriminator::KindOnly, 3238 CALENDAR_DATE_EVENT_TAGS, 3239 CALENDAR_REDUCERS 3240 ), 3241 event_contract_with_authoring_policy!( 3242 "radroots.calendar.time_event.v1", 3243 KIND_CALENDAR_TIME_EVENT, 3244 "Calendar Time Event", 3245 "RadrootsAuthoredCalendarTimeEvent / RadrootsParsedNip52CalendarTimeEvent / RadrootsAdmittedCalendarTimeEvent", 3246 EventClass::Addressable, 3247 EventPrivacy::Public, 3248 AuthorRole::Any, 3249 ContentSchema::PlainText, 3250 EventAuthoringPolicy::TypedOnly, 3251 EventDiscriminator::KindOnly, 3252 CALENDAR_TIME_EVENT_TAGS, 3253 CALENDAR_REDUCERS 3254 ), 3255 event_contract!( 3256 "radroots.calendar.collection.v1", 3257 KIND_CALENDAR, 3258 "Calendar Collection", 3259 "RadrootsAdmittedCalendar", 3260 EventClass::Addressable, 3261 EventPrivacy::Public, 3262 AuthorRole::Any, 3263 ContentSchema::PlainText, 3264 EventDiscriminator::KindOnly, 3265 CALENDAR_COLLECTION_TAGS, 3266 CALENDAR_REDUCERS 3267 ), 3268 event_contract!( 3269 "radroots.calendar.rsvp.v1", 3270 KIND_CALENDAR_EVENT_RSVP, 3271 "Calendar RSVP", 3272 "RadrootsAdmittedCalendarEventRsvp", 3273 EventClass::Addressable, 3274 EventPrivacy::Public, 3275 AuthorRole::Any, 3276 ContentSchema::PlainText, 3277 EventDiscriminator::KindOnly, 3278 CALENDAR_RSVP_TAGS, 3279 CALENDAR_REDUCERS 3280 ), 3281 event_contract!( 3282 "radroots.list_set.starter_pack.v1", 3283 KIND_LIST_SET_STARTER_PACK, 3284 "Starter Pack Set", 3285 "RadrootsListSet", 3286 EventClass::Addressable, 3287 EventPrivacy::Public, 3288 AuthorRole::Any, 3289 ContentSchema::JsonObject, 3290 EventDiscriminator::KindOnly, 3291 LIST_SET_TAGS, 3292 SOCIAL_REDUCERS 3293 ), 3294 event_contract!( 3295 "radroots.list_set.media_starter_pack.v1", 3296 KIND_LIST_SET_MEDIA_STARTER_PACK, 3297 "Media Starter Pack Set", 3298 "RadrootsListSet", 3299 EventClass::Addressable, 3300 EventPrivacy::Public, 3301 AuthorRole::Any, 3302 ContentSchema::JsonObject, 3303 EventDiscriminator::KindOnly, 3304 LIST_SET_TAGS, 3305 SOCIAL_REDUCERS 3306 ), 3307 event_contract!( 3308 "radroots.farm.profile.v1", 3309 KIND_FARM, 3310 "Farm", 3311 "RadrootsFarm", 3312 EventClass::Addressable, 3313 EventPrivacy::Public, 3314 AuthorRole::Farmer, 3315 ContentSchema::JsonObject, 3316 EventDiscriminator::KindOnly, 3317 FARM_TAGS, 3318 FARM_OPS_REDUCERS 3319 ), 3320 event_contract!( 3321 "radroots.farm.plot.v1", 3322 KIND_PLOT, 3323 "Plot", 3324 "RadrootsPlot", 3325 EventClass::Addressable, 3326 EventPrivacy::Public, 3327 AuthorRole::Farmer, 3328 ContentSchema::JsonObject, 3329 EventDiscriminator::KindOnly, 3330 FARM_TAGS, 3331 FARM_OPS_REDUCERS 3332 ), 3333 event_contract!( 3334 "radroots.farm.coop.v1", 3335 KIND_COOP, 3336 "Coop", 3337 "RadrootsCoop", 3338 EventClass::Addressable, 3339 EventPrivacy::Public, 3340 AuthorRole::Farmer, 3341 ContentSchema::JsonObject, 3342 EventDiscriminator::KindOnly, 3343 FARM_TAGS, 3344 FARM_OPS_REDUCERS 3345 ), 3346 event_contract!( 3347 "radroots.farm.document.v1", 3348 KIND_DOCUMENT, 3349 "Document", 3350 "RadrootsDocument", 3351 EventClass::Addressable, 3352 EventPrivacy::Public, 3353 AuthorRole::Farmer, 3354 ContentSchema::JsonObject, 3355 EventDiscriminator::KindOnly, 3356 D_TAGS, 3357 FARM_OPS_REDUCERS 3358 ), 3359 event_contract!( 3360 "radroots.farm.resource_area.v1", 3361 KIND_RESOURCE_AREA, 3362 "Resource Area", 3363 "RadrootsResourceArea", 3364 EventClass::Addressable, 3365 EventPrivacy::Public, 3366 AuthorRole::Farmer, 3367 ContentSchema::JsonObject, 3368 EventDiscriminator::KindOnly, 3369 FARM_TAGS, 3370 FARM_OPS_REDUCERS 3371 ), 3372 event_contract!( 3373 "radroots.farm.resource_harvest_cap.v1", 3374 KIND_RESOURCE_HARVEST_CAP, 3375 "Resource Harvest Capacity", 3376 "RadrootsResourceHarvestCap", 3377 EventClass::Addressable, 3378 EventPrivacy::Public, 3379 AuthorRole::Farmer, 3380 ContentSchema::JsonObject, 3381 EventDiscriminator::KindOnly, 3382 FARM_TAGS, 3383 FARM_OPS_REDUCERS 3384 ), 3385 event_contract!( 3386 "radroots.account.claim.v1", 3387 KIND_ACCOUNT_CLAIM, 3388 "Account Claim", 3389 "RadrootsAccountClaim", 3390 EventClass::Addressable, 3391 EventPrivacy::Public, 3392 AuthorRole::Any, 3393 ContentSchema::JsonObject, 3394 EventDiscriminator::KindOnly, 3395 D_TAGS, 3396 PROFILE_REDUCERS 3397 ), 3398 event_contract!( 3399 "radroots.farm.workspace_manifest.v1", 3400 KIND_FARM_WORKSPACE_MANIFEST, 3401 "Farm Workspace Manifest", 3402 "RadrootsFarmWorkspaceManifest", 3403 EventClass::Addressable, 3404 EventPrivacy::Encrypted, 3405 AuthorRole::Farmer, 3406 ContentSchema::JsonObject, 3407 EventDiscriminator::KindOnly, 3408 D_TAGS, 3409 FARM_OPS_REDUCERS 3410 ), 3411 event_contract!( 3412 "radroots.operational_listing.published.v1", 3413 KIND_CLASSIFIED_LISTING, 3414 "Operational Listing", 3415 "RadrootsOperationalListing", 3416 EventClass::Addressable, 3417 EventPrivacy::Public, 3418 AuthorRole::Seller, 3419 ContentSchema::Markdown, 3420 EventDiscriminator::ClassifiedListingPartition( 3421 ClassifiedListingPartition::OperationalListing, 3422 ), 3423 OPERATIONAL_LISTING_TAGS, 3424 OPERATIONAL_LISTING_REDUCERS 3425 ), 3426 event_contract_with_authoring_policy!( 3427 "radroots.food.availability.v1", 3428 KIND_CLASSIFIED_LISTING, 3429 "Food Availability", 3430 "RadrootsFoodAvailabilityDetails / RadrootsInboundFoodAvailabilityProjection", 3431 EventClass::Addressable, 3432 EventPrivacy::Public, 3433 AuthorRole::Seller, 3434 ContentSchema::Markdown, 3435 EventAuthoringPolicy::TypedOnly, 3436 EventDiscriminator::AdmissionOnly, 3437 FOOD_AVAILABILITY_TAGS, 3438 FOOD_AVAILABILITY_REDUCERS 3439 ), 3440 experimental_event_contract!( 3441 "radroots.knowledge.source.v1", 3442 KIND_KNOWLEDGE_SOURCE, 3443 "Knowledge Source", 3444 "RadrootsKnowledgeSource", 3445 EventClass::Addressable, 3446 EventPrivacy::Public, 3447 AuthorRole::Any, 3448 ContentSchema::JsonObject, 3449 EventDiscriminator::TagEquals { 3450 name: "contract", 3451 value: "radroots.knowledge.source.v1", 3452 }, 3453 KNOWLEDGE_SOURCE_TAGS, 3454 KNOWLEDGE_REDUCERS 3455 ), 3456 experimental_event_contract!( 3457 "radroots.knowledge.evidence_bounty.v1", 3458 KIND_EVIDENCE_BOUNTY, 3459 "Evidence Bounty", 3460 "RadrootsEvidenceBounty", 3461 EventClass::Addressable, 3462 EventPrivacy::Public, 3463 AuthorRole::Any, 3464 ContentSchema::JsonObject, 3465 EventDiscriminator::TagEquals { 3466 name: "contract", 3467 value: "radroots.knowledge.evidence_bounty.v1", 3468 }, 3469 EVIDENCE_BOUNTY_TAGS, 3470 KNOWLEDGE_REDUCERS 3471 ), 3472 experimental_event_contract!( 3473 "radroots.knowledge.claim.v1", 3474 KIND_KNOWLEDGE_CLAIM, 3475 "Knowledge Claim", 3476 "RadrootsKnowledgeClaim", 3477 EventClass::Regular, 3478 EventPrivacy::Public, 3479 AuthorRole::Any, 3480 ContentSchema::JsonObject, 3481 EventDiscriminator::TagEquals { 3482 name: "contract", 3483 value: "radroots.knowledge.claim.v1", 3484 }, 3485 KNOWLEDGE_CLAIM_TAGS, 3486 KNOWLEDGE_REDUCERS 3487 ), 3488 experimental_event_contract!( 3489 "radroots.knowledge.relation.v1", 3490 KIND_KNOWLEDGE_RELATION, 3491 "Knowledge Relation", 3492 "RadrootsKnowledgeRelation", 3493 EventClass::Regular, 3494 EventPrivacy::Public, 3495 AuthorRole::Any, 3496 ContentSchema::JsonObject, 3497 EventDiscriminator::TagEquals { 3498 name: "contract", 3499 value: "radroots.knowledge.relation.v1", 3500 }, 3501 KNOWLEDGE_RELATION_TAGS, 3502 KNOWLEDGE_REDUCERS 3503 ), 3504 experimental_event_contract!( 3505 "radroots.knowledge.review.v1", 3506 KIND_KNOWLEDGE_REVIEW, 3507 "Knowledge Review", 3508 "RadrootsKnowledgeReview", 3509 EventClass::Regular, 3510 EventPrivacy::Public, 3511 AuthorRole::Any, 3512 ContentSchema::JsonObject, 3513 EventDiscriminator::TagEquals { 3514 name: "contract", 3515 value: "radroots.knowledge.review.v1", 3516 }, 3517 KNOWLEDGE_REVIEW_TAGS, 3518 KNOWLEDGE_REDUCERS 3519 ), 3520 experimental_event_contract!( 3521 "radroots.knowledge.field_report.v1", 3522 KIND_KNOWLEDGE_FIELD_REPORT, 3523 "Knowledge Field Report", 3524 "RadrootsKnowledgeFieldReport", 3525 EventClass::Regular, 3526 EventPrivacy::Public, 3527 AuthorRole::Any, 3528 ContentSchema::JsonObject, 3529 EventDiscriminator::TagEquals { 3530 name: "contract", 3531 value: "radroots.knowledge.field_report.v1", 3532 }, 3533 KNOWLEDGE_FIELD_REPORT_TAGS, 3534 KNOWLEDGE_REDUCERS 3535 ), 3536 experimental_event_contract!( 3537 "radroots.knowledge.change_proposal.v1", 3538 KIND_KNOWLEDGE_CHANGE_PROPOSAL, 3539 "Knowledge Change Proposal", 3540 "RadrootsKnowledgeChangeProposal", 3541 EventClass::Regular, 3542 EventPrivacy::Public, 3543 AuthorRole::Any, 3544 ContentSchema::JsonObject, 3545 EventDiscriminator::TagEquals { 3546 name: "contract", 3547 value: "radroots.knowledge.change_proposal.v1", 3548 }, 3549 KNOWLEDGE_CHANGE_PROPOSAL_TAGS, 3550 KNOWLEDGE_REDUCERS 3551 ), 3552 experimental_event_contract!( 3553 "radroots.knowledge.contribution_attestation.v1", 3554 KIND_CONTRIBUTION_ATTESTATION, 3555 "Contribution Attestation", 3556 "RadrootsContributionAttestation", 3557 EventClass::Regular, 3558 EventPrivacy::Public, 3559 AuthorRole::Any, 3560 ContentSchema::JsonObject, 3561 EventDiscriminator::TagEquals { 3562 name: "contract", 3563 value: "radroots.knowledge.contribution_attestation.v1", 3564 }, 3565 KNOWLEDGE_CONTRIBUTION_TAGS, 3566 KNOWLEDGE_REDUCERS 3567 ), 3568 event_contract!( 3569 "radroots.application.handler.v1", 3570 KIND_APPLICATION_HANDLER, 3571 "Application Handler", 3572 "RadrootsApplicationHandler", 3573 EventClass::Addressable, 3574 EventPrivacy::Public, 3575 AuthorRole::Application, 3576 ContentSchema::JsonObject, 3577 EventDiscriminator::KindOnly, 3578 D_TAGS, 3579 SOCIAL_REDUCERS 3580 ), 3581 event_contract!( 3582 "radroots.group.metadata.v1", 3583 KIND_GROUP_METADATA, 3584 "Group Metadata", 3585 "RadrootsGroupMetadata", 3586 EventClass::Addressable, 3587 EventPrivacy::Public, 3588 AuthorRole::Moderator, 3589 ContentSchema::JsonObject, 3590 EventDiscriminator::KindOnly, 3591 GROUP_STATE_TAGS, 3592 GROUP_REDUCERS 3593 ), 3594 event_contract!( 3595 "radroots.group.admins.v1", 3596 KIND_GROUP_ADMINS, 3597 "Group Admins", 3598 "RadrootsGroupAdmins", 3599 EventClass::Addressable, 3600 EventPrivacy::Public, 3601 AuthorRole::Moderator, 3602 ContentSchema::JsonObject, 3603 EventDiscriminator::KindOnly, 3604 GROUP_STATE_TAGS, 3605 GROUP_REDUCERS 3606 ), 3607 event_contract!( 3608 "radroots.group.members.v1", 3609 KIND_GROUP_MEMBERS, 3610 "Group Members", 3611 "RadrootsGroupMembers", 3612 EventClass::Addressable, 3613 EventPrivacy::Public, 3614 AuthorRole::Moderator, 3615 ContentSchema::JsonObject, 3616 EventDiscriminator::KindOnly, 3617 GROUP_STATE_TAGS, 3618 GROUP_REDUCERS 3619 ), 3620 event_contract!( 3621 "radroots.group.roles.v1", 3622 KIND_GROUP_ROLES, 3623 "Group Roles", 3624 "RadrootsGroupRoles", 3625 EventClass::Addressable, 3626 EventPrivacy::Public, 3627 AuthorRole::Moderator, 3628 ContentSchema::JsonObject, 3629 EventDiscriminator::KindOnly, 3630 GROUP_STATE_TAGS, 3631 GROUP_REDUCERS 3632 ), 3633 event_contract_with_authoring_policy!( 3634 "radroots.trade.proposal.v1", 3635 KIND_TRADE_PROPOSAL, 3636 "Trade Proposal", 3637 "RadrootsTradeMutationEnvelopeV1", 3638 EventClass::Regular, 3639 EventPrivacy::Public, 3640 AuthorRole::Buyer, 3641 ContentSchema::JsonObject, 3642 EventAuthoringPolicy::TypedOnly, 3643 EventDiscriminator::ContentJsonFieldEquals { 3644 field: "contract_id", 3645 value: "radroots.trade.proposal.v1", 3646 }, 3647 TRADE_MUTATION_TAGS, 3648 TRADE_MUTATION_REDUCERS 3649 ), 3650 event_contract_with_authoring_policy!( 3651 "radroots.trade.decision.v1", 3652 KIND_TRADE_DECISION, 3653 "Trade Decision", 3654 "RadrootsTradeMutationEnvelopeV1", 3655 EventClass::Regular, 3656 EventPrivacy::Public, 3657 AuthorRole::Seller, 3658 ContentSchema::JsonObject, 3659 EventAuthoringPolicy::TypedOnly, 3660 EventDiscriminator::ContentJsonFieldEquals { 3661 field: "contract_id", 3662 value: "radroots.trade.decision.v1", 3663 }, 3664 TRADE_MUTATION_TAGS, 3665 TRADE_MUTATION_REDUCERS 3666 ), 3667 event_contract_with_authoring_policy!( 3668 "radroots.trade.revision_proposal.v1", 3669 KIND_TRADE_REVISION_PROPOSAL, 3670 "Trade Revision Proposal", 3671 "RadrootsTradeMutationEnvelopeV1", 3672 EventClass::Regular, 3673 EventPrivacy::Public, 3674 AuthorRole::Any, 3675 ContentSchema::JsonObject, 3676 EventAuthoringPolicy::TypedOnly, 3677 EventDiscriminator::ContentJsonFieldEquals { 3678 field: "contract_id", 3679 value: "radroots.trade.revision_proposal.v1", 3680 }, 3681 TRADE_MUTATION_TAGS, 3682 TRADE_MUTATION_REDUCERS 3683 ), 3684 event_contract_with_authoring_policy!( 3685 "radroots.trade.revision_decision.v1", 3686 KIND_TRADE_REVISION_DECISION, 3687 "Trade Revision Decision", 3688 "RadrootsTradeMutationEnvelopeV1", 3689 EventClass::Regular, 3690 EventPrivacy::Public, 3691 AuthorRole::Any, 3692 ContentSchema::JsonObject, 3693 EventAuthoringPolicy::TypedOnly, 3694 EventDiscriminator::ContentJsonFieldEquals { 3695 field: "contract_id", 3696 value: "radroots.trade.revision_decision.v1", 3697 }, 3698 TRADE_MUTATION_TAGS, 3699 TRADE_MUTATION_REDUCERS 3700 ), 3701 event_contract_with_authoring_policy!( 3702 "radroots.trade.cancellation.v1", 3703 KIND_TRADE_CANCELLATION, 3704 "Trade Cancellation", 3705 "RadrootsTradeMutationEnvelopeV1", 3706 EventClass::Regular, 3707 EventPrivacy::Public, 3708 AuthorRole::Buyer, 3709 ContentSchema::JsonObject, 3710 EventAuthoringPolicy::TypedOnly, 3711 EventDiscriminator::ContentJsonFieldEquals { 3712 field: "contract_id", 3713 value: "radroots.trade.cancellation.v1", 3714 }, 3715 TRADE_MUTATION_TAGS, 3716 TRADE_MUTATION_REDUCERS 3717 ), 3718 event_contract_with_authoring_policy!( 3719 "radroots.rhi.evidence_attestation.v1", 3720 KIND_RHI_EVIDENCE_ATTESTATION, 3721 "RHI Evidence Attestation", 3722 "RadrootsRhiEvidenceAttestationV1", 3723 EventClass::Regular, 3724 EventPrivacy::Public, 3725 AuthorRole::Service, 3726 ContentSchema::JsonObject, 3727 EventAuthoringPolicy::TypedOnly, 3728 EventDiscriminator::KindOnly, 3729 RHI_EVIDENCE_ATTESTATION_TAGS, 3730 TRADE_VALIDATION_REDUCERS 3731 ), 3732 ]; 3733 3734 pub fn all_kind_contracts() -> &'static [KindContract] { 3735 all_kind_contracts_registry_v7() 3736 } 3737 3738 /// Returns the immutable kind-contract inventory for registry v7. 3739 pub const fn all_kind_contracts_registry_v7() -> &'static [KindContract] { 3740 KIND_CONTRACTS_REGISTRY_V7 3741 } 3742 3743 pub fn all_event_contracts() -> &'static [EventContract] { 3744 all_event_contracts_registry_v7() 3745 } 3746 3747 /// Returns the immutable event-contract inventory for registry v7. 3748 pub const fn all_event_contracts_registry_v7() -> &'static [EventContract] { 3749 EVENT_CONTRACTS_REGISTRY_V7 3750 } 3751 3752 pub fn contract_families() -> &'static [ContractFamilyMetadata] { 3753 CONTRACT_FAMILIES 3754 } 3755 3756 pub fn event_contract_family(contract: &EventContract) -> Option<ContractFamily> { 3757 contract_family_for_id(contract.id) 3758 } 3759 3760 pub fn kind_contract_family(contract: &KindContract) -> Option<ContractFamily> { 3761 Some(match contract.kind { 3762 KIND_PROFILE | KIND_FOLLOW | KIND_ACCOUNT_CLAIM => ContractFamily::Profile, 3763 KIND_SEAL | KIND_MESSAGE | KIND_MESSAGE_FILE | KIND_GIFT_WRAP => ContractFamily::Message, 3764 KIND_COMMENT 3765 | KIND_DELETION_REQUEST 3766 | KIND_GEOCHAT 3767 | KIND_POST 3768 | KIND_REACTION 3769 | KIND_REPOST 3770 | KIND_GENERIC_REPOST 3771 | KIND_ARTICLE 3772 | KIND_FILE_METADATA => ContractFamily::Social, 3773 KIND_RELAY_AUTH | KIND_HTTP_AUTH => ContractFamily::Relay, 3774 KIND_GROUP_PUT_USER 3775 | KIND_GROUP_REMOVE_USER 3776 | KIND_GROUP_EDIT_METADATA 3777 | KIND_GROUP_DELETE_EVENT 3778 | KIND_GROUP_CREATE_GROUP 3779 | KIND_GROUP_DELETE_GROUP 3780 | KIND_GROUP_CREATE_INVITE 3781 | KIND_GROUP_JOIN_REQUEST 3782 | KIND_GROUP_LEAVE_REQUEST 3783 | KIND_GROUP_METADATA 3784 | KIND_GROUP_ADMINS 3785 | KIND_GROUP_MEMBERS 3786 | KIND_GROUP_ROLES => ContractFamily::Group, 3787 KIND_LIST_MUTE 3788 | KIND_LIST_PINNED_NOTES 3789 | KIND_LIST_READ_WRITE_RELAYS 3790 | KIND_LIST_BOOKMARKS 3791 | KIND_LIST_COMMUNITIES 3792 | KIND_LIST_PUBLIC_CHATS 3793 | KIND_LIST_BLOCKED_RELAYS 3794 | KIND_LIST_SEARCH_RELAYS 3795 | KIND_LIST_SIMPLE_GROUPS 3796 | KIND_LIST_RELAY_FEEDS 3797 | KIND_LIST_INTERESTS 3798 | KIND_LIST_MEDIA_FOLLOWS 3799 | KIND_LIST_EMOJIS 3800 | KIND_LIST_DM_RELAYS 3801 | KIND_LIST_GOOD_WIKI_AUTHORS 3802 | KIND_LIST_GOOD_WIKI_RELAYS 3803 | KIND_LIST_SET_FOLLOW 3804 | KIND_LIST_SET_GENERIC 3805 | KIND_LIST_SET_RELAY 3806 | KIND_LIST_SET_BOOKMARK 3807 | KIND_LIST_SET_CURATION 3808 | KIND_LIST_SET_VIDEO 3809 | KIND_LIST_SET_PICTURE 3810 | KIND_LIST_SET_KIND_MUTE 3811 | KIND_LIST_SET_INTEREST 3812 | KIND_LIST_SET_EMOJI 3813 | KIND_LIST_SET_RELEASE_ARTIFACT 3814 | KIND_LIST_SET_APP_CURATION 3815 | KIND_LIST_SET_STARTER_PACK 3816 | KIND_LIST_SET_MEDIA_STARTER_PACK => ContractFamily::List, 3817 KIND_CALENDAR_DATE_EVENT 3818 | KIND_CALENDAR_TIME_EVENT 3819 | KIND_CALENDAR 3820 | KIND_CALENDAR_EVENT_RSVP => ContractFamily::Calendar, 3821 KIND_FARM 3822 | KIND_PLOT 3823 | KIND_COOP 3824 | KIND_DOCUMENT 3825 | KIND_RESOURCE_AREA 3826 | KIND_RESOURCE_HARVEST_CAP 3827 | KIND_FARM_WORKSPACE_MANIFEST 3828 | KIND_FARM_CRDT_CHANGE => ContractFamily::Farm, 3829 KIND_CLASSIFIED_LISTING => ContractFamily::Market, 3830 KIND_RHI_EVIDENCE_ATTESTATION 3831 | KIND_TRADE_PROPOSAL 3832 | KIND_TRADE_DECISION 3833 | KIND_TRADE_REVISION_PROPOSAL 3834 | KIND_TRADE_REVISION_DECISION 3835 | KIND_TRADE_CANCELLATION => ContractFamily::Trade, 3836 KIND_WIKI_MERGE_REQUEST 3837 | KIND_WIKI_ARTICLE 3838 | KIND_WIKI_REDIRECT 3839 | KIND_KNOWLEDGE_SOURCE 3840 | KIND_EVIDENCE_BOUNTY 3841 | KIND_KNOWLEDGE_CLAIM 3842 | KIND_KNOWLEDGE_RELATION 3843 | KIND_KNOWLEDGE_REVIEW 3844 | KIND_KNOWLEDGE_FIELD_REPORT 3845 | KIND_KNOWLEDGE_CHANGE_PROPOSAL 3846 | KIND_CONTRIBUTION_ATTESTATION => ContractFamily::Knowledge, 3847 KIND_JOB_FEEDBACK => ContractFamily::Job, 3848 _ if [ 3849 is_request_kind(contract.kind), 3850 is_result_kind(contract.kind), 3851 ] 3852 .contains(&true) => 3853 { 3854 ContractFamily::Job 3855 } 3856 _ => return None, 3857 }) 3858 } 3859 3860 pub fn kind_contract(kind: u32) -> Option<&'static KindContract> { 3861 kind_contract_registry_v7(kind) 3862 } 3863 3864 /// Resolves a kind contract from the immutable registry-v7 inventory. 3865 pub fn kind_contract_registry_v7(kind: u32) -> Option<&'static KindContract> { 3866 KIND_CONTRACTS_REGISTRY_V7 3867 .iter() 3868 .find(|contract| contract.kind == kind) 3869 } 3870 3871 pub fn event_contract(id: &str) -> Option<&'static EventContract> { 3872 event_contract_registry_v7(id) 3873 } 3874 3875 /// Resolves an event contract from the immutable registry-v7 inventory. 3876 /// 3877 /// Event-store reconciliation v1 depends on this historical entry point. 3878 /// Later registries must retain it and add a new versioned lookup. 3879 pub fn event_contract_registry_v7(id: &str) -> Option<&'static EventContract> { 3880 EVENT_CONTRACTS_REGISTRY_V7 3881 .iter() 3882 .find(|contract| contract.id == id) 3883 } 3884 3885 pub fn event_contracts_for_kind(kind: u32) -> impl Iterator<Item = &'static EventContract> { 3886 EVENT_CONTRACTS_REGISTRY_V7 3887 .iter() 3888 .filter(move |contract| contract.kind == kind) 3889 } 3890 3891 pub fn identify_event_contract( 3892 kind: u32, 3893 tags: &[Vec<String>], 3894 content: &str, 3895 ) -> Result<&'static EventContract, ContractMatchError> { 3896 identify_event_contract_in_registry( 3897 kind, 3898 tags, 3899 content, 3900 KIND_CONTRACTS_REGISTRY_V7, 3901 EVENT_CONTRACTS_REGISTRY_V7, 3902 ) 3903 } 3904 3905 fn identify_event_contract_in_registry( 3906 kind: u32, 3907 tags: &[Vec<String>], 3908 content: &str, 3909 kind_contracts: &'static [KindContract], 3910 event_contracts: &'static [EventContract], 3911 ) -> Result<&'static EventContract, ContractMatchError> { 3912 crate::require_invariant( 3913 kind_contracts.iter().any(|contract| contract.kind == kind), 3914 &|| ContractMatchError::UnsupportedKind(kind), 3915 )?; 3916 identify_from_contracts( 3917 event_contracts 3918 .iter() 3919 .filter(move |contract| contract.kind == kind), 3920 kind, 3921 tags, 3922 content, 3923 ) 3924 } 3925 3926 pub fn validate_event_contract( 3927 event: &EventEnvelope, 3928 ) -> Result<&'static EventContract, ContractValidationError> { 3929 validate_event_contract_in_registry( 3930 event, 3931 KIND_CONTRACTS_REGISTRY_V7, 3932 EVENT_CONTRACTS_REGISTRY_V7, 3933 ) 3934 } 3935 3936 /// Validates against the immutable event-contract inventory used by registry 7. 3937 /// 3938 /// Event-store migration 0002 depends on this historical entry point. Later 3939 /// registries must retain it and add a new versioned validator. 3940 pub fn validate_event_contract_registry_v7( 3941 event: &EventEnvelope, 3942 ) -> Result<&'static EventContract, ContractValidationError> { 3943 validate_event_contract_in_registry( 3944 event, 3945 KIND_CONTRACTS_REGISTRY_V7, 3946 EVENT_CONTRACTS_REGISTRY_V7, 3947 ) 3948 } 3949 3950 fn validate_event_contract_in_registry( 3951 event: &EventEnvelope, 3952 kind_contracts: &'static [KindContract], 3953 event_contracts: &'static [EventContract], 3954 ) -> Result<&'static EventContract, ContractValidationError> { 3955 let tags = event.tags_as_vec(); 3956 let contract = identify_event_contract_in_registry( 3957 event.kind_u32(), 3958 &tags, 3959 event.content(), 3960 kind_contracts, 3961 event_contracts, 3962 ) 3963 .map_err(|error| ContractValidationError::ContractMatch { error })?; 3964 validate_event_contract_parts_in_registry( 3965 event.kind_u32(), 3966 &tags, 3967 event.content(), 3968 contract, 3969 event_contracts, 3970 false, 3971 )?; 3972 Ok(contract) 3973 } 3974 3975 pub fn validate_event_contract_shape( 3976 event: &EventEnvelope, 3977 contract_id: &str, 3978 ) -> Result<(), ContractValidationError> { 3979 let tags = event.tags_as_vec(); 3980 validate_event_contract_parts(event.kind_u32(), &tags, event.content(), contract_id) 3981 } 3982 3983 /// Validates a contract selected explicitly by an admission boundary. 3984 /// 3985 /// This is the required path for contracts whose discriminator is 3986 /// [`EventDiscriminator::AdmissionOnly`]. The selected contract's kind, 3987 /// content, tags, and custom invariants are still validated in full. 3988 pub fn validate_event_contract_for_admission( 3989 event: &EventEnvelope, 3990 contract_id: &str, 3991 ) -> Result<&'static EventContract, ContractValidationError> { 3992 let contract = 3993 event_contract(contract_id).ok_or_else(|| ContractValidationError::UnknownContract { 3994 contract_id: contract_id.to_owned(), 3995 })?; 3996 let tags = event.tags_as_vec(); 3997 validate_event_contract_parts_in_registry( 3998 event.kind_u32(), 3999 &tags, 4000 event.content(), 4001 contract, 4002 EVENT_CONTRACTS_REGISTRY_V7, 4003 true, 4004 )?; 4005 Ok(contract) 4006 } 4007 4008 pub fn validate_event_contract_parts( 4009 kind: u32, 4010 tags: &[Vec<String>], 4011 content: &str, 4012 contract_id: &str, 4013 ) -> Result<(), ContractValidationError> { 4014 let contract = 4015 event_contract(contract_id).ok_or_else(|| ContractValidationError::UnknownContract { 4016 contract_id: contract_id.to_owned(), 4017 })?; 4018 validate_event_contract_parts_in_registry( 4019 kind, 4020 tags, 4021 content, 4022 contract, 4023 EVENT_CONTRACTS_REGISTRY_V7, 4024 false, 4025 ) 4026 } 4027 4028 fn validate_event_contract_parts_in_registry( 4029 kind: u32, 4030 tags: &[Vec<String>], 4031 content: &str, 4032 contract: &EventContract, 4033 event_contracts: &'static [EventContract], 4034 admission_selected: bool, 4035 ) -> Result<(), ContractValidationError> { 4036 crate::require_invariant(kind == contract.kind, &|| { 4037 ContractValidationError::KindMismatch { 4038 expected: contract.kind, 4039 actual: kind, 4040 } 4041 })?; 4042 crate::require_invariant( 4043 admission_selected || !matches!(contract.discriminator, EventDiscriminator::AdmissionOnly), 4044 &|| ContractValidationError::AdmissionRequired { 4045 contract_id: contract.id, 4046 }, 4047 )?; 4048 validate_classified_listing_partition_parts(tags, contract)?; 4049 validate_content_shape_parts(content, contract)?; 4050 validate_contract_tags_parts_in_registry(tags, contract, event_contracts)?; 4051 if admission_selected { 4052 validate_admission_selected_contract_parts(kind, tags, contract)?; 4053 } 4054 validate_discriminator_parts(content, contract, admission_selected)?; 4055 validate_custom_calendar_contract_parts(tags, contract)?; 4056 validate_custom_knowledge_contract_parts(content, contract)?; 4057 Ok(()) 4058 } 4059 4060 fn validate_admission_selected_contract_parts( 4061 kind: u32, 4062 tags: &[Vec<String>], 4063 contract: &EventContract, 4064 ) -> Result<(), ContractValidationError> { 4065 if contract.id == "radroots.social.deletion_request.v1" 4066 && !tags.iter().any(|tag| { 4067 tag.first() 4068 .is_some_and(|name| matches!(name.as_str(), "e" | "a")) 4069 }) 4070 { 4071 return Err(ContractValidationError::ContractMatch { 4072 error: ContractMatchError::UnsupportedShape(kind), 4073 }); 4074 } 4075 Ok(()) 4076 } 4077 4078 fn validate_classified_listing_partition_parts( 4079 tags: &[Vec<String>], 4080 contract: &EventContract, 4081 ) -> Result<(), ContractValidationError> { 4082 let EventDiscriminator::ClassifiedListingPartition(expected) = contract.discriminator else { 4083 return Ok(()); 4084 }; 4085 if classify_classified_listing_raw_tags_registry_v7(tags) == expected { 4086 Ok(()) 4087 } else { 4088 Err(ContractValidationError::ContractMatch { 4089 error: ContractMatchError::UnsupportedShape(contract.kind), 4090 }) 4091 } 4092 } 4093 4094 fn classify_classified_listing_raw_tags_registry_v7( 4095 tags: &[Vec<String>], 4096 ) -> ClassifiedListingPartition { 4097 let mut has_focused_marker = false; 4098 let mut has_operational_marker = false; 4099 4100 for name in tags 4101 .iter() 4102 .filter_map(|tag| tag.first().map(String::as_str)) 4103 { 4104 match name { 4105 "radroots:price_unit" | "radroots:quantity" => has_focused_marker = true, 4106 "radroots:primary_bin" | "radroots:bin" | "radroots:price" => { 4107 has_operational_marker = true; 4108 } 4109 _ => {} 4110 } 4111 4112 if has_focused_marker && has_operational_marker { 4113 return ClassifiedListingPartition::Ambiguous; 4114 } 4115 } 4116 4117 match (has_focused_marker, has_operational_marker) { 4118 (true, false) => ClassifiedListingPartition::FocusedFoodAvailability, 4119 (false, true) => ClassifiedListingPartition::OperationalListing, 4120 (false, false) => ClassifiedListingPartition::GenericNip99, 4121 (true, true) => ClassifiedListingPartition::Ambiguous, 4122 } 4123 } 4124 4125 fn identify_from_contracts<'a, I>( 4126 contracts: I, 4127 kind: u32, 4128 tags: &[Vec<String>], 4129 content: &str, 4130 ) -> Result<&'a EventContract, ContractMatchError> 4131 where 4132 I: IntoIterator<Item = &'a EventContract>, 4133 { 4134 let mut matched = None; 4135 let mut matched_count = 0; 4136 4137 for contract in contracts { 4138 if discriminator_matches(&contract.discriminator, tags, content) { 4139 matched = Some(contract); 4140 matched_count += 1; 4141 } 4142 } 4143 4144 match (matched, matched_count) { 4145 (Some(contract), 1) => Ok(contract), 4146 (None, _) => Err(ContractMatchError::UnsupportedShape(kind)), 4147 (Some(_), _) => Err(ContractMatchError::AmbiguousShape(kind)), 4148 } 4149 } 4150 4151 fn contract_family_for_id(id: &str) -> Option<ContractFamily> { 4152 const PREFIX_FAMILIES: [(&str, ContractFamily); 18] = [ 4153 ("radroots.account.", ContractFamily::Account), 4154 ("radroots.application.", ContractFamily::Application), 4155 ("radroots.calendar.", ContractFamily::Calendar), 4156 ("radroots.farm.", ContractFamily::Farm), 4157 ("radroots.group.", ContractFamily::Group), 4158 ("radroots.http.", ContractFamily::Http), 4159 ("radroots.job.", ContractFamily::Job), 4160 ("radroots.knowledge.", ContractFamily::Knowledge), 4161 ("radroots.wiki.", ContractFamily::Knowledge), 4162 ("radroots.list.", ContractFamily::List), 4163 ("radroots.list_set.", ContractFamily::List), 4164 ("radroots.operational_listing.", ContractFamily::Market), 4165 ("radroots.food.", ContractFamily::Market), 4166 ("radroots.message.", ContractFamily::Message), 4167 ("radroots.profile.", ContractFamily::Profile), 4168 ("radroots.relay.", ContractFamily::Relay), 4169 ("radroots.social.", ContractFamily::Social), 4170 ("radroots.trade.", ContractFamily::Trade), 4171 ]; 4172 4173 PREFIX_FAMILIES 4174 .iter() 4175 .find_map(|(prefix, family)| id.starts_with(prefix).then_some(*family)) 4176 } 4177 4178 fn validate_content_shape_parts( 4179 content: &str, 4180 contract: &EventContract, 4181 ) -> Result<(), ContractValidationError> { 4182 match contract.content_schema { 4183 ContentSchema::Empty => { 4184 if content.is_empty() { 4185 Ok(()) 4186 } else { 4187 Err(ContractValidationError::ContentMustBeEmpty { 4188 contract_id: contract.id, 4189 }) 4190 } 4191 } 4192 ContentSchema::JsonObject => parse_content_object(content, contract.id).map(|_| ()), 4193 _ => Ok(()), 4194 } 4195 } 4196 4197 #[cfg(test)] 4198 fn validate_contract_tags_parts( 4199 tags: &[Vec<String>], 4200 contract: &EventContract, 4201 ) -> Result<(), ContractValidationError> { 4202 validate_contract_tags_parts_in_registry(tags, contract, EVENT_CONTRACTS_REGISTRY_V7) 4203 } 4204 4205 fn validate_contract_tags_parts_in_registry( 4206 tags: &[Vec<String>], 4207 contract: &EventContract, 4208 event_contracts: &'static [EventContract], 4209 ) -> Result<(), ContractValidationError> { 4210 for tag_contract in contract.tags { 4211 let count = tag_count(tags, tag_contract.name); 4212 let has_multiple_contracts_for_name = contract 4213 .tags 4214 .iter() 4215 .filter(|candidate| candidate.name == tag_contract.name) 4216 .count() 4217 > 1; 4218 match tag_contract.cardinality { 4219 TagCardinality::RequiredOne => { 4220 crate::require_invariant(count != 0, &|| ContractValidationError::MissingTag { 4221 contract_id: contract.id, 4222 name: tag_contract.name, 4223 })?; 4224 crate::require_invariant( 4225 [count == 1, has_multiple_contracts_for_name].contains(&true), 4226 &|| ContractValidationError::TagCardinalityMismatch { 4227 contract_id: contract.id, 4228 name: tag_contract.name, 4229 }, 4230 )?; 4231 } 4232 TagCardinality::RequiredMany => { 4233 crate::require_invariant(count != 0, &|| ContractValidationError::MissingTag { 4234 contract_id: contract.id, 4235 name: tag_contract.name, 4236 })?; 4237 } 4238 TagCardinality::OptionalOne => { 4239 crate::require_invariant( 4240 [count <= 1, has_multiple_contracts_for_name].contains(&true), 4241 &|| ContractValidationError::TagCardinalityMismatch { 4242 contract_id: contract.id, 4243 name: tag_contract.name, 4244 }, 4245 )?; 4246 } 4247 TagCardinality::OptionalMany => {} 4248 } 4249 if tag_contract.name == "contract" { 4250 let actual = tag_value(tags, "contract").map(ToOwned::to_owned); 4251 crate::require_invariant(actual.as_deref() == Some(contract.id), &|| { 4252 ContractValidationError::TagValueMismatch { 4253 contract_id: contract.id, 4254 name: "contract", 4255 expected: contract.id.to_owned(), 4256 actual: actual.clone(), 4257 } 4258 })?; 4259 } 4260 validate_contract_tag_values_in_registry(tags, contract, tag_contract, event_contracts)?; 4261 } 4262 Ok(()) 4263 } 4264 4265 fn validate_contract_tag_values_in_registry( 4266 tags: &[Vec<String>], 4267 contract: &EventContract, 4268 tag_contract: &TagContract, 4269 event_contracts: &'static [EventContract], 4270 ) -> Result<(), ContractValidationError> { 4271 for tag in tags 4272 .iter() 4273 .filter(|tag| tag.first().map(|value| value.as_str()) == Some(tag_contract.name)) 4274 { 4275 crate::require_invariant( 4276 tag_value_is_valid_in_registry(tag, tag_contract.value_type, event_contracts), 4277 &|| ContractValidationError::TagValueMismatch { 4278 contract_id: contract.id, 4279 name: tag_contract.name, 4280 expected: tag_value_type_expectation(tag_contract.value_type).to_owned(), 4281 actual: tag.get(1).cloned(), 4282 }, 4283 )?; 4284 } 4285 Ok(()) 4286 } 4287 4288 #[cfg(test)] 4289 fn tag_value_is_valid(tag: &[String], value_type: TagValueType) -> bool { 4290 tag_value_is_valid_in_registry(tag, value_type, EVENT_CONTRACTS_REGISTRY_V7) 4291 } 4292 4293 fn tag_value_is_valid_in_registry( 4294 tag: &[String], 4295 value_type: TagValueType, 4296 event_contracts: &'static [EventContract], 4297 ) -> bool { 4298 let Some(value) = tag.get(1).map(String::as_str) else { 4299 return false; 4300 }; 4301 match value_type { 4302 TagValueType::AddressableCoordinate => AddressableCoordinate::parse(value).is_ok(), 4303 TagValueType::CalendarDate => CalendarDate::parse(value).is_ok(), 4304 TagValueType::CalendarEventCoordinate => { 4305 canonical_calendar_event_coordinate_is_valid(value) 4306 } 4307 TagValueType::CalendarFreeBusy => matches!(value, "free" | "busy"), 4308 TagValueType::CalendarRsvpStatus => { 4309 matches!(value, "accepted" | "declined" | "tentative") 4310 } 4311 TagValueType::CalendarUid => CalendarUid::parse(value).is_ok(), 4312 TagValueType::ContractId => event_contracts.iter().any(|contract| contract.id == value), 4313 TagValueType::DTag => DTag::parse(value).is_ok(), 4314 TagValueType::EventId | TagValueType::Sha256 => EventId::parse(value).is_ok(), 4315 TagValueType::EventPointer => event_pointer_tag_is_valid(tag), 4316 TagValueType::Geohash => geohash_is_valid(value), 4317 TagValueType::IanaTimeZoneId => IanaTimeZoneId::parse(value).is_ok(), 4318 TagValueType::Kind => value.parse::<u32>().is_ok(), 4319 TagValueType::MutationId => { 4320 MutationId::parse(value).is_ok_and(|identifier| identifier.to_hex() == value) 4321 } 4322 TagValueType::Nip01Coordinate => Nip01Coordinate::parse(value).is_ok(), 4323 TagValueType::PublicKey => parse_public_key(value).is_ok(), 4324 TagValueType::RelayUrl => relay_url_is_valid(value), 4325 TagValueType::Text => visible_text_is_valid(value), 4326 TagValueType::UnixTimestamp => value.parse::<u64>().is_ok(), 4327 TagValueType::Uri => CalendarUri::parse(value).is_ok(), 4328 TagValueType::Url => url_is_valid(value), 4329 TagValueType::UtcDayIndex => canonical_u64(value).is_some(), 4330 TagValueType::Uuid => uuid_is_valid(value), 4331 } 4332 } 4333 4334 fn event_pointer_tag_is_valid(tag: &[String]) -> bool { 4335 if tag.len() < 5 { 4336 return false; 4337 } 4338 let id = tag[1].as_str(); 4339 let author = tag[2].as_str(); 4340 let kind = tag[3].as_str(); 4341 let d_tag = tag[4].as_str(); 4342 [ 4343 EventId::parse(id).is_ok(), 4344 parse_public_key(author).is_ok(), 4345 kind.parse::<u32>().is_ok(), 4346 [d_tag.is_empty(), DTag::parse(d_tag).is_ok()].contains(&true), 4347 tag.iter() 4348 .skip(5) 4349 .all(|relay| relay_url_is_valid(relay.as_str())), 4350 ] == [true; 5] 4351 } 4352 4353 fn visible_text_is_valid(value: &str) -> bool { 4354 [ 4355 !value.trim().is_empty(), 4356 !value.chars().any(char::is_control), 4357 ] == [true; 2] 4358 } 4359 4360 fn url_is_valid(value: &str) -> bool { 4361 [ 4362 value 4363 .strip_prefix("https://") 4364 .or_else(|| value.strip_prefix("http://")) 4365 .is_some_and(|remainder| !remainder.is_empty()), 4366 value.trim() == value, 4367 !value.chars().any(char::is_control), 4368 ] == [true; 3] 4369 } 4370 4371 fn geohash_is_valid(value: &str) -> bool { 4372 [ 4373 !value.is_empty(), 4374 value.len() <= 12, 4375 value 4376 .bytes() 4377 .all(|byte| matches!(byte.to_ascii_lowercase(), b'0'..=b'9' | b'b'..=b'h' | b'j'..=b'k' | b'm'..=b'n' | b'p'..=b'z')), 4378 ] == [true; 3] 4379 } 4380 4381 fn uuid_is_valid(value: &str) -> bool { 4382 let bytes = value.as_bytes(); 4383 [ 4384 bytes.len() == 36, 4385 bytes.iter().enumerate().all(|(index, byte)| match index { 4386 8 | 13 | 18 | 23 => *byte == b'-', 4387 _ => byte.is_ascii_hexdigit(), 4388 }), 4389 ] == [true; 2] 4390 } 4391 4392 fn tag_value_type_expectation(value_type: TagValueType) -> &'static str { 4393 match value_type { 4394 TagValueType::AddressableCoordinate => "addressable_coordinate", 4395 TagValueType::CalendarDate => "calendar_date_yyyy_mm_dd", 4396 TagValueType::CalendarEventCoordinate => "canonical_kind_31922_or_31923_coordinate", 4397 TagValueType::CalendarFreeBusy => "calendar_free_or_busy", 4398 TagValueType::CalendarRsvpStatus => "calendar_rsvp_status", 4399 TagValueType::CalendarUid => "canonical_128_bit_base64url_calendar_uid", 4400 TagValueType::ContractId => "contract_id", 4401 TagValueType::DTag => "d_tag", 4402 TagValueType::EventId => "event_id", 4403 TagValueType::EventPointer => "event_pointer", 4404 TagValueType::Geohash => "geohash", 4405 TagValueType::IanaTimeZoneId => "canonical_iana_time_zone_id", 4406 TagValueType::Kind => "kind", 4407 TagValueType::MutationId => "lowercase_64_hex_mutation_id", 4408 TagValueType::Nip01Coordinate => "nip01_coordinate", 4409 TagValueType::PublicKey => "public_key", 4410 TagValueType::RelayUrl => "relay_url", 4411 TagValueType::Sha256 => "sha256", 4412 TagValueType::Text => "text", 4413 TagValueType::UnixTimestamp => "unix_timestamp", 4414 TagValueType::Uri => "absolute_uri", 4415 TagValueType::Url => "url", 4416 TagValueType::UtcDayIndex => "canonical_decimal_utc_day_index", 4417 TagValueType::Uuid => "uuid", 4418 } 4419 } 4420 4421 fn canonical_u64(value: &str) -> Option<u64> { 4422 let valid = [ 4423 !value.is_empty(), 4424 [value.len() > 1, value.starts_with('0')] != [true; 2], 4425 value.bytes().all(|byte| byte.is_ascii_digit()), 4426 ]; 4427 (valid == [true; 3]).then_some(value.parse().ok()).flatten() 4428 } 4429 4430 fn validate_custom_calendar_contract_parts( 4431 tags: &[Vec<String>], 4432 contract: &EventContract, 4433 ) -> Result<(), ContractValidationError> { 4434 match contract.id { 4435 "radroots.calendar.date_event.v1" => validate_calendar_date_contract(tags, contract), 4436 "radroots.calendar.time_event.v1" => validate_calendar_time_contract(tags, contract), 4437 "radroots.calendar.collection.v1" => validate_calendar_collection_contract(tags, contract), 4438 "radroots.calendar.rsvp.v1" => validate_calendar_rsvp_contract(tags, contract), 4439 _ => Ok(()), 4440 } 4441 } 4442 4443 fn validate_calendar_collection_contract( 4444 tags: &[Vec<String>], 4445 contract: &EventContract, 4446 ) -> Result<(), ContractValidationError> { 4447 validate_exact_calendar_tags(tags, contract, &["d", "title", "description", "image"])?; 4448 validate_canonical_calendar_text_tags(tags, contract, &["title", "description"])?; 4449 validate_calendar_event_reference_tags(tags, contract)?; 4450 validate_calendar_blossom_image(tags, contract)?; 4451 4452 let event_references = tags 4453 .iter() 4454 .filter(|tag| tag.first().map(String::as_str) == Some("a")) 4455 .collect::<Vec<_>>(); 4456 for (index, reference) in event_references.iter().enumerate() { 4457 crate::require_invariant( 4458 !event_references 4459 .iter() 4460 .skip(index + 1) 4461 .any(|candidate| candidate.get(1) == reference.get(1)), 4462 &|| { 4463 calendar_tag_mismatch( 4464 contract, 4465 "a", 4466 "duplicate_free_calendar_event_coordinates", 4467 reference.get(1).cloned(), 4468 ) 4469 }, 4470 )?; 4471 } 4472 Ok(()) 4473 } 4474 4475 fn validate_calendar_rsvp_contract( 4476 tags: &[Vec<String>], 4477 contract: &EventContract, 4478 ) -> Result<(), ContractValidationError> { 4479 validate_exact_calendar_tags(tags, contract, &["d", "status", "fb"])?; 4480 validate_calendar_event_reference_tags(tags, contract)?; 4481 validate_calendar_rsvp_pointer_tag(tags, contract, "e", true)?; 4482 validate_calendar_rsvp_pointer_tag(tags, contract, "p", false)?; 4483 4484 let event_author = tags 4485 .iter() 4486 .find(|tag| tag.first().map(String::as_str) == Some("a")) 4487 .and_then(|tag| tag.get(1)) 4488 .and_then(|coordinate| crate::id::AddressableCoordinateParts::parse(coordinate).ok()) 4489 .map(|parts| parts.pubkey); 4490 if let Some(author_hint) = tag_value(tags, "p") { 4491 let hint = parse_public_key(author_hint).ok(); 4492 crate::require_invariant( 4493 [ 4494 hint.as_ref() == event_author.as_ref(), 4495 hint.as_ref().is_some_and(|key| key.to_hex() == author_hint), 4496 ] == [true; 2], 4497 &|| { 4498 calendar_tag_mismatch( 4499 contract, 4500 "p", 4501 "canonical_calendar_event_author_matching_a_coordinate", 4502 Some(author_hint.to_owned()), 4503 ) 4504 }, 4505 )?; 4506 } 4507 Ok(()) 4508 } 4509 4510 fn validate_calendar_event_reference_tags( 4511 tags: &[Vec<String>], 4512 contract: &EventContract, 4513 ) -> Result<(), ContractValidationError> { 4514 for tag in tags 4515 .iter() 4516 .filter(|tag| tag.first().map(String::as_str) == Some("a")) 4517 { 4518 let coordinate_is_valid = tag 4519 .get(1) 4520 .is_some_and(|value| canonical_calendar_event_coordinate_is_valid(value)); 4521 let relay_is_valid = tag 4522 .get(2) 4523 .is_none_or(|relay| !relay.is_empty() && relay_url_is_valid(relay)); 4524 crate::require_invariant( 4525 [ 4526 (2..=3).contains(&tag.len()), 4527 coordinate_is_valid, 4528 relay_is_valid, 4529 ] == [true; 3], 4530 &|| { 4531 calendar_tag_mismatch( 4532 contract, 4533 "a", 4534 "canonical_kind_31922_or_31923_coordinate_with_optional_relay", 4535 tag.get(1).cloned(), 4536 ) 4537 }, 4538 )?; 4539 } 4540 Ok(()) 4541 } 4542 4543 fn validate_calendar_rsvp_pointer_tag( 4544 tags: &[Vec<String>], 4545 contract: &EventContract, 4546 name: &'static str, 4547 event_id: bool, 4548 ) -> Result<(), ContractValidationError> { 4549 for tag in tags 4550 .iter() 4551 .filter(|tag| tag.first().map(String::as_str) == Some(name)) 4552 { 4553 let value_is_canonical = tag.get(1).is_some_and(|value| { 4554 if event_id { 4555 EventId::parse(value).is_ok_and(|parsed| parsed.to_hex() == *value) 4556 } else { 4557 parse_public_key(value).is_ok_and(|parsed| parsed.to_hex() == *value) 4558 } 4559 }); 4560 let relay_is_valid = tag 4561 .get(2) 4562 .is_none_or(|relay| !relay.is_empty() && relay_url_is_valid(relay)); 4563 crate::require_invariant( 4564 [ 4565 (2..=3).contains(&tag.len()), 4566 value_is_canonical, 4567 relay_is_valid, 4568 ] == [true; 3], 4569 &|| { 4570 calendar_tag_mismatch( 4571 contract, 4572 name, 4573 if event_id { 4574 "canonical_event_id_with_optional_relay" 4575 } else { 4576 "canonical_public_key_with_optional_relay" 4577 }, 4578 tag.get(1).cloned(), 4579 ) 4580 }, 4581 )?; 4582 } 4583 Ok(()) 4584 } 4585 4586 fn validate_canonical_calendar_text_tags( 4587 tags: &[Vec<String>], 4588 contract: &EventContract, 4589 names: &[&'static str], 4590 ) -> Result<(), ContractValidationError> { 4591 for name in names { 4592 for tag in tags 4593 .iter() 4594 .filter(|tag| tag.first().map(String::as_str) == Some(*name)) 4595 { 4596 crate::require_invariant( 4597 tag.get(1) 4598 .is_some_and(|value| canonical_calendar_tag_text_is_valid(value)), 4599 &|| { 4600 calendar_tag_mismatch( 4601 contract, 4602 name, 4603 "canonical_visible_calendar_text", 4604 tag.get(1).cloned(), 4605 ) 4606 }, 4607 )?; 4608 } 4609 } 4610 Ok(()) 4611 } 4612 4613 fn validate_calendar_blossom_image( 4614 tags: &[Vec<String>], 4615 contract: &EventContract, 4616 ) -> Result<(), ContractValidationError> { 4617 tag_value(tags, "image") 4618 .map(|image| { 4619 crate::require_invariant(BlobUrl::parse(image).is_ok(), &|| { 4620 calendar_tag_mismatch( 4621 contract, 4622 "image", 4623 "structural_blossom_hash_path_url", 4624 Some(image.to_owned()), 4625 ) 4626 }) 4627 }) 4628 .transpose() 4629 .map(|_| ()) 4630 } 4631 4632 fn validate_calendar_date_contract( 4633 tags: &[Vec<String>], 4634 contract: &EventContract, 4635 ) -> Result<(), ContractValidationError> { 4636 validate_exact_calendar_tags( 4637 tags, 4638 contract, 4639 &[ 4640 "d", "title", "start", "end", "location", "g", "summary", "image", "t", "r", "name", 4641 ], 4642 )?; 4643 validate_calendar_participant_tags(tags, contract)?; 4644 validate_calendar_inclusion_request_tags(tags, contract)?; 4645 validate_canonical_calendar_common_tags(tags, contract)?; 4646 4647 let forbidden_day_tag = tags 4648 .iter() 4649 .find(|tag| tag.first().map(String::as_str) == Some("D")); 4650 crate::require_invariant(forbidden_day_tag.is_none(), &|| { 4651 calendar_tag_mismatch( 4652 contract, 4653 "D", 4654 "forbidden_on_calendar_date_event", 4655 forbidden_day_tag.and_then(|tag| tag.get(1)).cloned(), 4656 ) 4657 })?; 4658 4659 let start = calendar_date_tag(tags, contract, "start")?; 4660 optional_calendar_date_tag(tags, contract, "end")? 4661 .map(|end| { 4662 crate::require_invariant(end > start, &|| { 4663 calendar_tag_mismatch( 4664 contract, 4665 "end", 4666 "gregorian_date_later_than_start", 4667 Some(end.as_str().to_owned()), 4668 ) 4669 }) 4670 }) 4671 .transpose() 4672 .map(|_| ()) 4673 } 4674 4675 fn validate_calendar_time_contract( 4676 tags: &[Vec<String>], 4677 contract: &EventContract, 4678 ) -> Result<(), ContractValidationError> { 4679 validate_exact_calendar_tags( 4680 tags, 4681 contract, 4682 &[ 4683 "d", 4684 "title", 4685 "start", 4686 "end", 4687 "start_tzid", 4688 "end_tzid", 4689 "location", 4690 "g", 4691 "summary", 4692 "image", 4693 "D", 4694 "t", 4695 "r", 4696 "name", 4697 ], 4698 )?; 4699 validate_calendar_participant_tags(tags, contract)?; 4700 validate_calendar_inclusion_request_tags(tags, contract)?; 4701 validate_canonical_calendar_common_tags(tags, contract)?; 4702 4703 let start = canonical_calendar_u64_tag(tags, contract, "start")?; 4704 let end = optional_canonical_calendar_u64_tag(tags, contract, "end")?; 4705 crate::require_invariant(!end.is_some_and(|end| end <= start), &|| { 4706 calendar_tag_mismatch( 4707 contract, 4708 "end", 4709 "canonical_unix_seconds_later_than_start", 4710 tag_value(tags, "end").map(ToOwned::to_owned), 4711 ) 4712 })?; 4713 4714 let expected_days = covered_utc_days(start, end).map_err(|_| { 4715 calendar_tag_mismatch( 4716 contract, 4717 "D", 4718 "complete_ascending_utc_day_coverage_with_maximum_366_days", 4719 None, 4720 ) 4721 })?; 4722 let mut expected_days = expected_days.into_iter(); 4723 let mut actual_days = tags 4724 .iter() 4725 .filter(|tag| tag.first().map(String::as_str) == Some("D")); 4726 loop { 4727 match (expected_days.next(), actual_days.next()) { 4728 (Some(expected), Some(actual)) 4729 if actual.get(1).and_then(|value| canonical_u64(value)) == Some(expected) => {} 4730 (None, None) => break, 4731 (_, actual) => { 4732 return Err(calendar_tag_mismatch( 4733 contract, 4734 "D", 4735 "complete_ascending_utc_day_coverage", 4736 actual.and_then(|tag| tag.get(1)).cloned(), 4737 )); 4738 } 4739 } 4740 } 4741 Ok(()) 4742 } 4743 4744 fn validate_exact_calendar_tags( 4745 tags: &[Vec<String>], 4746 contract: &EventContract, 4747 names: &[&'static str], 4748 ) -> Result<(), ContractValidationError> { 4749 for name in names { 4750 for tag in tags 4751 .iter() 4752 .filter(|tag| tag.first().map(String::as_str) == Some(*name)) 4753 { 4754 crate::require_invariant(tag.len() == 2, &|| { 4755 calendar_tag_mismatch(contract, name, "exact_two_element_tag", tag.get(1).cloned()) 4756 })?; 4757 } 4758 } 4759 Ok(()) 4760 } 4761 4762 fn validate_calendar_participant_tags( 4763 tags: &[Vec<String>], 4764 contract: &EventContract, 4765 ) -> Result<(), ContractValidationError> { 4766 crate::require_invariant( 4767 tag_count(tags, "p") <= RADROOTS_CALENDAR_MAX_PARTICIPANTS, 4768 &|| calendar_tag_mismatch(contract, "p", "bounded_participant_count", None), 4769 )?; 4770 for tag in tags 4771 .iter() 4772 .filter(|tag| tag.first().map(String::as_str) == Some("p")) 4773 { 4774 let pubkey_is_canonical = tag.get(1).is_some_and(|value| { 4775 parse_public_key(value).is_ok_and(|pubkey| pubkey.to_hex() == *value) 4776 }); 4777 let relay_is_valid = tag 4778 .get(2) 4779 .map(|relay| relay.is_empty() || relay_url_is_valid(relay)) 4780 .unwrap_or(true); 4781 let role_is_valid = tag 4782 .get(3) 4783 .map(|role| canonical_calendar_tag_text_is_valid(role)) 4784 .unwrap_or(true); 4785 let placeholder_is_canonical = !(tag.len() == 3 && tag[2].is_empty()); 4786 crate::require_invariant( 4787 [ 4788 (2..=4).contains(&tag.len()), 4789 pubkey_is_canonical, 4790 relay_is_valid, 4791 role_is_valid, 4792 placeholder_is_canonical, 4793 ] == [true; 5], 4794 &|| { 4795 calendar_tag_mismatch( 4796 contract, 4797 "p", 4798 "participant_pubkey_with_optional_relay_and_role", 4799 tag.get(1).cloned(), 4800 ) 4801 }, 4802 )?; 4803 } 4804 Ok(()) 4805 } 4806 4807 fn validate_calendar_inclusion_request_tags( 4808 tags: &[Vec<String>], 4809 contract: &EventContract, 4810 ) -> Result<(), ContractValidationError> { 4811 for tag in tags 4812 .iter() 4813 .filter(|tag| tag.first().map(String::as_str) == Some("a")) 4814 { 4815 let coordinate_is_calendar = tag 4816 .get(1) 4817 .is_some_and(|value| canonical_calendar_coordinate_is_valid(value)); 4818 let relay_is_valid = tag 4819 .get(2) 4820 .is_none_or(|relay| !relay.is_empty() && relay_url_is_valid(relay)); 4821 crate::require_invariant( 4822 [ 4823 (2..=3).contains(&tag.len()), 4824 coordinate_is_calendar, 4825 relay_is_valid, 4826 ] == [true; 3], 4827 &|| { 4828 calendar_tag_mismatch( 4829 contract, 4830 "a", 4831 "kind_31924_coordinate_with_optional_relay", 4832 tag.get(1).cloned(), 4833 ) 4834 }, 4835 )?; 4836 } 4837 Ok(()) 4838 } 4839 4840 fn canonical_calendar_coordinate_is_valid(value: &str) -> bool { 4841 let Some((kind, remainder)) = value.split_once(':') else { 4842 return false; 4843 }; 4844 let Some((pubkey, d_tag)) = remainder.split_once(':') else { 4845 return false; 4846 }; 4847 let Ok(parts) = crate::id::AddressableCoordinateParts::parse(value) else { 4848 return false; 4849 }; 4850 [ 4851 kind == "31924", 4852 pubkey == parts.pubkey.to_hex(), 4853 d_tag == parts.d_tag.as_str(), 4854 ] == [true; 3] 4855 } 4856 4857 fn canonical_calendar_event_coordinate_is_valid(value: &str) -> bool { 4858 let Some((kind, remainder)) = value.split_once(':') else { 4859 return false; 4860 }; 4861 let Some((pubkey, d_tag)) = remainder.split_once(':') else { 4862 return false; 4863 }; 4864 let Ok(parts) = crate::id::AddressableCoordinateParts::parse(value) else { 4865 return false; 4866 }; 4867 [ 4868 matches!( 4869 parts.kind, 4870 KIND_CALENDAR_DATE_EVENT | KIND_CALENDAR_TIME_EVENT 4871 ), 4872 matches!(kind, "31922" | "31923"), 4873 pubkey == parts.pubkey.to_hex(), 4874 d_tag == parts.d_tag.as_str(), 4875 ] == [true; 4] 4876 } 4877 4878 fn validate_canonical_calendar_common_tags( 4879 tags: &[Vec<String>], 4880 contract: &EventContract, 4881 ) -> Result<(), ContractValidationError> { 4882 for name in ["title", "location", "summary", "t", "name"] { 4883 for tag in tags 4884 .iter() 4885 .filter(|tag| tag.first().map(String::as_str) == Some(name)) 4886 { 4887 crate::require_invariant( 4888 tag.get(1) 4889 .is_some_and(|value| canonical_calendar_tag_text_is_valid(value)), 4890 &|| { 4891 calendar_tag_mismatch( 4892 contract, 4893 name, 4894 "canonical_visible_calendar_text", 4895 tag.get(1).cloned(), 4896 ) 4897 }, 4898 )?; 4899 } 4900 } 4901 tag_value(tags, "g") 4902 .map(|geohash| { 4903 crate::require_invariant(canonical_calendar_geohash_is_valid(geohash), &|| { 4904 calendar_tag_mismatch( 4905 contract, 4906 "g", 4907 "canonical_lowercase_geohash", 4908 Some(geohash.to_owned()), 4909 ) 4910 }) 4911 }) 4912 .transpose()?; 4913 validate_calendar_blossom_image(tags, contract)?; 4914 Ok(()) 4915 } 4916 4917 fn calendar_date_tag( 4918 tags: &[Vec<String>], 4919 contract: &EventContract, 4920 name: &'static str, 4921 ) -> Result<CalendarDate, ContractValidationError> { 4922 let value = tag_value(tags, name).ok_or(ContractValidationError::MissingTag { 4923 contract_id: contract.id, 4924 name, 4925 })?; 4926 CalendarDate::parse(value).map_err(|_| { 4927 calendar_tag_mismatch( 4928 contract, 4929 name, 4930 "calendar_date_yyyy_mm_dd", 4931 Some(value.to_owned()), 4932 ) 4933 }) 4934 } 4935 4936 fn optional_calendar_date_tag( 4937 tags: &[Vec<String>], 4938 contract: &EventContract, 4939 name: &'static str, 4940 ) -> Result<Option<CalendarDate>, ContractValidationError> { 4941 tag_value(tags, name) 4942 .map(|_| calendar_date_tag(tags, contract, name)) 4943 .transpose() 4944 } 4945 4946 fn canonical_calendar_u64_tag( 4947 tags: &[Vec<String>], 4948 contract: &EventContract, 4949 name: &'static str, 4950 ) -> Result<u64, ContractValidationError> { 4951 let value = tag_value(tags, name).ok_or(ContractValidationError::MissingTag { 4952 contract_id: contract.id, 4953 name, 4954 })?; 4955 canonical_u64(value).ok_or_else(|| { 4956 calendar_tag_mismatch( 4957 contract, 4958 name, 4959 "canonical_decimal_u64", 4960 Some(value.to_owned()), 4961 ) 4962 }) 4963 } 4964 4965 fn optional_canonical_calendar_u64_tag( 4966 tags: &[Vec<String>], 4967 contract: &EventContract, 4968 name: &'static str, 4969 ) -> Result<Option<u64>, ContractValidationError> { 4970 tag_value(tags, name) 4971 .map(|_| canonical_calendar_u64_tag(tags, contract, name)) 4972 .transpose() 4973 } 4974 4975 fn calendar_tag_mismatch( 4976 contract: &EventContract, 4977 name: &'static str, 4978 expected: &'static str, 4979 actual: Option<String>, 4980 ) -> ContractValidationError { 4981 ContractValidationError::TagValueMismatch { 4982 contract_id: contract.id, 4983 name, 4984 expected: expected.to_owned(), 4985 actual, 4986 } 4987 } 4988 4989 fn validate_custom_knowledge_contract_parts( 4990 content: &str, 4991 contract: &EventContract, 4992 ) -> Result<(), ContractValidationError> { 4993 let Some(expected_schema) = custom_knowledge_schema(contract.id) else { 4994 return Ok(()); 4995 }; 4996 let object = parse_content_object(content, contract.id)?; 4997 reject_forbidden_knowledge_fields(&object, contract.id)?; 4998 4999 match object.get("schema").and_then(|value| value.as_str()) { 5000 Some(actual) if actual == expected_schema => {} 5001 Some(_) => { 5002 return Err(ContractValidationError::ContentFieldMismatch { 5003 contract_id: contract.id, 5004 field: "schema", 5005 expected: expected_schema.to_owned(), 5006 }); 5007 } 5008 None => { 5009 return Err(ContractValidationError::MissingContentField { 5010 contract_id: contract.id, 5011 field: "schema", 5012 }); 5013 } 5014 } 5015 5016 match object 5017 .get("schema_version") 5018 .and_then(|value| value.as_u64()) 5019 { 5020 Some(1) => Ok(()), 5021 Some(_) => Err(ContractValidationError::ContentFieldMismatch { 5022 contract_id: contract.id, 5023 field: "schema_version", 5024 expected: "1".to_owned(), 5025 }), 5026 None => Err(ContractValidationError::MissingContentField { 5027 contract_id: contract.id, 5028 field: "schema_version", 5029 }), 5030 } 5031 } 5032 5033 fn validate_discriminator_parts( 5034 content: &str, 5035 contract: &EventContract, 5036 admission_selected: bool, 5037 ) -> Result<(), ContractValidationError> { 5038 crate::require_invariant( 5039 admission_selected || !matches!(contract.discriminator, EventDiscriminator::AdmissionOnly), 5040 &|| ContractValidationError::AdmissionRequired { 5041 contract_id: contract.id, 5042 }, 5043 )?; 5044 let (field, value) = match &contract.discriminator { 5045 EventDiscriminator::ContentJsonFieldEquals { field, value } => (*field, *value), 5046 EventDiscriminator::EnvelopeType(value) => ("type", *value), 5047 _ => return Ok(()), 5048 }; 5049 let object = parse_content_object(content, contract.id)?; 5050 match object.get(field).and_then(|actual| actual.as_str()) { 5051 Some(actual) if actual == value => Ok(()), 5052 Some(_) => Err(ContractValidationError::ContentFieldMismatch { 5053 contract_id: contract.id, 5054 field, 5055 expected: value.to_owned(), 5056 }), 5057 None => Err(ContractValidationError::MissingContentField { 5058 contract_id: contract.id, 5059 field, 5060 }), 5061 } 5062 } 5063 5064 fn parse_content_object( 5065 content: &str, 5066 contract_id: &'static str, 5067 ) -> Result<serde_json::Map<String, serde_json::Value>, ContractValidationError> { 5068 match serde_json::from_str::<serde_json::Value>(content) { 5069 Ok(serde_json::Value::Object(object)) => Ok(object), 5070 _ => Err(ContractValidationError::InvalidJsonContent { contract_id }), 5071 } 5072 } 5073 5074 fn reject_forbidden_knowledge_fields( 5075 object: &serde_json::Map<String, serde_json::Value>, 5076 contract_id: &'static str, 5077 ) -> Result<(), ContractValidationError> { 5078 for field in [ 5079 "review_status", 5080 "canon_status", 5081 "approved_for_canon", 5082 "rights_status", 5083 "trust_status", 5084 "trusted", 5085 ] { 5086 crate::require_invariant(!object.contains_key(field), &|| { 5087 ContractValidationError::ForbiddenContentField { contract_id, field } 5088 })?; 5089 } 5090 Ok(()) 5091 } 5092 5093 fn custom_knowledge_schema(contract_id: &str) -> Option<&'static str> { 5094 match contract_id { 5095 "radroots.knowledge.source.v1" => Some("radroots.knowledge.source.v1"), 5096 "radroots.knowledge.evidence_bounty.v1" => Some("radroots.knowledge.evidence_bounty.v1"), 5097 "radroots.knowledge.claim.v1" => Some("radroots.knowledge.claim.v1"), 5098 "radroots.knowledge.relation.v1" => Some("radroots.knowledge.relation.v1"), 5099 "radroots.knowledge.review.v1" => Some("radroots.knowledge.review.v1"), 5100 "radroots.knowledge.field_report.v1" => Some("radroots.knowledge.field_report.v1"), 5101 "radroots.knowledge.change_proposal.v1" => Some("radroots.knowledge.change_proposal.v1"), 5102 "radroots.knowledge.contribution_attestation.v1" => { 5103 Some("radroots.knowledge.contribution_attestation.v1") 5104 } 5105 _ => None, 5106 } 5107 } 5108 5109 fn discriminator_matches( 5110 discriminator: &EventDiscriminator, 5111 tags: &[Vec<String>], 5112 content: &str, 5113 ) -> bool { 5114 match discriminator { 5115 EventDiscriminator::KindOnly => true, 5116 EventDiscriminator::AdmissionOnly => false, 5117 EventDiscriminator::ClassifiedListingPartition(expected) => { 5118 classify_classified_listing_raw_tags_registry_v7(tags) == *expected 5119 } 5120 EventDiscriminator::DTagExact(expected) => tag_value(tags, "d") == Some(*expected), 5121 EventDiscriminator::DTagPrefix(prefix) => tag_value(tags, "d") 5122 .map(|value| value.starts_with(prefix)) 5123 .unwrap_or(false), 5124 EventDiscriminator::DTagSuffix(suffix) => tag_value(tags, "d") 5125 .map(|value| value.ends_with(suffix)) 5126 .unwrap_or(false), 5127 EventDiscriminator::TagEquals { name, value } => tag_value(tags, name) == Some(*value), 5128 EventDiscriminator::ContentJsonFieldEquals { field, value } => { 5129 content_json_string_field_equals(content, field, value) 5130 } 5131 EventDiscriminator::EnvelopeType(expected) => { 5132 content_json_string_field_equals(content, "type", expected) 5133 } 5134 EventDiscriminator::Composite(parts) => parts 5135 .iter() 5136 .all(|part| discriminator_matches(part, tags, content)), 5137 } 5138 } 5139 5140 fn tag_value<'a>(tags: &'a [Vec<String>], name: &str) -> Option<&'a str> { 5141 tags.iter().find_map(|tag| { 5142 if tag.first().map(|value| value.as_str()) == Some(name) { 5143 tag.get(1).map(|value| value.as_str()) 5144 } else { 5145 None 5146 } 5147 }) 5148 } 5149 5150 fn tag_count(tags: &[Vec<String>], name: &str) -> usize { 5151 tags.iter() 5152 .filter(|tag| tag.first().map(|value| value.as_str()) == Some(name)) 5153 .count() 5154 } 5155 5156 fn content_json_string_field_equals(content: &str, field: &str, value: &str) -> bool { 5157 serde_json::from_str::<serde_json::Value>(content) 5158 .ok() 5159 .and_then(|json| { 5160 json.get(field) 5161 .and_then(|field| field.as_str()) 5162 .map(|field| field == value) 5163 }) 5164 .unwrap_or(false) 5165 } 5166 5167 #[cfg(test)] 5168 #[cfg_attr(coverage_nightly, coverage(off))] 5169 mod tests;