atagen

macltok

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

Blame: docs/review-2026-09-24.md

96fdf90aacd6atagen2026-09-08 08:24 UTC1
# macltok review, 2026-09-24
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
Second full review, taken after the restructure and Tier 0 landed. It covers
96fdf90aacd6atagen2026-09-08 08:24 UTC4
correctness, races, robustness, duplication, antipatterns and how easy the crate
96fdf90aacd6atagen2026-09-08 08:24 UTC5
is to build a client on, then folds in the API gaps reported by Malice, the
96fdf90aacd6atagen2026-09-08 08:24 UTC6
first real client built on macltok.
96fdf90aacd6atagen2026-09-08 08:24 UTC7
96fdf90aacd6atagen2026-09-08 08:24 UTC8
Every finding carries an id so fixes can reference it. Status starts as OPEN.
96fdf90aacd6atagen2026-09-08 08:24 UTC9
SDK claims cite matrix-sdk 0.18 source under `~/.cargo/registry` and were
96fdf90aacd6atagen2026-09-08 08:24 UTC10
checked against the code rather than recalled.
96fdf90aacd6atagen2026-09-08 08:24 UTC11
96fdf90aacd6atagen2026-09-08 08:24 UTC12
Baseline when written. `cargo fmt --check` clean, all default-feature tests
96fdf90aacd6atagen2026-09-08 08:24 UTC13
pass. The sqlite and clippy runs are recorded at the end.
96fdf90aacd6atagen2026-09-08 08:24 UTC14
96fdf90aacd6atagen2026-09-08 08:24 UTC15
## C. Correctness
96fdf90aacd6atagen2026-09-08 08:24 UTC16
fa9114fcf75fatagen2026-09-24 12:51 UTC17
### C1. `Verify(Reject)` never rejects. done
96fdf90aacd6atagen2026-09-08 08:24 UTC18
96fdf90aacd6atagen2026-09-08 08:24 UTC19
`VerificationController::reject` (`src/encryption.rs:339-389`) validates the
96fdf90aacd6atagen2026-09-08 08:24 UTC20
SAS state twice and returns `Ok(())` without ever calling `sas.mismatch()`. The
96fdf90aacd6atagen2026-09-08 08:24 UTC21
peer is never told the emoji did not match, the local flow stays in
96fdf90aacd6atagen2026-09-08 08:24 UTC22
comparison, and the command reports success. A user who presses "they don't
96fdf90aacd6atagen2026-09-08 08:24 UTC23
match" gets no protection from the one action meant to provide it. No test
96fdf90aacd6atagen2026-09-08 08:24 UTC24
exercises `Reject`.
96fdf90aacd6atagen2026-09-08 08:24 UTC25
fa9114fcf75fatagen2026-09-24 12:51 UTC26
### C2. Sync dies permanently after `SyncState::Error`. done
96fdf90aacd6atagen2026-09-08 08:24 UTC27
96fdf90aacd6atagen2026-09-08 08:24 UTC28
The connection watcher maps `SyncState::Error` to `ConnectionState::Offline`
96fdf90aacd6atagen2026-09-08 08:24 UTC29
(`src/engine.rs:1338-1341`) and nothing ever calls `SyncService::start` again.
96fdf90aacd6atagen2026-09-08 08:24 UTC30
In the SDK the supervisor breaks out of its loop on error, and even offline
96fdf90aacd6atagen2026-09-08 08:24 UTC31
mode ends in `Error` when its reachability check fails
96fdf90aacd6atagen2026-09-08 08:24 UTC32
(`matrix-sdk-ui sync_service.rs:313-339`). `start()` is the only way back
96fdf90aacd6atagen2026-09-08 08:24 UTC33
(`sync_service.rs:629-649`). The client sees "Offline" forever while the engine
96fdf90aacd6atagen2026-09-08 08:24 UTC34
still reports itself healthy. `SyncState::Terminated` has the same problem, it
96fdf90aacd6atagen2026-09-08 08:24 UTC35
becomes `ConnectionState::Stopped` while the engine keeps running, so
96fdf90aacd6atagen2026-09-08 08:24 UTC36
`Stopped` now means two different things.
96fdf90aacd6atagen2026-09-08 08:24 UTC37
96fdf90aacd6atagen2026-09-08 08:24 UTC38
Fix direction. Restart with backoff on `Error` and `Terminated`, and report
96fdf90aacd6atagen2026-09-08 08:24 UTC39
`Stopped` only from engine shutdown.
96fdf90aacd6atagen2026-09-08 08:24 UTC40
fa9114fcf75fatagen2026-09-24 12:51 UTC41
### C3. A room's send queue stays disabled after any send failure. done
96fdf90aacd6atagen2026-09-08 08:24 UTC42
96fdf90aacd6atagen2026-09-08 08:24 UTC43
The SDK disables a room's queue after every send error, recoverable or not
96fdf90aacd6atagen2026-09-08 08:24 UTC44
(`matrix-sdk send_queue/mod.rs:1011-1019`), and nothing in matrix-sdk-ui turns
96fdf90aacd6atagen2026-09-08 08:24 UTC45
it back on. macltok re-enables only inside `RetrySend`
96fdf90aacd6atagen2026-09-08 08:24 UTC46
(`src/engine.rs:2178`), and does so globally. One network blip therefore
96fdf90aacd6atagen2026-09-08 08:24 UTC47
pauses every later message in that room until the user explicitly retries a
96fdf90aacd6atagen2026-09-08 08:24 UTC48
specific transaction. Element X re-enables queues when sync returns to
96fdf90aacd6atagen2026-09-08 08:24 UTC49
running.
96fdf90aacd6atagen2026-09-08 08:24 UTC50
96fdf90aacd6atagen2026-09-08 08:24 UTC51
Fix direction. Re-enable on each transition to `Online`, and keep
96fdf90aacd6atagen2026-09-08 08:24 UTC52
`RetrySend` for unwedging a single item.
96fdf90aacd6atagen2026-09-08 08:24 UTC53
fa9114fcf75fatagen2026-09-24 12:51 UTC54
### C4. Own encrypted uploads cannot be fetched, and the media cache is split between two formats. done, with a round trip test through the sdk cache
96fdf90aacd6atagen2026-09-08 08:24 UTC55
96fdf90aacd6atagen2026-09-08 08:24 UTC56
macltok writes ciphertext into the media store under the `Encrypted` source key
96fdf90aacd6atagen2026-09-08 08:24 UTC57
and decrypts on every read (`src/engine.rs:2799-2821`, `2864-2874`). The SDK
96fdf90aacd6atagen2026-09-08 08:24 UTC58
stores plaintext under that same key, both in `Media::get_media_content`
96fdf90aacd6atagen2026-09-08 08:24 UTC59
(`matrix-sdk media.rs:451-480`, `522-528`) and in the send queue. After an
96fdf90aacd6atagen2026-09-08 08:24 UTC60
upload the send queue renames its plaintext cache entry to the remote
96fdf90aacd6atagen2026-09-08 08:24 UTC61
`Encrypted` source (`send_queue/upload.rs:928-953`). So fetching your own sent
96fdf90aacd6atagen2026-09-08 08:24 UTC62
image in an encrypted room reads plaintext, runs it through
96fdf90aacd6atagen2026-09-08 08:24 UTC63
`AttachmentDecryptor`, fails the hash check and returns `MediaUnavailable`. The
96fdf90aacd6atagen2026-09-08 08:24 UTC64
reverse also holds. Any SDK path that reads a macltok-written entry gets
96fdf90aacd6atagen2026-09-08 08:24 UTC65
ciphertext as if it were the image. The media tests force plaintext rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC66
(`tests/media.rs:766`), which is why this passed.
96fdf90aacd6atagen2026-09-08 08:24 UTC67
96fdf90aacd6atagen2026-09-08 08:24 UTC68
Fix direction. Cache plaintext, matching the SDK, and keep the byte bound by
96fdf90aacd6atagen2026-09-08 08:24 UTC69
decrypting in a streaming fashion before the write.
96fdf90aacd6atagen2026-09-08 08:24 UTC70
31ce2453c82fatagen2026-09-24 13:07 UTC71
### C5. Media single-flight is poisoned by a leader panic. done, shared-future single flight in the media query
96fdf90aacd6atagen2026-09-08 08:24 UTC72
96fdf90aacd6atagen2026-09-08 08:24 UTC73
In `spawn_media_fetch` (`src/engine.rs:1870-1911`) followers push their id and
96fdf90aacd6atagen2026-09-08 08:24 UTC74
return at once, trusting the leader to answer for them. If `fetch_media`
96fdf90aacd6atagen2026-09-08 08:24 UTC75
panics, the leader's own id gets "command task panicked", the followers get
96fdf90aacd6atagen2026-09-08 08:24 UTC76
nothing, and the `(MediaId, MediaSize)` entry is never removed. Every later
96fdf90aacd6atagen2026-09-08 08:24 UTC77
fetch of that media then becomes a follower of a dead leader and never
96fdf90aacd6atagen2026-09-08 08:24 UTC78
completes. Largely superseded if media fetch becomes a direct query (see
96fdf90aacd6atagen2026-09-08 08:24 UTC79
M2), but the fix is the same either way. Use a drop guard or a shared future
96fdf90aacd6atagen2026-09-08 08:24 UTC80
so the entry always clears.
96fdf90aacd6atagen2026-09-08 08:24 UTC81
fa9114fcf75fatagen2026-09-24 12:51 UTC82
### C6. Back pagination can stick at "start reached" forever. done, the sdk already remembers
96fdf90aacd6atagen2026-09-08 08:24 UTC83
96fdf90aacd6atagen2026-09-08 08:24 UTC84
`reached_start` is a cached `AtomicBool` that is only ever set from a
96fdf90aacd6atagen2026-09-08 08:24 UTC85
pagination result (`src/engine.rs:4060-4071`, `4097`). A limited sync with a
96fdf90aacd6atagen2026-09-08 08:24 UTC86
gap makes the event cache shrink to the last chunk
96fdf90aacd6atagen2026-09-08 08:24 UTC87
(`event_cache/caches/room/state.rs:917-924`), which the timeline applies as
96fdf90aacd6atagen2026-09-08 08:24 UTC88
`VectorDiff::Clear`. History is unloaded again but the flag stays true, so
96fdf90aacd6atagen2026-09-08 08:24 UTC89
every later `Paginate` emits `StartReached` without fetching anything. The
96fdf90aacd6atagen2026-09-08 08:24 UTC90
room scrolls to a stub and stops.
96fdf90aacd6atagen2026-09-08 08:24 UTC91
96fdf90aacd6atagen2026-09-08 08:24 UTC92
Fix direction. Drop the flag and follow `Timeline::live_back_pagination_status`
96fdf90aacd6atagen2026-09-08 08:24 UTC93
(`matrix-sdk-ui timeline/pagination.rs:130-138`), which is authoritative.
96fdf90aacd6atagen2026-09-08 08:24 UTC94
fa9114fcf75fatagen2026-09-24 12:51 UTC95
### C7. Soft logout destroys the store. done, `SessionAccessRejected { soft_logout }` and a same-device login
96fdf90aacd6atagen2026-09-08 08:24 UTC96
96fdf90aacd6atagen2026-09-08 08:24 UTC97
`SessionChange::UnknownToken` carries `soft_logout`
96fdf90aacd6atagen2026-09-08 08:24 UTC98
(`matrix-sdk client/mod.rs:166`), but the watcher collapses it into a
96fdf90aacd6atagen2026-09-08 08:24 UTC99
payload-free `SessionAccessRejected` (`src/engine.rs:1314`). The README and the
96fdf90aacd6atagen2026-09-08 08:24 UTC100
event's doc comment both tell the client to delete the saved record and the
96fdf90aacd6atagen2026-09-08 08:24 UTC101
store. On a soft logout the correct response is to re-authenticate and keep
96fdf90aacd6atagen2026-09-08 08:24 UTC102
the store, so following the documentation throws away every E2EE key for
96fdf90aacd6atagen2026-09-08 08:24 UTC103
nothing.
96fdf90aacd6atagen2026-09-08 08:24 UTC104
fa9114fcf75fatagen2026-09-24 12:51 UTC105
### C8. Logout and rejected access leave the engine running. done
96fdf90aacd6atagen2026-09-08 08:24 UTC106
96fdf90aacd6atagen2026-09-08 08:24 UTC107
`spawn_logout` emits `LoggedOut` and returns (`src/engine.rs:825-852`). The
96fdf90aacd6atagen2026-09-08 08:24 UTC108
sync service, watchers and room actors keep running against a dead session,
96fdf90aacd6atagen2026-09-08 08:24 UTC109
and every later command fails with an authentication error. Neither logout nor
96fdf90aacd6atagen2026-09-08 08:24 UTC110
a hard `UnknownToken` shuts the engine down. The engine should stop itself,
96fdf90aacd6atagen2026-09-08 08:24 UTC111
emitting `LoggedOut` or the rejection before `Stopped`.
96fdf90aacd6atagen2026-09-08 08:24 UTC112
fa9114fcf75fatagen2026-09-24 12:51 UTC113
### C9. `MarkRead` can move the read marker backwards and costs two requests. done, open rooms go through the timeline so stale receipts get dropped
96fdf90aacd6atagen2026-09-08 08:24 UTC114
96fdf90aacd6atagen2026-09-08 08:24 UTC115
`mark_read` (`src/engine.rs:2072-2095`) calls `Room::send_single_receipt`
96fdf90aacd6atagen2026-09-08 08:24 UTC116
twice. That call does no ordering check
96fdf90aacd6atagen2026-09-08 08:24 UTC117
(`matrix-sdk room/mod.rs:2167-2201`), so a client that marks an older event
96fdf90aacd6atagen2026-09-08 08:24 UTC118
drags both the public receipt and the fully-read marker back. The timeline API
96fdf90aacd6atagen2026-09-08 08:24 UTC119
refuses stale receipts (`timeline/controller/mod.rs:1621-1690`), and
96fdf90aacd6atagen2026-09-08 08:24 UTC120
`Room::send_multiple_receipts` sets fully read and read in one
96fdf90aacd6atagen2026-09-08 08:24 UTC121
`/read_markers` call (`room/mod.rs:2213-2230`). Private read receipts are not
96fdf90aacd6atagen2026-09-08 08:24 UTC122
offered at all.
96fdf90aacd6atagen2026-09-08 08:24 UTC123
96fdf90aacd6atagen2026-09-08 08:24 UTC124
Fix direction. When the room is open, route through the actor and use
96fdf90aacd6atagen2026-09-08 08:24 UTC125
`Timeline::send_multiple_receipts`. Otherwise use a single
96fdf90aacd6atagen2026-09-08 08:24 UTC126
`Room::send_multiple_receipts`.
96fdf90aacd6atagen2026-09-08 08:24 UTC127
fa9114fcf75fatagen2026-09-24 12:51 UTC128
### C10. `create_direct` always creates a new room. done
96fdf90aacd6atagen2026-09-08 08:24 UTC129
96fdf90aacd6atagen2026-09-08 08:24 UTC130
`Client::create_dm` never reuses an existing DM
96fdf90aacd6atagen2026-09-08 08:24 UTC131
(`matrix-sdk client/mod.rs:1830-1849`). Starting a chat with someone you
96fdf90aacd6atagen2026-09-08 08:24 UTC132
already talk to creates a duplicate every time. `Client::get_dm_room` exists
96fdf90aacd6atagen2026-09-08 08:24 UTC133
for the lookup (`client/mod.rs:1852`).
96fdf90aacd6atagen2026-09-08 08:24 UTC134
31ce2453c82fatagen2026-09-24 13:07 UTC135
### C11. Unregistered tasks swallow panics, contradicting the design doc. done, command tasks answer `Interrupted`, actor internals are supervised and panic the actor
96fdf90aacd6atagen2026-09-08 08:24 UTC136
96fdf90aacd6atagen2026-09-08 08:24 UTC137
The design doc says a watcher panic is terminal. But the timeline watcher
96fdf90aacd6atagen2026-09-08 08:24 UTC138
(`src/engine.rs:589`), pagination worker (`446`), pagination result task
96fdf90aacd6atagen2026-09-08 08:24 UTC139
(`472`) and verification watcher (`src/encryption.rs:276`) are bare
96fdf90aacd6atagen2026-09-08 08:24 UTC140
`tokio::spawn`s. A panic in a timeline watcher freezes that timeline in
96fdf90aacd6atagen2026-09-08 08:24 UTC141
silence while commands keep succeeding. The reverse mistake also exists. The
96fdf90aacd6atagen2026-09-08 08:24 UTC142
account-data writers (`src/engine.rs:1111`, `1141`) and the per-room media
96fdf90aacd6atagen2026-09-08 08:24 UTC143
upload watcher (`1945`) are registered as `TaskContext::Watcher`, so a panic
96fdf90aacd6atagen2026-09-08 08:24 UTC144
in a single write kills the whole engine.
96fdf90aacd6atagen2026-09-08 08:24 UTC145
fa9114fcf75fatagen2026-09-24 12:51 UTC146
### C12. The room actor blocks on network requests. done, redact reactions and receipts run beside the actor. replies stay serial so sends keep their order
96fdf90aacd6atagen2026-09-08 08:24 UTC147
96fdf90aacd6atagen2026-09-08 08:24 UTC148
Each room actor runs its commands serially and awaits each one. Most sends
96fdf90aacd6atagen2026-09-08 08:24 UTC149
only enqueue, but `Timeline::redact` on a remote event is a direct HTTP call
96fdf90aacd6atagen2026-09-08 08:24 UTC150
(`timeline/mod.rs:630`), and so is removing a reaction
96fdf90aacd6atagen2026-09-08 08:24 UTC151
(`controller/mod.rs:595`). `Room::make_reply_event` can fetch the replied-to
96fdf90aacd6atagen2026-09-08 08:24 UTC152
event (`room/reply.rs:108`). A slow homeserver therefore stalls every later
96fdf90aacd6atagen2026-09-08 08:24 UTC153
send, paginate and thread command in that room behind one redaction.
96fdf90aacd6atagen2026-09-08 08:24 UTC154
fa9114fcf75fatagen2026-09-24 12:51 UTC155
### C13. Media registry evicts media that is still on screen. done, lru by stamp and released on close
96fdf90aacd6atagen2026-09-08 08:24 UTC156
96fdf90aacd6atagen2026-09-08 08:24 UTC157
`MediaRegistry::insert_all` (`src/engine.rs:358-370`) evicts in first-insertion
96fdf90aacd6atagen2026-09-08 08:24 UTC158
order and does not refresh position on re-registration. One registry of 2048
96fdf90aacd6atagen2026-09-08 08:24 UTC159
entries is shared by every room. In a long-lived session, the first messages
96fdf90aacd6atagen2026-09-08 08:24 UTC160
loaded in a room that is still open are the first evicted, and fetching them
96fdf90aacd6atagen2026-09-08 08:24 UTC161
then fails with `MediaUnknown`. Closed timelines never release their entries,
96fdf90aacd6atagen2026-09-08 08:24 UTC162
so they compete with open ones.
96fdf90aacd6atagen2026-09-08 08:24 UTC163
31ce2453c82fatagen2026-09-24 13:07 UTC164
### C14. Smaller correctness issues. done, bar the lagged session watcher which stays as is
96fdf90aacd6atagen2026-09-08 08:24 UTC165
96fdf90aacd6atagen2026-09-08 08:24 UTC166
- `CommandResultError::Failed` renders its `Display` through `Debug`
96fdf90aacd6atagen2026-09-08 08:24 UTC167
  (`src/transport.rs:840`), so users see a struct dump.
