mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
feat(relay): channel labels (#8175)
🤖 ## Summary A plugin or agent often needs to tag its own channels and then find them again, for example "my attention-config channel" or "all channels of this workspace". Today a channel has no labels: the relay ignores any `t` tags in create or edit requests. This PR adds channel labels. - A create request can carry up to 8 labels as `["t", "<label>"]` tags. - An edit request with `t` tags replaces the label set. `["t", ""]` alone clears it, the same convention as `["ttl", ""]`. Only owners and admins can change labels, as for the name. - The relay publishes each label as a `t` tag on the channel's state events, after the channel type tag. Part 2 of 3, stacked on part 1. ### Related issue None found. ### Details - **Rules.** A label uses 1–64 characters from `a-z`, `0-9`, `.`, `:` and `-`. The relay refuses a label equal to a channel type name (`stream`, `forum`, `dm`, `workflow`, `system`), so a label cannot make a channel look like another type. - **No trust from labels.** Any admin can set a label. Only the relay-written creator tag (part 1) says who made a channel. - **Storage.** New migration `0061_channel_labels.sql` adds `channels.labels TEXT[] NOT NULL DEFAULT '{}'`. Channels without labels look and behave as before. - **Readers.** The CLI and `buzz-acp` read the channel type from the first `t` tag. Before this PR they used the last one, which would now be a label. - **Spec.** NIP-CL gets a "Channel labels" section with these rules. ### Testing - Relay tests: labels at creation appear after the type tag, edit replaces and clears them, invalid labels are refused, and members who are not admins cannot change them. - Unit tests for the label rules: allowed characters, length, reserved type names, the 8-label cap and the clear marker. - `buzz-acp` test: the channel type comes from the first `t` tag, not a label. ### 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 ← this PR 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 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:
@@ -192,7 +192,8 @@ impl ChannelSummary {
|
||||
match key {
|
||||
"d" => channel_id = val.map(str::to_string),
|
||||
"name" => name = val.map(str::to_string),
|
||||
"t" => channel_type = val.map(str::to_string),
|
||||
// The first `t` is the channel type; later `t` tags are labels.
|
||||
"t" if channel_type.is_none() => channel_type = val.map(str::to_string),
|
||||
// NIP-29 emits both `private` and `public` (Buzz adds the latter).
|
||||
// The presence of either tag is the source of truth; tag value is unused.
|
||||
"private" => visibility = Some("private".to_string()),
|
||||
@@ -2008,6 +2009,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_event_reads_the_type_from_the_first_t_tag() {
|
||||
let ev = event(json!([
|
||||
["d", "11111111-1111-1111-1111-111111111111"],
|
||||
["name", "labeled"],
|
||||
["t", "forum"],
|
||||
["t", "workspace"],
|
||||
["P", "ab".repeat(32)],
|
||||
]));
|
||||
let s = ChannelSummary::from_event(&ev).expect("parse");
|
||||
assert_eq!(s.channel_type.as_deref(), Some("forum"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_event_extracts_known_tags() {
|
||||
let ev = event(json!([
|
||||
|
||||
Reference in New Issue
Block a user