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