mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
778 lines
33 KiB
Rust
778 lines
33 KiB
Rust
//! Buzz V2 kind number registry.
|
||
//!
|
||
//! This module is the authoritative source for Buzz kind numbers.
|
||
//! All constants are `u32` — NIP-01 specifies kind as an unsigned integer,
|
||
//! and u32 covers the full range without truncation.
|
||
|
||
// Standard NIP kinds
|
||
/// NIP-01: User profile metadata.
|
||
pub const KIND_PROFILE: u32 = 0;
|
||
/// NIP-01: Short text note.
|
||
pub const KIND_TEXT_NOTE: u32 = 1;
|
||
/// NIP-02: Contact list / follow list.
|
||
pub const KIND_CONTACT_LIST: u32 = 3;
|
||
/// NIP-51: Mute list (replaceable, 10000–19999 range) — pubkeys/events/threads/words a user has muted.
|
||
///
|
||
/// User-owned global state, keyed by `(pubkey, kind)`. Same ownership/scope shape as kind:3.
|
||
pub const KIND_MUTE_LIST: u32 = 10000;
|
||
/// NIP-51: Pin list (replaceable) — events the user has pinned to their profile.
|
||
///
|
||
/// User-owned global state, keyed by `(pubkey, kind)`. The events referenced may live in
|
||
/// channels, but the pin list itself is profile-level state.
|
||
pub const KIND_PIN_LIST: u32 = 10001;
|
||
/// NIP-65: Relay list metadata (replaceable) — read/write relay preferences for the outbox model.
|
||
///
|
||
/// User-owned global state, keyed by `(pubkey, kind)`. Tags are `["r", url]` or
|
||
/// `["r", url, "read"]` / `["r", url, "write"]`.
|
||
pub const KIND_NIP65_RELAY_LIST_METADATA: u32 = 10002;
|
||
/// NIP-51: Bookmark list (replaceable) — events/articles/hashtags/URLs the user has bookmarked.
|
||
///
|
||
/// User-owned global state, keyed by `(pubkey, kind)`. References content but is not itself
|
||
/// channel-scoped content.
|
||
pub const KIND_BOOKMARK_LIST: u32 = 10003;
|
||
/// NIP-51: Emoji list (replaceable) — user preferred emojis and pointers to emoji sets.
|
||
pub const KIND_EMOJI_LIST: u32 = 10030;
|
||
/// NIP-51: Follow set (parameterized replaceable, 30000–39999 range) — named curated lists of pubkeys.
|
||
///
|
||
/// User-owned, keyed by `(pubkey, kind, d_tag)`. Allows multiple named follow lists on top of
|
||
/// the single kind:3 contact list (e.g. "close-friends", "news", "devs").
|
||
pub const KIND_FOLLOW_SET: u32 = 30000;
|
||
/// NIP-51: Bookmark set (parameterized replaceable) — named curated bookmark collections.
|
||
///
|
||
/// User-owned, keyed by `(pubkey, kind, d_tag)`.
|
||
pub const KIND_BOOKMARK_SET: u32 = 30003;
|
||
/// NIP-51 / NIP-30: Emoji set (parameterized replaceable).
|
||
///
|
||
/// User-owned, keyed by `(pubkey, kind, d_tag)`. Each member publishes their own
|
||
/// kind:30030 set (signed as themselves); the workspace emoji "palette" is the
|
||
/// client-side union of everyone's sets — a view computed on read, not stored
|
||
/// state. Ingest allowlists member-authored kind:30030/10030 (see
|
||
/// `required_scope_for_kind`), and the generic NIP-33 replace path keeps only the
|
||
/// latest per `(pubkey, d_tag)`.
|
||
pub const KIND_EMOJI_SET: u32 = 30030;
|
||
/// NIP-01: Channel metadata (replaceable). Not used by Buzz today.
|
||
pub const KIND_CHANNEL_METADATA: u32 = 41;
|
||
/// NIP-09: Event deletion request.
|
||
pub const KIND_DELETION: u32 = 5;
|
||
/// NIP-25: Content is emoji char or `+`/`-`.
|
||
pub const KIND_REACTION: u32 = 7;
|
||
/// NIP-17: Outer envelope for private DMs — hides sender, content, timestamp.
|
||
pub const KIND_GIFT_WRAP: u32 = 1059;
|
||
/// NIP-94: File metadata attachment.
|
||
pub const KIND_FILE_METADATA: u32 = 1063;
|
||
/// NIP-23: Long-form content (articles, blog posts, RFCs).
|
||
/// Parameterized replaceable (NIP-33, 30000–39999 range) — keyed by `(pubkey, kind, d_tag)`.
|
||
/// Stored globally (channel_id = NULL); author-owned, not channel-scoped.
|
||
pub const KIND_LONG_FORM: u32 = 30023;
|
||
/// NIP-38: User status (general, music, or custom d-tag).
|
||
/// Parameterized replaceable (NIP-33, 30000–39999 range) — keyed by `(pubkey, kind, d_tag)`.
|
||
/// Stored globally (channel_id = NULL); user-owned personal data, not channel-scoped.
|
||
pub const KIND_USER_STATUS: u32 = 30315;
|
||
/// NIP-78 / NIP-RS: Per-client read state blob for cross-device read position sync.
|
||
/// Parameterized replaceable (NIP-33, 30000–39999 range) — keyed by `(pubkey, kind, d_tag)`.
|
||
/// Stored globally (channel_id = NULL); user-owned personal data, not channel-scoped.
|
||
/// Content is NIP-44 encrypted to the user's own keypair.
|
||
pub const KIND_READ_STATE: u32 = 30078;
|
||
/// NIP-42 auth event — never stored (carries bearer tokens).
|
||
pub const KIND_AUTH: u32 = 22242;
|
||
/// BUD-01: Blossom upload auth (used in upload.rs, not stored).
|
||
pub const KIND_BLOSSOM_AUTH: u32 = 24242;
|
||
/// Buzz custom one-time identity binding proof (ephemeral, not stored).
|
||
pub const KIND_NOSTR_IDENTITY_BINDING: u32 = 24243;
|
||
/// NIP-98: HTTP auth event (used in nip98.rs, not stored).
|
||
pub const KIND_HTTP_AUTH: u32 = 27235;
|
||
|
||
// NEW: Buzz command kinds (Pure Nostr plan)
|
||
/// Agent metadata + owner reference (replaceable, agent-authored).
|
||
pub const KIND_AGENT_PROFILE: u32 = 10100;
|
||
|
||
/// NIP-AE: Agent Engram (parameterized replaceable, agent-authored).
|
||
///
|
||
/// Encrypted memory record for AI agents. Addressed by `(pubkey_a, kind, d_tag)`,
|
||
/// where `d_tag` is an HMAC over the agent↔owner conversation key. See
|
||
/// `docs/nips/NIP-AE.md` and [`crate::engram`].
|
||
pub const KIND_AGENT_ENGRAM: u32 = 30174;
|
||
|
||
/// NIP-ER: Event Reminder (parameterized replaceable, author-only).
|
||
///
|
||
/// Encrypted, author-only reminder addressed by `(pubkey, kind, d_tag)`. The
|
||
/// public `not_before` tag tells supporting relays when the reminder is due;
|
||
/// the target, note, and state are NIP-44 encrypted to the author. Reads are
|
||
/// author-only (see [`AUTHOR_ONLY_KINDS`]). See `docs/nips/NIP-ER.md`.
|
||
pub const KIND_EVENT_REMINDER: u32 = 30300;
|
||
|
||
/// NIP-PL: encrypted push lease (parameterized replaceable, author-only).
|
||
///
|
||
/// The source event contains endpoint-bearing NIP-44 ciphertext and is readable
|
||
/// only by its authenticated author. Effective delivery state lives in the
|
||
/// dedicated push lease tables.
|
||
pub const KIND_PUSH_LEASE: u32 = 30350;
|
||
|
||
/// Kinds whose stored events are readable only by their author.
|
||
///
|
||
/// The relay must never reveal the existence, count, tags, content, schedule,
|
||
/// or search matches of these events to anyone but the authenticated author.
|
||
/// Shared across the ingest write path (NIP-ER `not_before` validation) and the
|
||
/// read path (REQ/COUNT/subscription author-only filtering).
|
||
///
|
||
/// Currently a tiny linear set. If this grows past ~4 kinds, convert to a
|
||
/// compile-time bitset or sorted array with binary search for hot-path use.
|
||
pub const AUTHOR_ONLY_KINDS: &[u32] = &[KIND_EVENT_REMINDER, KIND_PUSH_LEASE];
|
||
|
||
/// Kinds that require a result-level read gate beyond the filter-layer
|
||
/// `#p` check: even a reader who knows an event id MUST match the event's
|
||
/// `#p` tag to receive the event. This closes the kindless `{ids:[…]}` read
|
||
/// path for events whose existence must not be leaked.
|
||
///
|
||
/// Used by `filter_can_match_result_gated_kinds` to force the per-event
|
||
/// fallback path in COUNT rather than the fast SQL `count_events()`.
|
||
pub const RESULT_GATED_KINDS: &[u32] = &[KIND_DM_VISIBILITY, KIND_AGENT_TURN_METRIC];
|
||
|
||
/// Kinds whose stored events have `#p`-bound read access — readable only by
|
||
/// subscribers whose pubkey appears in the event's `#p` tag.
|
||
///
|
||
/// The relay enforces this at the filter layer (`p_gated_filters_authorized`):
|
||
/// a REQ that can match any kind in this set is closed unless the filter's
|
||
/// `#p` values exactly equal the authenticated reader's pubkey. For stored
|
||
/// (non-ephemeral) kinds in this set, the storage layer additionally writes a
|
||
/// NULL `search_tsv` so the event is unsearchable through NIP-50 FTS
|
||
/// (`schema/schema.sql` and `migrations/0001_initial_schema.sql` — drift
|
||
/// caught by `p_gated_persistent_kinds_have_storage_null_tsvector` in
|
||
/// `crates/buzz-search/tests/fts_integration.rs`).
|
||
///
|
||
/// Ephemeral kinds (20000–29999, e.g. [`KIND_AGENT_OBSERVER_FRAME`]) are
|
||
/// included for filter-layer enforcement but are never stored, so the
|
||
/// storage-layer search defense does not apply to them.
|
||
pub const P_GATED_KINDS: &[u32] = &[
|
||
KIND_AGENT_OBSERVER_FRAME,
|
||
KIND_MEMBER_ADDED_NOTIFICATION,
|
||
KIND_MEMBER_REMOVED_NOTIFICATION,
|
||
KIND_GIFT_WRAP,
|
||
KIND_DM_VISIBILITY,
|
||
// NIP-AM: agent turn metrics are encrypted to the owner and must not be
|
||
// readable by any unauthenticated or non-owner party, including via `ids`
|
||
// filters — see NIP-AM §Relay Behavior.
|
||
KIND_AGENT_TURN_METRIC,
|
||
];
|
||
|
||
/// NIP-AP: Agent Persona (parameterized replaceable, owner-authored).
|
||
///
|
||
/// Persona definition event published by the workspace owner. Addressed by
|
||
/// `(pubkey, kind, d_tag)` where `d_tag` is the plaintext persona slug.
|
||
/// Content is a JSON body containing persona fields (system_prompt,
|
||
/// display_name, avatar_url, runtime, model, provider, name_pool).
|
||
/// Designed for discoverability and sharing — d-tag is not blinded.
|
||
pub const KIND_PERSONA: u32 = 30175;
|
||
|
||
/// NIP-AP: Agent Team (parameterized replaceable, owner-authored).
|
||
///
|
||
/// Team definition event published by the workspace owner. Addressed by
|
||
/// `(pubkey, kind, d_tag)` where `d_tag` is the team's stable id. Content is a
|
||
/// JSON body projecting public team fields (name, description, persona_ids).
|
||
/// A team is a user-facing grouping of personas; publishing keeps it
|
||
/// authoritative across clients and reboots, mirroring `KIND_PERSONA`.
|
||
pub const KIND_TEAM: u32 = 30176;
|
||
|
||
/// NIP-AP: Managed Agent (parameterized replaceable, owner-authored).
|
||
///
|
||
/// Managed-agent definition event published by the workspace owner. Addressed
|
||
/// by `(pubkey, kind, d_tag)` where `d_tag` is the agent's pubkey. Content is
|
||
/// an explicit opt-IN allowlist projection of the agent record — it MUST never
|
||
/// carry the agent's secret key, NIP-OA auth tag, env vars, or runtime fields,
|
||
/// since these events are world-readable on the relay.
|
||
pub const KIND_MANAGED_AGENT: u32 = 30177;
|
||
|
||
// NIP-56 reporting
|
||
/// NIP-56: Report an event, pubkey, or blob to relay moderators (kind:1984).
|
||
///
|
||
/// Accepted at ingest, persisted to the tenant-scoped `moderation_reports`
|
||
/// queue, and never fanned out publicly. Reports are signals, not triggers:
|
||
/// the relay never auto-actions on them (NIP-56).
|
||
pub const KIND_REPORT: u32 = 1984;
|
||
|
||
/// Buzz product feedback submission. Accepted at ingest, sidecarred to the
|
||
/// deployment feedback table, and never stored or fanned out as an event.
|
||
pub const KIND_PRODUCT_FEEDBACK: u32 = 42000;
|
||
|
||
// NIP-29 group admin events
|
||
/// NIP-29: Add a user to a group.
|
||
pub const KIND_NIP29_PUT_USER: u32 = 9000;
|
||
/// NIP-29: Remove a user from a group.
|
||
pub const KIND_NIP29_REMOVE_USER: u32 = 9001;
|
||
/// NIP-29: Edit group metadata.
|
||
pub const KIND_NIP29_EDIT_METADATA: u32 = 9002;
|
||
/// NIP-29: Delete an event from a group.
|
||
pub const KIND_NIP29_DELETE_EVENT: u32 = 9005;
|
||
/// NIP-29: Create a new group.
|
||
pub const KIND_NIP29_CREATE_GROUP: u32 = 9007;
|
||
/// NIP-29: Delete a group.
|
||
pub const KIND_NIP29_DELETE_GROUP: u32 = 9008;
|
||
/// NIP-29: Create an invite to a group.
|
||
pub const KIND_NIP29_CREATE_INVITE: u32 = 9009;
|
||
/// NIP-29: Request to join a group.
|
||
pub const KIND_NIP29_JOIN_REQUEST: u32 = 9021;
|
||
/// NIP-29: Request to leave a group.
|
||
pub const KIND_NIP29_LEAVE_REQUEST: u32 = 9022;
|
||
|
||
// Buzz community moderation commands (mod-signed, processed like 9030-series:
|
||
// validated + executed directly, never stored as regular events; every
|
||
// accepted command writes a `moderation_actions` audit row).
|
||
/// Moderation: ban a pubkey from the community (`p` tag target, optional
|
||
/// `expiration` + `reason` tags).
|
||
pub const KIND_MODERATION_BAN: u32 = 9040;
|
||
/// Moderation: lift a community ban (`p` tag target).
|
||
pub const KIND_MODERATION_UNBAN: u32 = 9041;
|
||
/// Moderation: timeout (write-block) a pubkey until an `expiration` tag
|
||
/// timestamp (`p` tag target, optional `reason`).
|
||
pub const KIND_MODERATION_TIMEOUT: u32 = 9042;
|
||
/// Moderation: clear a timeout early (`p` tag target).
|
||
pub const KIND_MODERATION_UNTIMEOUT: u32 = 9043;
|
||
/// Moderation: resolve a report (`report` tag = report event id hex,
|
||
/// `status` tag = resolved|dismissed, `action` tag =
|
||
/// delete|kick|ban|timeout|dismiss|escalate — see
|
||
/// `handlers/moderation_commands.rs` for the pinned vocabulary).
|
||
pub const KIND_MODERATION_RESOLVE_REPORT: u32 = 9044;
|
||
|
||
/// Returns `true` for community moderation command kinds (9040–9044).
|
||
///
|
||
/// The canonical route check — use this instead of scattering
|
||
/// `9040..=9044` matches across ingest/dispatch.
|
||
pub const fn is_moderation_command_kind(kind: u32) -> bool {
|
||
matches!(
|
||
kind,
|
||
KIND_MODERATION_BAN
|
||
| KIND_MODERATION_UNBAN
|
||
| KIND_MODERATION_TIMEOUT
|
||
| KIND_MODERATION_UNTIMEOUT
|
||
| KIND_MODERATION_RESOLVE_REPORT
|
||
)
|
||
}
|
||
|
||
// NIP-43 relay membership admin commands
|
||
/// NIP-43: Add a pubkey to the relay member list.
|
||
pub const RELAY_ADMIN_ADD_MEMBER: u32 = 9030;
|
||
/// NIP-43: Remove a pubkey from the relay member list.
|
||
pub const RELAY_ADMIN_REMOVE_MEMBER: u32 = 9031;
|
||
/// NIP-43: Change the role of an existing relay member.
|
||
pub const RELAY_ADMIN_CHANGE_ROLE: u32 = 9032;
|
||
/// Buzz: Set the workspace profile (icon). Admin/owner-signed command.
|
||
pub const RELAY_ADMIN_SET_WORKSPACE_PROFILE: u32 = 9033;
|
||
// NIP-43 relay membership announcement events (relay-signed)
|
||
/// NIP-43: Relay membership list snapshot (relay-signed, replaceable by convention).
|
||
pub const KIND_NIP43_MEMBERSHIP_LIST: u32 = 13534;
|
||
/// NIP-43: Member added announcement (relay-signed).
|
||
pub const KIND_NIP43_MEMBER_ADDED: u32 = 8000;
|
||
/// NIP-43: Member removed announcement (relay-signed).
|
||
pub const KIND_NIP43_MEMBER_REMOVED: u32 = 8001;
|
||
/// NIP-43: User leave request (user-signed, ephemeral).
|
||
pub const KIND_NIP43_LEAVE_REQUEST: u32 = 28936;
|
||
|
||
// NIP-IA identity archival requests (user/agent/owner-signed)
|
||
/// NIP-IA: Request that the relay archive a target identity.
|
||
pub const KIND_IA_ARCHIVE_REQUEST: u32 = 9035;
|
||
/// NIP-IA: Request that the relay unarchive a target identity.
|
||
pub const KIND_IA_UNARCHIVE_REQUEST: u32 = 9036;
|
||
|
||
// NIP-IA identity archival announcement events (relay-signed)
|
||
/// NIP-IA: Archived-identity delta (relay-signed).
|
||
pub const KIND_IA_ARCHIVED: u32 = 8002;
|
||
/// NIP-IA: Unarchived-identity delta (relay-signed).
|
||
pub const KIND_IA_UNARCHIVED: u32 = 8003;
|
||
/// NIP-IA: Archived identities list snapshot (relay-signed, replaceable).
|
||
pub const KIND_IA_ARCHIVED_LIST: u32 = 13535;
|
||
|
||
// NIP-29 group state (addressable range 39000–39003)
|
||
/// NIP-29: Addressable group metadata state.
|
||
pub const KIND_NIP29_GROUP_METADATA: u32 = 39000;
|
||
/// NIP-29: Addressable group admins list.
|
||
pub const KIND_NIP29_GROUP_ADMINS: u32 = 39001;
|
||
/// NIP-29: Addressable group members list.
|
||
pub const KIND_NIP29_GROUP_MEMBERS: u32 = 39002;
|
||
/// NIP-29: Addressable group roles definition.
|
||
pub const KIND_NIP29_GROUP_ROLES: u32 = 39003;
|
||
|
||
// Channel-window overlays (relay-signed, synthesized at query time, never
|
||
// stored). Appended to bridge `/query` responses for `top_level` window
|
||
// requests — see docs/bridge-channel-window.md.
|
||
/// Thread summary overlay: `e`/`d` tag = root event id, content =
|
||
/// `{reply_count, descendant_count, last_reply_at, participants}`.
|
||
pub const KIND_THREAD_SUMMARY: u32 = 39005;
|
||
/// Window bounds overlay: `d` tag = `<channel_id>:<request-cursor-or-head>`,
|
||
/// content = `{has_more, next_cursor}`. The only authority on exhaustion —
|
||
/// clients must not infer `has_more` from row counts.
|
||
pub const KIND_WINDOW_BOUNDS: u32 = 39006;
|
||
|
||
/// Workflow definition (parameterized replaceable, d=workflow_uuid).
|
||
pub const KIND_WORKFLOW_DEF: u32 = 30620;
|
||
|
||
/// NIP-DV: per-viewer DM visibility snapshot (relay-signed, parameterized
|
||
/// replaceable, d=viewer_pubkey). Carries one `h` tag per DM the viewer has
|
||
/// hidden from their sidebar. Re-published by the relay on every hide/unhide so
|
||
/// the latest event is always the authoritative hidden set. The relay knows
|
||
/// `hidden_at` per viewer; this is the only Nostr-visible projection of it.
|
||
pub const KIND_DM_VISIBILITY: u32 = 30622;
|
||
|
||
/// Lower bound of the NIP-33 parameterized replaceable range (30000–39999).
|
||
pub const PARAM_REPLACEABLE_KIND_MIN: u32 = 30000;
|
||
/// Upper bound of the NIP-33 parameterized replaceable range (30000–39999).
|
||
pub const PARAM_REPLACEABLE_KIND_MAX: u32 = 39999;
|
||
|
||
/// Lower bound of the ephemeral event range (20000–29999). Never stored.
|
||
pub const EPHEMERAL_KIND_MIN: u32 = 20000;
|
||
/// Upper bound of the ephemeral event range (20000–29999). Never stored.
|
||
pub const EPHEMERAL_KIND_MAX: u32 = 29999;
|
||
|
||
// Ephemeral events (20000–29999) — Redis pub/sub only, never stored.
|
||
/// Ephemeral: user presence update (online/away/offline).
|
||
pub const KIND_PRESENCE_UPDATE: u32 = 20001;
|
||
/// NIP-AB: Device pairing event. Ephemeral — relay may discard after delivery.
|
||
pub const KIND_PAIRING: u32 = 24134;
|
||
/// Ephemeral: typing indicator for a channel.
|
||
pub const KIND_TYPING_INDICATOR: u32 = 20002;
|
||
/// Ephemeral: owner-scoped encrypted agent observer telemetry and control frame.
|
||
pub const KIND_AGENT_OBSERVER_FRAME: u32 = 24200;
|
||
/// Ephemeral: huddle emoji reaction burst. Channel-scoped to the ephemeral
|
||
/// huddle channel with an `h` tag; never stored in the timeline.
|
||
pub const KIND_HUDDLE_REACTION: u32 = 24810;
|
||
// Stream messaging
|
||
/// NIP-29 group chat message kind. V1 used kind:10001 (replaceable range — wrong), then 40001.
|
||
///
|
||
/// Agent shutdown convention: the agent's owner sends a kind:9 message with content
|
||
/// `"!shutdown"` and a `#p` tag mentioning the agent. The harness exits gracefully.
|
||
/// This is a convention, not a new event kind — uses regular stream messages.
|
||
pub const KIND_STREAM_MESSAGE: u32 = 9;
|
||
/// V1 used kind:10002 (replaceable range — wrong).
|
||
pub const KIND_STREAM_MESSAGE_V2: u32 = 40002;
|
||
/// V1 used kind:10004 (replaceable range + NIP-51 collision — wrong).
|
||
pub const KIND_STREAM_MESSAGE_EDIT: u32 = 40003;
|
||
/// A stream message that has been pinned in a channel.
|
||
pub const KIND_STREAM_MESSAGE_PINNED: u32 = 40004;
|
||
/// A stream message that has been bookmarked by a user.
|
||
pub const KIND_STREAM_MESSAGE_BOOKMARKED: u32 = 40005;
|
||
/// A stream message scheduled for future delivery.
|
||
pub const KIND_STREAM_MESSAGE_SCHEDULED: u32 = 40006;
|
||
/// A reminder attached to a stream message or time.
|
||
pub const KIND_STREAM_REMINDER: u32 = 40007;
|
||
/// A diff/patch message showing file changes (unified diff format).
|
||
pub const KIND_STREAM_MESSAGE_DIFF: u32 = 40008;
|
||
/// Canvas (shared document) for a channel.
|
||
pub const KIND_CANVAS: u32 = 40100;
|
||
/// System message for channel state changes (join, leave, rename, etc.).
|
||
pub const KIND_SYSTEM_MESSAGE: u32 = 40099;
|
||
|
||
// Relay-only sidecar kinds (never client-submitted)
|
||
/// Channel metadata with computed fields (relay-signed sidecar).
|
||
pub const KIND_CHANNEL_SUMMARY: u32 = 40901;
|
||
/// Bulk presence state (relay-signed sidecar).
|
||
pub const KIND_PRESENCE_SNAPSHOT: u32 = 40902;
|
||
|
||
// Direct messages (41000–41999)
|
||
/// Open/create DM (p-tags = participants).
|
||
pub const KIND_DM_OPEN: u32 = 41010;
|
||
/// Add member to group DM.
|
||
pub const KIND_DM_ADD_MEMBER: u32 = 41011;
|
||
/// Hide DM from sidebar.
|
||
pub const KIND_DM_HIDE: u32 = 41012;
|
||
/// A new direct-message conversation was created.
|
||
pub const KIND_DM_CREATED: u32 = 41001;
|
||
|
||
// Agent job protocol (43000–43999)
|
||
// Not using NIP-90 kinds (5000–6999) — Buzz requires auth chains (depth ≤ 3, breadth ≤ 10).
|
||
/// An agent job was requested.
|
||
pub const KIND_JOB_REQUEST: u32 = 43001;
|
||
/// An agent accepted a job request.
|
||
pub const KIND_JOB_ACCEPTED: u32 = 43002;
|
||
/// Progress update for an in-flight agent job.
|
||
pub const KIND_JOB_PROGRESS: u32 = 43003;
|
||
/// Final result of a completed agent job.
|
||
pub const KIND_JOB_RESULT: u32 = 43004;
|
||
/// A job cancellation was requested.
|
||
pub const KIND_JOB_CANCEL: u32 = 43005;
|
||
/// An agent job failed with an error.
|
||
pub const KIND_JOB_ERROR: u32 = 43006;
|
||
|
||
/// Relay-signed notification: the target pubkey was added to a channel.
|
||
/// Stored globally (channel_id = None) with p-tag = target, h-tag = channel UUID.
|
||
pub const KIND_MEMBER_ADDED_NOTIFICATION: u32 = 44100;
|
||
|
||
/// Relay-signed notification: the target pubkey was removed from a channel.
|
||
/// Stored globally (channel_id = None) with p-tag = target, h-tag = channel UUID.
|
||
pub const KIND_MEMBER_REMOVED_NOTIFICATION: u32 = 44101;
|
||
|
||
/// NIP-AM: Agent Turn Metric — durable per-turn token-usage record (agent-authored).
|
||
///
|
||
/// Regular stored event (append-only, never replaced). The agent publishes one
|
||
/// event per completed turn, NIP-44 encrypted to its owner. Tags: exactly one `p`
|
||
/// (owner pubkey) and one `agent` (agent pubkey == event pubkey); no `h` tag.
|
||
/// Stored globally (channel_id = NULL); owner-scoped reads only (p-gated, NIP-42).
|
||
/// See `docs/nips/NIP-AM.md`.
|
||
pub const KIND_AGENT_TURN_METRIC: u32 = 44200;
|
||
|
||
// Forum / social (45000–45999)
|
||
// V1 used addressable range (30001–30003) — wrong.
|
||
/// A forum post (thread root).
|
||
pub const KIND_FORUM_POST: u32 = 45001;
|
||
/// A vote on a forum post.
|
||
pub const KIND_FORUM_VOTE: u32 = 45002;
|
||
/// A comment reply on a forum post.
|
||
pub const KIND_FORUM_COMMENT: u32 = 45003;
|
||
|
||
// Workflow engine (46000–46999)
|
||
/// Trigger workflow execution.
|
||
pub const KIND_WORKFLOW_TRIGGER: u32 = 46020;
|
||
/// Grant pending approval.
|
||
pub const KIND_APPROVAL_GRANT: u32 = 46030;
|
||
/// Deny pending approval.
|
||
pub const KIND_APPROVAL_DENY: u32 = 46031;
|
||
/// A workflow was triggered by a matching event.
|
||
pub const KIND_WORKFLOW_TRIGGERED: u32 = 46001;
|
||
/// A workflow step began execution.
|
||
pub const KIND_WORKFLOW_STEP_STARTED: u32 = 46002;
|
||
/// A workflow step completed successfully.
|
||
pub const KIND_WORKFLOW_STEP_COMPLETED: u32 = 46003;
|
||
/// A workflow step failed.
|
||
pub const KIND_WORKFLOW_STEP_FAILED: u32 = 46004;
|
||
/// The entire workflow completed successfully.
|
||
pub const KIND_WORKFLOW_COMPLETED: u32 = 46005;
|
||
/// The entire workflow failed.
|
||
pub const KIND_WORKFLOW_FAILED: u32 = 46006;
|
||
/// The workflow was cancelled before completion.
|
||
pub const KIND_WORKFLOW_CANCELLED: u32 = 46007;
|
||
/// A workflow step is waiting for human approval.
|
||
pub const KIND_WORKFLOW_APPROVAL_REQUESTED: u32 = 46010;
|
||
/// A pending workflow approval was granted.
|
||
pub const KIND_WORKFLOW_APPROVAL_GRANTED: u32 = 46011;
|
||
/// A pending workflow approval was denied.
|
||
pub const KIND_WORKFLOW_APPROVAL_DENIED: u32 = 46012;
|
||
|
||
// User groups (47000–47999)
|
||
|
||
// System / admin custom range (48000–48999)
|
||
/// An audit log entry was recorded.
|
||
pub const KIND_AUDIT_ENTRY: u32 = 48001;
|
||
/// A huddle (audio/video session) was started.
|
||
pub const KIND_HUDDLE_STARTED: u32 = 48100;
|
||
/// A participant joined a huddle.
|
||
pub const KIND_HUDDLE_PARTICIPANT_JOINED: u32 = 48101;
|
||
/// A participant left a huddle.
|
||
pub const KIND_HUDDLE_PARTICIPANT_LEFT: u32 = 48102;
|
||
/// A huddle ended.
|
||
pub const KIND_HUDDLE_ENDED: u32 = 48103;
|
||
/// Huddle channel guidelines/rules document.
|
||
pub const KIND_HUDDLE_GUIDELINES: u32 = 48106;
|
||
|
||
// Media (49000–49999)
|
||
/// Internal kind for media upload audit entries. Not a relay event kind.
|
||
pub const KIND_MEDIA_UPLOAD: u32 = 49001;
|
||
|
||
/// NIP-34: Repository announcement (parameterized replaceable, d-tag = repo-id).
|
||
pub const KIND_GIT_REPO_ANNOUNCEMENT: u32 = 30617;
|
||
/// NIP-34: Repository state — current branch/tag refs (parameterized replaceable, d-tag = repo-id).
|
||
pub const KIND_GIT_REPO_STATE: u32 = 30618;
|
||
/// NIP-34: Patch (git format-patch output).
|
||
pub const KIND_GIT_PATCH: u32 = 1617;
|
||
/// NIP-34: Pull request.
|
||
pub const KIND_GIT_PULL_REQUEST: u32 = 1618;
|
||
/// NIP-34: Pull request update (tip commit change).
|
||
pub const KIND_GIT_PR_UPDATE: u32 = 1619;
|
||
/// NIP-34: Issue.
|
||
pub const KIND_GIT_ISSUE: u32 = 1621;
|
||
/// NIP-34: Status — Open.
|
||
pub const KIND_GIT_STATUS_OPEN: u32 = 1630;
|
||
/// NIP-34: Status — Applied / Merged.
|
||
pub const KIND_GIT_STATUS_MERGED: u32 = 1631;
|
||
/// NIP-34: Status — Closed.
|
||
pub const KIND_GIT_STATUS_CLOSED: u32 = 1632;
|
||
/// NIP-34: Status — Draft.
|
||
pub const KIND_GIT_STATUS_DRAFT: u32 = 1633;
|
||
|
||
/// All registered kind constants — used for duplicate detection and iteration.
|
||
pub const ALL_KINDS: &[u32] = &[
|
||
KIND_PROFILE,
|
||
KIND_TEXT_NOTE,
|
||
KIND_CONTACT_LIST,
|
||
KIND_MUTE_LIST,
|
||
KIND_PIN_LIST,
|
||
KIND_NIP65_RELAY_LIST_METADATA,
|
||
KIND_BOOKMARK_LIST,
|
||
KIND_EMOJI_LIST,
|
||
KIND_FOLLOW_SET,
|
||
KIND_BOOKMARK_SET,
|
||
KIND_EMOJI_SET,
|
||
KIND_CHANNEL_METADATA,
|
||
KIND_DELETION,
|
||
KIND_REACTION,
|
||
KIND_GIFT_WRAP,
|
||
KIND_FILE_METADATA,
|
||
KIND_AGENT_PROFILE,
|
||
KIND_AGENT_ENGRAM,
|
||
KIND_EVENT_REMINDER,
|
||
KIND_PERSONA,
|
||
KIND_TEAM,
|
||
KIND_MANAGED_AGENT,
|
||
KIND_REPORT,
|
||
KIND_PRODUCT_FEEDBACK,
|
||
KIND_NIP29_PUT_USER,
|
||
KIND_NIP29_REMOVE_USER,
|
||
KIND_NIP29_EDIT_METADATA,
|
||
KIND_NIP29_DELETE_EVENT,
|
||
KIND_NIP29_CREATE_GROUP,
|
||
KIND_NIP29_DELETE_GROUP,
|
||
KIND_NIP29_CREATE_INVITE,
|
||
KIND_NIP29_JOIN_REQUEST,
|
||
KIND_NIP29_LEAVE_REQUEST,
|
||
KIND_MODERATION_BAN,
|
||
KIND_MODERATION_UNBAN,
|
||
KIND_MODERATION_TIMEOUT,
|
||
KIND_MODERATION_UNTIMEOUT,
|
||
KIND_MODERATION_RESOLVE_REPORT,
|
||
RELAY_ADMIN_ADD_MEMBER,
|
||
RELAY_ADMIN_REMOVE_MEMBER,
|
||
RELAY_ADMIN_CHANGE_ROLE,
|
||
RELAY_ADMIN_SET_WORKSPACE_PROFILE,
|
||
KIND_NIP43_MEMBERSHIP_LIST,
|
||
KIND_NIP43_MEMBER_ADDED,
|
||
KIND_NIP43_MEMBER_REMOVED,
|
||
KIND_NIP43_LEAVE_REQUEST,
|
||
KIND_IA_ARCHIVE_REQUEST,
|
||
KIND_IA_UNARCHIVE_REQUEST,
|
||
KIND_IA_ARCHIVED,
|
||
KIND_IA_UNARCHIVED,
|
||
KIND_IA_ARCHIVED_LIST,
|
||
KIND_NIP29_GROUP_METADATA,
|
||
KIND_NIP29_GROUP_ADMINS,
|
||
KIND_NIP29_GROUP_MEMBERS,
|
||
KIND_NIP29_GROUP_ROLES,
|
||
KIND_THREAD_SUMMARY,
|
||
KIND_WINDOW_BOUNDS,
|
||
KIND_PRESENCE_UPDATE,
|
||
KIND_TYPING_INDICATOR,
|
||
KIND_HUDDLE_REACTION,
|
||
KIND_BLOSSOM_AUTH,
|
||
KIND_PAIRING,
|
||
KIND_AGENT_OBSERVER_FRAME,
|
||
KIND_HTTP_AUTH,
|
||
KIND_STREAM_MESSAGE,
|
||
KIND_STREAM_MESSAGE_V2,
|
||
KIND_STREAM_MESSAGE_EDIT,
|
||
KIND_STREAM_MESSAGE_PINNED,
|
||
KIND_STREAM_MESSAGE_BOOKMARKED,
|
||
KIND_STREAM_MESSAGE_SCHEDULED,
|
||
KIND_STREAM_REMINDER,
|
||
KIND_STREAM_MESSAGE_DIFF,
|
||
KIND_CANVAS,
|
||
KIND_SYSTEM_MESSAGE,
|
||
KIND_CHANNEL_SUMMARY,
|
||
KIND_PRESENCE_SNAPSHOT,
|
||
KIND_DM_VISIBILITY,
|
||
KIND_DM_OPEN,
|
||
KIND_DM_ADD_MEMBER,
|
||
KIND_DM_HIDE,
|
||
KIND_DM_CREATED,
|
||
KIND_JOB_REQUEST,
|
||
KIND_JOB_ACCEPTED,
|
||
KIND_JOB_PROGRESS,
|
||
KIND_JOB_RESULT,
|
||
KIND_JOB_CANCEL,
|
||
KIND_JOB_ERROR,
|
||
KIND_MEMBER_ADDED_NOTIFICATION,
|
||
KIND_MEMBER_REMOVED_NOTIFICATION,
|
||
KIND_AGENT_TURN_METRIC,
|
||
KIND_WORKFLOW_DEF,
|
||
KIND_LONG_FORM,
|
||
KIND_USER_STATUS,
|
||
KIND_READ_STATE,
|
||
KIND_FORUM_POST,
|
||
KIND_FORUM_VOTE,
|
||
KIND_FORUM_COMMENT,
|
||
KIND_WORKFLOW_TRIGGER,
|
||
KIND_APPROVAL_GRANT,
|
||
KIND_APPROVAL_DENY,
|
||
KIND_WORKFLOW_TRIGGERED,
|
||
KIND_WORKFLOW_STEP_STARTED,
|
||
KIND_WORKFLOW_STEP_COMPLETED,
|
||
KIND_WORKFLOW_STEP_FAILED,
|
||
KIND_WORKFLOW_COMPLETED,
|
||
KIND_WORKFLOW_FAILED,
|
||
KIND_WORKFLOW_CANCELLED,
|
||
KIND_WORKFLOW_APPROVAL_REQUESTED,
|
||
KIND_WORKFLOW_APPROVAL_GRANTED,
|
||
KIND_WORKFLOW_APPROVAL_DENIED,
|
||
KIND_AUDIT_ENTRY,
|
||
KIND_HUDDLE_STARTED,
|
||
KIND_HUDDLE_PARTICIPANT_JOINED,
|
||
KIND_HUDDLE_PARTICIPANT_LEFT,
|
||
KIND_HUDDLE_ENDED,
|
||
KIND_HUDDLE_GUIDELINES,
|
||
KIND_MEDIA_UPLOAD,
|
||
KIND_GIT_REPO_ANNOUNCEMENT,
|
||
KIND_GIT_REPO_STATE,
|
||
KIND_GIT_PATCH,
|
||
KIND_GIT_PULL_REQUEST,
|
||
KIND_GIT_PR_UPDATE,
|
||
KIND_GIT_ISSUE,
|
||
KIND_GIT_STATUS_OPEN,
|
||
KIND_GIT_STATUS_MERGED,
|
||
KIND_GIT_STATUS_CLOSED,
|
||
KIND_GIT_STATUS_DRAFT,
|
||
];
|
||
|
||
/// Returns `true` if `kind` is in the ephemeral range (20000–29999).
|
||
pub const fn is_ephemeral(kind: u32) -> bool {
|
||
kind >= EPHEMERAL_KIND_MIN && kind <= EPHEMERAL_KIND_MAX
|
||
}
|
||
|
||
/// Returns `true` if `kind` is replaceable (NIP-01: kinds 0, 3, 41, 10000–19999).
|
||
/// NIP-33 parameterized-replaceable kinds (30000–39999) use a different replacement
|
||
/// key (includes `d`-tag) and are handled separately via `replace_parameterized_event`.
|
||
pub const fn is_replaceable(kind: u32) -> bool {
|
||
matches!(kind, 0 | 3 | KIND_CHANNEL_METADATA | 10000..=19999)
|
||
}
|
||
|
||
/// Returns `true` if `kind` is in the NIP-33 parameterized replaceable range (30000–39999).
|
||
///
|
||
/// These events are keyed by `(pubkey, kind, d_tag)` — the latest `created_at` wins.
|
||
pub const fn is_parameterized_replaceable(kind: u32) -> bool {
|
||
kind >= PARAM_REPLACEABLE_KIND_MIN && kind <= PARAM_REPLACEABLE_KIND_MAX
|
||
}
|
||
|
||
/// Returns `true` if `kind` is a workflow execution event (46001–46012).
|
||
/// These must not trigger workflows (prevents infinite loops).
|
||
pub const fn is_workflow_execution_kind(kind: u32) -> bool {
|
||
kind >= KIND_WORKFLOW_TRIGGERED && kind <= KIND_WORKFLOW_APPROVAL_DENIED
|
||
}
|
||
|
||
/// Returns `true` if `kind` is a NIP-43 relay membership admin command (9030–9032)
|
||
/// or the Buzz workspace-profile admin command (9033).
|
||
pub const fn is_relay_admin_kind(kind: u32) -> bool {
|
||
matches!(
|
||
kind,
|
||
RELAY_ADMIN_ADD_MEMBER
|
||
| RELAY_ADMIN_REMOVE_MEMBER
|
||
| RELAY_ADMIN_CHANGE_ROLE
|
||
| RELAY_ADMIN_SET_WORKSPACE_PROFILE
|
||
)
|
||
}
|
||
|
||
/// Returns `true` if `kind` is a NIP-IA identity archival request (9035–9036).
|
||
///
|
||
/// Only the user-signed *request* kinds are matched. The relay-signed delta and
|
||
/// snapshot kinds (8002/8003/13535) are emitted by the relay, never ingested as
|
||
/// commands, so they are intentionally excluded.
|
||
pub const fn is_identity_archive_request_kind(kind: u32) -> bool {
|
||
matches!(kind, KIND_IA_ARCHIVE_REQUEST | KIND_IA_UNARCHIVE_REQUEST)
|
||
}
|
||
|
||
/// Returns `true` if `kind` is a Buzz command kind that requires transactional execution.
|
||
pub const fn is_command_kind(kind: u32) -> bool {
|
||
matches!(
|
||
kind,
|
||
KIND_WORKFLOW_DEF
|
||
| KIND_DM_OPEN
|
||
| KIND_DM_ADD_MEMBER
|
||
| KIND_DM_HIDE
|
||
| KIND_WORKFLOW_TRIGGER
|
||
| KIND_APPROVAL_GRANT
|
||
| KIND_APPROVAL_DENY
|
||
)
|
||
}
|
||
|
||
/// Returns `true` if `kind` is a relay-only sidecar kind.
|
||
/// Client submission of these kinds must be rejected.
|
||
pub const fn is_relay_only_kind(kind: u32) -> bool {
|
||
matches!(
|
||
kind,
|
||
KIND_CHANNEL_SUMMARY
|
||
| KIND_PRESENCE_SNAPSHOT
|
||
| KIND_DM_VISIBILITY
|
||
| KIND_THREAD_SUMMARY
|
||
| KIND_WINDOW_BOUNDS
|
||
)
|
||
}
|
||
|
||
/// Extract the kind from a nostr Event as u32.
|
||
/// NIP-01 specifies kind as an unsigned integer; u32 covers the full range.
|
||
pub fn event_kind_u32(event: &nostr::Event) -> u32 {
|
||
event.kind.as_u16() as u32
|
||
}
|
||
|
||
/// Extract the kind from a nostr Event as i32 (for Postgres INT columns).
|
||
/// Safe: all Buzz kinds fit in i32 (max 65535 < i32::MAX).
|
||
pub fn event_kind_i32(event: &nostr::Event) -> i32 {
|
||
event.kind.as_u16() as i32
|
||
}
|
||
|
||
// Compile-time: new kinds are in the expected ranges.
|
||
const _: () = assert!(is_replaceable(KIND_AGENT_PROFILE)); // 10100 ∈ 10000–19999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_PERSONA)); // 30175 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_TEAM)); // 30176 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_MANAGED_AGENT)); // 30177 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_WORKFLOW_DEF)); // 30620 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_EVENT_REMINDER)); // 30300 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_DM_VISIBILITY)); // 30622 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_THREAD_SUMMARY)); // 39005 ∈ 30000–39999
|
||
const _: () = assert!(is_parameterized_replaceable(KIND_WINDOW_BOUNDS)); // 39006 ∈ 30000–39999
|
||
|
||
// Compile-time: NIP-34 parameterized replaceable kinds are in the correct range.
|
||
const _: () = assert!(
|
||
KIND_GIT_REPO_ANNOUNCEMENT >= PARAM_REPLACEABLE_KIND_MIN
|
||
&& KIND_GIT_REPO_ANNOUNCEMENT <= PARAM_REPLACEABLE_KIND_MAX
|
||
);
|
||
const _: () = assert!(
|
||
KIND_GIT_REPO_STATE >= PARAM_REPLACEABLE_KIND_MIN
|
||
&& KIND_GIT_REPO_STATE <= PARAM_REPLACEABLE_KIND_MAX
|
||
);
|
||
|
||
// Compile-time: all Buzz kind constants fit in nostr's u16-backed Kind.
|
||
const _: () = assert!(KIND_AUTH <= u16::MAX as u32);
|
||
const _: () = assert!(KIND_CANVAS <= u16::MAX as u32);
|
||
const _: () = assert!(KIND_HUDDLE_GUIDELINES <= u16::MAX as u32);
|
||
const _: () = assert!(EPHEMERAL_KIND_MIN < EPHEMERAL_KIND_MAX);
|
||
// Compile-time: KIND_AGENT_TURN_METRIC is a regular stored kind (not ephemeral, not replaceable).
|
||
const _: () = assert!(!is_ephemeral(KIND_AGENT_TURN_METRIC));
|
||
const _: () = assert!(!is_replaceable(KIND_AGENT_TURN_METRIC));
|
||
const _: () = assert!(!is_parameterized_replaceable(KIND_AGENT_TURN_METRIC));
|
||
const _: () = assert!(KIND_AGENT_TURN_METRIC <= u16::MAX as u32);
|
||
// Moderation kinds fit u16 and are neither replaceable nor ephemeral:
|
||
// 1984 is a regular event (persisted to the queue, never fanned out);
|
||
// 9040–9044 are direct commands (executed, never stored).
|
||
const _: () = assert!(KIND_REPORT <= u16::MAX as u32);
|
||
const _: () = assert!(KIND_MODERATION_RESOLVE_REPORT <= u16::MAX as u32);
|
||
const _: () = assert!(!is_ephemeral(KIND_REPORT));
|
||
const _: () = assert!(is_moderation_command_kind(KIND_MODERATION_BAN));
|
||
const _: () = assert!(is_moderation_command_kind(KIND_MODERATION_RESOLVE_REPORT));
|
||
const _: () = assert!(!is_moderation_command_kind(KIND_REPORT));
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn no_duplicate_kind_values() {
|
||
let mut seen = std::collections::HashSet::new();
|
||
for &k in ALL_KINDS {
|
||
assert!(seen.insert(k), "duplicate kind value: {k}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn parameterized_replaceable_range() {
|
||
assert!(!is_parameterized_replaceable(29999));
|
||
assert!(is_parameterized_replaceable(30000));
|
||
assert!(is_parameterized_replaceable(30023)); // NIP-23 long-form
|
||
assert!(is_parameterized_replaceable(39000)); // NIP-29 group metadata
|
||
assert!(is_parameterized_replaceable(39999));
|
||
assert!(!is_parameterized_replaceable(40000));
|
||
}
|
||
|
||
#[test]
|
||
fn replaceable_and_parameterized_are_disjoint() {
|
||
for kind in 0..=65535u32 {
|
||
assert!(
|
||
!(is_replaceable(kind) && is_parameterized_replaceable(kind)),
|
||
"kind {kind} is both replaceable and parameterized replaceable"
|
||
);
|
||
}
|
||
}
|
||
}
|