Expand description
Matrix-shaped event store — rooms/events/state/members/relations/
receipts/account-data/txn-dedup/filters half of messenger.db (the
devices/keys/backup half is matrix_keys_store.rs, a separate work
item). See the messenger protocol notes
§2 for the full DDL this module implements (the “Manager decisions on
this plan” section at the top of that file overrides the body — this
module follows those corrections, noted inline where they apply) and §1
for the id-format rules.
This server adopts Matrix’s own event envelope, state-resolution model,
and CS API shape for everything except federation and /login — CS API
v1.19, room version 11. Crypto stays entirely client-side: every
content blob this module stores is opaque JSON it never inspects
except for m.relates_to (relation bookkeeping, kept in cleartext at
the top level of content even for m.room.encrypted, per the Matrix
spec’s own accepted trade-off — plan §10 item 3) and the redaction
allow-list (§2’s redact_event, which strips by event_type, never by
interpreting the payload’s meaning).
§Single-writer discipline
Every function here takes an already-open Connection; the caller
(state.rs, from P14 onward) holds it behind one std::sync::Mutex,
the same discipline db/social_db already use — this module never
locks anything itself and never calls std::sync::Mutex internally.
That single-writer guarantee is what makes [next_stream_id] safe: it
is always called inside the same transaction as the row it stamps, and
there is never a second writer racing it.
§Cross-database rule
This module stores user_id INTEGER. The nick lives on
messenger_sessions, which crate::nick reads and writes. The
matrix_users.nick column is left in place and is not the source of
truth. There is no identity database. matrix_users maps user_id to
its Matrix id (mxid) once: public_id is immutable, so the mapping
does not change.
The one place a label does end up in this database is the displayname
field of an m.room.member event’s content — the Matrix convention,
and what a client names a DM and lists members by. Callers stamp the
identity database’s effective label into every join/invite member
event they write, and refresh_member_displayname re-stamps it into
the user’s existing member events when the label changes.
Structs§
- Account
Data Row - Displayname
Refresh - What one
refresh_member_displaynamepass did. - DmAdoption
- What
adopt_dm_room_for_legacyneeds to bind a native DM room to a legacy conversation, grouped for the same lint-clean reasonRoomBootstrap’s doc states. - DmMigration
Counts - What one
migrate_dm_conversationcall actually wrote. - DmMigration
Extras - Everything
migrate_dm_conversationneeds beyond the room bootstrap itself, grouped into one value for the same “stays lint-clean without#[allow(clippy::too_many_arguments)]” reasonRoomBootstrap’s own doc comment states. - Legacy
DmDirect Hint - One
m.directmerge hint (plan P12 scope: “merge into their existingm.direct, do not overwrite other entries”) —user_id’s globalm.directaccount data gainspeer_mxidmapped to the migrated room id, alongside whatever entries it already has. - Legacy
DmMessage Import - One legacy
dm_messagesrow queued for import (plan §7 step 5).event_idis minted by the caller ([crate::matrix_migration] in the binary crate) so it can be recorded in the caller’s own bookkeeping (the idempotency proof compares event ids across two runs) without a second round trip back into this module.contentis already the fullorg.example.legacy_dmJSON body ({"legacy_message_id","legacy_conversation_id","nonce_b64","ciphertext_b64"}). - Legacy
DmRead Receipt - One reader’s
m.readreceipt to set once its target message has been imported —up_to_legacy_message_idis the LASTdm_messages.idthis reader actually hadread_atstamped for insocial.db(never a later one — plan P12 scope: “never over-count as read”).ts_msis that same message’s ownread_at, converted to epoch milliseconds. - Matrix
Event - NewState
Event - One state event queued for
create_room_with_state— the same shapeapply_state_eventtakes per-field, minusroom_id/origin_server_ts/now(shared by the whole batch, passed once tocreate_room_with_stateitself rather than repeated per event). - Public
Room Summary - One row of
GET /publicRooms’schunk— a public-join_ruleroom’s directory summary.name/topicareNonewhen the room never had anm.room.name/m.room.topicstate event (plan §5: only achannel-kind room is everjoin_rule = 'public', but this reads the column directly rather than also filtering onkind, so it stays correct even if a future piece ever makes agrouproom public). - Receipt
Row - Redaction
- The redaction
redact_event_markedwrites: which event, in which room, by whom, under which new event id. - Room
- Room
Bootstrap - The room-metadata half of
create_room_with_state’s input — every field [insert_room_row] needs, grouped into one value instead of nine positional arguments (this function’s own params stay lint-clean without an#[allow(clippy::too_many_arguments)], unlike the legacy single-rowcreate_room/[insert_room_row] this batch shares its INSERT with). - Room
Member - State
Event Write - One state event for
apply_state_event: its identity, the state slot it fills, its content, and the two clocks (origin_server_tsin milliseconds for the event row,nowas RFC 3339 for the membership cache’supdated_at).
Enums§
- Deduped
Write - What
insert_timeline_event_deduped/redact_event_dedupedfound: a brand-new write, or the SAME event a prior submission of this(user, device, txn_id)already produced (plan §4send/redactrows: “idempotent per (user, device, txn_id): a repeat returns the SAME event_id with no second insert”). - History
Visibility - History
Window - How much of a room’s timeline a caller may read, per
visible_upper_bound.UpTois INCLUSIVE (a caller who left the room still sees them.room.memberleave event itself, since that is the last event they witnessed). - Join
Rule - Matrix
IdError - Why parsing an mxid failed.
- Matrix
Store Error - Why a write into this store was refused, on top of a real database
failure — same shape as
dm_db::MessageError/IdentityKeyError. - Membership
- Power
Action - An action
m.room.power_levelsgates by a named threshold field, plusStateDefaultfor “may send an otherwise-unlisted state event type”. - Receipt
Type - Room
Kind - Our own room-kind label (
rooms.kind) — drives the power-level/history- visibility defaults of plan §5. Not a Matrix wire field. - TxnDedup
Entry - What
txn_dedup_lookupfound for a given(user_id, device_id, txn_id).
Constants§
- GLOBAL_
ACCOUNT_ DATA_ ROOM room_idvalue meaning “global account data” — seecreate_matrix_schema’s doc comment on why this is"", neverNULL.- MATRIX_
EVENT_ CONTENT_ MAX_ BYTES - Ceiling on the serialized
contentof any state/timeline event this server accepts (plan §3.8, mirroringdm_db::DM_MAX_CIPHERTEXT_BYTES) — checked by every route that takes client-supplied event content (routes::matrix::rooms::put_statefirst; messaging/to-device pieces reuse the same constant rather than minting their own). - MATRIX_
ROOM_ VERSION - Room version pinned for every room this server creates (plan §1) — v11
still lists the creator explicitly in
m.room.power_levels.users, unlike v12’s “infinite implicit power” simplification, which this plan does not adopt. - RESERVED_
LOCALPART_ PREFIX - Localpart prefix reserved for a future Application Service (bridge)
namespace (plan §10 item 6).
matrix_store’s own id generators never mint a localpart starting with this — seeensure_matrix_user— so a later AS registration slots in without a retroactive id-collision audit.
Functions§
- account_
data_ since - Every
(user_id, room_id)account-data row changed strictly aftersince_stream— the/syncdelta shape for both global and per-room account data (caller passesGLOBAL_ACCOUNT_DATA_ROOMor a real room id). - adopt_
dm_ room_ for_ legacy - Bind an EXISTING native DM room to legacy conversation
adoption.legacy_dm_id, instead of minting a second room for the same pair (rooms.dm_pair_keyisUNIQUE, so a second insert is refused): setsrooms.legacy_dm_id, writes any missing legacy key state event, then importsmessagesandreceiptsthrough the shared catch-up body ([catch_up_dm_in_tx]) — all in ONE transaction, so a failure leaves the room exactly as it was.messagesfollowcatch_up_dm_conversation’s contract (not yet inlegacy_dm_message_map, ascending bylegacy_message_id). - apply_
state_ event - Insert one state event and refresh the two projections that read off it:
current_state(always) and, form.room.member/m.room.power_levels, the denormalizedroom_memberscache (plan §2). All inside one transaction with the event row. - can
- Whether
mxid’suser_levelreaches the thresholdactionnames (falling back to the Matrix default when the corresponding field is missing — seePowerAction::field_and_default). - can_
act_ on - Matrix’s room-v11 membership-change authorization rule for acting on
ANOTHER user (manager review, 2026-09-24 — a real gap in the first cut of
canalone): reaching the flatactionthreshold viacanis necessary but not sufficient — the sender’s own level must ALSO be STRICTLY GREATER than the target’s current level, so a level-50 admin can never kick/ban/unban a level-100 owner or an equal-level peer, even though 50 reaches the flat kick/ban threshold.self_leaveis the one exception the spec carves out: a member may always change their OWN membership toleave(declining an invite, or a self-kick, is just a leave) regardless of level — passtrueonly whennew_membershipfor this call isleaveand let the identity check below decide whether it actually applies. - catch_
up_ dm_ conversation - A catch-up pass for an ALREADY migrated room (P15) — the binary crate’s
own
matrix_migrationmodule calls this both from a live legacy DM send’s bridge (routes::dm::create_message, right after its own insert commits) and from a boot pass over a conversationroom_by_legacy_dm_idalready finds a room for.messages(already filtered by the caller to exclude anything already inlegacy_dm_message_map, ordered ascending bylegacy_message_id) lands as neworg.example.legacy_dmtimeline events plus theirlegacy_dm_message_maprows; then each ofreceiptsadvances that reader’sm.readreceipt — never moved backwards ([upsert_receipt_in_tx]’s own guarantee, the SAME one a live/receiptcall gets) — all in ONE transaction. Reuses the exact per-event helpersmigrate_dm_conversationitself uses, so a caught-up room’s rows are indistinguishable from ones a fresh migration (or a live Matrix send) would have produced. - clear_
dm_ pair_ key - Free
room_id’sdm_pair_keyslot (set it toNULL) — called when a prior DM between the same pair is no longer live (one party left), so a freshly created room between the same two users can claim that pair key without violatingrooms.dm_pair_key’sUNIQUEconstraint. The old room keeps every other field; only its claim on the pair key is released. - create_
filter - Store an opaque Filter JSON object, returning its new
filter_id. - create_
matrix_ schema - Create every table/index this module needs — idempotent (
IF NOT EXISTS/INSERT OR IGNOREthroughout), so it runs on every boot. Called from [init_messenger_db] and directly by this module’s own tests against a plain in-memory connection (no SQLCipher key needed for schema creation) — same splitsocial_db::create_social_schemauses. - create_
room - Insert a room’s metadata row only — no bootstrap state events (those go
through
apply_state_eventseparately, e.g.m.room.create,m.room.member,m.room.power_levels, per plan §5/§7). Kept as a standalone single-row entry point for a future non-transactional caller and for this module’s own tests;create_room_with_stateis the P5 batch entry point everything underroutes::matrix::roomsuses instead. - create_
room_ with_ state - Create a room AND its full bootstrap event set in ONE transaction (plan
P5 correction) — the room row ([
insert_room_row]), then every entry ofstate_eventsin order via [apply_state_event_in_tx] (so, e.g., the creator’s ownm.room.memberMUST precedem.room.power_levelsinstate_eventsfor [refresh_power_levels] to see them as an existing member to stamp — see that function’s own doc comment), then commit. A failure on the room insert OR on any one state event leaves NEITHER a room row NOR any event row behind (rusqlite rolls back aTransactiondropped withoutcommit(), the same guarantee [populate_relations]’s duplicate-annotation refusal already relies on). - current_
state_ all - current_
state_ event - ensure_
matrix_ user - Register
user_id’s mxid on first touch (idempotent — a later call for the sameuser_idis a no-op) and return it. Refuses to mint a reserved localpart. - ensure_
schema - Creates every messenger table that does not exist yet (idempotent).
- event_
level - The power level required to send an event of
event_type:power_levels.events[event_type]if present, elsestate_default(Matrix default50) for a state event orevents_default(Matrix default0) for a timeline event. - events_
in_ room_ after - The oldest-first timeline window strictly after
since_stream, capped atlimit— the incremental-/syncand forward-/messagesshape. - events_
in_ room_ before - The newest-first page strictly before
before_stream, capped atlimit—GET /messages?dir=band the initial-/sync“newest N” window (§3.2: the caller reverses it if an ascending page is needed). - forget_
membership - Delete
user_id’sroom_membersrow inroom_id, but ONLY if its current membership isleave—POST /rooms/{roomId}/forget’s own gate AND its effect are the same one-row conditional delete, so there is no separate read-then-write race to worry about. Returns the number of rows deleted (0if the row was absent or not currentlyleave), so the route layer can tell a genuine forget from a no-op gate refusal. - get_
account_ data - get_
event - get_
filter - Fetch
user_id’s ownfilter_id’s definition — scoped touser_idso one account can never read another’s stored filter by guessing an id. - get_
receipt - get_
room - highest_
mapped_ legacy_ message_ id - The highest
legacy_message_idalready imported intoroom_id’sorg.example.legacy_dmtimeline (P15:matrix_migration’s own catch-up cursor).legacy_dm_message_mapcarries noroom_idof its own —dm_messages.idis a single autoincrement column shared by every legacy conversation, not scoped per conversation — so this joins througheventsto find only the rows imported into THIS room. Message ids are ascending within one conversation and every migration/catch-up pass imports every not-yet-mapped row up to “now”, soid > thisis exactly that conversation’s not-yet-imported set — the caller’s own cheap alternative to re-checkinglegacy_dm_message_event_idonce per message.Nonefor a room with no legacy message imported yet (a conversation that had zero messages at migration time). - insert_
legacy_ dm_ message_ map - insert_
timeline_ event - Insert one timeline event (
state_keyalwaysNULL) inside its own transaction: mint the next stream id, insert the row, and — ifcontentcarries a top-levelm.relates_to(cleartext even form.room.encrypted, plan §10 item 3) — populate [relations]. Refusing a duplicatem.annotationrolls back the whole transaction, so a refused call leaves no partialeventsrow. It never records atxn_id: a write that carries one goes throughinsert_timeline_event_deduped, which stores it next to its dedup record in the same transaction. - insert_
timeline_ event_ deduped insert_timeline_event, but dedup-checked and recorded in the SAME transaction as the insert (P6 binding rule) — a repeat of(sender_user_id, device_id, txn_id)returns the ORIGINAL event without a secondeventsrow, and this is race-free even under a hypothetical second writer (never actually possible under this module’s single-writer discipline) because the lookup, the insert, and the dedup record all commit or roll back together.txn_dedup_lookup/txn_dedup_recordtake&Connection; passing&tx(aTransaction) works viaDeref— seeapply_state_event_in_tx’s own sibling functions for the same pattern.- is_
local_ server_ name - Whether
nameis the minted server name or an enabled local alias. - is_
reserved_ localpart - True if
localpartis reserved for the future Application Service namespace (plan §10 item 6) and must never be minted as a real user’s mxid localpart. - legacy_
dm_ message_ event_ id - matrix_
server_ name - Homeserver name used when minting and parsing local ids.
- matrix_
user_ ids - Every
user_idthat has a [matrix_users] row — the boot backfill’s worklist of users whose member events may predate thedisplaynamestamping. - max_
stream_ id - The last stream id issued to any table — the counter’s current value.
Since every stream-ordered table shares this one counter, this is also
the highest
stream_idthat exists anywhere in the database right now. - member_
state_ changed_ in_ window - Every
m.room.memberstate event whose CURRENT value as ofupto_inclusivewas itself set within(since_exclusive, upto_inclusive]—routes::matrix::sync’s §3.3 clause-(b) lazy-load “gap rule” (matrix-spec#942): a member who joined/left/changed profile inside a truncated timeline gap must still be reported even when they never sent anything into the visible window. Bounded toupto_inclusivethroughout (never the LIVEcurrent_stateprojection, which could have moved past a/syncbuild’s own snapshot) so this stays part of one consistent cut. - member_
state_ keys_ in_ window - Every mxid (state key) that has an
m.room.memberevent inroom_idwithstream_idin(from_exclusive, to_inclusive]— the candidates whose membership MAY have changed inside the window; the caller comparesroom_memberagainstmembership_atto find the real transitions. - membership_
at mxid’s membership inroom_idas ofat_stream_id: the latestm.room.memberevent for that state key withstream_id <= at_stream_id, orNonewhen there was none yet (or its content carries no known membership). The point-in-time counterpart ofroom_member— the device-list delta needs “what was this user’s membership BEFORE the sync window opened” to tell a newly shared room from a profile update.- messenger_
db_ config - The SQLCipher store configuration for
pathand a hex key (seeparse_db_key). - migrate_
dm_ conversation - The P12 migration’s own batch entry point:
bootstrap’s room row, then every entry ofstate_events(identical shape tocreate_room_with_state’s own loop — same order requirement: a member’sm.room.membermust precedem.room.power_levelsfor [refresh_power_levels] to see them, see that function’s own doc), then everyextras.messagesentry as anorg.example.legacy_dmtimeline event plus itslegacy_dm_message_maprow, thenextras.receipts, thenextras.direct_hints— all in ONE transaction.extras.messagesmust already be filtered by the caller to exclude anylegacy_message_idalready present inlegacy_dm_message_mapand ordered ascending bylegacy_message_id; this function does not re-check either, since the binary crate’s ownmatrix_migrationmodule already established the room does not exist yet viaroom_by_legacy_dm_idbefore ever calling this. - mxid_
for_ public_ id - Format
public_id(the identity database’s immutableusers.public_id) as this server’s own mxid:@<public_id>:example.org. - mxid_of
user_id’s mxid, ifensure_matrix_userhas ever run for it.- new_
event_ id - Mint a fresh event id:
"$" + 22 random URL-safe-base64 chars— opaque, no reference-hash content addressing (no federation to need it). - new_
room_ id - Mint a fresh room id:
"!" + 22 random URL-safe-base64 chars + ":example.org". - non_
member_ state_ changed_ in_ window - Every state event type OTHER than
m.room.memberwhose CURRENT value as ofupto_inclusivewas itself set within(since_exclusive, upto_inclusive]—routes::matrix::sync’s per-room state delta for every state type this server does not lazy-load (“every OTHER state-event type changed sincesince… always included in full”).since_exclusive = 0naturally reproduces every current state event of these types (the initial-sync/full_statecase), since every such event’s ownstream_idis> 0— one code path serves both. - notification_
count unread_notifications.notification_countforuser_idinroom(plan §3.5): the count of message-like timeline events (see [NOTIFICATION_MESSAGE_TYPES_SQL]) strictly after the FURTHER-AHEAD of the caller’s ownm.read/m.read.privatereceipt — resolved by the target events’ ownstream_id(timeline position), never by a receipt row’s ownstream_idcolumn (a different axis — seeupsert_receipt’s own doc) — and bounded above byvisible_upper_bound(aleave/ban’d member’s count never includes anything past their own departure). Excludes the caller’s own sends (nobody is notified about their own message) and, being restricted to [NOTIFICATION_MESSAGE_TYPES_SQL], every state event andm.reaction. Not a member of the room at all (HistoryWindow::Nothing) is always0— there is nothing this caller could be notified about.- open_
messenger_ db - Opens the messenger store (tesserax-store: SQLCipher, WAL, one writer) and makes sure every table exists. The key is applied first on every connection the engine opens.
- open_
read_ pool - Parallel read-only connections (same file, same key) for the store opened by
open_messenger_db. - parse_
db_ key - Parses the 32-byte database key given as 64 hex characters (the raw SQLCipher key).
- private_
room_ messages_ sent_ since - Timeline (non-state, non-redaction) events
sender_user_idhas sent into any PRIVATE room (join_rule = 'invite') withorigin_server_ts >= since_ms—routes::matrix::messaging’s per-sender daily send-rate counter (plan §3.8: “mirroringDM_MESSAGE_DAILY_LIMIT… own copy of the constant” — the constant itself lives in the route module per this codebase’s convention; this function is only the count query, since raw SQL againstevents/roomsstays inside this module). State events (state_key IS NOT NULL, sent via/state) and redactions are excluded — this counts actual message-shaped sends, not every write a sender makes. - public_
id_ from_ mxid - Parse
@localpart:server_name, returning the localpart — refusing anything not addressed tomatrix_server_name()(plan §1: no federation, so a foreign-server mxid can never be a real local user). - public_
rooms_ page - One page of the public-room directory, ordered by
rooms.id— a stable pagination key (a room id never changes once minted).after_room_idis the previous page’s own last room id (Nonefor the first page);search_term, when non-empty, keeps only rooms whose currentm.room.namecontains it (case-insensitive substring — Matrix’s owngeneric_search_term). - receipts_
changed_ in_ room - Every receipt row in
room_idwhose own write-orderstream_id(NOT the TARGET event’s position — seeupsert_receipt’s own doc on that distinction) falls in(since_exclusive, upto_inclusive]—routes::matrix::sync’s per-roomm.receiptephemeral delta (plan §3.6). - redact_
event - redact_
event_ deduped redact_event, but dedup-checked and recorded in the SAME transaction as the redaction (P6 binding rule, mirroringinsert_timeline_event_deduped— see its own doc for the race-freedom argument).- redact_
event_ marked redact_event, but mergesextra_contentinto the resultingm.room.redactionevent’s own content, in the SAME transaction as the redaction itself —routes::matrix::moderation’s (P11) ONLY caller, which stamps{"org.example.site_moderation": true}on a site-moderator’s redaction of a public-room message (plan §4’s/api/matrix-admin/rooms/{roomId}/moderaterow) so every client can render it distinctly from an ordinary member-initiated redaction. Every other redaction path (redact_event,redact_event_deduped) never needs this and stays untouched.- refresh_
member_ displayname - Re-stamp
displaynameintouser_id‘s ownm.room.memberevent in every room where they are currentlyjoined orinvited, so the other members’ clients see the new name. A room whose current member event already carries exactlydisplaynameis skipped (the pass is idempotent, which is what lets the boot backfill call it for every user on every start). The new event keeps the current content (an invite keeps itsis_directmarker) and only replacesdisplayname; ajoinrefresh is sent by the user, aninviterefresh keeps the inviter as its sender —stripped_invite_statereads the inviter off that field.leave/banrows are never touched. - relations_
of - Every event related to
target_event_id, newest-first (Matrix’s own default order forGET /relations), optionally narrowed to onerel_typeand/or oneevent_type, strictly beforebefore_stream(passi64::MAXfor a first page), capped atlimit— P6’sroutes::matrix::messaging::get_relations. - room_
by_ dm_ pair_ key - The room currently holding
pair_keyas itsdm_pair_key, if any — the binary crate’sroutes::matrix::rooms::create_room’s DM-reuse lookup (plan P5 correction: “a second DM between the same pair returns the existing room id only if it is still a live DM for both”). - room_
by_ legacy_ dm_ id - The room already migrated from
dm_conversations.id = legacy_dm_id, if any — the binary crate’s ownmatrix_migrationmodule’s idempotency gate (plan §7 step 2: “For eachdm_conversationsrow without arooms.legacy_dm_idmatch”). - room_
heroes - Up to
limitother members (join/invite) ofroom_id, oldest membership-change first, excludingexclude_user_id—/sync’sm.heroes(plan §3.4). - room_
member (room_id, user_id)’s singleroom_membersrow, if any — the targeted counterpart ofroom_membersfor a “is this one user a member, and what membership/power level do they hold” check (every gate inroutes::matrix::roomsneeds exactly this, not a full-room scan).- room_
members - Every member row of
room_id, optionally filtered to onemembership. - rooms_
changed_ in_ window - Every room in
room_ids(typically the caller’s currently joined rooms) with at least oneevents,receipts, or room-scopedaccount_datarow whosestream_idfalls in(since_exclusive, upto_inclusive]—routes::matrix::sync’s changed-room prefilter: an incremental sync must not run a room block builder’s own dozen-odd queries against EVERY joined room just to discover that most did not change (with a few hundred rooms that is thousands of queries per wake, all held under the singlemessenger.dbmutex). This set is EXACT, not merely a safe over-approximation: every field a room’s/syncblock can ever report (timeline,state, per-roomaccount_data, andm.receipt) derives from exactly these three tables — the one exception, typing, is an in-memorycrate::typing::TypingRegistryfact this function has no visibility into and the caller checks separately. Returns an empty set without querying whenroom_idsis empty. - rooms_
for_ user - Every room id
user_idhas aroom_membersrow in, optionally filtered to onemembership— the “which rooms is this user in” query behind/sync’s room list andon_credential_revoked’s wake fan-out. - rooms_
with_ member_ events_ in_ window - Every room in
room_idsthat has at least onem.room.memberevent withstream_idin(from_exclusive, to_inclusive]— ONE indexed query for the whole room set, so the device-list delta only does per-room work for rooms where a membership could have changed (an incremental/syncruns it on every wake; seerooms_changed_in_windowfor the same rule). Returns an empty vec without querying whenroom_idsis empty. - set_
local_ aliases - Local DNS aliases of the one homeserver are a server-name CHECK only: an mxid addressed to an enabled
alias resolves to the same local account. No second homeserver exists and ids are always minted
with
matrix_server_name(). Off by default. - set_
matrix_ server_ name - Set the homeserver name once, before any mxid or room id is minted.
The default until this runs is
example.org. - state_
events_ of_ type_ at - The point-in-time projection of every
event_typestate event as ofat_stream_id— the latest such event perstate_keywithstream_id <= at_stream_id— forGET /rooms/{roomId}/members?at=(plan §3.3: a client fetching the full member list as of a given/synctoken, rather than the livecurrent_state_allprojection). Astate_keywhose first event lands AFTERat_stream_idis correctly absent (it did not exist yet at that point in the room’s history). - stripped_
invite_ state - The stripped state Matrix attaches as
unsigned.invite_room_stateon an invite’s ownm.room.memberevent (name/join_rules/encryption/create, plus the inviter’s own membership event) — recomputed fresh every time it is served, NEVER stored on the member event itself:unsignedis not part of the canonical, signed event, and this server’sevents.contentcolumn holds only the canonical, signed shape. Computing it at read time also means it always reflects the room’s CURRENT state (e.g. a rename after the invite was sent), which is what a client opening an invite it received a while ago actually wants to see.inviter_user_idis the member event’s ownsender_user_id— the caller already has it. - stripped_
state_ json - One
{content, state_key, type, sender}MatrixStrippedStateEvent—senderalready resolved to an mxid,contentalready parsed JSON.pub(beyond this module’s ownstripped_invite_state) for the binary crate’sroutes::matrix::syncmodule:GET /sync’sinvite_state.eventsneeds the SAME stripped shape for the invitee’s OWNm.room.memberevent, whichstripped_invite_statedoes not itself include (see that function’s own doc — it strips the INVITER’s state, not the invitee’s own invite event, since its other call site,routes::matrix::client_event_json’sunsigned.invite_room_state, is already attaching it TO that very event). - txn_
dedup_ count_ since - How many
(user_id, device_id)sends/redacts were recorded sincesince(an RFC-3339 timestamp, compared as a plain string againstcreated_atvalues this server ALWAYS writes in that same shape — safe without thedatetime()normalizationlookup_web_credential’s own doc warns about, which is specifically about comparing against SQLite’s owndatetime()output, a different textual shape) —routes::matrix::messaging’s short-window burst-rate counter (plan §3.8: “20 events / 10s per device”). Readstxn_dedup, the one table that already carries a per-DEVICE identity for a write (eventsitself has nodevice_idcolumn). - txn_
dedup_ lookup - Check whether
(user_id, device_id, txn_id)was already handled, without recording anything. - txn_
dedup_ record - Record that
(user_id, device_id, txn_id)has now been handled, producingevent_id(orNonefor a to-device send). Call only aftertxn_dedup_lookupreturnedTxnDedupEntry::NotSeen— this does not itself check for a race, per this module’s single-writer discipline. - txn_
id_ for_ event - The
txn_id(user_id, device_id)used to produceevent_id, if any —client_event_json’sunsigned.transaction_id, which Matrix reveals ONLY to the same device that sent the event, never to any other viewer (including the sender’s OTHER devices). A miss (None) is the overwhelmingly common case (every event not authored by this exact device on this exact send) and costs a single indexedtxn_dedupprimary-key lookup. - upsert_
account_ data - Upsert one account-data entry. Pass
GLOBAL_ACCOUNT_DATA_ROOMfor global data (m.direct,m.push_rules, …); a real room id for per-room data. - upsert_
receipt - Upsert
user_id‘sreceipt_typereceipt inroom_id, refusing to move it BACKWARDS: compared by the TARGET events’ ownstream_id(timeline position), never by the receipt row’s ownstream_idcolumn (which orders receipt writes for/sync’s ephemeral-since-last-batch filter, a different axis). Returns the resultingstream_idstamped on the row — the existing one, unchanged, on a refused backward move. - user_
id_ of - The internal
user_idbehindmxid, if known. - user_
ids_ with_ leave_ transition_ in_ rooms - Every
user_idwhosem.room.memberstate transitioned toleaveorbanin one ofroom_ids, withstream_idin(from_exclusive, to_inclusive]—routes::matrix::keys’sGET /keys/changesdevice_lists.leftset (plan §3.7): a user who no longer shares any room with the caller. This function only finds the CANDIDATE departures inside the given room set; the caller (which already knows its own current shared-room membership) is responsible for the second half of the plan’s rule — excluding anyone who still shares SOME OTHER room with it today. Bounded toroom_ids(typically every room the caller is or was in) rather than scanning every room this server has ever created. Returns an empty vec without querying whenroom_idsis empty. - user_
level power_levels.users[mxid], falling back tousers_default(Matrix default0) when either field is missing.- validate_
power_ levels_ change - The
m.room.power_levelsv11 change-authorization rules beyond the flatPUT statePowerCheck already gating who may send this event type at all (manager review, 2026-09-24 — a real gap: without this, any admin who merely reachesstate_defaultcould PUT themselves straight to100).sender_mxid’s level is read fromold(their authority BEFORE this change takes effect): - visible_
upper_ bound - The history-visibility rule every timeline-reading route enforces
(
routes::matrix::messaging’s/messages,/event,/relations; P10’s/syncreuses this too):