96fdf90aacd6atagen2026-09-08 08:24 UTC168
- `download_bounded` builds a fresh `reqwest::Client` per fetch just to build a
96fdf90aacd6atagen2026-09-08 08:24 UTC169
  request (`src/engine.rs:3181`). That reloads the TLS roots every time.
96fdf90aacd6atagen2026-09-08 08:24 UTC170
  `http_client.get(...)` is enough. On a 401 it refreshes the token and then
96fdf90aacd6atagen2026-09-08 08:24 UTC171
  fails rather than retrying once.
96fdf90aacd6atagen2026-09-08 08:24 UTC172
- On `Lagged`, the session watcher assumes the dropped message was a token
96fdf90aacd6atagen2026-09-08 08:24 UTC173
  refresh (`src/engine.rs:1315`). A dropped `UnknownToken` is lost.
96fdf90aacd6atagen2026-09-08 08:24 UTC174
- Leaving a room does not close its open actor, so its timeline, typing
96fdf90aacd6atagen2026-09-08 08:24 UTC175
  subscription and room subscription outlive the membership.
96fdf90aacd6atagen2026-09-08 08:24 UTC176
- `open_room` on a room that is already open succeeds with no `TimelineReset`
96fdf90aacd6atagen2026-09-08 08:24 UTC177
  (`src/engine.rs:1608`). A second frontend sharing the engine gets success and
