atagen

macltok

public
Star0
Watch0

Commit 7c7832147a2e

tarball

atagen <boss@atagen.co>2026-10-25 09:12 UTC

1 file changed+69-44
docs: bring mentions discovery in line with the inbox

The reply rule accepts an explicit mention without a highlight, and the watch snapshot is replaced before the hook acks. Also records the split checkpoint record, the failure gaps and British spellings.
Modified file: docs/mentions-discovery.md+69 additions−44 deletions
@@ -48,8 +48,9 @@ The default one-event initial window cannot recover all earlier pings, including
4848 messages sent while the client was offline. Raising
4949 `SyncServiceBuilder::with_room_list_timeline_limit` increases the initial
5050 window but does not establish continuous history. The response hook processes
51-joined-room events and records limited timelines, initial room coverage,
52-checkpoint discontinuities, and recovery gaps.
51+joined-room events and records limited timelines of rooms it had already
52+observed, checkpoint discontinuities, and recovery gaps. A room seen for the
53+first time adds no gap, since every room starts that way.
5354
5455 ## Observation and durable commit
5556
@@ -84,14 +85,27 @@ a handler and placing work on a queue without awaiting its commit does not
8485 close the crash window.
8586
8687 macltok uses shared `pos` through its SDK service and commits each response
87-batch through one serialized mentions actor before the hook acknowledges it.
88-The actor records joined-room events, raw encrypted candidates, limited
89-timeline gaps, and the connection checkpoint in the same inbox revision.
90-Stable room and event keys make replay idempotent. Context capture, mirror
91-publication, and event-stream delivery continue after the commit. The classic
88+batch through one serialised mentions actor before the hook acknowledges it.
89+The actor records events from joined rooms and from rooms the same response
90+leaves, raw encrypted candidates, limited timeline gaps, and the connection
91+checkpoint in one store commit. Stable room and event keys make replay
92+idempotent. The mentions watch snapshot is replaced before the ack, while
93+mirror and event-stream delivery and context capture run after it. The classic
9294 sync `next_batch` ordering differs and does not establish this guarantee.
9395 Nor does this ordering make arbitrary concurrent sync loops safe.
9496
97+The checkpoint keeps the position the committed response started from as well
98+as the one it ended at. A crash after the commit but before the SDK saves `pos`
99+replays from that earlier position, and the replay is a superset of what was
100+committed, so it is not treated as a discontinuity. Any other starting position
101+records a `SyncDiscontinuity` gap and marks every observed room unresolved.
102+
103+The hook never fails sync. A failed write keeps the inbox in memory, records a
104+`StoreFailed` gap, acks, and retries the write a minute later, so a restart
105+before a write lands shows up as a discontinuity. The hook also stops waiting
106+after ten seconds and records `HookTimedOut`, so a stuck actor cannot hold the
107+position lock.
108+
95109 Sources are `crates/matrix-sdk/src/event_handler/mod.rs`,
96110 `crates/matrix-sdk/src/event_handler/static_events.rs`,
97111 `crates/matrix-sdk/src/sliding_sync/client.rs`,
@@ -114,13 +128,13 @@ acknowledgement returns a sync error without advancing the position.
114128
115129 The hook runs outside SDK store locks and supports local futures on wasm and
116130 sendable futures natively. The default absent hook preserves existing SDK
117-behavior. Detached response processing observes a dropped store
131+behaviour. Detached response processing observes a dropped store
118132 acknowledgement after its caller is cancelled. Checkpoint replay works after
119133 SDK room state was saved, and stable event keys deduplicate repeated commits.
120134
121135 The SDK audit and focused validation covered commit ordering, failed and
122136 dropped acknowledgements, caller cancellation, replay after room-state
123-persistence, restoring a previous checkpoint, absent-hook behavior, builder
137+persistence, restoring a previous checkpoint, absent-hook behaviour, builder
124138 forwarding, and connection isolation. The SDK UI library also received a
125139 focused wasm compilation check with its `js` feature. The SDK changes are
126140 published to the user's fork under `mentions-commit-hook`, and the manifest
@@ -153,9 +167,11 @@ store-free client uses memory. Even with persistence, the asynchronous cache
153167 consumer is not awaited before Sliding Sync saves its position.
154168
155169 The inbox retains unresolved encrypted candidates as encrypted raw events
156-within a configurable bound. It retries due candidates at startup and resets
157-their retry time when decryption reports arrive. Candidate eviction sets the
158-coverage truncation flag. Imported keys and backups cannot reconstruct events
170+within a configurable bound. It retries due candidates at startup. A report of
171+resolved events retries the encrypted candidates of that room, and lag or a
172+new backup retries all of them. Candidates still unresolved a week after they
173+were first seen expire behind a `CandidateExpired` gap. Over the bound the
174+oldest events go first, and eviction sets the coverage truncation flag. Imported keys and backups cannot reconstruct events
159175 already discarded by every cache.
160176
161177 Sources are `crates/matrix-sdk/src/event_cache/mod.rs`,
@@ -168,19 +184,19 @@ Sources are `crates/matrix-sdk/src/event_cache/mod.rs`,
168184 Structured `m.mentions.user_ids` and `m.mentions.room` supply semantic
169185 candidates. The classifier does not infer pings from rendered bodies or
170186 unread counts. A reply relationship alone does not prove that a reply notifies
171-the user, so reply reasons require notification metadata and confirmation that
172-the referenced event belongs to the signed-in user.
187+the user, so reply reasons require confirmation that the referenced event
188+belongs to the signed-in user plus either an explicit user mention or a
189+notifying highlight.
173190
174191 `Room::event_push_actions` evaluates push rules but does not independently
175-filter ignored senders. The classifier calls `Client::is_user_ignored` and
176-rechecks entries when the ignore list changes.
192+filter ignored senders. The service reads the ignore list once per response
193+and again when it changes, and rechecks entries then.
177194
178195 A room mute can suppress evaluated notification actions while structured
179196 mention metadata remains present. The classifier retains explicit user
180-mentions in muted rooms and marks notification delivery as suppressed. Reply
181-reasons require both a reply to the user and a notifying highlight. Room-wide
197+mentions in muted rooms and marks notification delivery as suppressed. Room-wide
182198 reasons require the relevant push conditions and sender power level. Missing
183-authorization context remains unresolved for reevaluation. Keyword-only
199+authorisation context remains unresolved for reevaluation. Keyword-only
184200 notifications are outside the inbox. The classifier excludes the signed-in
185201 user's own events even when they contain explicit mention metadata.
186202
@@ -197,7 +213,7 @@ Ruma exposes
197213 It accepts opaque `from`, optional `limit`, and optional `only` fields.
198214 Responses contain notifications and an optional `next_token`. macltok requests
199215 50 notifications per page. Entries include raw events, room IDs, timestamps,
200-actions, and server notification read state, which does not initialize or
216+actions, and server notification read state, which does not initialise or
201217 replace local inbox read state.
202218
203219 The service uses this endpoint as a bounded candidate source, validates
@@ -207,7 +223,7 @@ including unresolved encrypted candidates. A failed commit leaves the prior
207223 cursor available for replay.
208224
209225 An exhausted notification list does not mean complete room history. Muted-room
210-pings, encrypted mentions not recognized by the server, expired notifications,
226+pings, encrypted mentions not recognised by the server, expired notifications,
211227 and server-specific retention can be absent. Recovery inspects at most 20 pages
212228 of 50 notifications per run and resumes from committed cursors later. The
213229 older-history cursor is separate from the cursor for scans from the newest
@@ -224,8 +240,7 @@ remain visible when continuity cannot be established.
224240 Coverage exposes the earliest observed timestamp, history-endpoint support,
225241 recovery activity, known room gaps, unresolved candidate count, and retention
226242 truncation. The earliest timestamp is a summary, not proof that every room is
227-covered after that time. The inbox keeps its incomplete-history flag even
228-after notification pagination ends.
243+covered after that time. Each gap carries a typed `MentionGapReason`.
229244
230245 Complete offline mention recovery across arbitrary muted and encrypted rooms
231246 is not supplied by this endpoint or by the current sync configuration. It
@@ -242,10 +257,12 @@ inbox commits and discovery checkpoints.
242257
243258 The passphrase wraps a random SDK `StoreCipher`. Logical keys are hashed, and
244259 authenticated encrypted records include schema, account identity, and logical
245-key. Restore rejects mismatched accounts, unsupported schemas, corrupt records,
246-and inconsistent record counts or byte totals. Atomic transactions compare the
247-previous encrypted header before publishing a new revision, so stale writers
248-cannot overwrite a newer checkpoint.
260+key. Restore treats mismatched accounts, unsupported schemas, corrupt records,
261+and inconsistent record counts or byte totals as unreadable, empties the store
262+and records a `StoreReset` gap rather than failing `Account::build`. A store
263+that cannot be opened at all leaves the inbox in memory behind `StoreFailed`.
264+Atomic transactions compare the previous encrypted header before publishing a
265+new revision, so stale writers cannot overwrite a newer checkpoint.
249266
250267 The default budget is 64 MiB of encrypted records, hashed keys, header, and
251268 wrapped cipher, excluding database page overhead. The store record cap is
@@ -255,22 +272,29 @@ limit requires eviction, the store drops bounded data and marks coverage
255272 truncated.
256273
257274 The storage tests cover atomic restore of records and checkpoints, stale
258-writers, capacity failure, encrypted physical records, wrong credentials or
259-account, corruption, unsupported schemas, and injected SQLite write failure.
260-The inbox tests cover batch failure, notification recovery, live context
261-updates, and capture persistence across session resume.
262-
263-
264-The inbox is serialized into one `inbox` value and encrypted as a single
265-record on every revision. Each update reserializes and re-encrypts the retained
266-inbox, so write cost scales with retained data. Decrypted previews, saved
267-context, raw candidates, pending capture state, media source registrations,
268-read state, coverage, and sync checkpoints stay inside the authenticated
269-encrypted payload. SDK SQLite custom values remain plaintext and are not used
275+writers, capacity failure, encrypted physical records, resets on wrong
276+credentials or account, corruption and unsupported schemas, and injected
277+SQLite write failure. The inbox tests cover failed writes, checkpoint-only
278+writes, replay from the previous checkpoint, reset of an unreadable inbox,
279+notification recovery, live context updates, and capture persistence across
280+session resume.
281+
282+The store holds two records. `inbox` carries entries, saved events, context
283+windows, candidates, edits, redactions, coverage and recovery cursors. `sync`
284+carries the connection checkpoints, observed rooms, per-room repair coverage
285+and the inbox revision it belongs to. Every response writes `sync`, and
286+`inbox` only when the response changed it, both in one commit. A quiet
287+response therefore costs one small record, while a changed inbox is still
288+reserialised and re-encrypted whole. Background completions are applied in
289+memory and written together at most once a second. When the budget is short,
290+eviction runs against a size estimate and the inbox is written once.
291+Decrypted previews, saved context, raw candidates, pending capture state,
292+media source registrations, read state, coverage, and sync checkpoints stay
293+inside the authenticated encrypted payloads. SDK SQLite custom values remain plaintext and are not used
270294 for decrypted mentions.
271295
272296 Context capture in `src/engine/context.rs` sizes the before and after requests
273-independently and retains the media registrations produced by normalization.
297+independently and retains the media registrations produced by normalisation.
274298 Each capture requests up to 10 events on each side. At most two capture jobs
275299 run concurrently, with up to 128 pending or failed entries considered for
276300 scheduling at a time. Capture uses independent `/context` and `/messages`
@@ -278,7 +302,7 @@ requests, so it does not paginate the live cache. Media source registrations
278302 are encrypted with saved events and registered again when context is read.
279303 Media bodies and key material do not enter the mentions public model.
280304
281-One account-scoped mentions actor serializes store commits for entries,
305+One account-scoped mentions actor serialises store commits for entries,
282306 unresolved candidates, shared context events, read state, revisions, discovery
283307 checkpoints, and retention decisions.
284308
@@ -308,7 +332,7 @@ that boundary, including older messages resolved by late decryption.
308332 ## Executable probes
309333
310334 `tests/mentions_discovery.rs` exercises unopened-room update and cache access,
311-push-rule behavior for ignored senders and muted rooms, and typed
335+push-rule behaviour for ignored senders and muted rooms, and typed
312336 notification-history pagination.
313337
314338 `tests/mentions_decryption.rs` exchanges a real encrypted event and a delayed
@@ -327,11 +351,12 @@ demonstrates that returning a handler error still advances the position.
327351 with revision boundaries, independent context limits, and persistence across
328352 session resume. `tests/mentions_late_decryption.rs` verifies that a delayed room key
329353 updates the production inbox once. `src/mentions/service_tests.rs` covers
330-checkpoint rollback, notification-history recovery, and context updates.
354+failed and checkpoint-only writes, checkpoint replay, store reset,
355+notification-history recovery, and context updates.
331356 `src/mentions/store/tests.rs` exercises encrypted record restore, capacity,
332357 stale writers, and corruption handling.
333358
334-These probes establish SDK behavior against mocked responses. They do not
359+These probes establish SDK behaviour against mocked responses. They do not
335360 establish homeserver notification retention, room-list completeness, or
336361 complete history on a deployed server.