lib

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

commit 678c66042402ca3618ea80278e59c5aed35f55e0
parent 3c9c22168ec41dc26315645d4b1f1b7611d12128
Author: triesap <tyson@radroots.org>
Date:   Fri, 12 Jun 2026 16:16:26 -0700

events: add surface kind classifiers

- add explicit Home Events Market Map Profile farm-ops kind sets
- separate moderation reports and draft listings from public feed candidates
- keep NIP-65 relay lists under list classification instead of social feeds
- update social classifier tests and conformance vectors

Diffstat:
Mcrates/events/src/kinds.rs | 304++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/events/src/list.rs | 4++--
Mcrates/events_codec/tests/social_events.rs | 18++++++++++++++++--
Mspec/conformance/vectors/social/upgraded_boundaries.v1.json | 33+++++++++++++++++++++++++++++++++
Mspec/social-events.md | 8++++++++
5 files changed, 337 insertions(+), 30 deletions(-)

diff --git a/crates/events/src/kinds.rs b/crates/events/src/kinds.rs @@ -200,33 +200,108 @@ pub const KIND_JOB_RESULT_MIN: u32 = 6000; pub const KIND_JOB_RESULT_MAX: u32 = 6999; pub const KIND_JOB_FEEDBACK: u32 = 7000; -pub const PUBLIC_SOCIAL_KINDS: [u32; 14] = [ +pub const HOME_FEED_CANDIDATE_KINDS: [u32; 9] = [ + KIND_POST, + KIND_REPOST, + KIND_GENERIC_REPOST, + KIND_ARTICLE, + KIND_LISTING, + KIND_CALENDAR_DATE_EVENT, + KIND_CALENDAR_TIME_EVENT, + KIND_FARM, + KIND_PUBLIC_FILE_METADATA, +]; + +pub const EVENTS_CANDIDATE_KINDS: [u32; 4] = [ + KIND_CALENDAR_DATE_EVENT, + KIND_CALENDAR_TIME_EVENT, + KIND_CALENDAR, + KIND_CALENDAR_EVENT_RSVP, +]; + +pub const MARKET_CANDIDATE_KINDS: [u32; 3] = [KIND_LISTING, KIND_FARM, KIND_PUBLIC_FILE_METADATA]; + +pub const MAP_CANDIDATE_KINDS: [u32; 7] = [ + KIND_FARM, + KIND_LISTING, + KIND_CALENDAR_DATE_EVENT, + KIND_CALENDAR_TIME_EVENT, + KIND_POST, + KIND_ARTICLE, + KIND_PUBLIC_FILE_METADATA, +]; + +pub const PROFILE_PUBLIC_CONTENT_KINDS: [u32; 8] = [ + KIND_POST, + KIND_REPOST, + KIND_GENERIC_REPOST, + KIND_ARTICLE, + KIND_LISTING, + KIND_CALENDAR_DATE_EVENT, + KIND_CALENDAR_TIME_EVENT, + KIND_PUBLIC_FILE_METADATA, +]; + +pub const MODERATION_ADMIN_CANDIDATE_KINDS: [u32; 1] = [KIND_REPORT]; + +pub const DRAFT_OWNER_CANDIDATE_KINDS: [u32; 1] = [KIND_LISTING_DRAFT]; + +pub const NIP29_GROUP_KINDS: [u32; 13] = [ + KIND_GROUP_METADATA, + KIND_GROUP_ADMINS, + KIND_GROUP_MEMBERS, + KIND_GROUP_ROLES, + KIND_GROUP_PUT_USER, + KIND_GROUP_REMOVE_USER, + KIND_GROUP_EDIT_METADATA, + KIND_GROUP_DELETE_EVENT, + KIND_GROUP_CREATE_GROUP, + KIND_GROUP_DELETE_GROUP, + KIND_GROUP_CREATE_INVITE, + KIND_GROUP_JOIN_REQUEST, + KIND_GROUP_LEAVE_REQUEST, +]; + +pub const PRIVATE_FARM_OPS_KINDS: [u32; 16] = [ + KIND_FARM_WORKSPACE_MANIFEST, + KIND_FARM_CRDT_CHANGE, + KIND_FARM_FILE_METADATA, + KIND_GROUP_METADATA, + KIND_GROUP_ADMINS, + KIND_GROUP_MEMBERS, + KIND_GROUP_ROLES, + KIND_GROUP_PUT_USER, + KIND_GROUP_REMOVE_USER, + KIND_GROUP_EDIT_METADATA, + KIND_GROUP_DELETE_EVENT, + KIND_GROUP_CREATE_GROUP, + KIND_GROUP_DELETE_GROUP, + KIND_GROUP_CREATE_INVITE, + KIND_GROUP_JOIN_REQUEST, + KIND_GROUP_LEAVE_REQUEST, +]; + +pub const PUBLIC_SOCIAL_KINDS: [u32; 11] = [ KIND_POST, KIND_REPOST, KIND_REACTION, KIND_GENERIC_REPOST, KIND_PUBLIC_FILE_METADATA, KIND_COMMENT, - KIND_REPORT, - KIND_LIST_READ_WRITE_RELAYS, KIND_ARTICLE, - KIND_LISTING_DRAFT, KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_TIME_EVENT, KIND_CALENDAR, KIND_CALENDAR_EVENT_RSVP, ]; -pub const UNAMBIGUOUS_PUBLIC_SOCIAL_KINDS: [u32; 13] = [ +pub const UNAMBIGUOUS_PUBLIC_SOCIAL_KINDS: [u32; 10] = [ KIND_POST, KIND_REPOST, KIND_REACTION, KIND_GENERIC_REPOST, KIND_COMMENT, - KIND_REPORT, - KIND_LIST_READ_WRITE_RELAYS, KIND_ARTICLE, - KIND_LISTING_DRAFT, KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_TIME_EVENT, KIND_CALENDAR, @@ -241,12 +316,9 @@ pub const MVP_SOCIAL_KINDS: [u32; 5] = [ KIND_CALENDAR_TIME_EVENT, ]; -pub const PRODUCTION_SOCIAL_KINDS: [u32; 7] = [ +pub const PRODUCTION_SOCIAL_KINDS: [u32; 4] = [ KIND_REPOST, KIND_GENERIC_REPOST, - KIND_REPORT, - KIND_LIST_READ_WRITE_RELAYS, - KIND_LISTING_DRAFT, KIND_CALENDAR, KIND_CALENDAR_EVENT_RSVP, ]; @@ -275,10 +347,7 @@ pub const fn is_unambiguous_public_social_kind(kind: u32) -> bool { | KIND_REACTION | KIND_GENERIC_REPOST | KIND_COMMENT - | KIND_REPORT - | KIND_LIST_READ_WRITE_RELAYS | KIND_ARTICLE - | KIND_LISTING_DRAFT | KIND_CALENDAR_DATE_EVENT | KIND_CALENDAR_TIME_EVENT | KIND_CALENDAR @@ -307,17 +376,110 @@ pub const fn is_mvp_social_kind(kind: u32) -> bool { pub const fn is_production_social_kind(kind: u32) -> bool { matches!( kind, - KIND_REPOST + KIND_REPOST | KIND_GENERIC_REPOST | KIND_CALENDAR | KIND_CALENDAR_EVENT_RSVP + ) +} + +#[inline] +pub const fn is_home_feed_candidate_kind(kind: u32) -> bool { + matches!( + kind, + KIND_POST + | KIND_REPOST | KIND_GENERIC_REPOST - | KIND_REPORT - | KIND_LIST_READ_WRITE_RELAYS - | KIND_LISTING_DRAFT + | KIND_ARTICLE + | KIND_LISTING + | KIND_CALENDAR_DATE_EVENT + | KIND_CALENDAR_TIME_EVENT + | KIND_FARM + | KIND_PUBLIC_FILE_METADATA + ) +} + +#[inline] +pub const fn is_events_candidate_kind(kind: u32) -> bool { + matches!( + kind, + KIND_CALENDAR_DATE_EVENT + | KIND_CALENDAR_TIME_EVENT | KIND_CALENDAR | KIND_CALENDAR_EVENT_RSVP ) } #[inline] +pub const fn is_market_candidate_kind(kind: u32) -> bool { + matches!(kind, KIND_LISTING | KIND_FARM | KIND_PUBLIC_FILE_METADATA) +} + +#[inline] +pub const fn is_map_candidate_kind(kind: u32) -> bool { + matches!( + kind, + KIND_FARM + | KIND_LISTING + | KIND_CALENDAR_DATE_EVENT + | KIND_CALENDAR_TIME_EVENT + | KIND_POST + | KIND_ARTICLE + | KIND_PUBLIC_FILE_METADATA + ) +} + +#[inline] +pub const fn is_profile_public_content_kind(kind: u32) -> bool { + matches!( + kind, + KIND_POST + | KIND_REPOST + | KIND_GENERIC_REPOST + | KIND_ARTICLE + | KIND_LISTING + | KIND_CALENDAR_DATE_EVENT + | KIND_CALENDAR_TIME_EVENT + | KIND_PUBLIC_FILE_METADATA + ) +} + +#[inline] +pub const fn is_moderation_admin_candidate_kind(kind: u32) -> bool { + kind == KIND_REPORT +} + +#[inline] +pub const fn is_draft_owner_candidate_kind(kind: u32) -> bool { + kind == KIND_LISTING_DRAFT +} + +#[inline] +pub const fn is_nip29_group_kind(kind: u32) -> bool { + matches!( + kind, + KIND_GROUP_METADATA + | KIND_GROUP_ADMINS + | KIND_GROUP_MEMBERS + | KIND_GROUP_ROLES + | KIND_GROUP_PUT_USER + | KIND_GROUP_REMOVE_USER + | KIND_GROUP_EDIT_METADATA + | KIND_GROUP_DELETE_EVENT + | KIND_GROUP_CREATE_GROUP + | KIND_GROUP_DELETE_GROUP + | KIND_GROUP_CREATE_INVITE + | KIND_GROUP_JOIN_REQUEST + | KIND_GROUP_LEAVE_REQUEST + ) +} + +#[inline] +pub const fn is_private_farm_ops_kind(kind: u32) -> bool { + kind == KIND_FARM_WORKSPACE_MANIFEST + || kind == KIND_FARM_CRDT_CHANGE + || kind == KIND_FARM_FILE_METADATA + || is_nip29_group_kind(kind) +} + +#[inline] pub const fn is_trade_service_request_kind(kind: u32) -> bool { matches!( kind, @@ -607,10 +769,10 @@ mod tests { #[test] fn classifies_public_social_kinds() { - assert_eq!(PUBLIC_SOCIAL_KINDS.len(), 14); - assert_eq!(UNAMBIGUOUS_PUBLIC_SOCIAL_KINDS.len(), 13); + assert_eq!(PUBLIC_SOCIAL_KINDS.len(), 11); + assert_eq!(UNAMBIGUOUS_PUBLIC_SOCIAL_KINDS.len(), 10); assert_eq!(MVP_SOCIAL_KINDS.len(), 5); - assert_eq!(PRODUCTION_SOCIAL_KINDS.len(), 7); + assert_eq!(PRODUCTION_SOCIAL_KINDS.len(), 4); assert!(is_public_social_kind(KIND_POST)); assert!(is_public_social_kind(KIND_PUBLIC_FILE_METADATA)); @@ -621,17 +783,18 @@ mod tests { assert!(is_public_social_kind(KIND_CALENDAR_TIME_EVENT)); assert!(is_public_social_kind(KIND_REPOST)); assert!(is_public_social_kind(KIND_GENERIC_REPOST)); - assert!(is_public_social_kind(KIND_REPORT)); assert!(is_public_social_kind(KIND_CALENDAR)); assert!(is_public_social_kind(KIND_CALENDAR_EVENT_RSVP)); - assert!(is_public_social_kind(KIND_LISTING_DRAFT)); - assert!(is_public_social_kind(KIND_LIST_READ_WRITE_RELAYS)); + assert!(!is_public_social_kind(KIND_REPORT)); + assert!(!is_public_social_kind(KIND_LISTING)); + assert!(!is_public_social_kind(KIND_LISTING_DRAFT)); + assert!(!is_public_social_kind(KIND_LIST_READ_WRITE_RELAYS)); assert!(!is_public_social_kind(KIND_FARM_CRDT_CHANGE)); assert!(!is_public_social_kind(KIND_FARM_WORKSPACE_MANIFEST)); assert!(is_mvp_social_kind(KIND_ARTICLE)); assert!(!is_mvp_social_kind(KIND_REPORT)); - assert!(is_production_social_kind(KIND_REPORT)); + assert!(!is_production_social_kind(KIND_REPORT)); assert!(!is_production_social_kind(KIND_ARTICLE)); assert!(is_ambiguous_public_social_kind(KIND_PUBLIC_FILE_METADATA)); assert!(!is_unambiguous_public_social_kind( @@ -641,6 +804,95 @@ mod tests { } #[test] + fn classifies_product_surface_candidate_kinds() { + assert_eq!(HOME_FEED_CANDIDATE_KINDS.len(), 9); + assert_eq!(EVENTS_CANDIDATE_KINDS.len(), 4); + assert_eq!(MARKET_CANDIDATE_KINDS.len(), 3); + assert_eq!(MAP_CANDIDATE_KINDS.len(), 7); + assert_eq!(PROFILE_PUBLIC_CONTENT_KINDS.len(), 8); + assert_eq!(MODERATION_ADMIN_CANDIDATE_KINDS.len(), 1); + assert_eq!(DRAFT_OWNER_CANDIDATE_KINDS.len(), 1); + assert_eq!(NIP29_GROUP_KINDS.len(), 13); + assert_eq!(PRIVATE_FARM_OPS_KINDS.len(), 16); + + assert!(is_home_feed_candidate_kind(KIND_POST)); + assert!(is_home_feed_candidate_kind(KIND_REPOST)); + assert!(is_home_feed_candidate_kind(KIND_GENERIC_REPOST)); + assert!(is_home_feed_candidate_kind(KIND_ARTICLE)); + assert!(is_home_feed_candidate_kind(KIND_LISTING)); + assert!(is_home_feed_candidate_kind(KIND_CALENDAR_DATE_EVENT)); + assert!(is_home_feed_candidate_kind(KIND_CALENDAR_TIME_EVENT)); + assert!(is_home_feed_candidate_kind(KIND_FARM)); + assert!(is_home_feed_candidate_kind(KIND_PUBLIC_FILE_METADATA)); + assert!(!is_home_feed_candidate_kind(KIND_LISTING_DRAFT)); + assert!(!is_home_feed_candidate_kind(KIND_REPORT)); + assert!(!is_home_feed_candidate_kind(KIND_FARM_CRDT_CHANGE)); + assert!(!is_home_feed_candidate_kind(KIND_RELAY_AUTH)); + assert!(!is_home_feed_candidate_kind(KIND_HTTP_AUTH)); + + assert!(is_events_candidate_kind(KIND_CALENDAR_DATE_EVENT)); + assert!(is_events_candidate_kind(KIND_CALENDAR_TIME_EVENT)); + assert!(is_events_candidate_kind(KIND_CALENDAR)); + assert!(is_events_candidate_kind(KIND_CALENDAR_EVENT_RSVP)); + assert!(!is_events_candidate_kind(KIND_POST)); + assert!(!is_events_candidate_kind(KIND_FARM_CRDT_CHANGE)); + + assert!(is_market_candidate_kind(KIND_LISTING)); + assert!(is_market_candidate_kind(KIND_FARM)); + assert!(is_market_candidate_kind(KIND_PUBLIC_FILE_METADATA)); + assert!(!is_market_candidate_kind(KIND_LISTING_DRAFT)); + assert!(!is_market_candidate_kind(KIND_REPORT)); + + assert!(is_map_candidate_kind(KIND_FARM)); + assert!(is_map_candidate_kind(KIND_LISTING)); + assert!(is_map_candidate_kind(KIND_CALENDAR_DATE_EVENT)); + assert!(is_map_candidate_kind(KIND_CALENDAR_TIME_EVENT)); + assert!(is_map_candidate_kind(KIND_POST)); + assert!(is_map_candidate_kind(KIND_ARTICLE)); + assert!(is_map_candidate_kind(KIND_PUBLIC_FILE_METADATA)); + assert!(!is_map_candidate_kind(KIND_LISTING_DRAFT)); + assert!(!is_map_candidate_kind(KIND_REPORT)); + + assert!(is_profile_public_content_kind(KIND_POST)); + assert!(is_profile_public_content_kind(KIND_REPOST)); + assert!(is_profile_public_content_kind(KIND_GENERIC_REPOST)); + assert!(is_profile_public_content_kind(KIND_ARTICLE)); + assert!(is_profile_public_content_kind(KIND_LISTING)); + assert!(is_profile_public_content_kind(KIND_CALENDAR_DATE_EVENT)); + assert!(is_profile_public_content_kind(KIND_CALENDAR_TIME_EVENT)); + assert!(is_profile_public_content_kind(KIND_PUBLIC_FILE_METADATA)); + assert!(!is_profile_public_content_kind(KIND_LISTING_DRAFT)); + assert!(!is_profile_public_content_kind(KIND_REPORT)); + + assert!(is_moderation_admin_candidate_kind(KIND_REPORT)); + assert!(!is_moderation_admin_candidate_kind(KIND_POST)); + assert!(is_draft_owner_candidate_kind(KIND_LISTING_DRAFT)); + assert!(!is_draft_owner_candidate_kind(KIND_LISTING)); + + assert!(is_private_farm_ops_kind(KIND_FARM_WORKSPACE_MANIFEST)); + assert!(is_private_farm_ops_kind(KIND_FARM_CRDT_CHANGE)); + assert!(is_private_farm_ops_kind(KIND_FARM_FILE_METADATA)); + assert!(is_nip29_group_kind(KIND_GROUP_METADATA)); + assert!(is_private_farm_ops_kind(KIND_GROUP_METADATA)); + assert!(is_private_farm_ops_kind(KIND_GROUP_ADMINS)); + assert!(is_private_farm_ops_kind(KIND_GROUP_MEMBERS)); + assert!(is_private_farm_ops_kind(KIND_GROUP_ROLES)); + assert!(is_private_farm_ops_kind(KIND_GROUP_PUT_USER)); + assert!(is_private_farm_ops_kind(KIND_GROUP_REMOVE_USER)); + assert!(is_private_farm_ops_kind(KIND_GROUP_EDIT_METADATA)); + assert!(is_private_farm_ops_kind(KIND_GROUP_DELETE_EVENT)); + assert!(is_private_farm_ops_kind(KIND_GROUP_CREATE_GROUP)); + assert!(is_private_farm_ops_kind(KIND_GROUP_DELETE_GROUP)); + assert!(is_private_farm_ops_kind(KIND_GROUP_CREATE_INVITE)); + assert!(is_private_farm_ops_kind(KIND_GROUP_JOIN_REQUEST)); + assert!(is_private_farm_ops_kind(KIND_GROUP_LEAVE_REQUEST)); + assert!(!is_private_farm_ops_kind(KIND_RELAY_AUTH)); + assert!(!is_private_farm_ops_kind(KIND_HTTP_AUTH)); + assert!(!is_private_farm_ops_kind(KIND_REPORT)); + assert!(!is_private_farm_ops_kind(KIND_LISTING_DRAFT)); + } + + #[test] fn exposes_nip29_group_kind_constants() { assert_eq!(KIND_GROUP_PUT_USER, 9000); assert_eq!(KIND_GROUP_REMOVE_USER, 9001); diff --git a/crates/events/src/list.rs b/crates/events/src/list.rs @@ -18,7 +18,7 @@ pub struct RadrootsListEntry { #[cfg(test)] mod tests { use super::*; - use crate::kinds::{KIND_LIST_READ_WRITE_RELAYS, is_public_social_kind}; + use crate::kinds::{KIND_LIST_READ_WRITE_RELAYS, is_nip51_standard_list_kind}; #[test] fn generic_list_model_covers_nip65_relay_entries() { @@ -39,6 +39,6 @@ mod tests { assert_eq!(list.entries.len(), 2); assert_eq!(list.entries[0].tag, "r"); assert_eq!(list.entries[0].values[1], "read"); - assert!(is_public_social_kind(KIND_LIST_READ_WRITE_RELAYS)); + assert!(is_nip51_standard_list_kind(KIND_LIST_READ_WRITE_RELAYS)); } } diff --git a/crates/events_codec/tests/social_events.rs b/crates/events_codec/tests/social_events.rs @@ -7,8 +7,10 @@ use radroots_events::{ file_metadata::RadrootsFileMetadata, group::{RadrootsGroupEditableMetadata, RadrootsGroupMetadata}, kinds::{ - KIND_ARTICLE, KIND_FARM, KIND_FARM_CRDT_CHANGE, KIND_GROUP_METADATA, KIND_LISTING, - KIND_POST, KIND_PUBLIC_FILE_METADATA, is_public_social_kind, + KIND_ARTICLE, KIND_FARM, KIND_FARM_CRDT_CHANGE, KIND_GROUP_METADATA, KIND_HTTP_AUTH, + KIND_LISTING, KIND_LISTING_DRAFT, KIND_POST, KIND_PUBLIC_FILE_METADATA, KIND_RELAY_AUTH, + KIND_REPORT, is_home_feed_candidate_kind, is_market_candidate_kind, + is_private_farm_ops_kind, is_public_social_kind, }, social::RadrootsSocialMediaDimensions, }; @@ -97,6 +99,16 @@ fn social_events_reject_private_farm_ops_semantics_in_public_codecs() { assert!(is_public_social_kind(KIND_PUBLIC_FILE_METADATA)); assert!(!is_public_social_kind(KIND_FARM_CRDT_CHANGE)); assert!(!is_public_social_kind(KIND_LISTING)); + assert!(is_home_feed_candidate_kind(KIND_LISTING)); + assert!(is_home_feed_candidate_kind(KIND_FARM)); + assert!(is_home_feed_candidate_kind(KIND_PUBLIC_FILE_METADATA)); + assert!(!is_home_feed_candidate_kind(KIND_LISTING_DRAFT)); + assert!(!is_home_feed_candidate_kind(KIND_REPORT)); + assert!(!is_home_feed_candidate_kind(KIND_FARM_CRDT_CHANGE)); + assert!(!is_home_feed_candidate_kind(KIND_RELAY_AUTH)); + assert!(!is_home_feed_candidate_kind(KIND_HTTP_AUTH)); + assert!(is_market_candidate_kind(KIND_LISTING)); + assert!(!is_market_candidate_kind(KIND_LISTING_DRAFT)); } #[test] @@ -117,6 +129,8 @@ fn social_events_keep_nip29_groups_out_of_public_social_classification() { let parts = group_metadata_to_wire_parts(&group).unwrap(); assert_eq!(parts.kind, KIND_GROUP_METADATA); assert!(!is_public_social_kind(KIND_GROUP_METADATA)); + assert!(!is_home_feed_candidate_kind(KIND_GROUP_METADATA)); + assert!(is_private_farm_ops_kind(KIND_GROUP_METADATA)); assert_eq!( group_metadata_from_event(parts.kind, &parts.tags, &parts.content) .unwrap() diff --git a/spec/conformance/vectors/social/upgraded_boundaries.v1.json b/spec/conformance/vectors/social/upgraded_boundaries.v1.json @@ -222,8 +222,41 @@ "expected": { "result": "ok", "public_social_kind": false, + "home_feed_candidate_kind": false, + "market_candidate_kind": false, + "private_farm_ops_kind": true, "group_infrastructure": true } + }, + { + "id": "social_surface_kind_sets_valid_010", + "kind": "social.surface_kinds.classification.valid", + "input": { + "kinds": { + "active_listing": 30402, + "listing_draft": 30403, + "report": 1984, + "farm_crdt_change": 78, + "relay_auth": 22242, + "http_auth": 27235 + } + }, + "expected": { + "result": "ok", + "home_feed_active_listing": true, + "home_feed_listing_draft": false, + "home_feed_report": false, + "home_feed_farm_crdt_change": false, + "home_feed_relay_auth": false, + "home_feed_http_auth": false, + "market_active_listing": true, + "market_listing_draft": false, + "moderation_report": true, + "draft_owner_listing_draft": true, + "private_farm_ops_farm_crdt_change": true, + "private_farm_ops_relay_auth": false, + "private_farm_ops_http_auth": false + } } ] } diff --git a/spec/social-events.md b/spec/social-events.md @@ -84,6 +84,14 @@ optional uppercase `D` tags for covered all-day dates. Calendar time events requ uppercase `D` tag so timestamped events retain a deterministic calendar-date anchor across codecs and language exports. +Product routing uses surface-specific kind classifiers rather than a broad public-social set. Home, +Events, Market, Map, and Profile public-content candidates are explicit. Active listing kind `30402` +can appear in public product surfaces, but listing draft kind `30403` is limited to draft-owner +contexts. Report kind `1984` is a moderation/admin candidate, not normal feed content. Relay and HTTP +auth kinds are transient and excluded from durable social and farm-ops candidate sets. Private farm +operations candidates include the farm workspace manifest, farm CRDT change envelope, farm file +metadata, and the supported NIP-29 group event subset. + `RadrootsListingDraft` and `RadrootsRelayList` are not separate model types in the target contract. Listing draft kind `30403` is represented through `RadrootsListing`, and NIP-51 standard and list-set entries, including NIP-65 relay metadata kind `10002`, are represented through