Skip to content

mobile: let admins set who joins a channel and default channels - #9071

Open
TheSentinel454 wants to merge 6 commits into
mainfrom
elrond/mobile-default-channel
Open

TheSentinel454 wants to merge 6 commits into
mainfrom
elrond/mobile-default-channel

Conversation

@TheSentinel454

Copy link
Copy Markdown
Contributor

Summary

Mobile channel details get a General access card on open channels:

  • New people join as (Member / Guest): channel owners and admins choose the role people get when they join or are added without one. People already in the channel keep their role.
  • Default channel switch: community owners and admins (per the relay-signed kind:13534 roster) can make an open, unarchived stream a default channel. The relay then joins every new community member to it with that join role.

Both settings already exist on the relay (NOSTR.md § Default channels; join_role on kind:9002). The desktop app gets the matching switch in block/buzz-app#2468. Until now mobile could neither show nor change them.

How it works

  • ChannelData parses join_role and the bare ["auto_join"] tag from kind:39000.
  • channelAdmissionProvider reads fresh kind:39000 on demand; nothing is cached across sessions.
  • ChannelActions.setChannelJoinRole / setDefaultChannel each send one partial kind:9002 (["h", id] plus the single changed tag), then confirm from a fresh readback. If the relay accepts a change that does not read back, the action throws ChannelChangeUnconfirmed. It is never reported as success and never resent. The card then shows what the relay holds.
  • The default-channel command is sent on its own because a community admin is not necessarily a channel admin. It never rides along with other details.
  • managesCommunity() names the owner/admin check that canManageCommunityInvites already used.
  • The legacy desktop/ app is untouched.

What got simpler

canManageCommunityInvites now delegates to the shared managesCommunity role check instead of repeating it. Otherwise this is additive: one card, one provider, two actions on existing owners.

Tests

channel_admission_journey_test.dart drives the real ChannelDetailsPage, ChannelActions and signer against a fake relay:

  • Admin journey: turn Default channel on by tapping the row (non-switch input), with switch semantics checked before and after; change join role to Guest through the chooser sheet; then a relay that accepts without applying, where the user sees "couldn't confirm" and the switch keeps the relay's value.
  • Role/eligibility table: channel admin only, community owner only, forum, private channel, and regular member. Each shows exactly the controls that role may use and sends nothing.

channel_detail_page_test "previews five members…" now centres See all before tapping it. The page is longer with the new card, and top alignment left the row under the frosted app bar.

Gated on a Blox workstation: just mobile-check, just file-size-check, the focused channel tests, and just mobile-test (4407 passed). The one failure, push_gateway_origin_validator_test "iOS build phase…", fails identically at base: the workstation's /etc/gitconfig include can't be expanded.

🤖 Generated with Claude Code

Channel details get a General access card on open channels. Channel
owners and admins choose whether new people join as Member or Guest;
community owners and admins can make an open, unarchived stream a
default channel. Each change is one partial kind:9002 confirmed from a
fresh kind:39000 readback; an accepted change that does not read back
is reported as unconfirmed, never as saved.

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheSentinel454
TheSentinel454 marked this pull request as ready for review November 4, 2026 19:15
@TheSentinel454
TheSentinel454 requested a review from a team as a code owner November 4, 2026 19:15
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 4813b8cd15411355f05599e263ab15dd04e7afcc...6b65c3a9e23d1180cb184c8414c792ed3e5f40b9.
A new review must complete for this exact range. When manual authorization
is required, a user with write access must comment exactly
@buzz-security-review 6b65c3a9e23d1180cb184c8414c792ed3e5f40b9 to authorize a new review.
Any previous review applies only to its recorded range.

A default-channel or join-role change the relay may have applied without
showing it, including one whose acknowledgement was lost, is now held per
channel outside the details page. It blocks further changes, survives
closing and reopening the page, and resolves only through a read-only
status check. Confirming a default-channel change requires an open,
unarchived stream, since archived and private metadata keep the stored
flag without advertising it. A relay rejection stays a definite failure.

