fix(concord): ban dialog tells the truth in Private communities; amy refuses to post while banned

In a Private community a ban also Refounds (CORD-05 §5), so the banned member
loses access to new messages — but the confirm dialog said they could keep
reading until removed. It now says the keys rotate when the ban will Refound.

amy `send` also refuses when the fold shows this account banned, instead of
publishing a message every reader drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-09-29 20:41:28 -04:00
co-authored by Claude Opus 5.5
parent 88149f66f3
commit 07a6a6d408
3 changed files with 11 additions and 1 deletions
@@ -92,6 +92,10 @@ object ConcordChannelCommands {
if (state.dissolved) {
return Output.error("dissolved", "community '$handle' has been dissolved and is read-only (CORD-02 §9)")
}
// CORD-04 §4: every reader drops a banned author's messages, so a post would vanish unseen.
if (state.authority.isBanned(ctx.signer.pubKey)) {
return Output.error("banned", "this account is banned from '$handle' (CORD-04 §4); its messages are hidden from everyone")
}
val channelId = resolve(ctx, sc, channelRef) ?: return Output.error("not_found", "no channel '$channelRef'")
// The channel's own plane (CORD-03 §1): root-derived when Public, its held key when
// Private — and a refusal, never the root plane, for a Private Channel we hold no key for.
@@ -3713,6 +3713,7 @@
<string name="concord_members_ban">Ban</string>
<string name="concord_members_ban_title">Ban member?</string>
<string name="concord_members_ban_message">They can no longer post, and their messages are hidden from everyone. Other apps may remove the community from their list. They can still read new messages until you also remove them from the community. You can unban them later.</string>
<string name="concord_members_ban_message_refound">They can no longer post, and their messages are hidden from everyone. This community is Private, so banning also rotates its keys: they lose access to new messages. You can unban them later, but they will need a new invite to read again.</string>
<string name="concord_members_make_admin">Make admin</string>
<string name="concord_members_remove_admin">Remove admin</string>
<string name="concord_members_roles_failed">Could not update this member's roles.</string>
@@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_message
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_message_refound
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_title
import com.vitorpamplona.amethyst.commons.resources.concord_members_empty
import com.vitorpamplona.amethyst.commons.resources.concord_members_kick
@@ -238,6 +239,9 @@ fun ConcordMembersScreen(
state?.authority?.canActOn(myPubKey, entry.pubkey, ConcordPermissions.BAN) == true,
// A Kick needs KICK and a strict outrank of the target (CORD-04 §6), the same rank rule.
canKickTarget = iCanKick && state?.authority?.canActOn(myPubKey, entry.pubkey, ConcordPermissions.KICK) == true,
// In a Private community a ban also Refounds (CORD-05 §5), so the dialog must not
// promise they keep reading.
banRotatesKeys = state?.banRequiresRefounding(listOf(entry.pubkey)) == true,
viewerCanManageRoles = iCanManageRoles,
// canActOn folds the whole rank rule for us: we hold MANAGE_ROLES, we're not
// banned, the target isn't the owner (unremovable), and we strictly outrank
@@ -263,6 +267,7 @@ private fun ConcordMemberRow(
viewerCanBan: Boolean,
canBanTarget: Boolean,
canKickTarget: Boolean,
banRotatesKeys: Boolean,
viewerCanManageRoles: Boolean,
canManageRolesOnTarget: Boolean,
assignableRoles: List<AssignableRole>,
@@ -326,7 +331,7 @@ private fun ConcordMemberRow(
if (confirmBan) {
ConcordConfirmMemberActionDialog(
title = Res.string.concord_members_ban_title,
message = Res.string.concord_members_ban_message,
message = if (banRotatesKeys) Res.string.concord_members_ban_message_refound else Res.string.concord_members_ban_message,
confirm = Res.string.concord_members_ban,
onConfirm = {
accountViewModel.setConcordBan(communityId, entry.pubkey, ban = true)