Skip to main content

Module service

Module service 

Source
Expand description

Concord v2 service — the stateful orchestration binding the pure v2 modules to storage + transport. Free functions, SessionGuard-gated at every write (a swap_session can land at any await — see CLAUDE.md), mirroring the v1 service’s discipline.

Signing + NIP-44 flow through the active [VectorSigner] (active_signer()): the live client’s signer for a NIP-46 bunker / NIP-55 offline account, else the local vault. Every identity op in v2 is sign_event / nip44_encrypt / nip44_decrypt — a remote signer’s whole surface — so create, send, join, invite, moderate, rotate, and refound all work keylessly (CORD-06 D1/D5 made the rekey locator public + its blobs pairwise NIP-44, so unlike v1 there is no raw-ECDH exception).

Structs§

AuthorityView
A community’s folded, delegation-authorized authority — the on-demand read view (a paged control-plane fetch + fold, nothing persisted). roles is the owner-seeded authorized roster (shared algebra with v1); banned the enforced banlist. floored/head_entities let a writer detect a WITHHELD entity (floored locally but no head folded) before replacing it blind.
ChannelPins
A channel’s pin list, read from the locally folded head.
FetchedEvent
A chat event opened from a channel fetch, tagged with the epoch its key decrypted under.
ListSyncOutcome
Sync memberships from the 13302 across devices: fetch this account’s list from bootstrap_relays (its held communities’ relays plus any caller-supplied set for a fresh device), and JOIN every live entry not already held — reconstructing the community from its join material and re-verifying the owner root. Returns the newly-rehydrated communities (so the caller can subscribe + notify). What one Community-List sync changed locally.
MintedLink
A minted public link: the shareable URL plus the addressable bundle event to publish and the link keypair to retain (in the Invite List) for later refresh or revocation.
RekeyFollow
The outcome of a rekey-follow pass.

Enums§

BundleAudience
Who an invite bundle is FOR — which decides the Private-Channel keys it may carry (CORD-05 §1 vs §2).
VendVerdict
Tombstone a channel (CORD-03 §2, deleted: true) + drop it locally. Reader-gated by What a client should do with a vended Private-Channel key right now.

Functions§

