fix(relay): join new members to every default channel, not the first 32

`list_auto_join_channels` stopped at 32 rows while kind:9002 kept accepting
`auto_join=true` on any number of channels, so with 33 flagged channels the
newest one carried the kind:39000 `auto_join` tag but no claim or kind:9030
add ever joined anyone to it, and nothing retried because the person was
already a relay member (Codex review on #8594).

Drop the cap rather than enforce it at flag time: the kind:9002 transaction
holds only the shared community admission lock and the one channel's row
lock, so a count-based refusal would need a new community-wide lock to be
race-free, and a private or archive flip on a flagged channel would still
let the stored count drift past it. The loader now returns every effective
channel, oldest first; the set is bounded by the community's channels, each
flagged by a relay owner or admin, and NOSTR.md says so.

Test: `every_default_channel_is_listed` (Postgres lane) flags 33 channels
and asserts the loader lists all of them in creation order; putting the
`LIMIT` back fails it (verified by mutation).

Verified: fmt; clippy -D warnings on buzz-db and buzz-relay (all targets);
the four auto_join Postgres tests. `auto_join_is_relay_admin_only_...`
failed once at its first query with the known `PoolTimedOut` lane flake and
passed on a solo rerun.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
Matt Toohey
2026-10-26 21:00:21 +11:00
parent e145803db6
commit 6ce1afd1b2
3 changed files with 60 additions and 15 deletions

View File

@@ -219,7 +219,10 @@ message in the channel.
When someone becomes a community member (an invite claim or a kind:9030 add)
the relay joins them to every effective default channel as a self-join: the
body of kind:9021, so the channel's `join_role` applies and the channel gets
its `member_joined` kind:40099, kind:39002 and kind:44100. It does this once,
its `member_joined` kind:40099, kind:39002 and kind:44100. There is no cap on
how many channels that is: whatever carries the kind:39000 tag is joined, and
the work a new member costs grows with the number of channels an owner or
admin flags. It does this once,
at that moment, and never again on login or reconnect, so a later kind:9022
leave stands. A channel that fails to join is logged and counted
(`buzz_auto_join_failures_total`), not retried: the kind:39000 tag is the

View File

@@ -616,13 +616,15 @@ async fn list_channels_with_operation(
rows.into_iter().map(row_to_channel_record).collect()
}
/// The most channels the relay joins a new community member into.
pub const AUTO_JOIN_CHANNEL_LIMIT: i64 = 32;
/// Lists the channels whose `auto_join` is effective: set on a stream channel
/// that is open, unarchived and not deleted. Oldest first, so the first
/// default channel a member sees is the one that was flagged first. Bounded by
/// [`AUTO_JOIN_CHANNEL_LIMIT`].
/// default channel a member sees is the one that was flagged first.
///
/// Every effective channel, not a page of them: kind:9002 accepts the flag on
/// any number of channels, and the kind:39000 `auto_join` tag promises the
/// relay joins new members there, so a cap here would silently break that
/// promise for the channels past it. The set is bounded by the community's
/// channels, each flagged by a relay owner or admin.
pub async fn list_auto_join_channels(
pool: &PgPool,
community_id: CommunityId,
@@ -645,20 +647,11 @@ pub async fn list_auto_join_channels(
WHERE community_id = $1 AND auto_join AND channel_type = 'stream'
AND visibility = 'open' AND archived_at IS NULL AND deleted_at IS NULL
ORDER BY created_at ASC
LIMIT $2
"#,
)
.bind(community_id.as_uuid())
.bind(AUTO_JOIN_CHANNEL_LIMIT)
.fetch_all(&mut *connection)
.await?;
if rows.len() as i64 == AUTO_JOIN_CHANNEL_LIMIT {
tracing::warn!(
community = %community_id,
limit = AUTO_JOIN_CHANNEL_LIMIT,
"auto_join channel list reached its cap; any channel beyond it is not applied"
);
}
rows.into_iter().map(row_to_channel_record).collect()
}

View File

@@ -618,6 +618,55 @@ fn archive_event(owner: &Keys, channel_id: Uuid, value: &str) -> nostr::Event {
.expect("sign 9002")
}
/// The loader returns every effective default channel. It used to stop at 32
/// while kind:9002 kept accepting the flag past that, so the kind:39000 tag
/// promised a join the relay never made for the newest channels. Mutation:
/// put a `LIMIT` back on `list_auto_join_channels` → RED.
#[tokio::test]
#[ignore = "requires Postgres"]
async fn every_default_channel_is_listed() {
let state = state().await;
let tenant = community(&state, "auto-join-all").await;
let admin = Keys::generate();
state
.db
.add_relay_member(
tenant.community(),
&admin.public_key().to_hex(),
"admin",
None,
&Keys::generate(),
)
.await
.map(|write| write.outcome)
.expect("seed relay admin");
let mut flagged = Vec::new();
for _ in 0..33 {
let channel_id = create_channel(&state, &tenant, &admin, &[]).await;
edit_channel(
&state,
&tenant,
&admin,
channel_id,
&[&["auto_join", "true"]],
)
.await
.expect("relay admin sets auto_join");
flagged.push(channel_id);
}
let listed: Vec<Uuid> = state
.db
.list_auto_join_channels(tenant.community())
.await
.expect("list auto_join channels")
.iter()
.map(|channel| channel.id)
.collect();
assert_eq!(listed, flagged, "every flagged channel, oldest first");
}
/// A resend checked after its first copy committed is a duplicate.
/// Validation runs on the new archive state and must not refuse the resend;
/// the write must then report it as a duplicate, with no change and no