96fdf90aacd6atagen2026-09-08 08:24 UTC178
  no snapshot.
96fdf90aacd6atagen2026-09-08 08:24 UTC179
- On shutdown, in-flight commands never get a terminal result. That is
96fdf90aacd6atagen2026-09-08 08:24 UTC180
  acceptable, but the docs should say that `Stopped` fails everything still
96fdf90aacd6atagen2026-09-08 08:24 UTC181
  outstanding.
96fdf90aacd6atagen2026-09-08 08:24 UTC182
- A store path reused after a login whose record was never persisted holds a
96fdf90aacd6atagen2026-09-08 08:24 UTC183
  crypto account for another device, and the SDK refuses it. Document one
96fdf90aacd6atagen2026-09-08 08:24 UTC184
  store directory per login.
96fdf90aacd6atagen2026-09-08 08:24 UTC185
96fdf90aacd6atagen2026-09-08 08:24 UTC186
## A. API shape and client-builder friendliness
96fdf90aacd6atagen2026-09-08 08:24 UTC187
31ce2453c82fatagen2026-09-24 13:07 UTC188
### A1. Three completion mechanisms, and the useful one is rationed. done, one awaited path and one error type
96fdf90aacd6atagen2026-09-08 08:24 UTC189
96fdf90aacd6atagen2026-09-08 08:24 UTC190
A client must know which of three patterns each operation uses.
96fdf90aacd6atagen2026-09-08 08:24 UTC191
96fdf90aacd6atagen2026-09-08 08:24 UTC192
- `send` returns a `CommandId`. The client correlates it by hand against
96fdf90aacd6atagen2026-09-08 08:24 UTC193
  `CommandSucceeded`, `CommandSucceededWithRoom` or `CommandFailed` on a
