atagen

macltok

public
Star0
Watch0
3dab4a4e73f48986ea8e34215bdeb7d5d3870773
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
82d37c369846atagen2026-10-25 10:55 UTC105
before a write lands shows up as a discontinuity. The gap goes again once a
82d37c369846atagen2026-10-25 10:55 UTC106
later write lands, since nothing was lost. The hook gives up after ten seconds
82d37c369846atagen2026-10-25 10:55 UTC107
waiting for room in the actor's queue, drops the response and records
82d37c369846atagen2026-10-25 10:55 UTC108
`HookTimedOut`. Once queued, it waits another ten seconds for the commit and
82d37c369846atagen2026-10-25 10:55 UTC109
then acks regardless, because a queued response still commits in order.
7c7832147a2eatagen2026-10-25 08:43 UTC110
cbb715e11963atagen2026-10-24 23:24 UTC111
Sources are `crates/matrix-sdk/src/event_handler/mod.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC112
`crates/matrix-sdk/src/event_handler/static_events.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC113
`crates/matrix-sdk/src/sliding_sync/client.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC114
`crates/matrix-sdk/src/sliding_sync/mod.rs`, and
cbb715e11963atagen2026-10-24 23:24 UTC115
`crates/matrix-sdk/src/sliding_sync/cache.rs`.
cbb715e11963atagen2026-10-24 23:24 UTC116
cbb715e11963atagen2026-10-24 23:24 UTC117
### SDK commit hook implementation
cbb715e11963atagen2026-10-24 23:24 UTC118
cbb715e11963atagen2026-10-24 23:24 UTC119
The local SDK workspace at `../matrix-rust-sdk-mentions` implements the
cbb715e11963atagen2026-10-24 23:24 UTC120
fallible asynchronous response-commit hook through `ResponseCommitHook` and
cbb715e11963atagen2026-10-24 23:24 UTC121
`ResponseCommitBatch`. `SyncServiceBuilder` forwards it only to its room-list
cbb715e11963atagen2026-10-24 23:24 UTC122
connection, so independent notification lookups are unaffected.
cbb715e11963atagen2026-10-24 23:24 UTC123
cbb715e11963atagen2026-10-24 23:24 UTC124
The hook receives the processed room events, original room payloads, limited
cbb715e11963atagen2026-10-24 23:24 UTC125
flags, pagination tokens, connection identity, and previous and next position
cbb715e11963atagen2026-10-24 23:24 UTC126
identifiers. It runs before response handlers and observers and before `pos`
cbb715e11963atagen2026-10-24 23:24 UTC127
is updated or saved. SDK room state may already have been saved. A successful
cbb715e11963atagen2026-10-24 23:24 UTC128
acknowledgement follows the macltok inbox transaction. A failed or dropped
cbb715e11963atagen2026-10-24 23:24 UTC129
acknowledgement returns a sync error without advancing the position.
cbb715e11963atagen2026-10-24 23:24 UTC130
cbb715e11963atagen2026-10-24 23:24 UTC131
The hook runs outside SDK store locks and supports local futures on wasm and
cbb715e11963atagen2026-10-24 23:24 UTC132
sendable futures natively. The default absent hook preserves existing SDK
7c7832147a2eatagen2026-10-25 08:43 UTC133
behaviour. Detached response processing observes a dropped store
cbb715e11963atagen2026-10-24 23:24 UTC134
acknowledgement after its caller is cancelled. Checkpoint replay works after
cbb715e11963atagen2026-10-24 23:24 UTC135
SDK room state was saved, and stable event keys deduplicate repeated commits.
cbb715e11963atagen2026-10-24 23:24 UTC136
cbb715e11963atagen2026-10-24 23:24 UTC137
The SDK audit and focused validation covered commit ordering, failed and
cbb715e11963atagen2026-10-24 23:24 UTC138
dropped acknowledgements, caller cancellation, replay after room-state
7c7832147a2eatagen2026-10-25 08:43 UTC139
persistence, restoring a previous checkpoint, absent-hook behaviour, builder
cbb715e11963atagen2026-10-24 23:24 UTC140
forwarding, and connection isolation. The SDK UI library also received a
cbb715e11963atagen2026-10-24 23:24 UTC141
focused wasm compilation check with its `js` feature. The SDK changes are
cbb715e11963atagen2026-10-24 23:24 UTC142
published to the user's fork under `mentions-commit-hook`, and the manifest
cbb715e11963atagen2026-10-24 23:24 UTC143
and both macltok lockfiles pin revision
cbb715e11963atagen2026-10-24 23:24 UTC144
`7aace883fbe03d4c3c118b55188fe9519ba8ce48`.
cbb715e11963atagen2026-10-24 23:24 UTC145
cbb715e11963atagen2026-10-24 23:24 UTC146
## Event cache and delayed decryption
cbb715e11963atagen2026-10-24 23:24 UTC147
cbb715e11963atagen2026-10-24 23:24 UTC148
`EventCache::subscribe` starts global room-update ingestion. The SDK room list
cbb715e11963atagen2026-10-24 23:24 UTC149
service already calls it during construction. Its generic update subscription
cbb715e11963atagen2026-10-24 23:24 UTC150
identifies the affected room, then `Room::event_cache` and
cbb715e11963atagen2026-10-24 23:24 UTC151
`RoomEventCache::find_event` can retrieve an event. This does not require a
cbb715e11963atagen2026-10-24 23:24 UTC152
`matrix-sdk-ui::Timeline` or a retained subscriber per room.
cbb715e11963atagen2026-10-24 23:24 UTC153
cbb715e11963atagen2026-10-24 23:24 UTC154
`EventCache::subscribe_to_decryption_reports` reports resolved event IDs,
cbb715e11963atagen2026-10-24 23:24 UTC155
lag, and backup availability. `EventCache::request_decryption` accepts room
cbb715e11963atagen2026-10-24 23:24 UTC156
and Megolm session IDs for explicit retries. `Room::decrypt_event` is also
cbb715e11963atagen2026-10-24 23:24 UTC157
public and can decrypt a retained raw event independently of a UI timeline.
cbb715e11963atagen2026-10-24 23:24 UTC158
cbb715e11963atagen2026-10-24 23:24 UTC159
The late-key probe confirms automatic decryption and retrieval of a structured
cbb715e11963atagen2026-10-24 23:24 UTC160
mention through the global cache without a UI timeline. Cached push actions
cbb715e11963atagen2026-10-24 23:24 UTC161
are optional. Re-evaluate them with `Room::event_push_actions` when needed,
cbb715e11963atagen2026-10-24 23:24 UTC162
and treat missing push context as unresolved rather than a negative result.
cbb715e11963atagen2026-10-24 23:24 UTC163
The room's own membership state is needed to build that push context.
cbb715e11963atagen2026-10-24 23:24 UTC164
cbb715e11963atagen2026-10-24 23:24 UTC165
The cache is not a durable ingestion journal. Its room broadcasts and generic
cbb715e11963atagen2026-10-24 23:24 UTC166
updates can lag, and cache retention can remove events. SQLite and IndexedDB
cbb715e11963atagen2026-10-24 23:24 UTC167
builders select persistent SDK event-cache stores, while the default
cbb715e11963atagen2026-10-24 23:24 UTC168
store-free client uses memory. Even with persistence, the asynchronous cache
cbb715e11963atagen2026-10-24 23:24 UTC169
consumer is not awaited before Sliding Sync saves its position.
cbb715e11963atagen2026-10-24 23:24 UTC170
cbb715e11963atagen2026-10-24 23:24 UTC171
The inbox retains unresolved encrypted candidates as encrypted raw events
7c7832147a2eatagen2026-10-25 08:43 UTC172
within a configurable bound. It retries due candidates at startup. A report of
7c7832147a2eatagen2026-10-25 08:43 UTC173
resolved events retries the encrypted candidates of that room, and lag or a
7c7832147a2eatagen2026-10-25 08:43 UTC174
new backup retries all of them. Candidates still unresolved a week after they
7c7832147a2eatagen2026-10-25 08:43 UTC175
were first seen expire behind a `CandidateExpired` gap. Over the bound the
7c7832147a2eatagen2026-10-25 08:43 UTC176
oldest events go first, and eviction sets the coverage truncation flag. Imported keys and backups cannot reconstruct events
cbb715e11963atagen2026-10-24 23:24 UTC177
already discarded by every cache.
cbb715e11963atagen2026-10-24 23:24 UTC178
cbb715e11963atagen2026-10-24 23:24 UTC179
Sources are `crates/matrix-sdk/src/event_cache/mod.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC180
`crates/matrix-sdk/src/event_cache/redecryptor.rs`,
cbb715e11963atagen2026-10-24 23:24 UTC181
`crates/matrix-sdk/src/event_cache/caches/room/mod.rs`, and
cbb715e11963atagen2026-10-24 23:24 UTC182
`crates/matrix-sdk/src/room/mod.rs`.
cbb715e11963atagen2026-10-24 23:24 UTC183
cbb715e11963atagen2026-10-24 23:24 UTC184
## Mention policy
cbb715e11963atagen2026-10-24 23:24 UTC185
cbb715e11963atagen2026-10-24 23:24 UTC186
Structured `m.mentions.user_ids` and `m.mentions.room` supply semantic
cbb715e11963atagen2026-10-24 23:24 UTC187
candidates. The classifier does not infer pings from rendered bodies or
cbb715e11963atagen2026-10-24 23:24 UTC188
unread counts. A reply relationship alone does not prove that a reply notifies
7c7832147a2eatagen2026-10-25 08:43 UTC189
the user, so reply reasons require confirmation that the referenced event
7c7832147a2eatagen2026-10-25 08:43 UTC190
belongs to the signed-in user plus either an explicit user mention or a
7c7832147a2eatagen2026-10-25 08:43 UTC191
notifying highlight.
cbb715e11963atagen2026-10-24 23:24 UTC192
cbb715e11963atagen2026-10-24 23:24 UTC193
`Room::event_push_actions` evaluates push rules but does not independently
7c7832147a2eatagen2026-10-25 08:43 UTC194
filter ignored senders. The service reads the ignore list once per response
7c7832147a2eatagen2026-10-25 08:43 UTC195
and again when it changes, and rechecks entries then.
cbb715e11963atagen2026-10-24 23:24 UTC196
cbb715e11963atagen2026-10-24 23:24 UTC197
A room mute can suppress evaluated notification actions while structured
cbb715e11963atagen2026-10-24 23:24 UTC198
mention metadata remains present. The classifier retains explicit user
7c7832147a2eatagen2026-10-25 08:43 UTC199
mentions in muted rooms and marks notification delivery as suppressed. Room-wide
cbb715e11963atagen2026-10-24 23:24 UTC200
reasons require the relevant push conditions and sender power level. Missing
96680d38cc31atagen2026-10-25 09:20 UTC201
authorisation context remains unresolved for reevaluation. An event that
96680d38cc31atagen2026-10-25 09:20 UTC202
notifies and highlights with no other reason, such as a loud keyword, enters the
82d37c369846atagen2026-10-25 10:55 UTC203
inbox as `Keyword`. The SDK's keyword rules only notify and play a sound, so
82d37c369846atagen2026-10-25 10:55 UTC204
`add_notification_keyword` reads the content rule the SDK names after the
82d37c369846atagen2026-10-25 10:55 UTC205
keyword and adds a highlight tweak when it lacks one, leaving its other actions
82d37c369846atagen2026-10-25 10:55 UTC206
alone. A rule the user made quiet stays quiet apart from the highlight. A
82d37c369846atagen2026-10-25 10:55 UTC207
change of reason alone keeps an entry's saved context. The classifier excludes the signed-in
cbb715e11963atagen2026-10-24 23:24 UTC208
user's own events even when they contain explicit mention metadata.
cbb715e11963atagen2026-10-24 23:24 UTC209
cbb715e11963atagen2026-10-24 23:24 UTC210
The existing `NotificationClient` combines notification evaluation with
cbb715e11963atagen2026-10-24 23:24 UTC211
ignored-sender filtering. Its public lookup takes known room and event IDs.
cbb715e11963atagen2026-10-24 23:24 UTC212
It is useful for resolving a candidate, but filtered results erase the
cbb715e11963atagen2026-10-24 23:24 UTC213
distinction between mute policy and an absent semantic mention. It is not the
cbb715e11963atagen2026-10-24 23:24 UTC214
sole classifier for this inbox.
cbb715e11963atagen2026-10-24 23:24 UTC215
cbb715e11963atagen2026-10-24 23:24 UTC216
## Historical candidates and gaps
cbb715e11963atagen2026-10-24 23:24 UTC217
cbb715e11963atagen2026-10-24 23:24 UTC218
Ruma exposes
cbb715e11963atagen2026-10-24 23:24 UTC219
`api::client::push::get_notifications::v3::Request`, sent with `Client::send`.
cbb715e11963atagen2026-10-24 23:24 UTC220
It accepts opaque `from`, optional `limit`, and optional `only` fields.
cbb715e11963atagen2026-10-24 23:24 UTC221
Responses contain notifications and an optional `next_token`. macltok requests
cbb715e11963atagen2026-10-24 23:24 UTC222
50 notifications per page. Entries include raw events, room IDs, timestamps,
7c7832147a2eatagen2026-10-25 08:43 UTC223
actions, and server notification read state, which does not initialise or
cbb715e11963atagen2026-10-24 23:24 UTC224
replace local inbox read state.
cbb715e11963atagen2026-10-24 23:24 UTC225
cbb715e11963atagen2026-10-24 23:24 UTC226
The service uses this endpoint as a bounded candidate source, validates
cbb715e11963atagen2026-10-24 23:24 UTC227
events after decryption, and applies the live mention policy. Each page's
cbb715e11963atagen2026-10-24 23:24 UTC228
observed candidates and cursor are committed together in the encrypted inbox,
cbb715e11963atagen2026-10-24 23:24 UTC229
including unresolved encrypted candidates. A failed commit leaves the prior
cbb715e11963atagen2026-10-24 23:24 UTC230
cursor available for replay.
cbb715e11963atagen2026-10-24 23:24 UTC231
cbb715e11963atagen2026-10-24 23:24 UTC232
An exhausted notification list does not mean complete room history. Muted-room
7c7832147a2eatagen2026-10-25 08:43 UTC233
pings, encrypted mentions not recognised by the server, expired notifications,
cbb715e11963atagen2026-10-24 23:24 UTC234
and server-specific retention can be absent. Recovery inspects at most 20 pages
cbb715e11963atagen2026-10-24 23:24 UTC235
of 50 notifications per run and resumes from committed cursors later. The
cbb715e11963atagen2026-10-24 23:24 UTC236
older-history cursor is separate from the cursor for scans from the newest
cbb715e11963atagen2026-10-24 23:24 UTC237
notifications. A newest scan records an anchor and runs at startup and every
cbb715e11963atagen2026-10-24 23:24 UTC238
60 seconds. It follows the committed cursor until it reaches that anchor,
cbb715e11963atagen2026-10-24 23:24 UTC239
resuming on later runs when needed to find newly added notifications.
cbb715e11963atagen2026-10-24 23:24 UTC240
cbb715e11963atagen2026-10-24 23:24 UTC241
SDK cache gaps retain pagination tokens and support room pagination, but
cbb715e11963atagen2026-10-24 23:24 UTC242
running that pagination mutates the live cache. Mention context capture uses
cbb715e11963atagen2026-10-24 23:24 UTC243
independent `/context` and `/messages` requests. Tokens may expire. Context can
cbb715e11963atagen2026-10-24 23:24 UTC244
be retried around a known event while the room remains joined, and known gaps
cbb715e11963atagen2026-10-24 23:24 UTC245
remain visible when continuity cannot be established.
cbb715e11963atagen2026-10-24 23:24 UTC246
cbb715e11963atagen2026-10-24 23:24 UTC247
Coverage exposes the earliest observed timestamp, history-endpoint support,
cbb715e11963atagen2026-10-24 23:24 UTC248
recovery activity, known room gaps, unresolved candidate count, and retention
cbb715e11963atagen2026-10-24 23:24 UTC249
truncation. The earliest timestamp is a summary, not proof that every room is
7c7832147a2eatagen2026-10-25 08:43 UTC250
covered after that time. Each gap carries a typed `MentionGapReason`.
cbb715e11963atagen2026-10-24 23:24 UTC251
cbb715e11963atagen2026-10-24 23:24 UTC252
Complete offline mention recovery across arbitrary muted and encrypted rooms
cbb715e11963atagen2026-10-24 23:24 UTC253
is not supplied by this endpoint or by the current sync configuration. It
cbb715e11963atagen2026-10-24 23:24 UTC254
would require a retained, replayable event source or bounded per-room history
cbb715e11963atagen2026-10-24 23:24 UTC255
recovery with proof of continuity. The notification endpoint does not provide
cbb715e11963atagen2026-10-24 23:24 UTC256
that guarantee.
cbb715e11963atagen2026-10-24 23:24 UTC257
cbb715e11963atagen2026-10-24 23:24 UTC258
## Storage, context, and API
cbb715e11963atagen2026-10-24 23:24 UTC259
cbb715e11963atagen2026-10-24 23:24 UTC260
`src/mentions/store` implements encrypted SQLite and IndexedDB record stores.
cbb715e11963atagen2026-10-24 23:24 UTC261
Persistent session construction opens the account store, and local logout
cbb715e11963atagen2026-10-24 23:24 UTC262
deletes it alongside the SDK stores. The mentions actor owns the store for
cbb715e11963atagen2026-10-24 23:24 UTC263
inbox commits and discovery checkpoints.
cbb715e11963atagen2026-10-24 23:24 UTC264
cbb715e11963atagen2026-10-24 23:24 UTC265
The passphrase wraps a random SDK `StoreCipher`. Logical keys are hashed, and
cbb715e11963atagen2026-10-24 23:24 UTC266
authenticated encrypted records include schema, account identity, and logical
82d37c369846atagen2026-10-25 10:55 UTC267
key. Restore treats mismatched accounts, older schemas, corrupt records, and
82d37c369846atagen2026-10-25 10:55 UTC268
inconsistent record counts or byte totals as unreadable, empties the store and
82d37c369846atagen2026-10-25 10:55 UTC269
records a `StoreReset` gap rather than failing `Account::build`. A busy or
82d37c369846atagen2026-10-25 10:55 UTC270
failing store and one written by a newer macltok are left alone, and the inbox
82d37c369846atagen2026-10-25 10:55 UTC271
runs in memory behind `StoreFailed`. The busy or failing case is retried every
82d37c369846atagen2026-10-25 10:55 UTC272
minute, and when it opens, what it held and what was built in memory since are
82d37c369846atagen2026-10-25 10:55 UTC273
merged.
7c7832147a2eatagen2026-10-25 08:43 UTC274
Atomic transactions compare the previous encrypted header before publishing a
7c7832147a2eatagen2026-10-25 08:43 UTC275
new revision, so stale writers cannot overwrite a newer checkpoint.
cbb715e11963atagen2026-10-24 23:24 UTC276
cbb715e11963atagen2026-10-24 23:24 UTC277
The default budget is 64 MiB of encrypted records, hashed keys, header, and
cbb715e11963atagen2026-10-24 23:24 UTC278
wrapped cipher, excluding database page overhead. The store record cap is
cbb715e11963atagen2026-10-24 23:24 UTC279
35,128. The inbox defaults to 5,000 entries, 25,000 shared context events, and
cbb715e11963atagen2026-10-24 23:24 UTC280
5,000 unresolved candidates within that byte budget. When a size or record
cbb715e11963atagen2026-10-24 23:24 UTC281
limit requires eviction, the store drops bounded data and marks coverage
cbb715e11963atagen2026-10-24 23:24 UTC282
truncated.
cbb715e11963atagen2026-10-24 23:24 UTC283
cbb715e11963atagen2026-10-24 23:24 UTC284
The storage tests cover atomic restore of records and checkpoints, stale
7c7832147a2eatagen2026-10-25 08:43 UTC285
writers, capacity failure, encrypted physical records, resets on wrong
7c7832147a2eatagen2026-10-25 08:43 UTC286
credentials or account, corruption and unsupported schemas, and injected
7c7832147a2eatagen2026-10-25 08:43 UTC287
SQLite write failure. The inbox tests cover failed writes, checkpoint-only
7c7832147a2eatagen2026-10-25 08:43 UTC288
writes, replay from the previous checkpoint, reset of an unreadable inbox,
7c7832147a2eatagen2026-10-25 08:43 UTC289
notification recovery, live context updates, and capture persistence across
7c7832147a2eatagen2026-10-25 08:43 UTC290
session resume.
7c7832147a2eatagen2026-10-25 08:43 UTC291
82d37c369846atagen2026-10-25 10:55 UTC292
The store holds three records. `inbox` carries entries, saved events, context
7c7832147a2eatagen2026-10-25 08:43 UTC293
windows, candidates, edits, redactions, coverage and recovery cursors. `sync`
82d37c369846atagen2026-10-25 10:55 UTC294
carries the connection checkpoints and the inbox revision it belongs to.
82d37c369846atagen2026-10-25 10:55 UTC295
`rooms` carries the rooms already observed, which drop out when left. Every
82d37c369846atagen2026-10-25 10:55 UTC296
response writes `sync`, and `inbox` or `rooms` only when the response changed
82d37c369846atagen2026-10-25 10:55 UTC297
them, all in one commit. Whether the inbox changed is decided by comparing the
82d37c369846atagen2026-10-25 10:55 UTC298
rooms the response touched, so a quiet response costs those rooms and one small
82d37c369846atagen2026-10-25 10:55 UTC299
record, while a changed inbox is still reserialised and re-encrypted whole, in
82d37c369846atagen2026-10-25 10:55 UTC300
one serialisation pass. Background completions are applied in memory and
82d37c369846atagen2026-10-25 10:55 UTC301
written together at most once a second, and published on that tick even while
82d37c369846atagen2026-10-25 10:55 UTC302
writes back off. When the budget is short, eviction runs against a size
82d37c369846atagen2026-10-25 10:55 UTC303
estimate and the inbox is written once.
7c7832147a2eatagen2026-10-25 08:43 UTC304
Decrypted previews, saved context, raw candidates, pending capture state,
7c7832147a2eatagen2026-10-25 08:43 UTC305
media source registrations, read state, coverage, and sync checkpoints stay
7c7832147a2eatagen2026-10-25 08:43 UTC306
inside the authenticated encrypted payloads. SDK SQLite custom values remain plaintext and are not used
cbb715e11963atagen2026-10-24 23:24 UTC307
for decrypted mentions.
cbb715e11963atagen2026-10-24 23:24 UTC308
cbb715e11963atagen2026-10-24 23:24 UTC309
Context capture in `src/engine/context.rs` sizes the before and after requests
7c7832147a2eatagen2026-10-25 08:43 UTC310
independently and retains the media registrations produced by normalisation.
cbb715e11963atagen2026-10-24 23:24 UTC311
Each capture requests up to 10 events on each side. At most two capture jobs
cbb715e11963atagen2026-10-24 23:24 UTC312
run concurrently, with up to 128 pending or failed entries considered for
cbb715e11963atagen2026-10-24 23:24 UTC313
scheduling at a time. Capture uses independent `/context` and `/messages`
cbb715e11963atagen2026-10-24 23:24 UTC314
requests, so it does not paginate the live cache. Media source registrations
cbb715e11963atagen2026-10-24 23:24 UTC315
are encrypted with saved events and registered again when context is read.
cbb715e11963atagen2026-10-24 23:24 UTC316
Media bodies and key material do not enter the mentions public model.
cbb715e11963atagen2026-10-24 23:24 UTC317
7c7832147a2eatagen2026-10-25 08:43 UTC318
One account-scoped mentions actor serialises store commits for entries,
cbb715e11963atagen2026-10-24 23:24 UTC319
unresolved candidates, shared context events, read state, revisions, discovery
cbb715e11963atagen2026-10-24 23:24 UTC320
checkpoints, and retention decisions.
cbb715e11963atagen2026-10-24 23:24 UTC321
cbb715e11963atagen2026-10-24 23:24 UTC322
Pending capture state is durable with the inbox, so interrupted capture can
cbb715e11963atagen2026-10-24 23:24 UTC323
resume after startup. When a room leaves the joined state, saved context
cbb715e11963atagen2026-10-24 23:24 UTC324
remains readable and its capture state becomes unavailable. Refetch is
cbb715e11963atagen2026-10-24 23:24 UTC325
disabled after departure.
cbb715e11963atagen2026-10-24 23:24 UTC326
cbb715e11963atagen2026-10-24 23:24 UTC327
Store publication follows commit. `ClientHandle` exposes page, context, retry,
cbb715e11963atagen2026-10-24 23:24 UTC328
read, read-through, and recovery methods. The public `Command`, mention models,
cbb715e11963atagen2026-10-24 23:24 UTC329
mirror state, and updates support serde when the `serde` feature is enabled,
cbb715e11963atagen2026-10-24 23:24 UTC330
so native and browser consumers can transport them. Browser worker integration
cbb715e11963atagen2026-10-24 23:24 UTC331
is a consumer phase after SDK and macltok revision publication. Malice's worker
cbb715e11963atagen2026-10-24 23:24 UTC332
still needs mention variants in its explicit `Request` dispatch and TypeScript
cbb715e11963atagen2026-10-24 23:24 UTC333
methods.
cbb715e11963atagen2026-10-24 23:24 UTC334
cbb715e11963atagen2026-10-24 23:24 UTC335
Mirror snapshots contain at most 50 entry previews, coverage, unread count,
cbb715e11963atagen2026-10-24 23:24 UTC336
and a committed revision boundary, with no saved context bodies. The watcher
cbb715e11963atagen2026-10-24 23:24 UTC337
coalesces updates and computes upserts and removals relative to the previous
cbb715e11963atagen2026-10-24 23:24 UTC338
bounded snapshot it observed. A missed update therefore leaves the latest
cbb715e11963atagen2026-10-24 23:24 UTC339
snapshot authoritative. A removal can mean an entry fell out of the top 50,
cbb715e11963atagen2026-10-24 23:24 UTC340
and does not imply that it was deleted from the inbox. Pagination uses
cbb715e11963atagen2026-10-24 23:24 UTC341
timestamp and stable event key within a discovery revision boundary.
cbb715e11963atagen2026-10-24 23:24 UTC342
Read-through boundaries exclude mentions first discovered or reactivated after
cbb715e11963atagen2026-10-24 23:24 UTC343
that boundary, including older messages resolved by late decryption.
cbb715e11963atagen2026-10-24 23:24 UTC344
cbb715e11963atagen2026-10-24 23:24 UTC345
## Executable probes
cbb715e11963atagen2026-10-24 23:24 UTC346
cbb715e11963atagen2026-10-24 23:24 UTC347
`tests/mentions_discovery.rs` exercises unopened-room update and cache access,
7c7832147a2eatagen2026-10-25 08:43 UTC348
push-rule behaviour for ignored senders and muted rooms, and typed
cbb715e11963atagen2026-10-24 23:24 UTC349
notification-history pagination.
cbb715e11963atagen2026-10-24 23:24 UTC350
cbb715e11963atagen2026-10-24 23:24 UTC351
`tests/mentions_decryption.rs` exchanges a real encrypted event and a delayed
cbb715e11963atagen2026-10-24 23:24 UTC352
room key between two mock clients, then retrieves the decrypted mention
cbb715e11963atagen2026-10-24 23:24 UTC353
without opening a UI timeline.
cbb715e11963atagen2026-10-24 23:24 UTC354
cbb715e11963atagen2026-10-24 23:24 UTC355
`tests/mentions_checkpoint.rs` holds an event handler pending and verifies
cbb715e11963atagen2026-10-24 23:24 UTC356
that the SDK has not persisted its position. It cancels the calling task,
cbb715e11963atagen2026-10-24 23:24 UTC357
then acknowledges the handler and verifies that detached response processing
cbb715e11963atagen2026-10-24 23:24 UTC358
still saves the position. A new Sliding Sync instance restores that position.
cbb715e11963atagen2026-10-24 23:24 UTC359
This is the historical event-handler ordering probe. It does not exercise the
cbb715e11963atagen2026-10-24 23:24 UTC360
production response-commit hook or the durable inbox transaction. It also
cbb715e11963atagen2026-10-24 23:24 UTC361
demonstrates that returning a handler error still advances the position.
cbb715e11963atagen2026-10-24 23:24 UTC362
cbb715e11963atagen2026-10-24 23:24 UTC363
`tests/mentions_inbox.rs` covers live mention classification, read-through
cbb715e11963atagen2026-10-24 23:24 UTC364
with revision boundaries, independent context limits, and persistence across
cbb715e11963atagen2026-10-24 23:24 UTC365
session resume. `tests/mentions_late_decryption.rs` verifies that a delayed room key
cbb715e11963atagen2026-10-24 23:24 UTC366
updates the production inbox once. `src/mentions/service_tests.rs` covers
7c7832147a2eatagen2026-10-25 08:43 UTC367
failed and checkpoint-only writes, checkpoint replay, store reset,
7c7832147a2eatagen2026-10-25 08:43 UTC368
notification-history recovery, and context updates.
cbb715e11963atagen2026-10-24 23:24 UTC369
`src/mentions/store/tests.rs` exercises encrypted record restore, capacity,
cbb715e11963atagen2026-10-24 23:24 UTC370
stale writers, and corruption handling.
cbb715e11963atagen2026-10-24 23:24 UTC371
7c7832147a2eatagen2026-10-25 08:43 UTC372
These probes establish SDK behaviour against mocked responses. They do not
cbb715e11963atagen2026-10-24 23:24 UTC373
establish homeserver notification retention, room-list completeness, or
cbb715e11963atagen2026-10-24 23:24 UTC374
complete history on a deployed server.
cbb715e11963atagen2026-10-24 23:24 UTC375
cbb715e11963atagen2026-10-24 23:24 UTC376
Focused validation passed 63 native tests and 12 shared inbox and IndexedDB
cbb715e11963atagen2026-10-24 23:24 UTC377
tests in headless Firefox. Compilation checks also pass without storage
cbb715e11963atagen2026-10-24 23:24 UTC378
features and for wasm with IndexedDB and mirror support. The full flake gate
cbb715e11963atagen2026-10-24 23:24 UTC379
was skipped by request.