atagen

macltok

public
Star0
Watch0
d9b96952ec6003585d618d852b489c48fc386d5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: docs/mentions-discovery.md

cbb715e11963atagen2026-10-24 23:24 UTC1
# Persistent mentions discovery
cbb715e11963atagen2026-10-24 23:24 UTC2
cbb715e11963atagen2026-10-24 23:24 UTC3
This records the SDK audit and current persistent mentions implementation.
cbb715e11963atagen2026-10-24 23:24 UTC4
Persistent sessions now commit discovery with Sliding Sync responses, retain an
cbb715e11963atagen2026-10-24 23:24 UTC5
encrypted inbox, and expose it through `ClientHandle` and the mirror.
cbb715e11963atagen2026-10-24 23:24 UTC6
Historical coverage remains incomplete because room-list sync and notification
cbb715e11963atagen2026-10-24 23:24 UTC7
history cannot prove that every past mention was observed.
cbb715e11963atagen2026-10-24 23:24 UTC8
cbb715e11963atagen2026-10-24 23:24 UTC9
The audited dependency is Matrix Rust SDK 0.18.0 at fork revision
cbb715e11963atagen2026-10-24 23:24 UTC10
`cc2b13ea185c0f694523a3a48d8aadfe366dd767`. macltok now pins fork revision
cbb715e11963atagen2026-10-24 23:24 UTC11
`7aace883fbe03d4c3c118b55188fe9519ba8ce48`, which adds the response commit
cbb715e11963atagen2026-10-24 23:24 UTC12
hook described below. SDK paths below are relative to the audited revision.
cbb715e11963atagen2026-10-24 23:24 UTC13
cbb715e11963atagen2026-10-24 23:24 UTC14
## Findings
cbb715e11963atagen2026-10-24 23:24 UTC15
cbb715e11963atagen2026-10-24 23:24 UTC16
The SDK can deliver and decrypt events without opening a UI timeline. macltok
cbb715e11963atagen2026-10-24 23:24 UTC17
records observed events and recovers candidates through notification history,
cbb715e11963atagen2026-10-24 23:24 UTC18
but neither source establishes complete historical coverage.
cbb715e11963atagen2026-10-24 23:24 UTC19
cbb715e11963atagen2026-10-24 23:24 UTC20
The SDK audit found that an awaited event handler can hold back the Sliding
cbb715e11963atagen2026-10-24 23:24 UTC21
Sync position until a macltok transaction commits, while a background
cbb715e11963atagen2026-10-24 23:24 UTC22
broadcast consumer cannot provide that guarantee. Handlers cannot propagate
cbb715e11963atagen2026-10-24 23:24 UTC23
commit errors, and response processing outlives cancellation of the calling
cbb715e11963atagen2026-10-24 23:24 UTC24
sync task. These limits motivated the fallible response-commit hook now
cbb715e11963atagen2026-10-24 23:24 UTC25
implemented in the pinned SDK fork.
cbb715e11963atagen2026-10-24 23:24 UTC26
cbb715e11963atagen2026-10-24 23:24 UTC27
## Sync coverage
cbb715e11963atagen2026-10-24 23:24 UTC28
cbb715e11963atagen2026-10-24 23:24 UTC29
macltok constructs `SyncService` in `src/engine/mod.rs` and uses the SDK room
cbb715e11963atagen2026-10-24 23:24 UTC30
list configuration. The list starts with the selective range `0..=19`. After
cbb715e11963atagen2026-10-24 23:24 UTC31
setup it grows by 100 rooms per response, without an SDK-side maximum, until
cbb715e11963atagen2026-10-24 23:24 UTC32
it reaches the server's room count. Recovery and sufficiently long idle
cbb715e11963atagen2026-10-24 23:24 UTC33
periods can return it to the initial selective range before it grows again.
cbb715e11963atagen2026-10-24 23:24 UTC34
The default list timeline limit is one event per room on initial delivery.
cbb715e11963atagen2026-10-24 23:24 UTC35
Subsequent changes for covered rooms continue arriving through sync.
cbb715e11963atagen2026-10-24 23:24 UTC36
cbb715e11963atagen2026-10-24 23:24 UTC37
Opening a room separately subscribes it with a
cbb715e11963atagen2026-10-24 23:24 UTC38
timeline limit of 20. macltok's `update_room_subscriptions` and
cbb715e11963atagen2026-10-24 23:24 UTC39
`watchers::room_subscriptions` manage those explicit subscriptions for active
cbb715e11963atagen2026-10-24 23:24 UTC40
room actors only. The mentions response hook filters entries to joined rooms.
cbb715e11963atagen2026-10-24 23:24 UTC41
The built-in list still includes invited rooms.
cbb715e11963atagen2026-10-24 23:24 UTC42
cbb715e11963atagen2026-10-24 23:24 UTC43
Sources are `crates/matrix-sdk-ui/src/room_list_service/mod.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC44
`crates/matrix-sdk-ui/src/room_list_service/state.rs`, and
cbb715e11963atagen2026-10-24 23:24 UTC45
`crates/matrix-sdk/src/sliding_sync/list/request_generator.rs`.
cbb715e11963atagen2026-10-24 23:24 UTC46
cbb715e11963atagen2026-10-24 23:24 UTC47
The default one-event initial window cannot recover all earlier pings, including
cbb715e11963atagen2026-10-24 23:24 UTC48
messages sent while the client was offline. Raising
cbb715e11963atagen2026-10-24 23:24 UTC49
`SyncServiceBuilder::with_room_list_timeline_limit` increases the initial
cbb715e11963atagen2026-10-24 23:24 UTC50
window but does not establish continuous history. The response hook processes
7c7832147a2eatagen2026-10-25 08:43 UTC51
joined-room events and records limited timelines of rooms it had already
7c7832147a2eatagen2026-10-25 08:43 UTC52
observed, checkpoint discontinuities, and recovery gaps. A room seen for the
7c7832147a2eatagen2026-10-25 08:43 UTC53
first time adds no gap, since every room starts that way.
cbb715e11963atagen2026-10-24 23:24 UTC54
cbb715e11963atagen2026-10-24 23:24 UTC55
## Observation and durable commit
cbb715e11963atagen2026-10-24 23:24 UTC56
cbb715e11963atagen2026-10-24 23:24 UTC57
`Client::subscribe_to_all_room_updates` returns a bounded broadcast receiver
cbb715e11963atagen2026-10-24 23:24 UTC58
of `RoomUpdates`. Its `joined` map includes processed timeline events with raw
cbb715e11963atagen2026-10-24 23:24 UTC59
decrypted content when available and optional evaluated push actions. It does
cbb715e11963atagen2026-10-24 23:24 UTC60
not require a UI timeline. Lag is an error that requires recovery, not an
cbb715e11963atagen2026-10-24 23:24 UTC61
invitation to skip to the next update.
cbb715e11963atagen2026-10-24 23:24 UTC62
cbb715e11963atagen2026-10-24 23:24 UTC63
`Client::observe_events` retains only the latest observation and can miss
cbb715e11963atagen2026-10-24 23:24 UTC64
intermediate events. It is unsuitable for durable discovery.
cbb715e11963atagen2026-10-24 23:24 UTC65
cbb715e11963atagen2026-10-24 23:24 UTC66
An earlier probe registers `Client::add_event_handler` for
cbb715e11963atagen2026-10-24 23:24 UTC67
`Raw<AnySyncTimelineEvent>` with `Room` context before sync starts. Its awaited
cbb715e11963atagen2026-10-24 23:24 UTC68
handler demonstrates event ordering, but it cannot report commit failures
cbb715e11963atagen2026-10-24 23:24 UTC69
reliably and is not the production ingestion path. Production uses the
cbb715e11963atagen2026-10-24 23:24 UTC70
fallible Sliding Sync response hook described below.
cbb715e11963atagen2026-10-24 23:24 UTC71
cbb715e11963atagen2026-10-24 23:24 UTC72
For Sliding Sync v5, the response processor awaits event handlers before
cbb715e11963atagen2026-10-24 23:24 UTC73
updating and persisting `pos`. The response hook commits the inbox first, so
cbb715e11963atagen2026-10-24 23:24 UTC74
a crash permits replay without skipping committed candidates. Stable event
cbb715e11963atagen2026-10-24 23:24 UTC75
keys make replay idempotent. The hook acknowledgement does not wait for
cbb715e11963atagen2026-10-24 23:24 UTC76
context capture, a UI subscriber, or the engine event stream to drain.
cbb715e11963atagen2026-10-24 23:24 UTC77
cbb715e11963atagen2026-10-24 23:24 UTC78
For ordinary event handlers, the SDK logs and discards handler errors, so
cbb715e11963atagen2026-10-24 23:24 UTC79
returning an error after a failed write still permits `pos` to advance. A
cbb715e11963atagen2026-10-24 23:24 UTC80
pending handler alone also does not provide a reliable shutdown path. The SDK
cbb715e11963atagen2026-10-24 23:24 UTC81
runs response handling in a detached task that outlives cancellation of
cbb715e11963atagen2026-10-24 23:24 UTC82
`sync_once` or its caller. Aborting the engine's sync task cannot prevent a
cbb715e11963atagen2026-10-24 23:24 UTC83
suspended handler from later returning and advancing the position. Registering
cbb715e11963atagen2026-10-24 23:24 UTC84
a handler and placing work on a queue without awaiting its commit does not
cbb715e11963atagen2026-10-24 23:24 UTC85
close the crash window.
cbb715e11963atagen2026-10-24 23:24 UTC86
cbb715e11963atagen2026-10-24 23:24 UTC87
macltok uses shared `pos` through its SDK service and commits each response
7c7832147a2eatagen2026-10-25 08:43 UTC88
batch through one serialised mentions actor before the hook acknowledges it.
7c7832147a2eatagen2026-10-25 08:43 UTC89
The actor records events from joined rooms and from rooms the same response
7c7832147a2eatagen2026-10-25 08:43 UTC90
leaves, raw encrypted candidates, limited timeline gaps, and the connection
7c7832147a2eatagen2026-10-25 08:43 UTC91
checkpoint in one store commit. Stable room and event keys make replay
7c7832147a2eatagen2026-10-25 08:43 UTC92
idempotent. The mentions watch snapshot is replaced before the ack, while
7c7832147a2eatagen2026-10-25 08:43 UTC93
mirror and event-stream delivery and context capture run after it. The classic
cbb715e11963atagen2026-10-24 23:24 UTC94
sync `next_batch` ordering differs and does not establish this guarantee.
cbb715e11963atagen2026-10-24 23:24 UTC95
Nor does this ordering make arbitrary concurrent sync loops safe.
cbb715e11963atagen2026-10-24 23:24 UTC96
7c7832147a2eatagen2026-10-25 08:43 UTC97
The checkpoint keeps the position the committed response started from as well
7c7832147a2eatagen2026-10-25 08:43 UTC98
as the one it ended at. A crash after the commit but before the SDK saves `pos`
7c7832147a2eatagen2026-10-25 08:43 UTC99
replays from that earlier position, and the replay is a superset of what was
7c7832147a2eatagen2026-10-25 08:43 UTC100
committed, so it is not treated as a discontinuity. Any other starting position
2c13c8db6174atagen2026-10-25 09:12 UTC101
records a `SyncDiscontinuity` gap.
7c7832147a2eatagen2026-10-25 08:43 UTC102
7c7832147a2eatagen2026-10-25 08:43 UTC103
The hook never fails sync. A failed write keeps the inbox in memory, records a
7c7832147a2eatagen2026-10-25 08:43 UTC104
`StoreFailed` gap, acks, and retries the write a minute later, so a restart
7c7832147a2eatagen2026-10-25 08:43 UTC105
before a write lands shows up as a discontinuity. The hook also stops waiting
7c7832147a2eatagen2026-10-25 08:43 UTC106
after ten seconds and records `HookTimedOut`, so a stuck actor cannot hold the
7c7832147a2eatagen2026-10-25 08:43 UTC107
position lock.
7c7832147a2eatagen2026-10-25 08:43 UTC108
cbb715e11963atagen2026-10-24 23:24 UTC109
Sources are `crates/matrix-sdk/src/event_handler/mod.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC110
`crates/matrix-sdk/src/event_handler/static_events.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC111
`crates/matrix-sdk/src/sliding_sync/client.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC112
`crates/matrix-sdk/src/sliding_sync/mod.rs`, and
cbb715e11963atagen2026-10-24 23:24 UTC113
`crates/matrix-sdk/src/sliding_sync/cache.rs`.
cbb715e11963atagen2026-10-24 23:24 UTC114
cbb715e11963atagen2026-10-24 23:24 UTC115
### SDK commit hook implementation
cbb715e11963atagen2026-10-24 23:24 UTC116
cbb715e11963atagen2026-10-24 23:24 UTC117
The local SDK workspace at `../matrix-rust-sdk-mentions` implements the
cbb715e11963atagen2026-10-24 23:24 UTC118
fallible asynchronous response-commit hook through `ResponseCommitHook` and
cbb715e11963atagen2026-10-24 23:24 UTC119
`ResponseCommitBatch`. `SyncServiceBuilder` forwards it only to its room-list
cbb715e11963atagen2026-10-24 23:24 UTC120
connection, so independent notification lookups are unaffected.
cbb715e11963atagen2026-10-24 23:24 UTC121
cbb715e11963atagen2026-10-24 23:24 UTC122
The hook receives the processed room events, original room payloads, limited
cbb715e11963atagen2026-10-24 23:24 UTC123
flags, pagination tokens, connection identity, and previous and next position
cbb715e11963atagen2026-10-24 23:24 UTC124
identifiers. It runs before response handlers and observers and before `pos`
cbb715e11963atagen2026-10-24 23:24 UTC125
is updated or saved. SDK room state may already have been saved. A successful
cbb715e11963atagen2026-10-24 23:24 UTC126
acknowledgement follows the macltok inbox transaction. A failed or dropped
cbb715e11963atagen2026-10-24 23:24 UTC127
acknowledgement returns a sync error without advancing the position.
cbb715e11963atagen2026-10-24 23:24 UTC128
cbb715e11963atagen2026-10-24 23:24 UTC129
The hook runs outside SDK store locks and supports local futures on wasm and
cbb715e11963atagen2026-10-24 23:24 UTC130
sendable futures natively. The default absent hook preserves existing SDK
7c7832147a2eatagen2026-10-25 08:43 UTC131
behaviour. Detached response processing observes a dropped store
cbb715e11963atagen2026-10-24 23:24 UTC132
acknowledgement after its caller is cancelled. Checkpoint replay works after
cbb715e11963atagen2026-10-24 23:24 UTC133
SDK room state was saved, and stable event keys deduplicate repeated commits.
cbb715e11963atagen2026-10-24 23:24 UTC134
cbb715e11963atagen2026-10-24 23:24 UTC135
The SDK audit and focused validation covered commit ordering, failed and
cbb715e11963atagen2026-10-24 23:24 UTC136
dropped acknowledgements, caller cancellation, replay after room-state
7c7832147a2eatagen2026-10-25 08:43 UTC137
persistence, restoring a previous checkpoint, absent-hook behaviour, builder
cbb715e11963atagen2026-10-24 23:24 UTC138
forwarding, and connection isolation. The SDK UI library also received a
cbb715e11963atagen2026-10-24 23:24 UTC139
focused wasm compilation check with its `js` feature. The SDK changes are
cbb715e11963atagen2026-10-24 23:24 UTC140
published to the user's fork under `mentions-commit-hook`, and the manifest
cbb715e11963atagen2026-10-24 23:24 UTC141
and both macltok lockfiles pin revision
cbb715e11963atagen2026-10-24 23:24 UTC142
`7aace883fbe03d4c3c118b55188fe9519ba8ce48`.
cbb715e11963atagen2026-10-24 23:24 UTC143
cbb715e11963atagen2026-10-24 23:24 UTC144
## Event cache and delayed decryption
cbb715e11963atagen2026-10-24 23:24 UTC145
cbb715e11963atagen2026-10-24 23:24 UTC146
`EventCache::subscribe` starts global room-update ingestion. The SDK room list
cbb715e11963atagen2026-10-24 23:24 UTC147
service already calls it during construction. Its generic update subscription
cbb715e11963atagen2026-10-24 23:24 UTC148
identifies the affected room, then `Room::event_cache` and
cbb715e11963atagen2026-10-24 23:24 UTC149
`RoomEventCache::find_event` can retrieve an event. This does not require a
cbb715e11963atagen2026-10-24 23:24 UTC150
`matrix-sdk-ui::Timeline` or a retained subscriber per room.
cbb715e11963atagen2026-10-24 23:24 UTC151
cbb715e11963atagen2026-10-24 23:24 UTC152
`EventCache::subscribe_to_decryption_reports` reports resolved event IDs,
cbb715e11963atagen2026-10-24 23:24 UTC153
lag, and backup availability. `EventCache::request_decryption` accepts room
cbb715e11963atagen2026-10-24 23:24 UTC154
and Megolm session IDs for explicit retries. `Room::decrypt_event` is also
cbb715e11963atagen2026-10-24 23:24 UTC155
public and can decrypt a retained raw event independently of a UI timeline.
cbb715e11963atagen2026-10-24 23:24 UTC156
cbb715e11963atagen2026-10-24 23:24 UTC157
The late-key probe confirms automatic decryption and retrieval of a structured
cbb715e11963atagen2026-10-24 23:24 UTC158
mention through the global cache without a UI timeline. Cached push actions
cbb715e11963atagen2026-10-24 23:24 UTC159
are optional. Re-evaluate them with `Room::event_push_actions` when needed,
cbb715e11963atagen2026-10-24 23:24 UTC160
and treat missing push context as unresolved rather than a negative result.
cbb715e11963atagen2026-10-24 23:24 UTC161
The room's own membership state is needed to build that push context.
cbb715e11963atagen2026-10-24 23:24 UTC162
cbb715e11963atagen2026-10-24 23:24 UTC163
The cache is not a durable ingestion journal. Its room broadcasts and generic
cbb715e11963atagen2026-10-24 23:24 UTC164
updates can lag, and cache retention can remove events. SQLite and IndexedDB
cbb715e11963atagen2026-10-24 23:24 UTC165
builders select persistent SDK event-cache stores, while the default
cbb715e11963atagen2026-10-24 23:24 UTC166
store-free client uses memory. Even with persistence, the asynchronous cache
cbb715e11963atagen2026-10-24 23:24 UTC167
consumer is not awaited before Sliding Sync saves its position.
cbb715e11963atagen2026-10-24 23:24 UTC168
cbb715e11963atagen2026-10-24 23:24 UTC169
The inbox retains unresolved encrypted candidates as encrypted raw events
7c7832147a2eatagen2026-10-25 08:43 UTC170
within a configurable bound. It retries due candidates at startup. A report of
7c7832147a2eatagen2026-10-25 08:43 UTC171
resolved events retries the encrypted candidates of that room, and lag or a
7c7832147a2eatagen2026-10-25 08:43 UTC172
new backup retries all of them. Candidates still unresolved a week after they
7c7832147a2eatagen2026-10-25 08:43 UTC173
were first seen expire behind a `CandidateExpired` gap. Over the bound the
7c7832147a2eatagen2026-10-25 08:43 UTC174
oldest events go first, and eviction sets the coverage truncation flag. Imported keys and backups cannot reconstruct events
cbb715e11963atagen2026-10-24 23:24 UTC175
already discarded by every cache.
cbb715e11963atagen2026-10-24 23:24 UTC176
cbb715e11963atagen2026-10-24 23:24 UTC177
Sources are `crates/matrix-sdk/src/event_cache/mod.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC178
`crates/matrix-sdk/src/event_cache/redecryptor.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC179
`crates/matrix-sdk/src/event_cache/caches/room/mod.rs`, and
cbb715e11963atagen2026-10-24 23:24 UTC180
`crates/matrix-sdk/src/room/mod.rs`.
cbb715e11963atagen2026-10-24 23:24 UTC181
cbb715e11963atagen2026-10-24 23:24 UTC182
## Mention policy
cbb715e11963atagen2026-10-24 23:24 UTC183
cbb715e11963atagen2026-10-24 23:24 UTC184
Structured `m.mentions.user_ids` and `m.mentions.room` supply semantic
cbb715e11963atagen2026-10-24 23:24 UTC185
candidates. The classifier does not infer pings from rendered bodies or
cbb715e11963atagen2026-10-24 23:24 UTC186
unread counts. A reply relationship alone does not prove that a reply notifies
7c7832147a2eatagen2026-10-25 08:43 UTC187
the user, so reply reasons require confirmation that the referenced event
7c7832147a2eatagen2026-10-25 08:43 UTC188
belongs to the signed-in user plus either an explicit user mention or a
7c7832147a2eatagen2026-10-25 08:43 UTC189
notifying highlight.
cbb715e11963atagen2026-10-24 23:24 UTC190
cbb715e11963atagen2026-10-24 23:24 UTC191
`Room::event_push_actions` evaluates push rules but does not independently
7c7832147a2eatagen2026-10-25 08:43 UTC192
filter ignored senders. The service reads the ignore list once per response
7c7832147a2eatagen2026-10-25 08:43 UTC193
and again when it changes, and rechecks entries then.
cbb715e11963atagen2026-10-24 23:24 UTC194
cbb715e11963atagen2026-10-24 23:24 UTC195
A room mute can suppress evaluated notification actions while structured
cbb715e11963atagen2026-10-24 23:24 UTC196
mention metadata remains present. The classifier retains explicit user
7c7832147a2eatagen2026-10-25 08:43 UTC197
mentions in muted rooms and marks notification delivery as suppressed. Room-wide
cbb715e11963atagen2026-10-24 23:24 UTC198
reasons require the relevant push conditions and sender power level. Missing
7c7832147a2eatagen2026-10-25 08:43 UTC199
authorisation context remains unresolved for reevaluation. Keyword-only
cbb715e11963atagen2026-10-24 23:24 UTC200
notifications are outside the inbox. The classifier excludes the signed-in
cbb715e11963atagen2026-10-24 23:24 UTC201
user's own events even when they contain explicit mention metadata.
cbb715e11963atagen2026-10-24 23:24 UTC202
cbb715e11963atagen2026-10-24 23:24 UTC203
The existing `NotificationClient` combines notification evaluation with
cbb715e11963atagen2026-10-24 23:24 UTC204
ignored-sender filtering. Its public lookup takes known room and event IDs.
cbb715e11963atagen2026-10-24 23:24 UTC205
It is useful for resolving a candidate, but filtered results erase the
cbb715e11963atagen2026-10-24 23:24 UTC206
distinction between mute policy and an absent semantic mention. It is not the
cbb715e11963atagen2026-10-24 23:24 UTC207
sole classifier for this inbox.
cbb715e11963atagen2026-10-24 23:24 UTC208
cbb715e11963atagen2026-10-24 23:24 UTC209
## Historical candidates and gaps
cbb715e11963atagen2026-10-24 23:24 UTC210
cbb715e11963atagen2026-10-24 23:24 UTC211
Ruma exposes
cbb715e11963atagen2026-10-24 23:24 UTC212
`api::client::push::get_notifications::v3::Request`, sent with `Client::send`.
cbb715e11963atagen2026-10-24 23:24 UTC213
It accepts opaque `from`, optional `limit`, and optional `only` fields.
cbb715e11963atagen2026-10-24 23:24 UTC214
Responses contain notifications and an optional `next_token`. macltok requests
cbb715e11963atagen2026-10-24 23:24 UTC215
50 notifications per page. Entries include raw events, room IDs, timestamps,
7c7832147a2eatagen2026-10-25 08:43 UTC216
actions, and server notification read state, which does not initialise or
cbb715e11963atagen2026-10-24 23:24 UTC217
replace local inbox read state.
cbb715e11963atagen2026-10-24 23:24 UTC218
cbb715e11963atagen2026-10-24 23:24 UTC219
The service uses this endpoint as a bounded candidate source, validates
cbb715e11963atagen2026-10-24 23:24 UTC220
events after decryption, and applies the live mention policy. Each page's
cbb715e11963atagen2026-10-24 23:24 UTC221
observed candidates and cursor are committed together in the encrypted inbox,
cbb715e11963atagen2026-10-24 23:24 UTC222
including unresolved encrypted candidates. A failed commit leaves the prior
cbb715e11963atagen2026-10-24 23:24 UTC223
cursor available for replay.
cbb715e11963atagen2026-10-24 23:24 UTC224
cbb715e11963atagen2026-10-24 23:24 UTC225
An exhausted notification list does not mean complete room history. Muted-room
7c7832147a2eatagen2026-10-25 08:43 UTC226
pings, encrypted mentions not recognised by the server, expired notifications,
cbb715e11963atagen2026-10-24 23:24 UTC227
and server-specific retention can be absent. Recovery inspects at most 20 pages
cbb715e11963atagen2026-10-24 23:24 UTC228
of 50 notifications per run and resumes from committed cursors later. The
cbb715e11963atagen2026-10-24 23:24 UTC229
older-history cursor is separate from the cursor for scans from the newest
cbb715e11963atagen2026-10-24 23:24 UTC230
notifications. A newest scan records an anchor and runs at startup and every
cbb715e11963atagen2026-10-24 23:24 UTC231
60 seconds. It follows the committed cursor until it reaches that anchor,
cbb715e11963atagen2026-10-24 23:24 UTC232
resuming on later runs when needed to find newly added notifications.
cbb715e11963atagen2026-10-24 23:24 UTC233
cbb715e11963atagen2026-10-24 23:24 UTC234
SDK cache gaps retain pagination tokens and support room pagination, but
cbb715e11963atagen2026-10-24 23:24 UTC235
running that pagination mutates the live cache. Mention context capture uses
cbb715e11963atagen2026-10-24 23:24 UTC236
independent `/context` and `/messages` requests. Tokens may expire. Context can
cbb715e11963atagen2026-10-24 23:24 UTC237
be retried around a known event while the room remains joined, and known gaps
cbb715e11963atagen2026-10-24 23:24 UTC238
remain visible when continuity cannot be established.
cbb715e11963atagen2026-10-24 23:24 UTC239
cbb715e11963atagen2026-10-24 23:24 UTC240
Coverage exposes the earliest observed timestamp, history-endpoint support,
cbb715e11963atagen2026-10-24 23:24 UTC241
recovery activity, known room gaps, unresolved candidate count, and retention
cbb715e11963atagen2026-10-24 23:24 UTC242
truncation. The earliest timestamp is a summary, not proof that every room is
7c7832147a2eatagen2026-10-25 08:43 UTC243
covered after that time. Each gap carries a typed `MentionGapReason`.
cbb715e11963atagen2026-10-24 23:24 UTC244
cbb715e11963atagen2026-10-24 23:24 UTC245
Complete offline mention recovery across arbitrary muted and encrypted rooms
cbb715e11963atagen2026-10-24 23:24 UTC246
is not supplied by this endpoint or by the current sync configuration. It
cbb715e11963atagen2026-10-24 23:24 UTC247
would require a retained, replayable event source or bounded per-room history
cbb715e11963atagen2026-10-24 23:24 UTC248
recovery with proof of continuity. The notification endpoint does not provide
cbb715e11963atagen2026-10-24 23:24 UTC249
that guarantee.
cbb715e11963atagen2026-10-24 23:24 UTC250
cbb715e11963atagen2026-10-24 23:24 UTC251
## Storage, context, and API
cbb715e11963atagen2026-10-24 23:24 UTC252
cbb715e11963atagen2026-10-24 23:24 UTC253
`src/mentions/store` implements encrypted SQLite and IndexedDB record stores.
cbb715e11963atagen2026-10-24 23:24 UTC254
Persistent session construction opens the account store, and local logout
cbb715e11963atagen2026-10-24 23:24 UTC255
deletes it alongside the SDK stores. The mentions actor owns the store for
cbb715e11963atagen2026-10-24 23:24 UTC256
inbox commits and discovery checkpoints.
cbb715e11963atagen2026-10-24 23:24 UTC257
cbb715e11963atagen2026-10-24 23:24 UTC258
The passphrase wraps a random SDK `StoreCipher`. Logical keys are hashed, and
cbb715e11963atagen2026-10-24 23:24 UTC259
authenticated encrypted records include schema, account identity, and logical
7c7832147a2eatagen2026-10-25 08:43 UTC260
key. Restore treats mismatched accounts, unsupported schemas, corrupt records,
7c7832147a2eatagen2026-10-25 08:43 UTC261
and inconsistent record counts or byte totals as unreadable, empties the store
7c7832147a2eatagen2026-10-25 08:43 UTC262
and records a `StoreReset` gap rather than failing `Account::build`. A store
7c7832147a2eatagen2026-10-25 08:43 UTC263
that cannot be opened at all leaves the inbox in memory behind `StoreFailed`.
7c7832147a2eatagen2026-10-25 08:43 UTC264
Atomic transactions compare the previous encrypted header before publishing a
7c7832147a2eatagen2026-10-25 08:43 UTC265
new revision, so stale writers cannot overwrite a newer checkpoint.
cbb715e11963atagen2026-10-24 23:24 UTC266
cbb715e11963atagen2026-10-24 23:24 UTC267
The default budget is 64 MiB of encrypted records, hashed keys, header, and
cbb715e11963atagen2026-10-24 23:24 UTC268
wrapped cipher, excluding database page overhead. The store record cap is
cbb715e11963atagen2026-10-24 23:24 UTC269
35,128. The inbox defaults to 5,000 entries, 25,000 shared context events, and
cbb715e11963atagen2026-10-24 23:24 UTC270
5,000 unresolved candidates within that byte budget. When a size or record
cbb715e11963atagen2026-10-24 23:24 UTC271
limit requires eviction, the store drops bounded data and marks coverage
cbb715e11963atagen2026-10-24 23:24 UTC272
truncated.
cbb715e11963atagen2026-10-24 23:24 UTC273
cbb715e11963atagen2026-10-24 23:24 UTC274
The storage tests cover atomic restore of records and checkpoints, stale
7c7832147a2eatagen2026-10-25 08:43 UTC275
writers, capacity failure, encrypted physical records, resets on wrong
7c7832147a2eatagen2026-10-25 08:43 UTC276
credentials or account, corruption and unsupported schemas, and injected
7c7832147a2eatagen2026-10-25 08:43 UTC277
SQLite write failure. The inbox tests cover failed writes, checkpoint-only
7c7832147a2eatagen2026-10-25 08:43 UTC278
writes, replay from the previous checkpoint, reset of an unreadable inbox,
7c7832147a2eatagen2026-10-25 08:43 UTC279
notification recovery, live context updates, and capture persistence across
7c7832147a2eatagen2026-10-25 08:43 UTC280
session resume.
7c7832147a2eatagen2026-10-25 08:43 UTC281
7c7832147a2eatagen2026-10-25 08:43 UTC282
The store holds two records. `inbox` carries entries, saved events, context
7c7832147a2eatagen2026-10-25 08:43 UTC283
windows, candidates, edits, redactions, coverage and recovery cursors. `sync`
2c13c8db6174atagen2026-10-25 09:12 UTC284
carries the connection checkpoints, observed rooms and the inbox
2c13c8db6174atagen2026-10-25 09:12 UTC285
revision it belongs to. Every response writes `sync`, and
7c7832147a2eatagen2026-10-25 08:43 UTC286
`inbox` only when the response changed it, both in one commit. A quiet
7c7832147a2eatagen2026-10-25 08:43 UTC287
response therefore costs one small record, while a changed inbox is still
7c7832147a2eatagen2026-10-25 08:43 UTC288
reserialised and re-encrypted whole. Background completions are applied in
7c7832147a2eatagen2026-10-25 08:43 UTC289
memory and written together at most once a second. When the budget is short,
7c7832147a2eatagen2026-10-25 08:43 UTC290
eviction runs against a size estimate and the inbox is written once.
7c7832147a2eatagen2026-10-25 08:43 UTC291
Decrypted previews, saved context, raw candidates, pending capture state,
7c7832147a2eatagen2026-10-25 08:43 UTC292
media source registrations, read state, coverage, and sync checkpoints stay
7c7832147a2eatagen2026-10-25 08:43 UTC293
inside the authenticated encrypted payloads. SDK SQLite custom values remain plaintext and are not used
cbb715e11963atagen2026-10-24 23:24 UTC294
for decrypted mentions.
cbb715e11963atagen2026-10-24 23:24 UTC295
cbb715e11963atagen2026-10-24 23:24 UTC296
Context capture in `src/engine/context.rs` sizes the before and after requests
7c7832147a2eatagen2026-10-25 08:43 UTC297
independently and retains the media registrations produced by normalisation.
cbb715e11963atagen2026-10-24 23:24 UTC298
Each capture requests up to 10 events on each side. At most two capture jobs
cbb715e11963atagen2026-10-24 23:24 UTC299
run concurrently, with up to 128 pending or failed entries considered for
cbb715e11963atagen2026-10-24 23:24 UTC300
scheduling at a time. Capture uses independent `/context` and `/messages`
cbb715e11963atagen2026-10-24 23:24 UTC301
requests, so it does not paginate the live cache. Media source registrations
cbb715e11963atagen2026-10-24 23:24 UTC302
are encrypted with saved events and registered again when context is read.
cbb715e11963atagen2026-10-24 23:24 UTC303
Media bodies and key material do not enter the mentions public model.
cbb715e11963atagen2026-10-24 23:24 UTC304
7c7832147a2eatagen2026-10-25 08:43 UTC305
One account-scoped mentions actor serialises store commits for entries,
cbb715e11963atagen2026-10-24 23:24 UTC306
unresolved candidates, shared context events, read state, revisions, discovery
cbb715e11963atagen2026-10-24 23:24 UTC307
checkpoints, and retention decisions.
cbb715e11963atagen2026-10-24 23:24 UTC308
cbb715e11963atagen2026-10-24 23:24 UTC309
Pending capture state is durable with the inbox, so interrupted capture can
cbb715e11963atagen2026-10-24 23:24 UTC310
resume after startup. When a room leaves the joined state, saved context
cbb715e11963atagen2026-10-24 23:24 UTC311
remains readable and its capture state becomes unavailable. Refetch is
cbb715e11963atagen2026-10-24 23:24 UTC312
disabled after departure.
cbb715e11963atagen2026-10-24 23:24 UTC313
cbb715e11963atagen2026-10-24 23:24 UTC314
Store publication follows commit. `ClientHandle` exposes page, context, retry,
cbb715e11963atagen2026-10-24 23:24 UTC315
read, read-through, and recovery methods. The public `Command`, mention models,
cbb715e11963atagen2026-10-24 23:24 UTC316
mirror state, and updates support serde when the `serde` feature is enabled,
cbb715e11963atagen2026-10-24 23:24 UTC317
so native and browser consumers can transport them. Browser worker integration
cbb715e11963atagen2026-10-24 23:24 UTC318
is a consumer phase after SDK and macltok revision publication. Malice's worker
cbb715e11963atagen2026-10-24 23:24 UTC319
still needs mention variants in its explicit `Request` dispatch and TypeScript
cbb715e11963atagen2026-10-24 23:24 UTC320
methods.
cbb715e11963atagen2026-10-24 23:24 UTC321
cbb715e11963atagen2026-10-24 23:24 UTC322
Mirror snapshots contain at most 50 entry previews, coverage, unread count,
cbb715e11963atagen2026-10-24 23:24 UTC323
and a committed revision boundary, with no saved context bodies. The watcher
cbb715e11963atagen2026-10-24 23:24 UTC324
coalesces updates and computes upserts and removals relative to the previous
cbb715e11963atagen2026-10-24 23:24 UTC325
bounded snapshot it observed. A missed update therefore leaves the latest
cbb715e11963atagen2026-10-24 23:24 UTC326
snapshot authoritative. A removal can mean an entry fell out of the top 50,
cbb715e11963atagen2026-10-24 23:24 UTC327
and does not imply that it was deleted from the inbox. Pagination uses
cbb715e11963atagen2026-10-24 23:24 UTC328
timestamp and stable event key within a discovery revision boundary.
cbb715e11963atagen2026-10-24 23:24 UTC329
Read-through boundaries exclude mentions first discovered or reactivated after
cbb715e11963atagen2026-10-24 23:24 UTC330
that boundary, including older messages resolved by late decryption.
cbb715e11963atagen2026-10-24 23:24 UTC331
cbb715e11963atagen2026-10-24 23:24 UTC332
## Executable probes
cbb715e11963atagen2026-10-24 23:24 UTC333
cbb715e11963atagen2026-10-24 23:24 UTC334
`tests/mentions_discovery.rs` exercises unopened-room update and cache access,
7c7832147a2eatagen2026-10-25 08:43 UTC335
push-rule behaviour for ignored senders and muted rooms, and typed
cbb715e11963atagen2026-10-24 23:24 UTC336
notification-history pagination.
cbb715e11963atagen2026-10-24 23:24 UTC337
cbb715e11963atagen2026-10-24 23:24 UTC338
`tests/mentions_decryption.rs` exchanges a real encrypted event and a delayed
cbb715e11963atagen2026-10-24 23:24 UTC339
room key between two mock clients, then retrieves the decrypted mention
cbb715e11963atagen2026-10-24 23:24 UTC340
without opening a UI timeline.
cbb715e11963atagen2026-10-24 23:24 UTC341
cbb715e11963atagen2026-10-24 23:24 UTC342
`tests/mentions_checkpoint.rs` holds an event handler pending and verifies
cbb715e11963atagen2026-10-24 23:24 UTC343
that the SDK has not persisted its position. It cancels the calling task,
cbb715e11963atagen2026-10-24 23:24 UTC344
then acknowledges the handler and verifies that detached response processing
cbb715e11963atagen2026-10-24 23:24 UTC345
still saves the position. A new Sliding Sync instance restores that position.
cbb715e11963atagen2026-10-24 23:24 UTC346
This is the historical event-handler ordering probe. It does not exercise the
cbb715e11963atagen2026-10-24 23:24 UTC347
production response-commit hook or the durable inbox transaction. It also
cbb715e11963atagen2026-10-24 23:24 UTC348
demonstrates that returning a handler error still advances the position.
cbb715e11963atagen2026-10-24 23:24 UTC349
cbb715e11963atagen2026-10-24 23:24 UTC350
`tests/mentions_inbox.rs` covers live mention classification, read-through
cbb715e11963atagen2026-10-24 23:24 UTC351
with revision boundaries, independent context limits, and persistence across
cbb715e11963atagen2026-10-24 23:24 UTC352
session resume. `tests/mentions_late_decryption.rs` verifies that a delayed room key
cbb715e11963atagen2026-10-24 23:24 UTC353
updates the production inbox once. `src/mentions/service_tests.rs` covers
7c7832147a2eatagen2026-10-25 08:43 UTC354
failed and checkpoint-only writes, checkpoint replay, store reset,
7c7832147a2eatagen2026-10-25 08:43 UTC355
notification-history recovery, and context updates.
cbb715e11963atagen2026-10-24 23:24 UTC356
`src/mentions/store/tests.rs` exercises encrypted record restore, capacity,
cbb715e11963atagen2026-10-24 23:24 UTC357
stale writers, and corruption handling.
cbb715e11963atagen2026-10-24 23:24 UTC358
7c7832147a2eatagen2026-10-25 08:43 UTC359
These probes establish SDK behaviour against mocked responses. They do not
cbb715e11963atagen2026-10-24 23:24 UTC360
establish homeserver notification retention, room-list completeness, or
cbb715e11963atagen2026-10-24 23:24 UTC361
complete history on a deployed server.
cbb715e11963atagen2026-10-24 23:24 UTC362
cbb715e11963atagen2026-10-24 23:24 UTC363
Focused validation passed 63 native tests and 12 shared inbox and IndexedDB
cbb715e11963atagen2026-10-24 23:24 UTC364
tests in headless Firefox. Compilation checks also pass without storage
cbb715e11963atagen2026-10-24 23:24 UTC365
features and for wasm with IndexedDB and mirror support. The full flake gate
cbb715e11963atagen2026-10-24 23:24 UTC366
was skipped by request.