96fdf90aacd6atagen2026-09-08 08:24 UTC194
  shared stream.
96fdf90aacd6atagen2026-09-08 08:24 UTC195
- Five direct methods (`join_room`, `create_room`, `create_direct`,
96fdf90aacd6atagen2026-09-08 08:24 UTC196
  `delete_devices`, and the internal room-result path) await a `oneshot` and
96fdf90aacd6atagen2026-09-08 08:24 UTC197
  return `CommandResultError`. These emit nothing on the stream.
96fdf90aacd6atagen2026-09-08 08:24 UTC198
- The account-data writes await a `oneshot` but return `ClientQueryError`,
96fdf90aacd6atagen2026-09-08 08:24 UTC199
  with no `CommandId` at all.
96fdf90aacd6atagen2026-09-08 08:24 UTC200
96fdf90aacd6atagen2026-09-08 08:24 UTC201
The headless example is mostly bookkeeping of ids. Malice gap 4 comes straight
96fdf90aacd6atagen2026-09-08 08:24 UTC202
from this. The one pattern a web backend can use, an awaited result per
96fdf90aacd6atagen2026-09-08 08:24 UTC203
request, is not offered for most operations.
96fdf90aacd6atagen2026-09-08 08:24 UTC204
31ce2453c82fatagen2026-09-24 13:07 UTC205
### A2. Pure reads modelled as commands, against the doc's own rule. done, `media` and `mxc_media` are handle queries
96fdf90aacd6atagen2026-09-08 08:24 UTC206
96fdf90aacd6atagen2026-09-08 08:24 UTC207
The design doc says operations are split by whether they change state. Media
96fdf90aacd6atagen2026-09-08 08:24 UTC208
fetch changes no state, yet it is a `MediaCommand` whose bytes arrive as a
96fdf90aacd6atagen2026-09-08 08:24 UTC209
`MediaReady` broadcast on the shared stream. Every frontend then receives every
96fdf90aacd6atagen2026-09-08 08:24 UTC210
other frontend's image bytes, up to 20 MiB each, and the requester has to
96fdf90aacd6atagen2026-09-08 08:24 UTC211
filter by `MediaId`.
96fdf90aacd6atagen2026-09-08 08:24 UTC212
960ac0721079atagen2026-09-25 00:24 UTC213
### A3. Every timeline item is a `Message`, with presentation strings in `body`. done
96fdf90aacd6atagen2026-09-08 08:24 UTC214
96fdf90aacd6atagen2026-09-08 08:24 UTC215
`normalise_timeline_item` (`src/normalise/timeline.rs`) flattens every event
96fdf90aacd6atagen2026-09-08 08:24 UTC216
into `TimelineMessage`. The consequences for a client are these.
96fdf90aacd6atagen2026-09-08 08:24 UTC217
96fdf90aacd6atagen2026-09-08 08:24 UTC218
- Membership changes, profile changes and room state changes arrive as
96fdf90aacd6atagen2026-09-08 08:24 UTC219
  messages whose body reads `[m.room.member]` or similar.
