feat(relay): add the system channel type (#8180)

🤖
## Summary
Buzz uses channel membership to decide who can read what, and every
channel a person belongs to shows up in their client as a chat. So there
is no way to make a group that exists only for access control, for
example "only this agent and its owner can read the agent's config".
Such a channel would appear in both people's sidebars, search results
and unread counts.

This stack adds a **system channel**: an ordinary channel that queries
leave out unless they ask for it. This first PR adds the type itself.

- `kind:9007` (create channel) with `["channel_type", "system"]` creates
a system channel.
- It is private unless the creator sends `["visibility", "open"]`.
- While it is private, its channel metadata event (kind:39000) carries
the NIP-29 `hidden` marker, which means "hide this group's metadata from
non-members". Until now the relay set `hidden` only for DMs.

On its own this PR does not yet hide system channels from queries. Part
2 does that.

Part 1 of 3, stacked on #8176 (the channel labels and creator lookup
stack, which this feature uses).

### Related issue
None found.

### Details
- Migration 0062 adds `system` to the `channel_type` Postgres enum. No
new columns.
- `buzz_core::channel::ChannelType::System`, and `system` is now a
reserved label name (labels cannot equal a channel type name).
- `buzz-admin` reconcile writes `hidden` with the same rule as the
relay. Usage metrics count system channels.
- **Rollback.** A relay older than this change cannot read `channel_type
= 'system'`. Do not roll back past it while system channels exist.
- **Spec.** NIP-CL gets a "System channels" section. `ARCHITECTURE.md`
lists the `System` type.

### Testing
- Relay test: a system channel is private and `hidden` by default;
opening it removes `hidden`; its first `t` tag is `system`.
- Migration embed and usage-metrics tests.

### Stack
These are two stacks. Merge them in this order. Client support
(creating, labeling and finding these channels) will be in
block/buzz-app, not the buzz SDK and CLI.

**Stack 1: channel labels and creator lookup (#8174–#8176)**

1. #8174 channel type and creator tags, and the channel creation time
2. #8175 channel labels
3. #8176 database-side label and creator match, and the rule for
choosing one channel

**Stack 2: system channels (#8180–#8182)**, stacked on stack 1 because
it uses labels and the creator tag

4. #8180 the system channel type ← this PR
5. #8181 leave system channels out of reads that do not name them
6. #8182 channel-state filters opt in to system channels

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
This commit is contained in:
Logan Johnson
2026-10-14 19:03:34 -04:00
committed by GitHub
parent a3909acc67
commit 71d7aa0436
14 changed files with 251 additions and 37 deletions

View File

@@ -24,12 +24,16 @@ plain NIP-29, a client cannot:
A client also cannot mark its own channels so that it can find them again.
Channel labels (below) do this.
Also, every channel that a person belongs to shows in their client as a chat.
So a group that exists only to control who can read some data appears in
sidebars, search results and unread counts. System channels (below) fix this.
## Channel identity tags
On every channel-state event that the relay signs, the relay MUST put these
tags in this order, before any other `t` or `P` tag:
1. `["t", <channel type>]`, for example `stream`, `forum`, `dm`, `workflow`.
1. `["t", <channel type>]`, for example `stream`, `forum`, `dm`, `workflow`, `system`.
2. `["t", <label>]` for each channel label, in stored order (see
[Channel labels](#channel-labels)).
3. `["P", <creator public key, hex>]`.
@@ -149,6 +153,19 @@ ID.
Every reader that follows this rule picks the same channel, even after either
channel is edited.
## System channels
A system channel exists only to control who can read some data, for example
config that only an agent and its owner can read. It has the channel type
`system`. In every other way it is an ordinary channel: it has members, roles,
messages and labels.
**Create.** A kind:9007 with `["channel_type", "system"]` creates a system
channel. It is private unless the kind:9007 also has `["visibility", "open"]`.
**Hidden.** While a system channel is private, its kind:39000 carries the
NIP-29 `hidden` tag.
## Trust
A client that relies on these tags MUST check that the relay's key signed the