cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 1 | # Persistent mentions discovery |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 2 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 3 | This records the SDK audit and current persistent mentions implementation. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 4 | Persistent sessions now commit discovery with Sliding Sync responses, retain an |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 5 | encrypted inbox, and expose it through `ClientHandle` and the mirror. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 6 | Historical coverage remains incomplete because room-list sync and notification |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 7 | history cannot prove that every past mention was observed. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 8 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 9 | The audited dependency is Matrix Rust SDK 0.18.0 at fork revision |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 10 | `cc2b13ea185c0f694523a3a48d8aadfe366dd767`. macltok now pins fork revision |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 11 | `7aace883fbe03d4c3c118b55188fe9519ba8ce48`, which adds the response commit |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 12 | hook described below. SDK paths below are relative to the audited revision. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 13 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 14 | ## Findings |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 15 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 16 | The SDK can deliver and decrypt events without opening a UI timeline. macltok |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 17 | records observed events and recovers candidates through notification history, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 18 | but neither source establishes complete historical coverage. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 19 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 20 | The SDK audit found that an awaited event handler can hold back the Sliding |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 21 | Sync position until a macltok transaction commits, while a background |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 22 | broadcast consumer cannot provide that guarantee. Handlers cannot propagate |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 23 | commit errors, and response processing outlives cancellation of the calling |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 24 | sync task. These limits motivated the fallible response-commit hook now |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 25 | implemented in the pinned SDK fork. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 26 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 27 | ## Sync coverage |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 28 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 29 | macltok constructs `SyncService` in `src/engine/mod.rs` and uses the SDK room |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 30 | list configuration. The list starts with the selective range `0..=19`. After |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 31 | setup it grows by 100 rooms per response, without an SDK-side maximum, until |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 32 | it reaches the server's room count. Recovery and sufficiently long idle |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 33 | periods can return it to the initial selective range before it grows again. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 34 | The default list timeline limit is one event per room on initial delivery. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 35 | Subsequent changes for covered rooms continue arriving through sync. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 36 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 37 | Opening a room separately subscribes it with a |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 38 | timeline limit of 20. macltok's `update_room_subscriptions` and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 39 | `watchers::room_subscriptions` manage those explicit subscriptions for active |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 40 | room actors only. The mentions response hook filters entries to joined rooms. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 41 | The built-in list still includes invited rooms. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 42 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 43 | Sources are `crates/matrix-sdk-ui/src/room_list_service/mod.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 44 | `crates/matrix-sdk-ui/src/room_list_service/state.rs`, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 45 | `crates/matrix-sdk/src/sliding_sync/list/request_generator.rs`. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 46 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 47 | The default one-event initial window cannot recover all earlier pings, including |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 48 | messages sent while the client was offline. Raising |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 49 | `SyncServiceBuilder::with_room_list_timeline_limit` increases the initial |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 50 | window but does not establish continuous history. The response hook processes |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 51 | joined-room events and records limited timelines, initial room coverage, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 52 | checkpoint discontinuities, and recovery gaps. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 53 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 54 | ## Observation and durable commit |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 55 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 56 | `Client::subscribe_to_all_room_updates` returns a bounded broadcast receiver |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 57 | of `RoomUpdates`. Its `joined` map includes processed timeline events with raw |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 58 | decrypted content when available and optional evaluated push actions. It does |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 59 | not require a UI timeline. Lag is an error that requires recovery, not an |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 60 | invitation to skip to the next update. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 61 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 62 | `Client::observe_events` retains only the latest observation and can miss |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 63 | intermediate events. It is unsuitable for durable discovery. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 64 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 65 | An earlier probe registers `Client::add_event_handler` for |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 66 | `Raw<AnySyncTimelineEvent>` with `Room` context before sync starts. Its awaited |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 67 | handler demonstrates event ordering, but it cannot report commit failures |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 68 | reliably and is not the production ingestion path. Production uses the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 69 | fallible Sliding Sync response hook described below. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 70 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 71 | For Sliding Sync v5, the response processor awaits event handlers before |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 72 | updating and persisting `pos`. The response hook commits the inbox first, so |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 73 | a crash permits replay without skipping committed candidates. Stable event |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 74 | keys make replay idempotent. The hook acknowledgement does not wait for |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 75 | context capture, a UI subscriber, or the engine event stream to drain. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 76 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 77 | For ordinary event handlers, the SDK logs and discards handler errors, so |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 78 | returning an error after a failed write still permits `pos` to advance. A |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 79 | pending handler alone also does not provide a reliable shutdown path. The SDK |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 80 | runs response handling in a detached task that outlives cancellation of |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 81 | `sync_once` or its caller. Aborting the engine's sync task cannot prevent a |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 82 | suspended handler from later returning and advancing the position. Registering |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 83 | a handler and placing work on a queue without awaiting its commit does not |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 84 | close the crash window. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 85 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 86 | macltok uses shared `pos` through its SDK service and commits each response |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 87 | batch through one serialized mentions actor before the hook acknowledges it. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 88 | The actor records joined-room events, raw encrypted candidates, limited |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 89 | timeline gaps, and the connection checkpoint in the same inbox revision. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 90 | Stable room and event keys make replay idempotent. Context capture, mirror |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 91 | publication, and event-stream delivery continue after the commit. The classic |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 92 | sync `next_batch` ordering differs and does not establish this guarantee. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 93 | Nor does this ordering make arbitrary concurrent sync loops safe. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 94 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 95 | Sources are `crates/matrix-sdk/src/event_handler/mod.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 96 | `crates/matrix-sdk/src/event_handler/static_events.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 97 | `crates/matrix-sdk/src/sliding_sync/client.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 98 | `crates/matrix-sdk/src/sliding_sync/mod.rs`, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 99 | `crates/matrix-sdk/src/sliding_sync/cache.rs`. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 100 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 101 | ### SDK commit hook implementation |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 102 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 103 | The local SDK workspace at `../matrix-rust-sdk-mentions` implements the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 104 | fallible asynchronous response-commit hook through `ResponseCommitHook` and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 105 | `ResponseCommitBatch`. `SyncServiceBuilder` forwards it only to its room-list |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 106 | connection, so independent notification lookups are unaffected. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 107 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 108 | The hook receives the processed room events, original room payloads, limited |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 109 | flags, pagination tokens, connection identity, and previous and next position |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 110 | identifiers. It runs before response handlers and observers and before `pos` |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 111 | is updated or saved. SDK room state may already have been saved. A successful |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 112 | acknowledgement follows the macltok inbox transaction. A failed or dropped |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 113 | acknowledgement returns a sync error without advancing the position. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 114 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 115 | The hook runs outside SDK store locks and supports local futures on wasm and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 116 | sendable futures natively. The default absent hook preserves existing SDK |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 117 | behavior. Detached response processing observes a dropped store |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 118 | acknowledgement after its caller is cancelled. Checkpoint replay works after |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 119 | SDK room state was saved, and stable event keys deduplicate repeated commits. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 120 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 121 | The SDK audit and focused validation covered commit ordering, failed and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 122 | dropped acknowledgements, caller cancellation, replay after room-state |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 123 | persistence, restoring a previous checkpoint, absent-hook behavior, builder |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 124 | forwarding, and connection isolation. The SDK UI library also received a |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 125 | focused wasm compilation check with its `js` feature. The SDK changes are |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 126 | published to the user's fork under `mentions-commit-hook`, and the manifest |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 127 | and both macltok lockfiles pin revision |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 128 | `7aace883fbe03d4c3c118b55188fe9519ba8ce48`. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 129 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 130 | ## Event cache and delayed decryption |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 131 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 132 | `EventCache::subscribe` starts global room-update ingestion. The SDK room list |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 133 | service already calls it during construction. Its generic update subscription |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 134 | identifies the affected room, then `Room::event_cache` and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 135 | `RoomEventCache::find_event` can retrieve an event. This does not require a |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 136 | `matrix-sdk-ui::Timeline` or a retained subscriber per room. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 137 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 138 | `EventCache::subscribe_to_decryption_reports` reports resolved event IDs, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 139 | lag, and backup availability. `EventCache::request_decryption` accepts room |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 140 | and Megolm session IDs for explicit retries. `Room::decrypt_event` is also |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 141 | public and can decrypt a retained raw event independently of a UI timeline. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 142 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 143 | The late-key probe confirms automatic decryption and retrieval of a structured |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 144 | mention through the global cache without a UI timeline. Cached push actions |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 145 | are optional. Re-evaluate them with `Room::event_push_actions` when needed, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 146 | and treat missing push context as unresolved rather than a negative result. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 147 | The room's own membership state is needed to build that push context. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 148 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 149 | The cache is not a durable ingestion journal. Its room broadcasts and generic |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 150 | updates can lag, and cache retention can remove events. SQLite and IndexedDB |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 151 | builders select persistent SDK event-cache stores, while the default |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 152 | store-free client uses memory. Even with persistence, the asynchronous cache |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 153 | consumer is not awaited before Sliding Sync saves its position. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 154 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 155 | The inbox retains unresolved encrypted candidates as encrypted raw events |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 156 | within a configurable bound. It retries due candidates at startup and resets |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 157 | their retry time when decryption reports arrive. Candidate eviction sets the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 158 | coverage truncation flag. Imported keys and backups cannot reconstruct events |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 159 | already discarded by every cache. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 160 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 161 | Sources are `crates/matrix-sdk/src/event_cache/mod.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 162 | `crates/matrix-sdk/src/event_cache/redecryptor.rs`, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 163 | `crates/matrix-sdk/src/event_cache/caches/room/mod.rs`, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 164 | `crates/matrix-sdk/src/room/mod.rs`. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 165 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 166 | ## Mention policy |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 167 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 168 | Structured `m.mentions.user_ids` and `m.mentions.room` supply semantic |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 169 | candidates. The classifier does not infer pings from rendered bodies or |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 170 | unread counts. A reply relationship alone does not prove that a reply notifies |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 171 | the user, so reply reasons require notification metadata and confirmation that |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 172 | the referenced event belongs to the signed-in user. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 173 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 174 | `Room::event_push_actions` evaluates push rules but does not independently |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 175 | filter ignored senders. The classifier calls `Client::is_user_ignored` and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 176 | rechecks entries when the ignore list changes. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 177 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 178 | A room mute can suppress evaluated notification actions while structured |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 179 | mention metadata remains present. The classifier retains explicit user |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 180 | mentions in muted rooms and marks notification delivery as suppressed. Reply |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 181 | reasons require both a reply to the user and a notifying highlight. Room-wide |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 182 | reasons require the relevant push conditions and sender power level. Missing |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 183 | authorization context remains unresolved for reevaluation. Keyword-only |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 184 | notifications are outside the inbox. The classifier excludes the signed-in |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 185 | user's own events even when they contain explicit mention metadata. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 186 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 187 | The existing `NotificationClient` combines notification evaluation with |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 188 | ignored-sender filtering. Its public lookup takes known room and event IDs. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 189 | It is useful for resolving a candidate, but filtered results erase the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 190 | distinction between mute policy and an absent semantic mention. It is not the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 191 | sole classifier for this inbox. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 192 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 193 | ## Historical candidates and gaps |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 194 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 195 | Ruma exposes |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 196 | `api::client::push::get_notifications::v3::Request`, sent with `Client::send`. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 197 | It accepts opaque `from`, optional `limit`, and optional `only` fields. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 198 | Responses contain notifications and an optional `next_token`. macltok requests |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 199 | 50 notifications per page. Entries include raw events, room IDs, timestamps, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 200 | actions, and server notification read state, which does not initialize or |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 201 | replace local inbox read state. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 202 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 203 | The service uses this endpoint as a bounded candidate source, validates |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 204 | events after decryption, and applies the live mention policy. Each page's |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 205 | observed candidates and cursor are committed together in the encrypted inbox, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 206 | including unresolved encrypted candidates. A failed commit leaves the prior |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 207 | cursor available for replay. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 208 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 209 | An exhausted notification list does not mean complete room history. Muted-room |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 210 | pings, encrypted mentions not recognized by the server, expired notifications, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 211 | and server-specific retention can be absent. Recovery inspects at most 20 pages |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 212 | of 50 notifications per run and resumes from committed cursors later. The |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 213 | older-history cursor is separate from the cursor for scans from the newest |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 214 | notifications. A newest scan records an anchor and runs at startup and every |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 215 | 60 seconds. It follows the committed cursor until it reaches that anchor, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 216 | resuming on later runs when needed to find newly added notifications. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 217 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 218 | SDK cache gaps retain pagination tokens and support room pagination, but |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 219 | running that pagination mutates the live cache. Mention context capture uses |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 220 | independent `/context` and `/messages` requests. Tokens may expire. Context can |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 221 | be retried around a known event while the room remains joined, and known gaps |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 222 | remain visible when continuity cannot be established. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 223 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 224 | Coverage exposes the earliest observed timestamp, history-endpoint support, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 225 | recovery activity, known room gaps, unresolved candidate count, and retention |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 226 | truncation. The earliest timestamp is a summary, not proof that every room is |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 227 | covered after that time. The inbox keeps its incomplete-history flag even |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 228 | after notification pagination ends. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 229 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 230 | Complete offline mention recovery across arbitrary muted and encrypted rooms |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 231 | is not supplied by this endpoint or by the current sync configuration. It |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 232 | would require a retained, replayable event source or bounded per-room history |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 233 | recovery with proof of continuity. The notification endpoint does not provide |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 234 | that guarantee. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 235 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 236 | ## Storage, context, and API |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 237 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 238 | `src/mentions/store` implements encrypted SQLite and IndexedDB record stores. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 239 | Persistent session construction opens the account store, and local logout |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 240 | deletes it alongside the SDK stores. The mentions actor owns the store for |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 241 | inbox commits and discovery checkpoints. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 242 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 243 | The passphrase wraps a random SDK `StoreCipher`. Logical keys are hashed, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 244 | authenticated encrypted records include schema, account identity, and logical |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 245 | key. Restore rejects mismatched accounts, unsupported schemas, corrupt records, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 246 | and inconsistent record counts or byte totals. Atomic transactions compare the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 247 | previous encrypted header before publishing a new revision, so stale writers |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 248 | cannot overwrite a newer checkpoint. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 249 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 250 | The default budget is 64 MiB of encrypted records, hashed keys, header, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 251 | wrapped cipher, excluding database page overhead. The store record cap is |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 252 | 35,128. The inbox defaults to 5,000 entries, 25,000 shared context events, and |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 253 | 5,000 unresolved candidates within that byte budget. When a size or record |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 254 | limit requires eviction, the store drops bounded data and marks coverage |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 255 | truncated. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 256 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 257 | The storage tests cover atomic restore of records and checkpoints, stale |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 258 | writers, capacity failure, encrypted physical records, wrong credentials or |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 259 | account, corruption, unsupported schemas, and injected SQLite write failure. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 260 | The inbox tests cover batch failure, notification recovery, live context |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 261 | updates, and capture persistence across session resume. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 262 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 263 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 264 | The inbox is serialized into one `inbox` value and encrypted as a single |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 265 | record on every revision. Each update reserializes and re-encrypts the retained |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 266 | inbox, so write cost scales with retained data. Decrypted previews, saved |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 267 | context, raw candidates, pending capture state, media source registrations, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 268 | read state, coverage, and sync checkpoints stay inside the authenticated |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 269 | encrypted payload. SDK SQLite custom values remain plaintext and are not used |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 270 | for decrypted mentions. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 271 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 272 | Context capture in `src/engine/context.rs` sizes the before and after requests |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 273 | independently and retains the media registrations produced by normalization. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 274 | Each capture requests up to 10 events on each side. At most two capture jobs |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 275 | run concurrently, with up to 128 pending or failed entries considered for |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 276 | scheduling at a time. Capture uses independent `/context` and `/messages` |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 277 | requests, so it does not paginate the live cache. Media source registrations |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 278 | are encrypted with saved events and registered again when context is read. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 279 | Media bodies and key material do not enter the mentions public model. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 280 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 281 | One account-scoped mentions actor serializes store commits for entries, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 282 | unresolved candidates, shared context events, read state, revisions, discovery |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 283 | checkpoints, and retention decisions. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 284 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 285 | Pending capture state is durable with the inbox, so interrupted capture can |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 286 | resume after startup. When a room leaves the joined state, saved context |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 287 | remains readable and its capture state becomes unavailable. Refetch is |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 288 | disabled after departure. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 289 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 290 | Store publication follows commit. `ClientHandle` exposes page, context, retry, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 291 | read, read-through, and recovery methods. The public `Command`, mention models, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 292 | mirror state, and updates support serde when the `serde` feature is enabled, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 293 | so native and browser consumers can transport them. Browser worker integration |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 294 | is a consumer phase after SDK and macltok revision publication. Malice's worker |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 295 | still needs mention variants in its explicit `Request` dispatch and TypeScript |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 296 | methods. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 297 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 298 | Mirror snapshots contain at most 50 entry previews, coverage, unread count, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 299 | and a committed revision boundary, with no saved context bodies. The watcher |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 300 | coalesces updates and computes upserts and removals relative to the previous |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 301 | bounded snapshot it observed. A missed update therefore leaves the latest |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 302 | snapshot authoritative. A removal can mean an entry fell out of the top 50, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 303 | and does not imply that it was deleted from the inbox. Pagination uses |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 304 | timestamp and stable event key within a discovery revision boundary. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 305 | Read-through boundaries exclude mentions first discovered or reactivated after |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 306 | that boundary, including older messages resolved by late decryption. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 307 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 308 | ## Executable probes |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 309 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 310 | `tests/mentions_discovery.rs` exercises unopened-room update and cache access, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 311 | push-rule behavior for ignored senders and muted rooms, and typed |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 312 | notification-history pagination. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 313 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 314 | `tests/mentions_decryption.rs` exchanges a real encrypted event and a delayed |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 315 | room key between two mock clients, then retrieves the decrypted mention |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 316 | without opening a UI timeline. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 317 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 318 | `tests/mentions_checkpoint.rs` holds an event handler pending and verifies |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 319 | that the SDK has not persisted its position. It cancels the calling task, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 320 | then acknowledges the handler and verifies that detached response processing |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 321 | still saves the position. A new Sliding Sync instance restores that position. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 322 | This is the historical event-handler ordering probe. It does not exercise the |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 323 | production response-commit hook or the durable inbox transaction. It also |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 324 | demonstrates that returning a handler error still advances the position. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 325 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 326 | `tests/mentions_inbox.rs` covers live mention classification, read-through |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 327 | with revision boundaries, independent context limits, and persistence across |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 328 | session resume. `tests/mentions_late_decryption.rs` verifies that a delayed room key |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 329 | updates the production inbox once. `src/mentions/service_tests.rs` covers |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 330 | checkpoint rollback, notification-history recovery, and context updates. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 331 | `src/mentions/store/tests.rs` exercises encrypted record restore, capacity, |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 332 | stale writers, and corruption handling. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 333 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 334 | These probes establish SDK behavior against mocked responses. They do not |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 335 | establish homeserver notification retention, room-list completeness, or |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 336 | complete history on a deployed server. |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 337 | |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 338 | Focused validation passed 63 native tests and 12 shared inbox and IndexedDB |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 339 | tests in headless Firefox. Compilation checks also pass without storage |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 340 | features and for wasm with IndexedDB and mirror support. The full flake gate |
cbb715e11963 | atagen | 2026-10-24 23:24 UTC | 341 | was skipped by request. |