You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rooms produce durable knowledge, and that output increasingly lands in stores outside the relay — a knowledge graph, an archive, a wiki, a git repo. Today the only way a service can tell the room "an artifact derived from these messages now exists over there" is a kind:9 chat message with structured text inside. That works — we run it in production — but it is invisible to the protocol: clients can't render or filter it, nothing can subscribe to "knowledge receipts in this channel", search can't treat it as a distinct thing, and third-party NIP-29 clients see opaque bot text.
This is the situation Buzz's own architecture principle addresses — "Event kinds are the only switch. … Adding a new feature means defining a new kind." (CONTRIBUTING.md, Architecture Overview.) And because the relay deliberately rejects unregistered kinds, an integration cannot solve it from the outside: the registry is the only door.
Motivating consumer: a shipping integration that derives pinned Buzz threads into an external knowledge store and posts receipts back as kind:9 (https://github.com/OriginTrail/buzz-dkg-integration). The kinds proposed here are provider-neutral — store is a free string, and the same two kinds serve an IPFS pinner, a git archiver, or a wiki exporter identically.
Proposed solution
Two regular (immutable) channel-scoped kinds. Numbers are a concrete suggestion; final placement is maintainers' call (same posture as #2451).
Suggested numbers: the registry allocates thousand-blocks per domain (43xxx jobs, 45xxx forum, 46xxx workflow, 47xxx reserved user-groups, 48xxx system, 49xxx media — crates/buzz-core/src/kind.rs); the next free block is 50000–50999 (nothing ≥50000 in ALL_KINDS; fits the u16 the nostr crate requires). Regular semantics on purpose: receipts are append-only facts.
Kind 50001 — knowledge receipt — "an artifact derived from these room events now exists in an external store."
Tags: h = channel (required — receipts inherit channel membership visibility and the fan-out scoping invariant); e = source event id(s), ≥1.
Content (JSON):
field
required
meaning
artifact_uri
yes
resolvable identifier of the artifact in the external store
store
yes
free-form store discriminator (e.g. ipfs, git, wiki, a knowledge-graph name)
artifact_kind
no
what the artifact is (e.g. decision-summary)
content_hash
no
sha256:<hex> over the canonical source set, for independent re-derivation
title
no
one human-readable line for rendering/search
Kind 50002 — knowledge promotion receipt — "that artifact was finalized/anchored." Same tags, plus one e referencing the kind:50001 it promotes. Content adds anchor (free-form object, e.g. {"system":"…","tx":"…"}) and permanence (finalized | anchored).
Trust semantics (explicit): a receipt is a signed claim by the posting member, not a relay-verified fact. The relay validates shape and channel scope only; it never fetches artifact_uri and cannot verify that the external artifact exists or matches. Verification is the consumer's job, via artifact_uri and content_hash. That is the honest ceiling for external stores — and precisely the line that separates this from #2451, whose in-relay artifacts the relay can validate.
Privacy: payloads carry pointers and hashes only — never the artifact body or credentials. A receipt necessarily reveals to channel members that content was exported to an external store; posting one is the service's declaration of that fact, which is a feature (today the same export happens with no protocol-visible trace). Whether receipts should be text-searchable is an operator/maintainer policy call — see search note below.
Relay wiring (per CONTRIBUTING § How to Add a New Event Kind): scope Scope::MessagesWrite (same class as stream messages/pins); no side effects beyond storage and fan-out in v1; standard events table, no side table. Search: FTS inclusion is not a uniform one-line change across installations — fresh databases get the positive allowlist from migration 0008 (empty-DB conditional), populated databases retain their earlier expression until an operator runs scripts/maintenance/nip_rs_search_allowlist.sql, and later migrations wrap whichever expression is installed (see 0014). Simplest v1: leave receipts out of FTS entirely and let title rendering carry discoverability; adding them to search later is an ordinary follow-up in that same framework.
Alternatives considered
Structured kind:9 content (status quo): zero changes, but invisible to kind-level filtering/rendering/search; every consumer writes a bespoke parser. We've run this to its ceiling.
Tags on kind:9: lighter, but still not subscribable or renderable as a type, and pollutes chat semantics.
Unregistered custom kinds: not possible — the relay rejects unknown kinds at ingest, by design.
Offer: prepared to implement the full nine-step path (constants, payload types, scope arm, tests in buzz-core + buzz-test-client, docs) with DCO sign-off and Conventional Commits once maintainers indicate direction and placement. Opened as an issue first per CONTRIBUTING; happy to reshape as a docs/nips/ draft alongside the existing in-repo NIP drafts if preferred.
Motivation
Rooms produce durable knowledge, and that output increasingly lands in stores outside the relay — a knowledge graph, an archive, a wiki, a git repo. Today the only way a service can tell the room "an artifact derived from these messages now exists over there" is a kind:9 chat message with structured text inside. That works — we run it in production — but it is invisible to the protocol: clients can't render or filter it, nothing can subscribe to "knowledge receipts in this channel", search can't treat it as a distinct thing, and third-party NIP-29 clients see opaque bot text.
This is the situation Buzz's own architecture principle addresses — "Event kinds are the only switch. … Adding a new feature means defining a new kind." (CONTRIBUTING.md, Architecture Overview.) And because the relay deliberately rejects unregistered kinds, an integration cannot solve it from the outside: the registry is the only door.
Motivating consumer: a shipping integration that derives pinned Buzz threads into an external knowledge store and posts receipts back as kind:9 (https://github.com/OriginTrail/buzz-dkg-integration). The kinds proposed here are provider-neutral —
storeis a free string, and the same two kinds serve an IPFS pinner, a git archiver, or a wiki exporter identically.Proposed solution
Two regular (immutable) channel-scoped kinds. Numbers are a concrete suggestion; final placement is maintainers' call (same posture as #2451).
Suggested numbers: the registry allocates thousand-blocks per domain (43xxx jobs, 45xxx forum, 46xxx workflow, 47xxx reserved user-groups, 48xxx system, 49xxx media —
crates/buzz-core/src/kind.rs); the next free block is 50000–50999 (nothing ≥50000 inALL_KINDS; fits theu16the nostr crate requires). Regular semantics on purpose: receipts are append-only facts.Kind 50001 — knowledge receipt — "an artifact derived from these room events now exists in an external store."
Tags:
h= channel (required — receipts inherit channel membership visibility and the fan-out scoping invariant);e= source event id(s), ≥1.Content (JSON):
artifact_uristoreipfs,git,wiki, a knowledge-graph name)artifact_kinddecision-summary)content_hashsha256:<hex>over the canonical source set, for independent re-derivationtitleKind 50002 — knowledge promotion receipt — "that artifact was finalized/anchored." Same tags, plus one
ereferencing the kind:50001 it promotes. Content addsanchor(free-form object, e.g.{"system":"…","tx":"…"}) andpermanence(finalized|anchored).Trust semantics (explicit): a receipt is a signed claim by the posting member, not a relay-verified fact. The relay validates shape and channel scope only; it never fetches
artifact_uriand cannot verify that the external artifact exists or matches. Verification is the consumer's job, viaartifact_uriandcontent_hash. That is the honest ceiling for external stores — and precisely the line that separates this from #2451, whose in-relay artifacts the relay can validate.Privacy: payloads carry pointers and hashes only — never the artifact body or credentials. A receipt necessarily reveals to channel members that content was exported to an external store; posting one is the service's declaration of that fact, which is a feature (today the same export happens with no protocol-visible trace). Whether receipts should be text-searchable is an operator/maintainer policy call — see search note below.
Relay wiring (per CONTRIBUTING § How to Add a New Event Kind): scope
Scope::MessagesWrite(same class as stream messages/pins); no side effects beyond storage and fan-out in v1; standard events table, no side table. Search: FTS inclusion is not a uniform one-line change across installations — fresh databases get the positive allowlist from migration0008(empty-DB conditional), populated databases retain their earlier expression until an operator runsscripts/maintenance/nip_rs_search_allowlist.sql, and later migrations wrap whichever expression is installed (see0014). Simplest v1: leave receipts out of FTS entirely and lettitlerendering carry discoverability; adding them to search later is an ordinary follow-up in that same framework.Alternatives considered
Additional context
buzz-core+buzz-test-client, docs) with DCO sign-off and Conventional Commits once maintainers indicate direction and placement. Opened as an issue first per CONTRIBUTING; happy to reshape as adocs/nips/draft alongside the existing in-repo NIP drafts if preferred.Questions for maintainers
statusfield, or two kinds (receipt / promotion) as proposed?docs/nips/draft before any implementation?