Expand description
Orchestration that ties the Community send/delete primitives to persistence, with multi-account safety. This is the layer Tauri commands are thin wrappers over: it publishes a message AND retains its ephemeral key (so the sender can later delete it), and deletes by loading that retained key back.
Every method is SessionGuard-gated: a swap_session can happen at any await
point, and persisting an ephemeral secret (or reading one) must never cross into
the wrong account’s DB.
Structs§
- Base
Catchup - Outcome of
catch_up_server_root: the base epoch reached, and whether an AUTHORIZED base rotation EXCLUDED us (a read-cut / private ban).removedis the catch-all “you’ve been removed” signal for a cryptographically cut member who can no longer read the banlist to learn it the normal way. - Community
Capabilities - The local user’s effective management capabilities in a community, resolved purely by the role engine (positions + permission bits; the owner is just the role at position 0 — NOTHING is owner-hardcoded). The frontend gates each management affordance on the matching bit, so an admin whose role carries a permission gets the exact same affordance as the owner.
Enums§
- Rekey
Outcome - The result of applying a received channel Rekey (3303).
Constants§
- MAX_
COMMUNITIES - Max communities a device may hold locally. The synced Community List is a single NIP-44 event (65 KB plaintext); past the cap even the slimmed list can’t encrypt, so a NEW join/create is rejected above it (the user leaves one to make room).
Functions§
- accept_
invite - Accept a parked invite and persist the member-view Community (the user-consented half of the carrier — the inbound handler only parks invites; this is reached from an explicit accept command). Guards against id-collision overwrites:
- accept_
public_ invite - Accept a fetched public-invite bundle: reject if expired, join via the guarded member-save (caps + id-collision checks), then patch in the preview’s display metadata (description/icon) so the new member sees them immediately.
- am_
i_ banned - Is the local user in this community’s (folded, cached) banlist? Drives BAN self-removal: a
banned member tears down locally (drop the community keys + wipe local chat data) exactly like a kick,
but CANNOT rejoin — re-detecting the ban on any later sync re-removes them, and admins can’t invite a
banned npub. Reads the cached banlist, so refresh it via
fetch_and_apply_banlistfirst for an authoritative (realtime or boot) check. - apply_
channel_ rekey - Apply a received, already-opened channel Rekey (
super::rekey::open_rekey_event) forcommunity. - apply_
server_ root_ rekey - Apply a received, already-opened SERVER-ROOT (base) Rekey for
community— the base counterpart toapply_channel_rekey. Verifies the rotator’s server-wide rotation authority (BAN, “role-based, not owner-only”), checks continuity against the held prior ROOT (when held), finds + opens MY ServerRoot-scope blob, and commits the new root via the atomic base head+archive write. The new root reaches me ONLY through my ECDH blob — if I was removed in this rotation I find no blob (NotARecipient) and recover nothing.SessionGuard-gated; synchronous (one guard + the write re-check suffice, same asapply_channel_rekey). - build_
presence - Announce presence (join/leave) into a channel: a kind-3306 inner signed by the active identity,
published under a fresh ephemeral outer. Content is
"leave", plain"join", or — for a join via a public invite — a small JSON{"by":"<inviter npub>","l":"<label>"}carrying attribution (which link/source brought this member; members-only). Client best-practice (not enforced); no deletion key retained. Callers treat failure as non-fatal.attribution=Some((inviter_npub, label))on an invite-join, elseNone. Build + sign a presence (3306) inner event WITHOUT publishing. Lets the caller record the local system event first (memory→DB, like an outgoing message) and publish in the background — the relay echo then dedups by this inner’s id.inner.idis the system-event dedup key. - caller_
can_ manage_ role_ id - Can the local caller grant/revoke
role_id— i.e. do they holdMANAGE_ROLESAND outrank that role’s position? The crown’s gate, expressed as the POSITION rule (NOT an owner check): the owner is just position 0, so in the single-@admin-role MVP this resolves to “owner only” because the @admin role sits directly below position 0 — but it generalizes to any role hierarchy.falseif the role is unknown. - caller_
can_ manage_ roles - True iff the local user may manage roles — i.e. holds the
MANAGE_ROLESpermission. Permission-based, NOT a hardcoded owner check: the owner is simply the uppermost role and holds every permission; any member granted a role carryingMANAGE_ROLESqualifies just the same. - caller_
capabilities - caller_
has_ permission - Does the local user hold
permissionin this community? The generalizedcaller_can_manage_roles: owner = supreme (every bit), otherwise the union of their granted roles’ bits (the role engine). Drives both the capability report and the producer-side authority gates — no hardcoded owner check. - can_
moderation_ hide - Can
actor_hexmoderation-hide a message authored byauthor_hexin this community? True iff the actor holds MANAGE_MESSAGES and strictly outranks the author (the owner is unhideable). This is the SINGLE source of truth for moderation authority — both the publish gate (publish_owner_hide) and the UI affordance (get_message_delete_options) call it, so the button shown can never disagree with what the publish will actually allow. - catch_
up_ channel_ rekeys - Catch a channel up to the latest epoch it is still a recipient of (windowed scan): fetch every
rekey published since our held epoch and apply the chain. Returns the channel’s new current epoch.
Idempotent + cheap on the steady state (no new rotations → one empty-window fetch → returns the
held epoch). 3303s are addressed by the server-root-derived
rekey_pseudonym, so this is a SEPARATE fetch from the channel message plane (the exception). - catch_
up_ server_ root - create_
community - Create a brand-new Community end-to-end: mint keys + the default channel, persist it locally, and publish its GroupRoot + ChannelMetadata to the Community’s relays. Returns the created Community. (The caller then runs the subscription refresh so it starts receiving.)
- create_
public_ invite - delete_
message - Delete a message the local user previously sent, by its INNER message id (what the UI holds). Loads the retained ephemeral key + the outer event id it points at, then NIP-09-deletes that outer event. Errors if no key is retained (not ours, or already deleted).
- dissolve_
community - fetch_
and_ apply_ banlist - fetch_
and_ apply_ control - Fetch the control plane ONCE and apply every slice — banlist, roles, invite links, metadata — from a
single REQ + single fold. Sync/join/boot call THIS instead of the four
fetch_and_apply_*in sequence (which was four identical REQs). Banlist is applied first so a caller’s subsequentam_i_bannedsees the freshest list. Each slice is best-effort; one failing doesn’t abort the rest. (Solo callers that need a single slice — e.g. revoke refreshing invite links — still use the individualfetch_and_apply_*.) - fetch_
and_ apply_ invite_ links - Fetch the control plane and apply the folded invite-link AGGREGATE locally: UNION the locators of
every per-creator vsk=8 edition whose
creatorheldCREATE_INVITEin the AUTHORIZED roster (the keyless gate, same shape as the banlist’s BAN check), advancing each authorized creator’s head (refuse-downgrade). The union is the source of truth for the Public/Private mode (is_public) + the metrics — NOT join-gating (joining is envelope-only). Returns the aggregate set (empty = Private). - fetch_
and_ apply_ metadata - Fetch the Community’s control plane and apply folded METADATA edits locally: the GroupRoot
(vsk=0 — community name/description/icon/banner) and each ChannelMetadata (vsk=2 — channel name). An
edition applies only if its signer held
MANAGE_METADATAin the AUTHORIZED roster (the keyless gate, same as the producer) AND is strictly newer than the head we hold (refuse-downgrade by version). Identity/transport fields (server_root_key,relays,owner_attestation) are NEVER taken from a metadata edit — a manage-metadata admin edits DISPLAY, not the community’s identity. Best-effort: returnsOkeven when nothing applied. This is what makes an owner/admin’s edit sync to every member. - fetch_
and_ apply_ roles - Fetch the Community’s role graph (real-npub control editions, kind 3308) and fold it into the local roster. Fetches by the server-root pseudonym (not by author — the outer is ephemeral), opens each edition under the server-root key, and folds: verify authorship, bind entity↔content, version-fold, quarantine gaps. Advances each entity’s monotonic head (the per-entity refuse-downgrade floor) and refreshes the roster cache. Returns the folded roster.
- fetch_
public_ invite - Fetch + decrypt the bundle for a public-invite token from the given bootstrap relays.
Queries the addressable coordinate (
d= token locator, author = token signer) and verifies the signer, so an impostor squatting the locator is rejected. - grant_
role - Grant
membera role (requires theMANAGE_ROLESpermission). Publishes the per-member Grant event. The member already holds read keys from membership; the roster entry adds write authority, exercised by signing their own control actions, which peers verify against the roster. - is_
proven_ owner - True iff the local user is the PROVEN owner of this community — derived by verifying the owner
attestation against
my_public_key()(keyless: the owner is the npub that signed the attestation binding this community_id). The check honest clients use to gate owner-only actions (mint invites, set images) and to render the owner crown. - is_
public - The computed Public/Private mode: a community is PUBLIC iff the folded per-creator invite-link
aggregate has ≥1 active locator, else PRIVATE. Every member computes the same value from the folded
editions, which is what lets it drive rekey-on-removal consistently (Private removals rekey the base
to the roster; Public ones don’t — anti-memberlist). Reads the cached aggregate, which is only as
fresh as the last successful latest-page sync (
fetch_and_apply_invite_links, wired best-effort into the sync path) — a member who only scrolled back, or whose sync failed, can hold a stale mode (which is whyrevoke_public_inviterefreshes the aggregate before deciding to privatize). - latest_
invite_ preview - Read-only freshen for an invite preview: build the bundle’s ephemeral community, fold the live control plane, and return the LATEST authorized display metadata — never the bundle’s mint-time snapshot (which goes stale the moment metadata is edited; mirrors the website preview). No DB floors and no persistence: the previewer isn’t a member, so there is no local state to anchor. Any failure falls back to the snapshot so a flaky relay can’t blank the preview.
- persist_
webxdc_ signal - Persist an inbound WebXDC peer signal as a kind-30078 event row — the SAME shape the DM
peer-advertisement handler writes (content
peer-advertisement/peer-left,reference_id= topic,webxdc-topic/webxdc-node-addrtags) — so the miniapp layer’sget_active_peer_advertisements(latest-per-npub, left-tombstone-aware) reads both transports identically. This is what lets a member who closed Vector mid-session rediscover the active players on reopen. Idempotent viaevent_exists. - preload_
community - Warm a community’s primary-channel first page into the RAM preload cache BEFORE the user joins,
so accepting opens a populated chat instead of paying the join sync. RAM-only and side-effect-
free: builds the member view from the bundle WITHOUT persisting (nothing is stored for a
community the user may decline), fetches one page, and stashes it keyed by community id (the
fetch also warms the relay connection). Best-effort — any failure just leaves Join to sync
normally. Spawn this behind a
SessionGuard; promotion on Join re-validates freshness. - publish_
banlist - Replace the Community banlist and publish it as a real-npub-signed 3308 EDITION (vsk=4) at the
community-scoped banlist locator (keyless; foldable + re-anchorable).
banned_hexis the full new list (latest-wins). The actor’s inner signature IS the authority proof; every member re-verifies it heldBANagainst the authorized roster on receipt. Publish FIRST, then persist locally on success — a failed publish must not leave us enforcing a ban no one else sees. - publish_
kick - publish_
my_ invite_ links - Publish the LOCAL user’s OWN invite-link set as a
CREATE_INVITE-gated vsk=8 control edition at their per-creator coordinate — one of the per-creator lists members fold into the aggregate active-set.my_locatorsis the FULL new set of THIS creator’s active link locators (hex; the token in the URL is the secret, never listed). Publish FIRST, then advance the head + merge into the cached aggregate on success (relay-authoritative + phantom-head rule). A creator manages only their own list — noMANAGE_INVITES. Carries the actor’svaccitation so a non-owner creator’s authority is verifiable. - publish_
owner_ hide - Moderation-hide: publish a 3305 delete for another member’s message, signed by the actor’s REAL npub (keyless). Authority is the inner signature, re-verified by every member against the owner-rooted roster (MANAGE_MESSAGES + a strict outrank of the target’s author). Permanent (the tombstone can’t be un-published).
- publish_
presence - publish_
presence_ event - Publish a pre-built presence inner (from
build_presence) to the channel’s recipient set. - publish_
typing_ signal - Publish a typing indicator (3311) into a channel: an inner “typing” event signed by the member,
sealed under the channel epoch key like presence. The Community-transport twin of the NIP-17
typing rumor. Ephemeral — never persisted/folded; the latency-sensitive single-attempt path
(
durable = false), and callers treat failure as non-fatal (a dropped keystroke ping is harmless; the next one ~every few seconds covers it). - publish_
webxdc_ signal - Publish a WebXDC realtime peer signal (3310) into a channel: an advertisement of the local
Iroh node for a Mini App session (
node_addr= Some) or a peer-left (node_addr= None). The Community-transport twin of the NIP-17 peer-advertisement/peer-left DM rumors — signed by the member’s real identity (a member can’t forge another player’s presence), sealed under the channel epoch key like presence. Callers treat failure as non-fatal (a missed ad only delays discovery; the next re-advertise covers it). - republish_
channel_ metadata - Rename a channel and republish its ChannelMetadata as a real-npub 3308 edition (vsk=2) so
members fold it via
fetch_and_apply_metadata. Keyless authority: the actor must holdMANAGE_CHANNELS(channel edits are a channel-management action; the owner holds every permission).channel_idmust be one ofcommunity’s channels. Publish-FIRST then persist on success (relay- authoritative, phantom-head-safe — same contract as the community GroupRoot). - republish_
community_ metadata - Persist edited Community display metadata and republish the GroupRoot as a real-npub 3308 edition
(vsk=0) so other members + re-anchoring pick it up. Keyless authority: the actor must hold
MANAGE_METADATA(the owner holds every permission). The caller mutatescommunity(name / description / icon / banner) first; this gates, saves it, then publishes the next edition version. - retry_
pending_ read_ cut - Retry an outstanding PRIVATE-community read-cut re-seal, if one is pending. Called from the sync
path so a re-seal that failed during a ban (e.g. a relay outage) AUTO-RECOVERS on the owner’s next
community sync — no manual re-ban needed. No-op if nothing is pending. If the community has since gone
PUBLIC the read-cut is moot (anti-memberlist: a Public ban doesn’t rotate the base), so the stale flag
is cleared. Best-effort + idempotent; the re-seal authority (BAN) is enforced by
rotate_server_root. - revoke_
public_ invite - Revoke a public invite: NIP-09-delete the bundle event (by its addressable coordinate, signed by the
token-derived key we re-derive from the retained token), forget the token locally, and republish the
invite-link registry so the mode tracks reality. If this was the LAST link, the community goes
Private → it is re-founded (privatize): the base key is rotated to the observed-participants set,
sealing out link-joined lurkers who never spoke. Creator-only: you can only retire YOUR OWN
links (the token is held only by its creator); the privatize rekey is
BAN-gated + needs a local key. - revoke_
role - Revoke a role from
member(owner/admin authority) — instant logical (the role record is dropped, so the grant-set check stops honoring their actions). The physical lockout (channel rekey per) is a later step; this only edits the grant. In the MVP a role is permission bits, NOT a channel read key (channels aren’t role-gated), so a revoke needs NO rekey and a bunker account can do it freely. WHEN role-gated channels ship, the rekey-on-revoke path must adopt the same bunker fail-fast guard aspublish_banlist/revoke_public_invite(a rekey needs a raw local key). - rotate_
channel - Rotate a channel’s key (a channel rekey): mint a fresh-random key for
current_epoch + 1, deliver it torecipientsas one self-proving 3303 event (epoch + every recipient blob + the prior-epoch commitment + my real-npub authority sig, all in one — the design tenet), publish it, then advance MY local epoch. Returns the new epoch. - send_
message - Publish a Community message and retain its ephemeral key in the account DB so the sender can delete it later. Returns the published outer event.
- send_
signed_ message - Publish a message whose inner authorship event was signed externally (via the active
signer — local OR bunker) and retain its ephemeral key. Use this from the command
layer where
client.signer()is available; it gives bunker accounts send parity with DMs. (Local-only callers/tests can usesend_message.) - set_
member_ grant - Set a member’s complete role set (owner/admin authority) and publish their per-member
Grant event (vsk=3). Empty
role_idsrevokes all of that member’s roles. Persists the updated local graph BEFORE the publish await (so our own client reflects it immediately and the write lands in the captured account); the relay echo dedups. - sync_
before_ admin_ write - FRESHEN-BEFORE-WRITE guard for an administrative write (rekey / ban / kick / grant / revoke / metadata): hop any base rotation + fold the LATEST control plane from ALL relays + (for a rekey) ingest channel activity, so the write acts on the freshest reachable truth — not just a stale local view. The demonstrated bug this fixes: privatizing before observing a member’s activity wrongly cut them.