96fdf90aacd6atagen2026-09-08 08:24 UTC220
- Redacted and undecryptable events carry hard-coded English bodies,
96fdf90aacd6atagen2026-09-08 08:24 UTC221
  `<message redacted>` and `<unable to decrypt message>`. Only UTD has a flag.
96fdf90aacd6atagen2026-09-08 08:24 UTC222
  A redaction cannot be told apart from a user who typed that text.
96fdf90aacd6atagen2026-09-08 08:24 UTC223
- `msgtype` is dropped, so emotes and notices cannot be rendered differently
96fdf90aacd6atagen2026-09-08 08:24 UTC224
  from text.
96fdf90aacd6atagen2026-09-08 08:24 UTC225
- No sender avatar, though the SDK profile has one. `display_name_ambiguous`
96fdf90aacd6atagen2026-09-08 08:24 UTC226
  is ignored, so two "Alice"s look identical.
96fdf90aacd6atagen2026-09-08 08:24 UTC227
- Reply references hold no preview content, and reply details are never
96fdf90aacd6atagen2026-09-08 08:24 UTC228
  fetched. The SDK leaves them `Unavailable` until the app calls
96fdf90aacd6atagen2026-09-08 08:24 UTC229
  `Timeline::fetch_details_for_event` (`timeline/event_item/content/reply.rs:38`),
96fdf90aacd6atagen2026-09-08 08:24 UTC230
  so replies to anything outside the loaded window never get a sender.
96fdf90aacd6atagen2026-09-08 08:24 UTC231
- `TimelineItem` is not `#[non_exhaustive]`, so adding a state-event variant
96fdf90aacd6atagen2026-09-08 08:24 UTC232
  later is a break.
96fdf90aacd6atagen2026-09-08 08:24 UTC233
96fdf90aacd6atagen2026-09-08 08:24 UTC234
This needs a content enum, for example `Message { msgtype, ... }`, `Redacted`,
96fdf90aacd6atagen2026-09-08 08:24 UTC235
`UnableToDecrypt`, `Membership`, `ProfileChange`, `RoomState`, `Poll` and
96fdf90aacd6atagen2026-09-08 08:24 UTC236
`Other { event_type }`. It is the largest single rendering gap.
96fdf90aacd6atagen2026-09-08 08:24 UTC237
960ac0721079atagen2026-09-25 00:24 UTC238
### A4. Timeline commands cannot target local echoes. done
96fdf90aacd6atagen2026-09-08 08:24 UTC239
96fdf90aacd6atagen2026-09-08 08:24 UTC240
`Edit`, `Redact` and `ToggleReaction` take an `OwnedEventId` and wrap it in
96fdf90aacd6atagen2026-09-08 08:24 UTC241
`TimelineEventItemId::EventId`. The SDK accepts transaction ids for all three,
96fdf90aacd6atagen2026-09-08 08:24 UTC242
but a client cannot edit, redact or react to a message it sent until the
96fdf90aacd6atagen2026-09-08 08:24 UTC243
server echo arrives. The commands should take the identifier that the model
96fdf90aacd6atagen2026-09-08 08:24 UTC244
already exposes.
96fdf90aacd6atagen2026-09-08 08:24 UTC245
960ac0721079atagen2026-09-25 00:24 UTC246
### A5. `RoomSummary` is too thin for a room list. done
96fdf90aacd6atagen2026-09-08 08:24 UTC247
96fdf90aacd6atagen2026-09-08 08:24 UTC248
Missing fields, all cheap SDK reads. Topic, canonical alias, joined and invited
96fdf90aacd6atagen2026-09-08 08:24 UTC249
member counts, heroes, tombstone and successor, inviter for invites, a latest
96fdf90aacd6atagen2026-09-08 08:24 UTC250
event preview, and space parents and children (Malice gap 3).
96fdf90aacd6atagen2026-09-08 08:24 UTC251
5a52012c9b1datagen2026-09-24 23:45 UTC252
### A6. No incoming verification. done, ported from malice's vendored copy
96fdf90aacd6atagen2026-09-08 08:24 UTC253
96fdf90aacd6atagen2026-09-08 08:24 UTC254
Only outgoing `StartVerification` exists. A request started from another
96fdf90aacd6atagen2026-09-08 08:24 UTC255
device, which is the normal "verify this new login" flow, is never observed.
96fdf90aacd6atagen2026-09-08 08:24 UTC256
macltok registers no handler for it, and the SDK has no stream, so the app
96fdf90aacd6atagen2026-09-08 08:24 UTC257
pattern is an event handler plus `Encryption::get_verification_request`
96fdf90aacd6atagen2026-09-08 08:24 UTC258
(`encryption/mod.rs:1060`).
96fdf90aacd6atagen2026-09-08 08:24 UTC259
31ce2453c82fatagen2026-09-24 13:07 UTC260
### A7. Missing `#[non_exhaustive]`. done
96fdf90aacd6atagen2026-09-08 08:24 UTC261
96fdf90aacd6atagen2026-09-08 08:24 UTC262
Adding the attribute later is itself a breaking change, so it belongs now. Output types that will
96fdf90aacd6atagen2026-09-08 08:24 UTC263
grow are `TimelineItem`, `CommandKind`, `RoomMembership`, `VerificationState`,
96fdf90aacd6atagen2026-09-08 08:24 UTC264
`HistoryState`, `KeyBackupState` and `RecoveryState`. Input structs that will
96fdf90aacd6atagen2026-09-08 08:24 UTC265
grow need a constructor plus `#[non_exhaustive]`. These are
96fdf90aacd6atagen2026-09-08 08:24 UTC266
`CreateRoomOptions`, `PasswordLogin`, `OAuthLogin` and `UploadThumbnail`.
96fdf90aacd6atagen2026-09-08 08:24 UTC267
31ce2453c82fatagen2026-09-24 13:07 UTC268
### A8. Smaller API issues. mostly done. `HistoryState::Failed(String)`, stringly power level events and fixed encryption defaults remain
96fdf90aacd6atagen2026-09-08 08:24 UTC269
96fdf90aacd6atagen2026-09-08 08:24 UTC270
- `MediaCommand::Upload` duplicates `UploadAttachment`, and the handle exposes
96fdf90aacd6atagen2026-09-08 08:24 UTC271
  both `upload_media` and `upload_attachment`.
