// Package musiquay implements the Musiquay protocol vocabulary: the event // kinds, tag names, addresses and payload structures that the relay and the // web app both encode into. // // Every structure here is a payload, not an event. A Track is the tag list and // content of a kind 32210 event; the envelope (id, pubkey, created_at, sig) is // the nostr layer's job. That split is what lets one vocabulary serve the // native relay (pkg/event.E) and the wasm client (pkg/core.Event). // // The vocabulary comes from the spec in the musiquay repo's docs/: // FEATURES_MUSIC.md, FEATURES_CREDITS.md, FEATURES_ROSTER.md, // FEATURES_CALENDAR.md, FEATURES_ACCESS.md, FEATURES_MERCHANDISE.md, // FEATURES_MARKETPLACE.md, PROTOCOL_FLOWS.md and the kind registry in // ARCHITECTURE.md. Tag names and value tokens are spelled once, here, as // constants: the docs are prose and examples, and drift between two // independent spellings of "mastering-engineer" is a silent data bug. // // Where a doc leaves something open, the code says so at the point it matters // instead of inventing a rule. Unknown tags survive a parse in Extra and are // re-emitted, so a payload this version does not understand still round-trips. package protocol // Musiquay's own kinds. The addressable block is 32210-32217; 3221, 3222 and // 32218-32220 are regular (immutable) because they are stored and counted. const ( KindTrack uint16 = 32210 KindAlbum uint16 = 32211 KindPlaylist uint16 = 32212 KindSetlist uint16 = 32213 KindReleaseCredits uint16 = 32214 KindTrackCredits uint16 = 32215 KindEdition uint16 = 32216 KindAccessFilter uint16 = 32217 KindPlayReport uint16 = 3221 KindPurchaseOrder uint16 = 3222 KindDeliveryReceipt uint16 = 32218 KindHostAggregate uint16 = 32219 KindPublisherRollup uint16 = 32220 ) // Kinds the vocabulary reuses rather than defining. const ( KindCreditLabel uint16 = 1985 // NIP-32 label: confirmation, attestation KindComment uint16 = 1111 // NIP-22 comment: wiki contribution KindCalendarDay uint16 = 31922 // NIP-52 date-based event KindCalendar uint16 = 31923 // NIP-52 time-based event, and the roster KindCalendarSet uint16 = 31924 // NIP-52 calendar collection KindRSVP uint16 = 31925 // NIP-52 RSVP, and the waitlist entry KindStall uint16 = 30017 // NIP-15 stall KindProduct uint16 = 30018 // NIP-15 product KindListing uint16 = 30402 // NIP-99 active classified listing KindListingOff uint16 = 30403 // NIP-99 draft or closed listing KindMusicStatus uint16 = 30315 // NIP-38 live now-playing, d = music KindBlobAuth uint16 = 24242 // BUD-11 Blossom authorization token KindBlobServers uint16 = 10063 // NIP-B7 Blossom server list KindZapRequest uint16 = 9734 // NIP-57 zap request, carries the asset a tag KindZapReceipt uint16 = 9735 // NIP-57 zap receipt, the Lightning proof KindSeal uint16 = 13 // NIP-59 seal KindGiftWrap uint16 = 1059 // NIP-59 gift wrap ) // KindNames is the registry, in kind order. It is the single source of truth // for the names used in logs, error messages and relay NIP-11 filtering. func KindName(k uint16) (s string) { switch k { case KindTrack: return "track" case KindAlbum: return "album" case KindPlaylist: return "playlist" case KindSetlist: return "setlist" case KindReleaseCredits: return "release-credits" case KindTrackCredits: return "track-credits" case KindEdition: return "edition" case KindAccessFilter: return "access-filter" case KindPlayReport: return "play-report" case KindPurchaseOrder: return "purchase-order" case KindDeliveryReceipt: return "delivery-receipt" case KindHostAggregate: return "host-aggregate" case KindPublisherRollup: return "publisher-rollup" case KindCreditLabel: return "credit-label" case KindComment: return "comment" case KindCalendarDay: return "calendar-day" case KindCalendar: return "calendar" case KindCalendarSet: return "calendar-set" case KindRSVP: return "rsvp" case KindStall: return "stall" case KindProduct: return "product" case KindListing: return "listing" case KindListingOff: return "listing-off" case KindMusicStatus: return "music-status" case KindBlobAuth: return "blob-auth" case KindBlobServers: return "blob-servers" case KindZapRequest: return "zap-request" case KindZapReceipt: return "zap-receipt" case KindSeal: return "seal" case KindGiftWrap: return "gift-wrap" } return "unknown" } // IsMusiquay reports whether k is defined by this protocol, as opposed to a // kind it reuses from a NIP. func IsMusiquay(k uint16) (ok bool) { switch k { case KindTrack, KindAlbum, KindPlaylist, KindSetlist, KindReleaseCredits, KindTrackCredits, KindEdition, KindAccessFilter, KindPlayReport, KindPurchaseOrder, KindDeliveryReceipt, KindHostAggregate, KindPublisherRollup: return true } return false } // IsAddressable reports whether a Musiquay kind is parameterized replaceable // (NIP-01 30000-39999 with a d tag). References to these use a tags: the event // id changes on every update, the address does not. func IsAddressable(k uint16) (ok bool) { switch k { case KindTrack, KindAlbum, KindPlaylist, KindSetlist, KindReleaseCredits, KindTrackCredits, KindEdition, KindAccessFilter: return true } return false } // IsRegular reports whether a Musiquay kind is immutable. Play reports, orders, // receipts and the aggregates are stored and counted, so they can never be // replaceable: a play count is a count of events. func IsRegular(k uint16) (ok bool) { switch k { case KindPlayReport, KindPurchaseOrder, KindDeliveryReceipt, KindHostAggregate, KindPublisherRollup: return true } return false } // IsPrivate reports whether a kind is never published to a relay as-is: the // purchase order and the delivery receipt travel only inside NIP-59 wraps. func IsPrivate(k uint16) (ok bool) { switch k { case KindPurchaseOrder, KindDeliveryReceipt: return true } return false } // InReplaceableRange reports whether NIP-01's kind-range rule would treat k as // parameterized replaceable (30000 <= kind < 40000), whatever the spec intends. // // This is a hazard the registry carries: the delivery receipt (32218), host // aggregate (32219) and publisher rollup (32220) are regular, counted events // that sit inside the replaceable range. A relay that applies the range rule // without an exception keeps only one event per (kind, host, empty d) and // silently drops every other receipt. Storage policy must ask IsRegular first, // and the spec should move those three out of the range before shipping. func InReplaceableRange(k uint16) (ok bool) { return k >= 30000 && k < 40000 } // RegularInReplaceableRange lists the Musiquay kinds that are regular yet sit // in NIP-01's replaceable range. Storage policy has to special-case exactly // these; nothing else in the vocabulary collides. func RegularInReplaceableRange() (ks []uint16) { ks = []uint16{:0:3} ks = push(ks, KindDeliveryReceipt) ks = push(ks, KindHostAggregate) ks = push(ks, KindPublisherRollup) return }