A failed community role read now explains why the default channel setting
is missing and retries in place.

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 4, 2026
The session's publish now throws a typed RelayNotConnected (still a
StateError with the same message) when it has no connection and sends
nothing. A channel access change that fails this way clears its
uncertain hold instead of blocking further changes forever.

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 4, 2026
Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 4, 2026
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 4, 2026
A change whose event was lost before reaching the relay stayed held
forever, since no read could ever show it. Once a read shows the held
change unapplied, the card offers Send again, which republishes the exact
signed event. The relay stores each kind:9002 by id in the same commit
that applies it and ignores a duplicate, so this applies the change at
most once.

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 4, 2026
Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Two blocking problems, both in how the card recovers when a change can't be confirmed. Everything else checks out against the relay: the one-tag kind:9002 commands match NOSTR.md § Default channels, the relay applies them as partial updates so name/about/visibility are untouched, auto_join is authorized by community role without channel membership, the permission gating matches the relay's rules, and the RelayNotConnected / RelayEventRejected changes keep existing callers working. Mobile checks and the full just mobile-test pass at this head.

IMPORTANT: a change that another admin overrides locks both controls

channel_management_actions.dart:332–337, resend at :350–371, card gating at channel_admission_card.dart:39–43.

  1. Admin A turns Default channel on. The relay stores and applies the 9002, but A's app never gets the OK.
  2. Admin B turns it back off before A's status check runs.
  3. A's check reads auto_join off, marks the change unseen and offers Send again.
  4. Send again republishes the same signed event. The relay answers OK true "duplicate:" and doesn't apply it again (crates/buzz-db/src/store/channel.rs:1546–1565, crates/buzz-relay/src/handlers/ingest.rs:3727–3732). The next check still reads off, so the change stays held.

While it's held, ready is false, so both the Default channel switch and the unrelated join-role row stay disabled. Reopening the page doesn't help, because the hold lives in an app-session provider. The only way out is waiting until the relay's ±15-minute timestamp window rejects the resend (ingest.rs:2615–2622), or switching community or restarting the app.

The root issue: current metadata shows the channel's value, not whether a particular command ran. Fix: treat a duplicate: reply (or any accepted resend) as "the command has settled". If the value still doesn't match, tell the user someone else changed it, show the relay's current value and re-enable the controls, without replaying the old intent. Keep the hold only while it's genuinely unknown whether the command reached the relay. The journey test's fake relay applies repeated event IDs again (channel_admission_journey_test.dart:73–84), and the mismatch step resolves only by setting the remote value to the target (:399–401). Make the fake dedupe by event ID, and add a second-admin overwrite case.

IMPORTANT: an older status check can clear a newer pending change

channel_management_actions.dart:324–337.

checkAdmissionChange reads held, awaits the kind:39000 fetch, then calls clear() or markUnseen() on whatever is held when the fetch returns. The only guard against overlapping checks is saving in the page widget (channel_admission_card.dart:33, :64–72). The held change deliberately outlives the page (channel_management_provider.dart:644–680).

Sequence: start a slow check of change A, close and reopen the page, start a second check of A. The second check confirms A and clears it. Make change B, which stays unconfirmed. The first check now returns a snapshot that matches A and clears B, which re-enables writes while B is unresolved. A late mismatch can likewise mark the wrong change unseen and offer to resend it. Separate subscriptions can complete out of order, because the relay handles each REQ in its own task.

Fix: only clear or mark the hold if it is still the exact event that check (or resend) read, and re-check the community after each await. Put that ownership in the provider/action layer rather than the widget. A journey case with a delayed response across a page remount and a newer change would cover it.

Non-blocking

The PR description says an unconfirmed change "is never reported as success and never resent". The code resends it on purpose from the Send again row (channel_admission_card.dart:150–160), so the description should describe that path.

This branch was successfully deployed

No deployments
codex-review — 6b65c3a9 Deployed Nov 4, 2026 by TheSentinel454 via Run Codex Security Review #12611
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants