Feature request: allowlist a new event kind (7373, audit-trail) for NIP-OA owner-attestation clients
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Motivation**
NIP-OA (Owner Attestation) has no revocation primitive — an `auth` tag (`["auth", owner_pubkey, conditions, sig]`) is the only evidence that an owner authorized a pubkey, and today that evidence only survives as long as the *authorized* party keeps including it somewhere (e.g. embedded in their own `kind:0` profile, as Buzz's own managed-agent flow does). If the authorized party never publishes it, or later drops it from a newer profile, nothing shows the grant ever happened — except the owner's own local client state, which isn't independently verifiable or portable across devices.
Anyone building a NIP-OA owner-attestation client that wants a durable, owner-controlled authorization history runs into this: they need to publish their own signed record of each mint/renew action, independent of whether the authorized party cooperates. That's what we're building in an external NIP-OA client, [vouchd](https://github.com/mxx/vouchd). Publishing this record against a `buzz-relay` instance currently fails:
```
OK false "restricted: unknown event kind"
```
`required_scope_for_kind()` in `crates/buzz-relay/src/handlers/ingest.rs` is a fixed match over known `KIND_*` constants with `_ => Err("restricted: unknown event kind")` as the catch-all — the audit kind we're using (`7373`) isn't among the matched constants, so the write is rejected regardless of correct signing/AUTH.
**Proposed solution**
Register the kind's required scope, following the existing "How to Add a New Event Kind" recipe in `CONTRIBUTING.md` — specifically just step 3:
```rust
// crates/buzz-core/src/kind.rs
pub const KIND_AUDIT_LOG: u32 = 7373;
```
```rust
// crates/buzz-relay/src/handlers/ingest.rs, required_scope_for_kind()
KIND_AUDIT_LOG => Ok(Scope::MessagesWrite),
```
`7373` doesn't collide with anything currently in `ALL_KINDS`. This is intentionally scoped to step 3 only — no change requested to Buzz's own persistence, search indexing, or side-effect handling (steps 4–6); the event's payload and consumption logic live entirely client-side. The relay just needs to accept, store, and serve it back like any other regular event, the same way it already does for `KIND_REPORT` or other `MessagesWrite`-scoped kinds. Step 7 (audit) is already automatic per `CONTRIBUTING.md`, so no additional work there either.
Happy to submit the PR (with a unit test for `required_scope_for_kind(7373, ...)` alongside the existing rejection test) once the approach is confirmed. Also open to a different kind number if `7373` conflicts with an internal reservation we can't see from outside.
**Alternatives considered**
- *Repurpose an already-allowlisted kind instead of a new one.* Rejected — overloading an existing kind's semantics with unrelated audit-record content is exactly the kind of interop confusion NIP-31's `alt`-tag guidance exists to avoid, and it'd make the audit events indistinguishable from whatever that kind normally means.
- *Keep the audit trail purely client-side (local browser storage), never publish to any relay.* Avoids touching Buzz at all, but gives up the two properties we actually need — independent verifiability by a third party, and portability across the owner's own devices/clients. The record's existence would depend entirely on one browser's local state.
- *Fork `buzz-relay` and carry a local patch.* Considered and set aside. The change is small and generic — it isn't tied to any vouchd-specific product logic, it's the same "add a new kind" pattern already merged repeatedly (`kind:30179`, `kind:30621`, `kind:30178`, etc.), and `buzz` is a very actively developed monorepo (~20 commits/day) — maintaining a fork just to carry a one-line `match` arm indefinitely seemed like a worse trade-off than asking upstream for something the project's own contribution docs already anticipate.
**Additional context**
- Relevant source: `crates/buzz-relay/src/handlers/ingest.rs` (`required_scope_for_kind`), `crates/buzz-core/src/kind.rs` (`KIND_*` constants, `ALL_KINDS`), `CONTRIBUTING.md` § "How to Add a New Event Kind".
- The client this is blocking: [github.com/mxx/vouchd](https://github.com/mxx/vouchd) — a browser-only NIP-OA owner-attestation panel that talks to a relay directly over WebSocket.
- Prior art for this exact shape of change: `feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)`, `feat(relay): accept kind:30621 multi-repo projects at ingest (#3171)`, `feat(relay): gate kind 30178 team-catalog reads behind the shared tag (#3358)`.
- Open question for maintainers: would you rather review this as a one-off kind addition (matching the precedent above), or is it more useful to define a small reserved sub-range for external/ecosystem-defined kinds, so future NIP-OA-adjacent tools don't need a fresh issue each time? No strong preference either way — happy to follow whichever fits Buzz's own conventions.
---
Before opening: searched [open issues and PRs](https://github.com/block/buzz/issues?q=is%3Aopen) for `event kind`, `required_scope_for_kind`, `ingest.rs`, `restricted: unknown event kind`, `audit trail` / `audit log`, `NIP-OA` + `audit`, and `add support for kind` / `accept kind`. No open duplicate found. The one loosely-related open issue is [#4937](https://github.com/block/buzz/issues/4937) ("relay: on closed relays, a direct-member agent loses its NIP-OA identity") — it also touches NIP-OA handling in `buzz-relay`, but it's a different bug (rate-limiting/owner-context loss for direct members), not about kind allowlisting or an audit trail, so not linking it as a duplicate.
Contributor guide
Research direction
Start with CONTRIBUTING.md’s “How to Add a New Event Kind” recipe, then inspect crates/buzz-core/src/kind.rs and required_scope_for_kind() in crates/buzz-relay/src/handlers/ingest.rs. Follow the existing rejection test when adding coverage for kind 7373. Done means the constant and MessagesWrite scope are registered, the unit test passes, and the relay accepts and serves the event without persistence or indexing changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100