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§
- Authority
View - A community’s folded, delegation-authorized authority — the on-demand read
view (a paged control-plane fetch + fold, nothing persisted).
rolesis the owner-seeded authorized roster (shared algebra with v1);bannedthe enforced banlist.floored/head_entitieslet a writer detect a WITHHELD entity (floored locally but no head folded) before replacing it blind. - Channel
Pins - A channel’s pin list, read from the locally folded head.
- Fetched
Event - A chat event opened from a channel fetch, tagged with the epoch its key decrypted under.
- List
Sync Outcome - 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. - Minted
Link - 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.
- Rekey
Follow - The outcome of a rekey-follow pass.
Enums§
- Bundle
Audience - Who an invite bundle is FOR — which decides the Private-Channel keys it may carry (CORD-05 §1 vs §2).
- Vend
Verdict - 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 openmis 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_hexis 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 thecommunity_rootas 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_ofsettling entitlement against a Grant this client JUST published (with/withoutrole 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
mwould 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. Mirrorscreate_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_channelwith 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 byMANAGE_CHANNELS. - create_
public_ channel_ with_ id create_public_channelwith 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 byMANAGE_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_idis the coordinate. Gated on the reader side byMANAGE_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_controlwhile 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.limitis 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 untilmax_pages, a drained relay, orkeep_pagingreturns 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 INCLUSIVEuntilwith 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 (themstag 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 bycreated_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
memberread 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_idsis a revoke. Gated on the reader side byMANAGE_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 thebase/invite/<naddr>#<fragment>URL.baseis 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_v2is 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, andaccept_public_linkruns 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_membersgates onroot_epoch > 0), so this is the ONLY way to seed a roster every honest client folds. UNLIKErefound_communitythe two sets are DECOUPLED: - refound_
community - Owner/admin Refounding (CORD-06 §3): roll the
community_rootto cryptographically removeremovedfrom 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_joinedis the community THIS call is recording a create/join for — the ONLY community whose entry is (re)stampednow, 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. ReturnsOk(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 userepublish_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-92imetaattachments). Returns the message’s rumor id (hex). - send_
chat_ message_ at send_chat_messagewith an explicit event time. The rumor id is a pure function of its inputs, so a GUI that picksat_mscan 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 (seepublish_chat). - send_
direct_ invite - Gift-wrap a Direct Invite (kind 3313) of this community straight to
recipientand publish it to the community relays.expires_at_ms(unix ms) optionally bounds its shelf life;labelis 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_hexwith 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_hexname the reacted-to message;target_kindis its rumor kind (kind::MESSAGE, orkind::COMMENTfor a threaded reply);emojicarries the NIP-30 pair whenemoji_contentis 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 byBANper 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_idis the coordinate; a rename or permission change is a versioned edit of the same id. Gated on the reader side byMANAGE_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
memberKICKED, 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 livememberlistremains 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).