96fdf90aacd6atagen2026-09-08 08:24 UTC272
- Convenience methods cover room and account commands but not timeline send,
96fdf90aacd6atagen2026-09-08 08:24 UTC273
  edit, paginate, mark read, typing, recovery or verification, so a client uses
96fdf90aacd6atagen2026-09-08 08:24 UTC274
  two styles side by side.
96fdf90aacd6atagen2026-09-08 08:24 UTC275
- `HistoryState::Failed(String)` and `RoomPowerLevels::events: Vec<(String, i64)>`
96fdf90aacd6atagen2026-09-08 08:24 UTC276
  are stringly typed where typed values exist.
96fdf90aacd6atagen2026-09-08 08:24 UTC277
- `rooms_filtered` re-implements the search half of `RoomListFilter::matches`,
96fdf90aacd6atagen2026-09-08 08:24 UTC278
  which itself lowercases the needle once per room.
96fdf90aacd6atagen2026-09-08 08:24 UTC279
- `RoomsChanged` is sent once per notable room-info update with no coalescing
96fdf90aacd6atagen2026-09-08 08:24 UTC280
  at source. A sync touching fifty rooms queues fifty invalidations, each of
96fdf90aacd6atagen2026-09-08 08:24 UTC281
  which prompts a full `rooms()` rebuild in a naive client.
96fdf90aacd6atagen2026-09-08 08:24 UTC282
- The encryption defaults (`auto_enable_backups`, `OneShot` backup download)
96fdf90aacd6atagen2026-09-08 08:24 UTC283
  are hard-coded with no override.
96fdf90aacd6atagen2026-09-08 08:24 UTC284
96fdf90aacd6atagen2026-09-08 08:24 UTC285
## D. Duplication and antipatterns
96fdf90aacd6atagen2026-09-08 08:24 UTC286
31ce2453c82fatagen2026-09-24 13:07 UTC287
mostly done by the engine split. the actor thread registry, one spawn helper, one responder, one media request path, one test support module. `get_room` lookups went from twenty one hand rolled errors to a helper per module
31ce2453c82fatagen2026-09-24 13:07 UTC288
96fdf90aacd6atagen2026-09-08 08:24 UTC289
Line references are from the mechanical inventory taken during this review.
96fdf90aacd6atagen2026-09-08 08:24 UTC290
96fdf90aacd6atagen2026-09-08 08:24 UTC291
- `RoomActor::run` is about 700 lines. The "queue while the thread is opening,
96fdf90aacd6atagen2026-09-08 08:24 UTC292
  else QueueFull" block appears four times (`src/engine.rs:3367-3402`,
96fdf90aacd6atagen2026-09-08 08:24 UTC293
  `3518-3549`, `3625-3644`), and emit-then-`break 'actor` about twenty times.
96fdf90aacd6atagen2026-09-08 08:24 UTC294
  A thread registry type with one `route(id, root, command)` method removes
96fdf90aacd6atagen2026-09-08 08:24 UTC295
  most of it.
96fdf90aacd6atagen2026-09-08 08:24 UTC296
- Six `spawn_*` helpers share one shape of clone, spawn, run, emit
96fdf90aacd6atagen2026-09-08 08:24 UTC297
  (`src/engine.rs:825-960`). `spawn_rename_device` hand-rolls the same thing
96fdf90aacd6atagen2026-09-08 08:24 UTC298
  without `emit_command_result`. `CommandFailure` is built by hand in nine
96fdf90aacd6atagen2026-09-08 08:24 UTC299
  places. The reply-or-event branch appears nine times. One `spawn_command(id,
96fdf90aacd6atagen2026-09-08 08:24 UTC300
  kind, future)` plus one completion sink covers all of them, and A1 removes
96fdf90aacd6atagen2026-09-08 08:24 UTC301
  the branch entirely.
96fdf90aacd6atagen2026-09-08 08:24 UTC302
- `media_http_request` (`src/engine.rs:2991-3175`) is six near-identical
96fdf90aacd6atagen2026-09-08 08:24 UTC303
  branches that differ by endpoint type and token mode.
96fdf90aacd6atagen2026-09-08 08:24 UTC304
- Twenty-one `client.get_room(...)` lookups with hand-written `RoomUnknown`
96fdf90aacd6atagen2026-09-08 08:24 UTC305
  errors, ten of them consecutive match arms in `execute_room_command`.
96fdf90aacd6atagen2026-09-08 08:24 UTC306
- `media_from_message` and `media_registration` walk `MessageType` twice for
96fdf90aacd6atagen2026-09-08 08:24 UTC307
  the same item. The descriptor can be derived from the registration.
96fdf90aacd6atagen2026-09-08 08:24 UTC308
- The verification controller repeats its "done, then cancelled, then error"
96fdf90aacd6atagen2026-09-08 08:24 UTC309
  terminal check eight times. It also carries an unused `_client` parameter,
96fdf90aacd6atagen2026-09-08 08:24 UTC310
  and the encryption worker builds a throwaway `VerificationController` to run
96fdf90aacd6atagen2026-09-08 08:24 UTC311
  `Recover`. `EncryptionWork` is a single-variant enum.
96fdf90aacd6atagen2026-09-08 08:24 UTC312
- `catch_unwind` with a "... panicked" string appears six times.
96fdf90aacd6atagen2026-09-08 08:24 UTC313
- Test helpers `receive_until`, `event_messages`, `diff_items`, `start_engine`
96fdf90aacd6atagen2026-09-08 08:24 UTC314
  and `stop` are copied across up to five test files. A shared
96fdf90aacd6atagen2026-09-08 08:24 UTC315
  `tests/support/mod.rs` removes them.
96fdf90aacd6atagen2026-09-08 08:24 UTC316
- `model.rs` has its test module in the middle of the file, with types
96fdf90aacd6atagen2026-09-08 08:24 UTC317
  defined after it.
