Feature Request: Cross-community channel sharing, authorized by community owners
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Motivation**
Buzz operators who run separate, independently-owned communities (e.g. two agencies collaborating on a joint project, a vendor and a client, or a parent org and a partner org) currently have no way to let members of Community B participate in a channel that lives in Community A, without either (a) inviting each B member into A individually as an A-scoped identity, which mixes rosters and breaks the "this is B's person" boundary, or (b) transferring/duplicating the channel, which moves ownership rather than sharing it.
This is a distinct product need from channel *transfer* ([#7183](https://github.com/block/buzz/issues/7183)), which is explicitly scoped as a move (source community gives the channel up) and lists "Sharing one channel across two live workspaces" as a **non-goal**, calling it "a different product." This issue is a request to build that different product: a channel that stays owned by its home community but is opt-in visible/postable to another community, with both community owners' consent.
**Why this isn't just "add members from another community"**
It's structurally blocked today, not just a UI gap:
```sql
-- schema/schema.sql
CREATE TABLE channel_members (
community_id UUID NOT NULL REFERENCES communities(id),
channel_id UUID NOT NULL,
pubkey BYTEA NOT NULL,
...
PRIMARY KEY (community_id, channel_id, pubkey),
FOREIGN KEY (community_id, channel_id)
REFERENCES channels (community_id, id) ON DELETE CASCADE
);
```
A `channel_members` row's `community_id` must equal the channel's own `community_id` — there's no way to represent "a member whose home community is B, sitting in a channel whose home community is A." And per `docs/multi-tenant-relay.md`, `resolve : channel_id → community_id` is a **total function** (one channel, one community), and `channels.community_id` is immutable after insert (P-RESOLVE / S2) — the RLS policy (`community_id = current_setting('app.community_id')`) and the tenant fence (`resolve(h) = resolve_host(host)`) both assume a channel has exactly one authorized community. Sharing requires a new, explicitly modeled admission path layered on top of that fence, not a bypass of it.
**Proposed solution**
An owner-authorized **channel grant** between communities (distinct from re-tenanting):
1. The channel's `community_id` (home) never changes — this is *not* re-tenanting and doesn't touch P-RESOLVE/S2.
2. A new authorization overlay (e.g. a `channel_community_grants` table: `channel_id, home_community_id, guest_community_id, granted_by, accepted_by, scope [read | post], created_at, revoked_at`) records that guest community B's members may access a specific channel owned by community A.
3. Bilateral consent: the home community owner (or channel owner) initiates the grant; the guest community owner must accept it before any of B's members gain access. Either owner can revoke unilaterally.
4. Authorization checks (and the RLS predicate) extend from "is this pubkey a `channel_members` row in the channel's community" to "...OR is this pubkey a member of a `guest_community_id` with an active, matching grant" — an explicit, auditable OR-clause, not a relaxation of the existing per-row `community_id` check.
5. Guest members show up in the channel roster with a visible "guest via Community B" indicator so nobody mistakes them for home-community members (this is also where several existing bugs about cross-community agents leaking into channels unlabeled — e.g. [#2515](https://github.com/block/buzz/issues/2515), [#6771](https://github.com/block/buzz/issues/6771) — should be checked against, so shared channels don't inherit the same "invisible outsider" failure mode).
6. Write an immutable, channel-visible audit record for grant/accept/revoke (actor, communities, timestamp), same discipline as requested for transfer in #7183.
7. **v1 scope:** same relay, two communities. Cross-relay sharing (hosted ↔ self-hosted) is a separate problem and out of scope here.
**Alternatives considered**
| Approach | Why it is not enough |
|---|---|
| Individually invite each B member into A | Works today but requires B members to hold A-scoped membership rows, mixes rosters, and gives A's owner no single revocation point — pulling out means removing members one by one. |
| Channel transfer (#7183) | A move, not a share; the source community loses the channel. Different problem, already flagged as such in that issue. |
| Whole-workspace export/restore ([#5731](https://github.com/block/buzz/issues/5731)) | Disaster-recovery tool, not a live shared-access mechanism. |
| Duplicate the channel and manually mirror messages | Loses a single source of truth, thread/canvas continuity, and real-time sync. |
**Non-goals**
- Cross-relay sharing (self-hosted ↔ hosted, or two separate hosts).
- Merging communities or their rosters.
- Automatic, non-consensual visibility — both owners must explicitly authorize.
Contributor guide
Assessment
This issue has not been assessed yet.