absorb_parked_channel_keys
Re-judge every parked key vend for this community and adopt the ones that now pass. Runs after a control follow (the fold moved, so verdicts can change) and on the boot sweep.
accept_direct_invite
Accept a Direct Invite: unwrap the 3313 giftwrap (Schnorr-verifying the seal), then run the shared accept path. The recipient’s consent IS this call. No network await precedes the accept, so the guard captured here suffices.
accept_migration_material
Accept v2 JoinMaterial recovered from a v1→v2 migration dissolution payload (m). The material IS a bundle’s membership subset — rebuild the invite and run the SHARED accept path, which re-verifies the owner root over the network and enforces the join-time ban gate (a banned-never-cut v1 member who can open m is refused here, fail-closed). No giftwrap to unwrap: the dissolution already authenticated the owner via its signature.
accept_parked_invite
Accept a PARKED Direct Invite from its stored bundle JSON (the wrap was already unwrapped + owner-verified at park time). Re-parses through the same fail-closed bundle validation, then runs the shared accept path (which re-verifies the owner root over the network). inviter_hex is the parked seal signer, for Guestbook Join attribution.
accept_public_link
Accept a public invite link: fetch its bundle (revocation-aware) and join.
bundle_of
Build the §1 invite bundle for this community, scoped to audience. A Public channel carries the community_root as its “key” (the joiner derives the real secret from the root); a Private one its own key — and only for a Member the folded roster shows entitled. The bundle self-certifies the owner, so the inviter’s identity is irrelevant to trust.
bundle_of_with_overlay
bundle_of settling entitlement against a Grant this client JUST published (with/without role ids), since the fold lags its own publish. This is the grant-vend path (CORD-03 “delivered on grant”).
channel_access_role
The companion Role minted alongside a Private channel — the channel’s access list (CORD-04 §2 scope: {"kind":"channel"}). Same name as the channel, and no permission bits: it confers read access, which is key possession, never authority. Position sits below every management role for the same reason.
clone_banlist_to_twin
Clone a v1 banlist onto the v2 twin (§migration Phase 1.3): the join-time ban gate needs the v2 banlist to name every v1-banned npub, else a banned-but-never-cut member who can open m would walk in. Owner-signed on the twin’s control plane.
clone_governance_to_twin
Clone v1 governance onto the twin (§migration Phase 1.3): every v1 member who was a FULL admin (effective permissions ⊇ ADMIN_ALL) is re-granted @admin on the twin (mapping v1’s Admin onto v2’s deterministic admin role id, CORD-04 §2). The owner is supreme by identity (never a grant) and banned members are skipped (a banned author’s editions fold out anyway, and re-granting would spring them back to admin on a future unban).
community_is_public
Whether this community is PUBLIC (CORD-05 §5): fold every creator’s Registry (vsk-8) that its author is authorized for (CREATE_INVITE, bound to their coordinate) into an aggregate live-link set — non-empty ⇒ a live link exists ⇒ Public; empty ⇒ Private. Retiring the last link is what flips it back.
create_community
Create a fresh v2 community owned by the local identity: mint the genesis (self-certifying id + the two owner editions), persist, publish the genesis control editions, and announce the owner’s Guestbook Join. Returns the saved community.
create_migration_twin
Mint a v2 migration TWIN whose primary channel REUSES the v1 primary channel id (§migration) so chat history stitches through the flip. Same owner identity, fresh salt/root. Additional v1 channels are added by the wizard via create_*_channel_with_id. Mirrors create_community’s persist-before-publish + floor seeding.
create_private_channel
Create a new PRIVATE channel (CORD-03 §2): mint a fresh id + an independent random key at channel-epoch 1, mint a companion channel-scoped Role that is the channel’s access list (CORD-04 §2), deliver the key to the entitled over the rekey plane (CORD-06 §1), then announce the channel (vsk 2, private). Epoch 0 is the root generation (“the first privatisation is epoch 1”), so the delivery commits its continuity to (0, community_root) — verifiable by every member and bound to THIS community’s root. The key ships BEFORE the announcement: an aborted attempt leaves only an unannounced crate (invisible), and a retry mints a fresh id, so there is no same-coordinate double-mint to fork on. Live public links are refreshed; they carry no private key, so this only re-states the public set.
create_private_channel_with_id
create_private_channel with a CALLER-CHOSEN id — the migration-only entry point (§migration) reusing a v1 private channel’s id so history stitches through the flip.
create_public_channel
Create a new PUBLIC channel (CORD-03 §2): mint a fresh id, publish its metadata edition (vsk 2), and add it to the held community. A Public channel derives its Chat Plane from the community_root (no per-channel key), so other members fold it in on their next control follow with nothing to distribute. Returns the new channel id. Reader-gated by MANAGE_CHANNELS.
create_public_channel_with_id
create_public_channel with a CALLER-CHOSEN id — the migration-only entry point (§migration) that reuses a v1 channel’s id so chat history stitches through the flip. Asserts the id isn’t already live in a DIFFERENT held v2 community before minting.
debug_explain_base_rekey
Follow rekeys for a held community: advance the base (root) epoch and each Private channel’s epoch as far as authorized rotations allow, adopting the fresh key we’re still a recipient of at each step and dropping a scope we’ve been removed from. Persists the result. Called when a rekey wrap arrives in realtime so a long-running bot keeps decrypting after a rotation instead of going silent.
delete_channel
Tombstone a channel (CORD-03 §2, deleted: true) + drop it locally. Reader-gated by MANAGE_CHANNELS; the coordinate stays folded as a grave so peers hide it.
dissolve_community
Owner dissolution / “Delete Community” (CORD-02 §9): publish the terminal tombstone at the dissolved plane (community_id-derived, epoch-free, so every past or present member resolves the same grave and a Refounding can never strand it). The tombstone’s presence IS the state; only the owner’s seal counts. Irreversible — on success the local hold is sealed read-only.
edit_channel_metadata
Add or edit a channel’s metadata (vsk 2, CORD-03 §2). channel_id is the coordinate. Gated on the reader side by MANAGE_CHANNELS.
edit_community_metadata
Edit the community metadata (vsk 0, CORD-02 §6). Gated on the reader side by MANAGE_METADATA.
ensure_admin_role
The community’s @admin role id: the folded Server-scope ADMIN_ALL role when one exists, else (with create_if_missing) a DETERMINISTIC mint — the same id on every device, so concurrent grants converge as editions of ONE entity instead of forking two Admin roles.
fetch_authority
Fetch + fold the community’s current authority (CORD-04), paging older like follow_control while the fold is gapped so a busy control plane can’t push the roster off the newest window. A fetch failure degrades fail-safe: owner-only authority plus the PERSISTED banlist — nobody gains standing from an outage, and a ban never lifts on withheld data.
fetch_channel
Fetch a channel’s newest messages — one page of fetch_channel_history. limit is one relay-side bound across the whole epoch-author OR-set, not per epoch; deeper history pages backwards via the walk.
fetch_channel_history
Walk a channel’s history newest-first (CORD-03 §3 “clients load a Channel newest-first and paginate backwards”), querying every held epoch’s Chat-Plane address one page-sized query at a time until max_pages, a drained relay, or keep_paging returns false for a page (the caller’s “I already hold these” early stop — consulted only on pages that opened something, so junk at the address can’t fake exhaustion). Pages step by INCLUSIVE until with wrap-id dedup, so a page boundary landing mid-second can’t skip siblings; a full page of only-already-seen wraps is a same-second WALL (relay filters are second-granular) and steps past it accepting that unseen same-second siblings beyond the relay cap are unreachable — logged, and a protocol-level limitation (the ms tag can’t be filtered server-side).
fetch_public_bundle
Fetch + decrypt the newest Live bundle at a public link’s coordinate ((33301, link_signer, "")). Revocation is authoritative-if-present: if ANY signer-valid tombstone is among the fetched events, refuse — never trust fetch ordering (a cross-relay union has no global newest-first sort, so a stale Live could otherwise win a partial-propagation race). Otherwise pick the newest valid Live by created_at. Read-only.
follow_control
Re-fold this community’s Control Plane and apply the current metadata + public channel set to the held community, persisting any change. Called when a control-plane wrap arrives in realtime (a rename, a new channel, an edited description) so a long-running bot tracks the community mid-session instead of freezing at its join-time view.
follow_rekeys
grant_admin
Grant the @admin role (minting it deterministically when absent), MERGED into the member’s existing grant — a grant entity replaces whole (CORD-04 §2), so a blind push would erase their other roles. Owner-only: the position-1 Admin is manageable only by position 0 (an equal never outranks it), and refusing before any publish keeps an unauthorized edition of the DETERMINISTIC admin entity from advancing this device’s own floor onto a head readers reject.
grant_channel_access
Grant member read access to a Private channel (CORD-03 “delivered on grant”): publish a Grant adding the channel’s access role, then vend the key as a CORD-05 §6 Direct Invite whose bundle carries exactly the channels they are now entitled to.
grant_roles
Grant or revoke a member’s Roles (vsk 3, CORD-04 §2). Empty role_ids is a revoke. Gated on the reader side by MANAGE_ROLES + outrank of every role + the member.
ingest_guestbook_event
Fold ONE live guestbook event into the store (the realtime path — no fetch). Returns whether it was new.
is_dissolved
Whether a valid owner-signed dissolution tombstone exists for this community on its relays (CORD-02 §9). A join refuses a dead community, and a live follow seals on sight. Fail-OPEN on a fetch error (absence of proof is not death), but any owner-verified tombstone found is authoritative.
judge_channel_key_vend
Judge a vended Private-Channel key against our OWN folded state.
kick_member
Cooperative Kick (CORD-04 §6, Guestbook plane): name the target; every reader honors it iff the signer holds KICK and strictly outranks them (the coalesce’s can_kick), so publishing without authority is inert. A kicked member may rejoin with a fresh invite — cryptographic severance is the ban/refound path.
leave_community
Leave a community: publish a Guestbook Leave and tear down the local hold.
memberlist
Fold the Complete Memberlist from the Guestbook plane. The proven owner is ALWAYS a member (derived from the self-certifying community_id — no network, so a lost/evicted genesis Join can’t drop them). Observed authors — anyone seen publishing on a channel — are folded in FORWARD-only per CORD-02 §5, so a member whose Join was lost still counts.
mint_public_link
Mint a public invite link for this community: a fresh token + link keypair, the bundle encrypted under the token key and published at (33301, link_signer, ""), and the base/invite/<naddr>#<fragment> URL. base is the deep-link domain (e.g. https://vectorapp.io); the fragment carries the token + bootstrap relays and never reaches a server.
moderation_delete
Moderation-hide: remove SOMEONE ELSE’s message under MANAGE_MESSAGES (CORD-04 §3/§5). Same kind-5 the author’s own delete uses — CORD defines no separate hide, the authority is what differs, and every reader re-derives it from the seal’s real npub against the folded Roster.
persist_community_image
Persist a freshly-published icon/banner onto the held row and return the fresh row. Reloads under the community’s follow lock: save_community_v2 is a whole-row save that prunes channels absent from the passed struct, so writing a stale pre-upload copy would drop rows a concurrent fold just landed.
pin_message
Pin a message: recover its wrap, rebuild its proof, append, republish.
preview_bundle
The fold half of preview_public_link, over an already-fetched bundle. Split out so a caller that only needs the community’s IDENTITY can read it off the bundle (it is self-certifying) and skip the Control-Plane walk entirely — the walk is the join gate, and accept_public_link runs it again regardless.
preview_public_link
Read-only rich preview of a public link: the decrypted bundle plus the LATEST display metadata folded live from the Control Plane (a v2 bundle deliberately carries no icon — the fold is the authority). Owner-root verification rides the fold, so a forged-root link can’t render a convincing preview; on a fold/transport failure the bundle snapshot is the fallback. Nothing persists — the caller hasn’t joined.
read_channel_pins
Read a channel’s pins from the locally folded head: unseal (private form), verify every entry, keep wire order. Local-only — the control follow is what moves the head.
refound_at_birth
BIRTH refound (§migration Phase 1.4): roll a freshly-minted migration twin from epoch 0 to epoch 1 so it can carry an owner-signed Guestbook SNAPSHOT of the full v1 memberlist — genesis (epoch 0) has no snapshot authority (fold_members gates on root_epoch > 0), so this is the ONLY way to seed a roster every honest client folds. UNLIKE refound_community the two sets are DECOUPLED:
refound_community
Owner/admin Refounding (CORD-06 §3): roll the community_root to cryptographically remove removed from a Private community (a Ban’s read-cut). Compacts the Control Plane under the new root (re-wraps each head VERBATIM — the inner owner/actor signatures survive, so no re-authoring), rekeys the base plus every Private channel (each sealed under the PRIOR root, D2, so a base-fork loser can still open them), and seeds the new epoch’s Guestbook snapshot. Requires BAN.
refresh_public_links
Refresh every live public link’s bundle behind its stable URL (CORD-05 §2) — e.g. after a Rekey/Refounding rolled the keys — by re-posting the bundle at the same coordinate with the CURRENT community state, so a link shared once keeps working across rotations. Best-effort.
republish_community_list
Rebuild this account’s 13302 from its held v2 communities, MERGE with the remote copy (preserving tombstones, other-device entries, unknown fields), and publish. just_joined is the community THIS call is recording a create/join for — the ONLY community whose entry is (re)stamped now, so it beats any prior tombstone (a deliberate re-join resurrects). Every OTHER held community that the remote has tombstoned is left tombstoned (a sibling device’s leave is NOT undone just because we joined something else — the W1 resurrection hole). Idempotent; best-effort — a list-publish failure never fails the membership change itself. Returns Ok(true) when the list was PUBLISHED, Ok(false) when the attempt was skipped without failing the caller (a failed remote fetch — see below). Callers that need the membership to actually land use republish_community_list_durable.
republish_community_list_durable
Record a membership across devices DURABLY: retry in the background until the list actually lands.
revoke_admin
Strip the @admin role from the member’s grant, preserving their other roles. A no-op when they don’t hold it. Owner-only, like grant_admin.
revoke_channel_access
Revoke member’s read access to a Private channel (CORD-03 “rekeyed on removal”): drop the channel’s access role from their Grant, then rotate the channel to its next epoch delivering the fresh key to everyone still entitled (CORD-06). The revoked member keeps whatever history they already read — a rekey protects the future, never the past.
revoke_public_link
Revoke a public link by its token hex (CORD-05 §2/§5): re-post its coordinate as a revocation tombstone (retiring the bundle behind the URL, so a fetcher finds the grave), tombstone the Invite List entry, and refresh the Registry. Retiring the LAST live link empties the Registry → the community reads Private (a Refounding is the owner’s separate read-cut).
send_chat_message
Full chat send: threaded reply (NIP-C7 q, the parent’s (rumor_id, author) hex pair), NIP-30 custom-emoji pairs, and verbatim extra tags (NIP-92 imeta attachments). Returns the message’s rumor id (hex).
send_chat_message_at
send_chat_message with an explicit event time. The rumor id is a pure function of its inputs, so a GUI that picks at_ms can precompute the id for its optimistic pending row — the in-process echo and the finalize then key the SAME id (the exact v1 pending → sent contract).
send_delete
Cooperative in-plane delete (kind 5, NIP-09 semantics): peers stop rendering target_id_hex. The wrap ciphertext on relays is scrubbed separately via the retained per-message stream key (see publish_chat).
send_direct_invite
Gift-wrap a Direct Invite (kind 3313) of this community straight to recipient and publish it to the community relays. expires_at_ms (unix ms) optionally bounds its shelf life; label is echoed in the joiner’s Guestbook Join. The bundle hands over the keys; the recipient consents by accepting (nothing joins on receipt). Returns the wrap.
send_edit
Edit one of your own messages (kind 3302): peers re-render target_id_hex with the replacement text. Author-enforced on the read side — only the original author’s edit folds.
send_message
Send a text message to a channel. Derives the channel’s Chat-Plane group key (community_root for a Public channel, the channel key for a Private one), seals it encrypted, and publishes. Returns the message’s rumor id (hex).
send_reaction
React to a channel message (kind 7, NIP-25 shape). target_id_hex / target_author_hex name the reacted-to message; target_kind is its rumor kind (kind::MESSAGE, or kind::COMMENT for a threaded reply); emoji carries the NIP-30 pair when emoji_content is a custom :shortcode:.
send_typing
Ephemeral typing indicator (kind 23311 in a 21059 wrap — relays never store it).
send_webxdc_signal
WebXDC realtime peer signal (kind 3310) — the v2 twin of v1’s publish_webxdc_signal: the same shared content shape, sealed on the channel’s chat plane, DURABLE (a reopening peer backfills a recent ad). Signed by the member’s real identity — a member can’t forge another player’s presence. Failure is non-fatal to callers (the next re-advertise covers a missed ad).
set_banlist
Replace the Banlist (vsk 4, CORD-04 §4) with banned (lowercase-hex npubs), the whole list on every edit. Gated on the reader side by BAN per head plus strict outrank per entry — mirrored here BEFORE the publish, so an unauthorized caller (an SDK bot without the bit, a demoted moderator) fails loudly instead of publishing an edition every reader silently rejects while its own local echo caches the phantom ban.
set_role
Create or edit a Role (vsk 1, CORD-04 §2). role.role_id is the coordinate; a rename or permission change is a versioned edit of the same id. Gated on the reader side by MANAGE_ROLES + outrank.
sever_banned_private_reads
Rotate every private channel a just-banned member could read (CORD-06 §1 applied per channel). A Public-community Ban skips the Refounding (CORD-05 §5), but the banlist and grant strip alone leave the member holding each private channel’s CURRENT epoch key — rotation is the only read severance.
stored_kick_verdict
Did the AUTHORIZED Guestbook coalesce rule member KICKED, per the stored plane?
stored_memberlist
The memberlist from LOCAL state only: the persisted Guestbook, plus locally observed authors (the synced events DB), plus roster grantees, minus the banlist. Instant and offline-correct; sync_guestbook (post-join, boot, reconnect, live ingest) keeps the store current. The live memberlist remains the authoritative walk — a refounding’s rekey recipient set must never trust a possibly-stale store.
sync_community_list
sync_guestbook
Catch the persisted Guestbook up from its stored cursor (a fresh hold seeds from zero). The fetch straddles the network, so the session re-checks before the store writes. Returns the events that were NEW to the store — the caller surfaces them (presence lines) and refreshes on non-empty.
twin_join_material
The twin’s JoinMaterial — the membership subset sealed into the migration m.
unpin_message
Unpin a message: the next edition without the entry (§7 — no deletion event).
upgrade_admin_role_pin_bit
Silent owner-side widening: an Admin role published before PIN_MESSAGES existed gains the bit, so delegated admins can curate pins in communities founded before this build. One edition, idempotent, converges across owner devices (both publish the same widened mask as editions of one entity).