96fdf90aacd6atagen2026-09-08 08:24 UTC318
96fdf90aacd6atagen2026-09-08 08:24 UTC319
## T. Test coverage
96fdf90aacd6atagen2026-09-08 08:24 UTC320
96fdf90aacd6atagen2026-09-08 08:24 UTC321
No test touches the whole encryption domain (recovery, backups, verification),
96fdf90aacd6atagen2026-09-08 08:24 UTC322
all notification commands, invite, kick, ban and unban, power levels,
96fdf90aacd6atagen2026-09-08 08:24 UTC323
`MarkRead`, `SetTyping`, `Redact`, `ToggleReaction`, `CloseThread`, `Logout`,
96fdf90aacd6atagen2026-09-08 08:24 UTC324
`RenameDevice`, `DeleteDevices`, `resolve_push_notification` or
96fdf90aacd6atagen2026-09-08 08:24 UTC325
`rooms_filtered`. No test uses an encrypted room, which is how C4 got through.
96fdf90aacd6atagen2026-09-08 08:24 UTC326
96fdf90aacd6atagen2026-09-08 08:24 UTC327
## M. Malice gaps, synthesised
96fdf90aacd6atagen2026-09-08 08:24 UTC328
96fdf90aacd6atagen2026-09-08 08:24 UTC329
Malice reported four gaps. Each one traces back to a finding above, and fixing
96fdf90aacd6atagen2026-09-08 08:24 UTC330
the underlying finding closes the gap without special-casing it.
96fdf90aacd6atagen2026-09-08 08:24 UTC331
96fdf90aacd6atagen2026-09-08 08:24 UTC332
| Malice gap | Root cause | Proposed shape |
96fdf90aacd6atagen2026-09-08 08:24 UTC333
| --- | --- | --- |
96fdf90aacd6atagen2026-09-08 08:24 UTC334
| Own profile | Not exposed at all | `ClientHandle::own_profile()`, a direct read via `Account::fetch_user_profile` (`account.rs:353`), returning a `Profile { user_id, display_name, avatar }`. Add `user_profile(user_id)` alongside. Setting the name and avatar become commands. |
96fdf90aacd6atagen2026-09-08 08:24 UTC335
| MXC avatar download | Media is reachable only through registered `MediaId`s (A2) | `ClientHandle::mxc_media(uri, MediaRequest)` as a bounded direct query, sharing the download, bound and cache code with timeline media. |
96fdf90aacd6atagen2026-09-08 08:24 UTC336
| Space children | `RoomSummary` gap (A5) | Wrap `matrix_sdk_ui::spaces::SpaceService` (`spaces/mod.rs`), which offers top-level joined spaces, `joined_parents_of_child` and a paginated `/hierarchy` `SpaceRoomList`. |
96fdf90aacd6atagen2026-09-08 08:24 UTC337
| Timeline media bytes per request | A1 and A2 | `ClientHandle::media(media_id, size)` as an awaited direct query. Remove `MediaCommand::Fetch` and `Event::MediaReady`. |
96fdf90aacd6atagen2026-09-08 08:24 UTC338
96fdf90aacd6atagen2026-09-08 08:24 UTC339
Malice's rule, that no `matrix-sdk` use remains in the app, holds once these
96fdf90aacd6atagen2026-09-08 08:24 UTC340
land. None of the four needs an escape hatch.
96fdf90aacd6atagen2026-09-08 08:24 UTC341
96fdf90aacd6atagen2026-09-08 08:24 UTC342
## Discussion items
96fdf90aacd6atagen2026-09-08 08:24 UTC343
96fdf90aacd6atagen2026-09-08 08:24 UTC344
These are genuine trade-offs rather than fixes, each with a recommendation.
96fdf90aacd6atagen2026-09-08 08:24 UTC345
96fdf90aacd6atagen2026-09-08 08:24 UTC346
### Q1. How far to take awaited results
96fdf90aacd6atagen2026-09-08 08:24 UTC347
96fdf90aacd6atagen2026-09-08 08:24 UTC348
Option 1. Keep `send` and add `handle.execute(command).await -> Result<CommandOutput, CommandFailure>`
96fdf90aacd6atagen2026-09-08 08:24 UTC349
by carrying an optional `oneshot` in `CommandEnvelope`. Stream result events
96fdf90aacd6atagen2026-09-08 08:24 UTC350
continue for `send`. This is additive, but it leaves two mechanisms and every
96fdf90aacd6atagen2026-09-08 08:24 UTC351
engine path still branches on reply versus event.
96fdf90aacd6atagen2026-09-08 08:24 UTC352
96fdf90aacd6atagen2026-09-08 08:24 UTC353
Option 2. Make awaited results the only completion path. Remove `CommandId`,
96fdf90aacd6atagen2026-09-08 08:24 UTC354
`CommandSucceeded*`, `CommandFailed` and `InteractiveAuthRequired` from the
96fdf90aacd6atagen2026-09-08 08:24 UTC355
stream, which then carries only state changes. Fire-and-forget callers spawn a
96fdf90aacd6atagen2026-09-08 08:24 UTC356
task. Every engine path produces one `Result` and the nine reply-or-event
96fdf90aacd6atagen2026-09-08 08:24 UTC357
branches disappear.
96fdf90aacd6atagen2026-09-08 08:24 UTC358
96fdf90aacd6atagen2026-09-08 08:24 UTC359
The cost of option 2 is ordering. Today a `TimelineReset` is guaranteed to
96fdf90aacd6atagen2026-09-08 08:24 UTC360
precede the `CommandSucceeded` for its `Open` or `Resync` on the same stream.
96fdf90aacd6atagen2026-09-08 08:24 UTC361
Across a stream and a `oneshot` the guarantee weakens to "enqueued before", so
96fdf90aacd6atagen2026-09-08 08:24 UTC362
a client draining the stream in another task can see the future resolve first.
96fdf90aacd6atagen2026-09-08 08:24 UTC363
Either `Open` and `Resync` return the snapshot in their result and stop
96fdf90aacd6atagen2026-09-08 08:24 UTC364
emitting `TimelineReset`, which then needs an epoch on `TimelineChanged` so
96fdf90aacd6atagen2026-09-08 08:24 UTC365
stale diffs from the old watcher can be discarded, or the contract states that
96fdf90aacd6atagen2026-09-08 08:24 UTC366
the snapshot arrives on the stream at some point after success. A second
96fdf90aacd6atagen2026-09-08 08:24 UTC367
frontend sharing the engine also stops seeing the other's outcomes, though it
96fdf90aacd6atagen2026-09-08 08:24 UTC368
still sees every resulting state change.
96fdf90aacd6atagen2026-09-08 08:24 UTC369
96fdf90aacd6atagen2026-09-08 08:24 UTC370
Recommendation. Option 2 with snapshot-in-result and an epoch. It is the
96fdf90aacd6atagen2026-09-08 08:24 UTC371
larger break, but it is the shape every client ends up wrapping the current API
96fdf90aacd6atagen2026-09-08 08:24 UTC372
into, and breakage is free now.
96fdf90aacd6atagen2026-09-08 08:24 UTC373
96fdf90aacd6atagen2026-09-08 08:24 UTC374
### Q2. Space children source
96fdf90aacd6atagen2026-09-08 08:24 UTC375
96fdf90aacd6atagen2026-09-08 08:24 UTC376
`SpaceService` gives the full graph, including unjoined children, over the
96fdf90aacd6atagen2026-09-08 08:24 UTC377
network via `/hierarchy`. Local `m.space.child` state is instant but lists only
96fdf90aacd6atagen2026-09-08 08:24 UTC378
what sync delivered and cannot show rooms the user has not joined.
96fdf90aacd6atagen2026-09-08 08:24 UTC379
Recommendation. Own a `SpaceService` in the engine, as with `SyncService`. Put
96fdf90aacd6atagen2026-09-08 08:24 UTC380
`parent_spaces` and `child_rooms` on `RoomSummary` from local state for the
96fdf90aacd6atagen2026-09-08 08:24 UTC381
sidebar, and add a paginated hierarchy query for browsing.
96fdf90aacd6atagen2026-09-08 08:24 UTC382
96fdf90aacd6atagen2026-09-08 08:24 UTC383
### Q3. Timeline content model depth
96fdf90aacd6atagen2026-09-08 08:24 UTC384
96fdf90aacd6atagen2026-09-08 08:24 UTC385
A3 can be closed with a coarse enum (message, redacted, UTD, state, other) or
96fdf90aacd6atagen2026-09-08 08:24 UTC386
with typed variants for membership and profile changes, room name and topic,
96fdf90aacd6atagen2026-09-08 08:24 UTC387
polls and calls. Coarse is quick but pushes raw event parsing onto every
96fdf90aacd6atagen2026-09-08 08:24 UTC388
client. Recommendation. Typed variants for membership, profile, name, topic,
96fdf90aacd6atagen2026-09-08 08:24 UTC389
avatar and encryption changes, which cover what Element shows inline, plus
96fdf90aacd6atagen2026-09-08 08:24 UTC390
`Other { event_type }` as the escape.
96fdf90aacd6atagen2026-09-08 08:24 UTC391
96fdf90aacd6atagen2026-09-08 08:24 UTC392
## Verification results
96fdf90aacd6atagen2026-09-08 08:24 UTC393
96fdf90aacd6atagen2026-09-08 08:24 UTC394
- `cargo fmt -- --check` clean.
96fdf90aacd6atagen2026-09-08 08:24 UTC395
- `cargo test --all-targets` 49 passed, none failed.
5a52012c9b1datagen2026-09-24 23:45 UTC396
5a52012c9b1datagen2026-09-24 23:45 UTC397
## follow-up review, 2026-09-25
5a52012c9b1datagen2026-09-24 23:45 UTC398
5a52012c9b1datagen2026-09-24 23:45 UTC399
three parallel reviews of the reworked engine, each finding checked against the
5a52012c9b1datagen2026-09-24 23:45 UTC400
code before fixing. all fixed in the same change
5a52012c9b1datagen2026-09-24 23:45 UTC401
5a52012c9b1datagen2026-09-24 23:45 UTC402
- close answered `Interrupted` when the actor had already gone, and anything
5a52012c9b1datagen2026-09-24 23:45 UTC403
  sent to an actor stopping after a leave was dropped. actors now say they're
