block / block/buzz

feat: knowledge receipt event kinds — a machine-readable record that room events were derived into an external store

Open
#4,529 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

**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 — `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:` 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.
- *In-relay knowledge artifacts (#2451)*: closest prior work and **complementary, not competing** — #2451's proposal/review artifacts live inside Buzz where the relay can validate them; this covers artifacts living in external stores, where a signed pointer + hash is the honest primitive. They compose: a crystallized #2451 artifact exported anywhere could emit a 50001, and its approved export a 50002.

**Additional context**

- Closest existing issues (searched 2026-07-29; none a duplicate): #2451 (complementary, above), #1702 (thread sealing), #242 (Human Signal Layer), #2433 (knowledge agent), #3261 (precedent for a small structured kind family). Room-knowledge is a live gap; this is its narrow external-store slice.
- 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.

**Questions for maintainers**

1. Does an external-artifact *receipt* primitive belong in the event registry, alongside (not instead of) the in-relay direction #2451 sketches?
2. One kind with a `status` field, or two kinds (receipt / promotion) as proposed?
3. Should receipts be in FTS at all, given the mixed allowlist/exclusion install base?
4. Issue-first is per CONTRIBUTING — would you rather see this as a `docs/nips/` draft before any implementation?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.