Files
buzz/crates
Matt Toohey e25f9dc45c fix(relay): guard auto_join=false, the kind:9030 add policy and claim disconnects
Address wesbillman's three review threads on #8594
(PRRT_kwDORgXb2s6xrYtL, PRRT_kwDORgXb2s6xrYtO, PRRT_kwDORgXb2s6xrYtV).

- kind:9002 `auto_join=false` no longer lands in a private channel
  (PRRT_kwDORgXb2s6xrYtL). The relay-role tier lets a relay admin who is
  not a channel member send an event carrying only `auto_join`, and the
  in-transaction guard checked only `true`, so a `false` on a private
  channel was stored, fanned out, recorded mentions for its `p` tags and
  got an `auto_join_changed` 40099. `apply_channel_metadata_event` now
  refuses either value, under the channel row lock, when the event leaves
  the channel private; `true` still also needs it unarchived. Both values
  get the same refusal, so the response no longer tells them apart. A
  relay admin takes a private channel out of the defaults before making
  it private or once it is open again.
- The kind:9030 auto-join honours the added member's
  `channel_add_policy` (PRRT_kwDORgXb2s6xrYtO).
  `auto_join_default_channels` takes the actor of a third-party add.
  kind:9030 passes its sender, and the policy is checked once with
  `decide_channel_add_policy`, as that sender's kind:9000 would be. A
  refusal skips every default channel, logged and counted in
  `buzz_auto_join_failures_total`; the roster add stands. Invite claims
  pass no actor: the claimer joins for themselves.
- A client disconnect no longer cuts an invite claim's auto-join short
  (PRRT_kwDORgXb2s6xrYtV). Both claim paths now run their post-commit
  follow-up (the NIP-43 membership-list fan-out, and on a join the
  member-added delta and the auto-join) in `finish_committed_claim`,
  which spawns it before the first of those awaits and awaits the
  `JoinHandle`. The response still waits for the joins, but dropping the
  handler future on a disconnect detaches the task instead of cancelling
  it partway.
- NOSTR.md "Default channels" documents the `false` refusal on private
  channels, the policy check on kind:9030 and that a claimer's
  disconnect does not stop the joins.

Tests (buzz-relay Postgres lane):
- `auto_join_is_relay_admin_only_and_advertised_while_effective`: the
  row that accepted the relay admin's `false` on the private channel now
  expects the same refusal as `true`, with no kind:9002 or 40099 stored
  and the stored flag unchanged.
- `adding_a_member_auto_joins_default_channels`: a new step adds a
  `nobody` agent (no default channel joined) and an `owner_only` agent
  owned by the adder (joined), each with its roster add standing.
- `claim_follow_up_survives_a_client_disconnect` (new): for a v2 and a
  v1 claim, the test holds the first default channel's row lock. It
  waits until the auto-join is blocked in `put_member` behind that lock,
  then aborts the request task, which drops the handler future as a
  disconnect does. After it releases the lock, the claimer joins both
  default channels. The lock-wait loop is shared with
  `auto_join_skips_a_default_channel_archived_while_it_joins`.

Mutations, each reverted after it failed its test: guarding only `true`
in the transaction; passing no actor at kind:9030; checking the policy
against the relay key instead of the adder; running the claim follow-up
inline.

Verified: fmt; clippy -D warnings --all-targets on buzz-db and
buzz-relay; the six auto_join / edit_metadata / kind:9030 / claim
Postgres tests above, plus `bounded_v2_claims_publish_side_effects_only_for_joined`,
`owner_mints_and_new_pubkey_claims` and `join_policy_gate_end_to_end`
for the restructured claim handler. The full Postgres lanes and the e2e
suite were not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-10-30 16:58:55 +11:00
..