5a52012c9b1datagen2026-09-24 23:45 UTC404
  leaving and hand back what they couldn't serve
5a52012c9b1datagen2026-09-24 23:45 UTC405
- closing a timeline released its media before stopping the watcher, so late
5a52012c9b1datagen2026-09-24 23:45 UTC406
  diffs re-registered it
5a52012c9b1datagen2026-09-24 23:45 UTC407
- thread receipts failed while the room was open and the thread wasn't
5a52012c9b1datagen2026-09-24 23:45 UTC408
- verification watchers subscribed after the state they needed, so a peer
5a52012c9b1datagen2026-09-24 23:45 UTC409
  starting sas left the flow waiting forever
5a52012c9b1datagen2026-09-24 23:45 UTC410
- a plain event could leave an encrypted file's ciphertext in the cache
5a52012c9b1datagen2026-09-24 23:45 UTC411
- downloads died at thirty seconds on the sdk client's timeout
5a52012c9b1datagen2026-09-24 23:45 UTC412
- `create_direct` made a second dm when asked twice before sync caught up
5a52012c9b1datagen2026-09-24 23:45 UTC413
- offline mode plus a server refusing sync bounced forever with no backoff
5a52012c9b1datagen2026-09-24 23:45 UTC414
- upload progress missed resumed uploads, failures could be lost to lag
5a52012c9b1datagen2026-09-24 23:45 UTC415
- `SendState::Failed { retriable }` meant the opposite of what a ui wants,
5a52012c9b1datagen2026-09-24 23:45 UTC416
  now `will_retry`, and `Queued` folded into `Sending`
5a52012c9b1datagen2026-09-24 23:45 UTC417
- room previews showed the `* edit` fallback, thumbnails claimed the original's
5a52012c9b1datagen2026-09-24 23:45 UTC418
  mimetype and ignored the asked size, v12 creators' power was dropped,
5a52012c9b1datagen2026-09-24 23:45 UTC419
  cleared names came back as empty strings
5a52012c9b1datagen2026-09-24 23:45 UTC420
- the example logged in afresh every run and broke its own store
5a52012c9b1datagen2026-09-24 23:45 UTC421
- adding the fixes tipped `Engine::run` past the compiler's query depth in every
5a52012c9b1datagen2026-09-24 23:45 UTC422
  consumer, so it now returns a boxed future