Files
buzz/crates/buzz-admin
Matt Toohey eba6fde4fd feat(relay): auto-join new community members to default channels
Join every new community member into the channels a relay owner or admin
flagged with `channels.auto_join` (R1, d65bf983a), at the moment they
become a member. This is the behaviour half (R2) of default channels:
clients see the flagged channels in their sidebar, unread state and push
with no client work, because the relay runs the kind:9021 body for them.

- `join_channel_as_self` is kind:9021's body extracted from
  `handle_join_request` and returns `Joined | AlreadyMember`: open and
  unarchived checks, skip-if-member, then `put_member(.., None, None)` so
  the channel's `join_role` applies, cache invalidation, the
  `member_joined` kind:40099, 39000/39002 and the kind:44100. The handler
  keeps its exact external behaviour; the archived guard is new in the
  shared body because only the 9021 validator held it before.
- `auto_join_default_channels` iterates `Db::list_auto_join_channels`
  (effective channels only) and calls the body per channel, best effort:
  a failure is logged and counted (`buzz_auto_join_failures_total`), a
  join counted (`buzz_auto_join_channels_total`), and the caller never
  fails, because the relay membership has already committed. No retry
  after that commit: re-running at any later point would resurrect
  kind:9022 leaves, and the kind:39000 `auto_join` tag is the record a
  client reconciles against. This is an accepted Review-Proven Rule 1
  deviation, documented in NOSTR.md.
- Called inline right after `publish_nip43_member_added` at the three
  user-facing join sites: the v2 invite claim `Joined` arm, the v1 claim
  `was_inserted` branch, and kind:9030 when the roster row was inserted.
  Never at login or reconnect. Inline means the kind:39002 roster already
  lists the member when the claim response returns.
- `buzz-admin add-member` is unchanged except for help text noting it
  writes the roster row only; it has no `AppState` to sign 39002/44100.

Tests: `claim_auto_joins_default_channels_once` (Postgres lane) covers a
v2 claim against a flagged, an archived-after-flagging, a
private-after-flagging and an unflagged channel: membership and 39002 in
the flagged one only, one 44100 and one `member_joined` 40099, then a
kind:9022 leave and re-claim that stays `already_member` without re-adding;
the v1 token path; and the shared body's archived refusal directly.
`adding_a_member_auto_joins_default_channels` is the first Postgres test
for kind:9030 add, with the same leave-then-repeat step. Deleting the call
at any of the three sites fails the matching test (verified by mutation).
`test_invite_claim_auto_joins_default_channel` is the e2e smoke, run
against a local relay: the claimer receives the kind:44100 and is on the
kind:39002 roster.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-10-29 22:57:58 +11:00
..