atagen

macltok

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

Blame: docs/design-decisions.md

96fdf90aacd6atagen2026-09-08 08:24 UTC1
# macltok design decisions
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
This document describes the contract implemented in the working tree.
96fdf90aacd6atagen2026-09-08 08:24 UTC4
96fdf90aacd6atagen2026-09-08 08:24 UTC5
## Engine execution
96fdf90aacd6atagen2026-09-08 08:24 UTC6
96fdf90aacd6atagen2026-09-08 08:24 UTC7
The engine keeps a contextual task registry. Every spawned task is registered
96fdf90aacd6atagen2026-09-08 08:24 UTC8
with either its command id and kind, a watcher name, or a room id and
96fdf90aacd6atagen2026-09-08 08:24 UTC9
generation. The run loop continuously joins completed tasks and removes their
96fdf90aacd6atagen2026-09-08 08:24 UTC10
context. A command-task panic produces its correlated `CommandFailed` event.
96fdf90aacd6atagen2026-09-08 08:24 UTC11
A watcher or current room-actor panic is terminal to the engine. Shutdown aborts
96fdf90aacd6atagen2026-09-08 08:24 UTC12
and joins the remaining registered tasks.
96fdf90aacd6atagen2026-09-08 08:24 UTC13
96fdf90aacd6atagen2026-09-08 08:24 UTC14
The engine emits `Authenticated`, then `ConnectionChanged(Connecting)`, before
96fdf90aacd6atagen2026-09-08 08:24 UTC15
starting the sync service. `ConnectionChanged` also represents the later SDK
96fdf90aacd6atagen2026-09-08 08:24 UTC16
states and the final `Stopped` transition. `Engine::run` returns an
96fdf90aacd6atagen2026-09-08 08:24 UTC17
`EngineError` for terminal failure and emits `Failed` before `Stopped` when the
96fdf90aacd6atagen2026-09-08 08:24 UTC18
event stream remains available.
96fdf90aacd6atagen2026-09-08 08:24 UTC19
96fdf90aacd6atagen2026-09-08 08:24 UTC20
The driver command channel remains lossless and clients must drain
96fdf90aacd6atagen2026-09-08 08:24 UTC21
`EventStream` independently of command submission. `ClientHandle` also exposes
96fdf90aacd6atagen2026-09-08 08:24 UTC22
a `watch` receiver for the current connection state. `RoomsChanged` is an
96fdf90aacd6atagen2026-09-08 08:24 UTC23
invalidation event. Call `ClientHandle::rooms` for current, normalised room
96fdf90aacd6atagen2026-09-08 08:24 UTC24
summaries.
96fdf90aacd6atagen2026-09-08 08:24 UTC25
96fdf90aacd6atagen2026-09-08 08:24 UTC26
`ClientHandle` sends `Command` values and receives a `CommandId`. Completion is
96fdf90aacd6atagen2026-09-08 08:24 UTC27
reported as `CommandSucceeded`, `CommandSucceededWithRoom`, or
96fdf90aacd6atagen2026-09-08 08:24 UTC28
`CommandFailed`. `CommandFailure` contains the id, outer `CommandKind`, typed
96fdf90aacd6atagen2026-09-08 08:24 UTC29
`CommandErrorKind`, and display message. Direct request and response methods
96fdf90aacd6atagen2026-09-08 08:24 UTC30
return `CommandResultError`, which is either a delivered `CommandFailure` or a
96fdf90aacd6atagen2026-09-08 08:24 UTC31
closed reply channel.
96fdf90aacd6atagen2026-09-08 08:24 UTC32
96fdf90aacd6atagen2026-09-08 08:24 UTC33
For timeline sends, success means the SDK send queue accepted the event;
96fdf90aacd6atagen2026-09-08 08:24 UTC34
network delivery is reported later through timeline send state.
96fdf90aacd6atagen2026-09-08 08:24 UTC35
96fdf90aacd6atagen2026-09-08 08:24 UTC36
`send` reports enqueueing, not completion. Consumers retain the returned id
96fdf90aacd6atagen2026-09-08 08:24 UTC37
until a terminal result with that id arrives; they match `CommandFailure::kind`
96fdf90aacd6atagen2026-09-08 08:24 UTC38
rather than parsing its display message. The event stream must be drained while
96fdf90aacd6atagen2026-09-08 08:24 UTC39
commands are submitted, even when several frontend clients share one engine.
96fdf90aacd6atagen2026-09-08 08:24 UTC40
96fdf90aacd6atagen2026-09-08 08:24 UTC41
## Room lifetime and ordering
96fdf90aacd6atagen2026-09-08 08:24 UTC42
96fdf90aacd6atagen2026-09-08 08:24 UTC43
An open room has an explicit generation and exactly one of three states.
96fdf90aacd6atagen2026-09-08 08:24 UTC44
96fdf90aacd6atagen2026-09-08 08:24 UTC45
- `Opening` owns the generation and actor request sender
96fdf90aacd6atagen2026-09-08 08:24 UTC46
- `Open` owns the generation and actor request sender
96fdf90aacd6atagen2026-09-08 08:24 UTC47
- `Closing` owns the generation and a bounded FIFO of deferred open and close commands
96fdf90aacd6atagen2026-09-08 08:24 UTC48
96fdf90aacd6atagen2026-09-08 08:24 UTC49
An `Open` command starts an actor immediately and returns its terminal result
96fdf90aacd6atagen2026-09-08 08:24 UTC50
only after the actor reports readiness. There is no separate readiness waiter or
96fdf90aacd6atagen2026-09-08 08:24 UTC51
readiness task. Repeated opens while opening are retained by the actor and each
96fdf90aacd6atagen2026-09-08 08:24 UTC52
gets one terminal result. A close changes the room to `Closing`, rejects work
96fdf90aacd6atagen2026-09-08 08:24 UTC53
that needs an open actor, and waits for that generation to exit. Opens and
96fdf90aacd6atagen2026-09-08 08:24 UTC54
closes during closing are queued in order; after exit, each deferred command is
96fdf90aacd6atagen2026-09-08 08:24 UTC55
replayed, with a fresh generation started when needed. Events from earlier
96fdf90aacd6atagen2026-09-08 08:24 UTC56
generations are ignored.
96fdf90aacd6atagen2026-09-08 08:24 UTC57
96fdf90aacd6atagen2026-09-08 08:24 UTC58
Each room actor serialises its timeline work in FIFO order. Different rooms run
96fdf90aacd6atagen2026-09-08 08:24 UTC59
concurrently. Room mutations which do not require an open timeline use a
96fdf90aacd6atagen2026-09-08 08:24 UTC60
separate keyed FIFO scheduler. Jobs with the same room, target, or direct-chat
96fdf90aacd6atagen2026-09-08 08:24 UTC61
key run in submission order while unrelated keys run concurrently. Notification
96fdf90aacd6atagen2026-09-08 08:24 UTC62
commands and encryption commands each have one dedicated serial worker. The
96fdf90aacd6atagen2026-09-08 08:24 UTC63
encryption worker owns the verification controller and cancels it before
96fdf90aacd6atagen2026-09-08 08:24 UTC64
shutdown completes.
96fdf90aacd6atagen2026-09-08 08:24 UTC65
96fdf90aacd6atagen2026-09-08 08:24 UTC66
Pagination has a per-timeline mutex gate and a session-owned completion path.
96fdf90aacd6atagen2026-09-08 08:24 UTC67
Pagination tasks are reaped as they finish. Closing a timeline aborts its
96fdf90aacd6atagen2026-09-08 08:24 UTC68
pagination work and emits one terminal result for every still-pending command.
96fdf90aacd6atagen2026-09-08 08:24 UTC69
There is no public pagination handle. The internal result channel is only the
96fdf90aacd6atagen2026-09-08 08:24 UTC70
session completion mechanism and is not an engine-wide routing design.
96fdf90aacd6atagen2026-09-08 08:24 UTC71
96fdf90aacd6atagen2026-09-08 08:24 UTC72
`TimelineDiff::try_apply` rejects indices that do not match the consumer's
96fdf90aacd6atagen2026-09-08 08:24 UTC73
snapshot without modifying that snapshot. A consumer then stops applying diffs
96fdf90aacd6atagen2026-09-08 08:24 UTC74
for that timeline and sends `TimelineCommand::Resync`. The timeline session
96fdf90aacd6atagen2026-09-08 08:24 UTC75
aborts and joins its old watcher, atomically subscribes to a fresh SDK snapshot
96fdf90aacd6atagen2026-09-08 08:24 UTC76
and diff stream, and emits `TimelineReset` before the resync command succeeds.
96fdf90aacd6atagen2026-09-08 08:24 UTC77
This works for rooms and threads without closing their timelines or pagination.
96fdf90aacd6atagen2026-09-08 08:24 UTC78
96fdf90aacd6atagen2026-09-08 08:24 UTC79
Timeline diffs and resets carry `TimelineId`, which distinguishes a room from a
96fdf90aacd6atagen2026-09-08 08:24 UTC80
thread by its root event. Thread actors and pagination state belong to the room
96fdf90aacd6atagen2026-09-08 08:24 UTC81
actor. Stateful timeline commands require the room actor. Standalone room
96fdf90aacd6atagen2026-09-08 08:24 UTC82
commands such as typing, receipts, send-queue control, membership actions, and
96fdf90aacd6atagen2026-09-08 08:24 UTC83
moderation use the SDK `Room` directly.
96fdf90aacd6atagen2026-09-08 08:24 UTC84
96fdf90aacd6atagen2026-09-08 08:24 UTC85
## Account construction and secrets
96fdf90aacd6atagen2026-09-08 08:24 UTC86
96fdf90aacd6atagen2026-09-08 08:24 UTC87
`Session::login_password`, `Session::begin_oauth`, and `Session::restore` use
96fdf90aacd6atagen2026-09-08 08:24 UTC88
one shared account client builder. It configures encrypted SQLite storage,
96fdf90aacd6atagen2026-09-08 08:24 UTC89
encryption settings, refresh-token handling, and enabled threading without
96fdf90aacd6atagen2026-09-08 08:24 UTC90
thread subscriptions. `Session::build` passes the authenticated client to the
96fdf90aacd6atagen2026-09-08 08:24 UTC91
engine.
96fdf90aacd6atagen2026-09-08 08:24 UTC92
96fdf90aacd6atagen2026-09-08 08:24 UTC93
Secret-bearing input types do not derive `Clone`. The store passphrase is
96fdf90aacd6atagen2026-09-08 08:24 UTC94
borrowed by the shared builder, password login passes its password by reference,
96fdf90aacd6atagen2026-09-08 08:24 UTC95
and authentication records are cloned only where SDK ownership requires it.
96fdf90aacd6atagen2026-09-08 08:24 UTC96
96fdf90aacd6atagen2026-09-08 08:24 UTC97
The caller owns durable authentication records. Save `Session::record()` after
96fdf90aacd6atagen2026-09-08 08:24 UTC98
login, then consume `SessionTokensChanged` and atomically replace that saved
96fdf90aacd6atagen2026-09-08 08:24 UTC99
record with `ClientHandle::session_record()`. A previously captured record may
96fdf90aacd6atagen2026-09-08 08:24 UTC100
hold an exhausted refresh token; failing to persist a rotation must be surfaced
96fdf90aacd6atagen2026-09-08 08:24 UTC101
rather than silently treating the stale record as safe to restore. Restore with
fa9114fcf75fatagen2026-09-24 12:51 UTC102
the same `StoreConfig` path and passphrase. logout and a rejected token both
fa9114fcf75fatagen2026-09-24 12:51 UTC103
stop the engine. logout and a hard rejection kill the record and the store, a
fa9114fcf75fatagen2026-09-24 12:51 UTC104
soft logout keeps the store for a same-device login, `Stopped` alone kills
fa9114fcf75fatagen2026-09-24 12:51 UTC105
nothing. Neither cookies nor application-specific secret storage belong in the
96fdf90aacd6atagen2026-09-08 08:24 UTC106
toolkit.
96fdf90aacd6atagen2026-09-08 08:24 UTC107
96fdf90aacd6atagen2026-09-08 08:24 UTC108
## Notifications and room data
96fdf90aacd6atagen2026-09-08 08:24 UTC109
96fdf90aacd6atagen2026-09-08 08:24 UTC110
The notification client is constructed lazily in an `OnceCell` and shares the
96fdf90aacd6atagen2026-09-08 08:24 UTC111
engine sync service. It is created only when notification resolution is first
96fdf90aacd6atagen2026-09-08 08:24 UTC112
requested. `PushNotification` exposes typed room and sender metadata and a
96fdf90aacd6atagen2026-09-08 08:24 UTC113
typed `PushNotificationEvent`. The event is `Raw<AnySyncTimelineEvent>` for a
96fdf90aacd6atagen2026-09-08 08:24 UTC114
timeline notification or `Raw<StrippedRoomMemberEvent>` for an invite. SDK UI
96fdf90aacd6atagen2026-09-08 08:24 UTC115
types do not cross the public boundary.
96fdf90aacd6atagen2026-09-08 08:24 UTC116
96fdf90aacd6atagen2026-09-08 08:24 UTC117
Room summaries are normalised once per `rooms` query. `ClientHandle::rooms_filtered`
96fdf90aacd6atagen2026-09-08 08:24 UTC118
normalises its name search term once before filtering. Summary generation runs
96fdf90aacd6atagen2026-09-08 08:24 UTC119
display-name requests with bounded concurrency. Sorting is descending latest
96fdf90aacd6atagen2026-09-08 08:24 UTC120
event timestamp, then case-insensitive name, then room id as a deterministic tie
96fdf90aacd6atagen2026-09-08 08:24 UTC121
break.
96fdf90aacd6atagen2026-09-08 08:24 UTC122
96fdf90aacd6atagen2026-09-08 08:24 UTC123
Global and room account data retain typed Matrix event-type parameters and raw
96fdf90aacd6atagen2026-09-08 08:24 UTC124
event content. The handle offers direct raw reads and engine-backed raw writes.
96fdf90aacd6atagen2026-09-08 08:24 UTC125
96fdf90aacd6atagen2026-09-08 08:24 UTC126
## Commands and errors
96fdf90aacd6atagen2026-09-08 08:24 UTC127
96fdf90aacd6atagen2026-09-08 08:24 UTC128
`Command` is grouped into `Account`, `Room`, `Timeline`, `Media`,
96fdf90aacd6atagen2026-09-08 08:24 UTC129
`Encryption`, `Notification`, and `Lifecycle` domains. `CommandKind` identifies
96fdf90aacd6atagen2026-09-08 08:24 UTC130
the outer domain in result events.
96fdf90aacd6atagen2026-09-08 08:24 UTC131
96fdf90aacd6atagen2026-09-08 08:24 UTC132
`RoomCommand::InviteUser` contains only room and user ids. It deliberately does
96fdf90aacd6atagen2026-09-08 08:24 UTC133
not expose a reason because the SDK invite helper accepts none. Kick, ban, and
96fdf90aacd6atagen2026-09-08 08:24 UTC134
unban retain optional reasons where the protocol supports them.
96fdf90aacd6atagen2026-09-08 08:24 UTC135
96fdf90aacd6atagen2026-09-08 08:24 UTC136
`TimelineCommand::Edit` accepts `MessageEditContent`. Its `Text` and `Emote`
96fdf90aacd6atagen2026-09-08 08:24 UTC137
variants carry `RichText`, so edited text and emotes preserve plain text,
96fdf90aacd6atagen2026-09-08 08:24 UTC138
optional Matrix HTML, and mentions. Empty edited bodies fail with `EmptyBody`.
96fdf90aacd6atagen2026-09-08 08:24 UTC139
96fdf90aacd6atagen2026-09-08 08:24 UTC140
`VerificationAction` contains `Confirm`, `Reject`, and `Cancel`. Starting a
96fdf90aacd6atagen2026-09-08 08:24 UTC141
verification is a target-bearing encryption command rather than an action.
96fdf90aacd6atagen2026-09-08 08:24 UTC142
Verification progress uses `Waiting`, `Comparison`, `Confirmed`, `Done`,
96fdf90aacd6atagen2026-09-08 08:24 UTC143
`Cancelled`, and typed `Failed(VerificationFailure)`. Request acceptance, SAS
96fdf90aacd6atagen2026-09-08 08:24 UTC144
startup and acceptance failures retain their message, while stream termination
96fdf90aacd6atagen2026-09-08 08:24 UTC145
has dedicated variants. A cancelled verification can be started again.
96fdf90aacd6atagen2026-09-08 08:24 UTC146
The watcher and action paths claim terminal emission so a racing cancellation
96fdf90aacd6atagen2026-09-08 08:24 UTC147
cannot replace `Done` with a local `Cancelled` event.
96fdf90aacd6atagen2026-09-08 08:24 UTC148
96fdf90aacd6atagen2026-09-08 08:24 UTC149
`CommandErrorKind` is the stable typed failure category. It includes room and
96fdf90aacd6atagen2026-09-08 08:24 UTC150
timeline availability, queue pressure, validation, media, verification,
96fdf90aacd6atagen2026-09-08 08:24 UTC151
authentication, network, server, store, timeline, encryption, and fallback
96fdf90aacd6atagen2026-09-08 08:24 UTC152
categories. The rendered message preserves operation context while internal
96fdf90aacd6atagen2026-09-08 08:24 UTC153
errors retain their source chains.
96fdf90aacd6atagen2026-09-08 08:24 UTC154
96fdf90aacd6atagen2026-09-08 08:24 UTC155
## Power levels
96fdf90aacd6atagen2026-09-08 08:24 UTC156
96fdf90aacd6atagen2026-09-08 08:24 UTC157
Power-level reads return the complete local `RoomPowerLevels` mapping.
96fdf90aacd6atagen2026-09-08 08:24 UTC158
`PowerLevelChanges` applies selected common and well-known event thresholds,
96fdf90aacd6atagen2026-09-08 08:24 UTC159
and `SetUserPowerLevel` changes one user mapping.
96fdf90aacd6atagen2026-09-08 08:24 UTC160
96fdf90aacd6atagen2026-09-08 08:24 UTC161
The SDK trait that applies a power-level event is crate-private. macltok keeps
96fdf90aacd6atagen2026-09-08 08:24 UTC162
an atomic local mapping, applies all requested changes to it, and sends one
96fdf90aacd6atagen2026-09-08 08:24 UTC163
state event. It does not patch upstream and does not send separate events for
96fdf90aacd6atagen2026-09-08 08:24 UTC164
individual fields.
96fdf90aacd6atagen2026-09-08 08:24 UTC165
96fdf90aacd6atagen2026-09-08 08:24 UTC166
## Public model boundaries
96fdf90aacd6atagen2026-09-08 08:24 UTC167
96fdf90aacd6atagen2026-09-08 08:24 UTC168
The public model uses owned ruma identifiers and typed data wherever it has a
96fdf90aacd6atagen2026-09-08 08:24 UTC169
stable representation. Raw Matrix event payloads remain `Raw` where arbitrary
96fdf90aacd6atagen2026-09-08 08:24 UTC170
event content must be preserved. Public output models are non-exhaustive where
96fdf90aacd6atagen2026-09-08 08:24 UTC171
they are expected to grow. SDK clients remain available through the handle for
96fdf90aacd6atagen2026-09-08 08:24 UTC172
direct state queries that do not need engine coordination.