atagen

macltok

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

Blame: docs/segmented-history.md

c9f0fd0ee1ccatagen2026-10-25 06:16 UTC1
# Segmented history implementation
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC2
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC3
The durable implementation is in the macltok `segmented-history` feature
9eabb6b7587datagen2026-10-25 07:08 UTC4
worktree. The combined SDK revision
9eabb6b7587datagen2026-10-25 07:08 UTC5
`9b2c17fb10703d90b177b02588912a34cecfa11f` is published on its
9eabb6b7587datagen2026-10-25 07:08 UTC6
`segmented-history` bookmark. It includes the reaction compatibility fix from
9eabb6b7587datagen2026-10-25 07:08 UTC7
`c0a` and segmented history from `49a9`. The macltok history branch includes
9eabb6b7587datagen2026-10-25 07:08 UTC8
pack fixes from `e4bb841a` and the `macltok-pack-fixes` worktree through
9eabb6b7587datagen2026-10-25 07:08 UTC9
`d1313f47`. All nine SDK pins and both lockfiles use the combined SDK revision.
9eabb6b7587datagen2026-10-25 07:08 UTC10
The unified macltok bookmark is named `segmented-history`.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC11
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC12
## Current behavior
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC13
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC14
Opening a room loads retained history and its gap rows, then fetches a bounded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC15
recent window from a fresh server position. The recent fetch is capped at four
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC16
pages of 50 raw events. It preserves retained sections and their older cursors.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC17
If it cannot establish continuity, it installs a durable gap between the
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC18
sections. It stops when continuity is established or accessible history ends.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC19
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC20
`HistoryRepairCommand::FillGap` takes a room ID and stable gap transaction ID.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC21
It fetches up to four backward pages of 50 events and advances only that gap's
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC22
saved cursor. Fill progress and events commit atomically. Concurrent history
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC23
operations for the room return `RoomBusy`. An empty page with an advancing
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC24
cursor keeps the gap. A repeated cursor pauses it. Accessible history ending
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC25
before the retained boundary marks the interval unavailable. Only reaching the
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC26
retained boundary joins sections. Already joined gaps are harmless to stale
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC27
callers.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC28
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC29
The cache stores gap identity, boundary evidence, cursor, and status alongside
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC30
the encrypted linked event chunks. SQLite supports legacy token data and the
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC31
new metadata. IndexedDB stores the optional encrypted metadata field. Room
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC32
close and restart retain sections and fill cursors. Timeline snapshots and
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC33
incremental updates project gaps as `TimelineItem::HistoryGap`. Gap rows retain
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC34
their identity while status changes. Joined markers disappear and unavailable
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC35
intervals remain visible.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC36
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC37
The recent fetch can complete while live sync continues. Cache installation
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC38
uses a candidate state and commits events, gap metadata, and cursor together.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC39
Sync publication is serialized with gap installation. Existing loaded timelines
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC40
receive incremental insertions that preserve retained item identities and
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC41
pending sends. Loading history outside the current timeline window still needs
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC42
a reset.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC43
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC44
Each SDK room cache owns a coordinator task. Recent callers share an in-flight
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC45
request. Cancelling one caller preserves the operation for other subscribers,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC46
and cancelling all callers stops fetching before installation. Started commits
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC47
finish before the coordinator accepts conflicting work. No mutex guard crosses
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC48
the network request. The idle task holds only a weak cache reference, so it does
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC49
not keep a room cache alive. The SDK UI calls `wait_for_history` as a fence,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC50
then takes an atomic event and gap snapshot while replacing its cache
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC51
subscription. The macltok watcher is stopped before its timeline reset is sent,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC52
so old diffs cannot arrive after the reset. A mirror publication acknowledgement
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC53
completes before the command response is released. The room actor starts recent
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC54
loading in the background when the room opens, so opening does not wait for the
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC55
network request.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC56
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC57
Limited sync results persist explicit gap markers in the event cache. Later sync
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC58
updates preserve those markers until the pagination chain reaches the retained
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC59
boundary. The integration uses the room's live timeline flush and watcher reset
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC60
path. It does not reseed the cache. Thread and pinned timelines remain open
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC61
through this update path.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC62
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC63
## Public and wire shapes
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC64
9eabb6b7587datagen2026-10-25 07:08 UTC65
`ClientHandle::fill_history_gap(room_id, gap_id)` is the convenience method for
9eabb6b7587datagen2026-10-25 07:08 UTC66
the targeted fill. It returns `Result<(), Error>`. The underlying command is
9eabb6b7587datagen2026-10-25 07:08 UTC67
`HistoryRepairCommand::FillGap { room_id, gap_id }`, sent as
9eabb6b7587datagen2026-10-25 07:08 UTC68
`Command::HistoryRepair`. The older policy and staged repair command variants
9eabb6b7587datagen2026-10-25 07:08 UTC69
remain available for serde compatibility, but room opening no longer consults
9eabb6b7587datagen2026-10-25 07:08 UTC70
the saved policy. `Continue` is invalid because continuation must identify its
9eabb6b7587datagen2026-10-25 07:08 UTC71
gap. `GetPolicy` and `SetPolicy` remain compatibility operations and do not
9eabb6b7587datagen2026-10-25 07:08 UTC72
control opening.
9eabb6b7587datagen2026-10-25 07:08 UTC73
9eabb6b7587datagen2026-10-25 07:08 UTC74
The inline gap lifecycle value `Fetching` is reserved and is not currently
9eabb6b7587datagen2026-10-25 07:08 UTC75
emitted by timeline projection. During a targeted fill, loading is signaled by
9eabb6b7587datagen2026-10-25 07:08 UTC76
a `HistoryRepairReport` with `gap_id` set and `phase` set to `Bridging`. Recent
9eabb6b7587datagen2026-10-25 07:08 UTC77
loading has its own room keyed `RecentHistoryState` with
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC78
`Idle`, `Loading`, `Loaded`, or `Failed` values. The mirror exposes that state in
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC79
`recent_history`, separately from gap continuity and repair progress.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC80
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC81
`TimelineItem::HistoryGap` carries a `HistoryGap` with `id`,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC82
`older_boundary`, `newer_boundary`, `lifecycle`, `continuity`, and `failure`.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC83
Lifecycle values are `Idle`, `Fetching`, `Paused`, `Failed`, and `Unavailable`.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC84
Continuity values are `Unresolved`, `Verified`, and `AccessibleStart`. The gap
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC85
ID is an owned transaction ID. Boundary values are optional owned event IDs.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC86
Failure is an optional displayable string. `TimelineState.items` contains these
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC87
items in order with event rows. Mirror snapshots serialize the same item shape,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC88
and incremental timeline changes carry it through the normal reset or changed
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC89
diff.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC90
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC91
Serde enums use externally tagged variants. A command payload is shaped as
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC92
follows, with the outer `Command` adding its `HistoryRepair` tag.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC93
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC94
```json
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC95
{
9eabb6b7587datagen2026-10-25 07:08 UTC96
  "HistoryRepair": {
9eabb6b7587datagen2026-10-25 07:08 UTC97
    "FillGap": {
9eabb6b7587datagen2026-10-25 07:08 UTC98
      "room_id": "!room:example.org",
9eabb6b7587datagen2026-10-25 07:08 UTC99
      "gap_id": "opaque-gap-transaction-id"
9eabb6b7587datagen2026-10-25 07:08 UTC100
    }
9eabb6b7587datagen2026-10-25 07:08 UTC101
  }
9eabb6b7587datagen2026-10-25 07:08 UTC102
}
9eabb6b7587datagen2026-10-25 07:08 UTC103
```
9eabb6b7587datagen2026-10-25 07:08 UTC104
9eabb6b7587datagen2026-10-25 07:08 UTC105
A gap row in `TimelineState.items` has the following shape.
9eabb6b7587datagen2026-10-25 07:08 UTC106
9eabb6b7587datagen2026-10-25 07:08 UTC107
```json
9eabb6b7587datagen2026-10-25 07:08 UTC108
{
9eabb6b7587datagen2026-10-25 07:08 UTC109
  "HistoryGap": {
9eabb6b7587datagen2026-10-25 07:08 UTC110
    "id": "opaque-gap-transaction-id",
9eabb6b7587datagen2026-10-25 07:08 UTC111
    "older_boundary": "$older:example.org",
9eabb6b7587datagen2026-10-25 07:08 UTC112
    "newer_boundary": "$newer:example.org",
9eabb6b7587datagen2026-10-25 07:08 UTC113
    "lifecycle": "Idle",
9eabb6b7587datagen2026-10-25 07:08 UTC114
    "continuity": "Unresolved",
9eabb6b7587datagen2026-10-25 07:08 UTC115
    "failure": null
9eabb6b7587datagen2026-10-25 07:08 UTC116
  }
9eabb6b7587datagen2026-10-25 07:08 UTC117
}
9eabb6b7587datagen2026-10-25 07:08 UTC118
```
9eabb6b7587datagen2026-10-25 07:08 UTC119
9eabb6b7587datagen2026-10-25 07:08 UTC120
The mirror snapshot stores recent loading separately by room ID. This fragment
9eabb6b7587datagen2026-10-25 07:08 UTC121
shows the serialized `State.recent_history` field.
9eabb6b7587datagen2026-10-25 07:08 UTC122
9eabb6b7587datagen2026-10-25 07:08 UTC123
```json
9eabb6b7587datagen2026-10-25 07:08 UTC124
{
9eabb6b7587datagen2026-10-25 07:08 UTC125
  "recent_history": {
9eabb6b7587datagen2026-10-25 07:08 UTC126
    "!room:example.org": "Loading"
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC127
  }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC128
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC129
```
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC130
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC131
The final history repair publication acknowledgement is internal, skipped by
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC132
serde, and not part of application wire data. The mirror applies the report
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC133
before acknowledging it, so command completion follows publication.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC134
9eabb6b7587datagen2026-10-25 07:08 UTC135
## Malice integration
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC136
9eabb6b7587datagen2026-10-25 07:08 UTC137
Pin the published macltok `segmented-history` revision in Malice's native and
9eabb6b7587datagen2026-10-25 07:08 UTC138
worker manifests and regenerate their lockfiles. The SDK dependency is already
9eabb6b7587datagen2026-10-25 07:08 UTC139
pinned by macltok.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC140
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC141
Malice should remove the saved policy and threshold controls from room opening.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC142
Render each inline gap between its neighboring event sections. A fill should
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC143
preserve the visible event anchor, composer draft, reply or edit state, pending
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC144
sends, and encryption identity. Keep event-context windows independent from
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC145
live room history.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC146
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC147
## Lost interval investigation
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC148
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC149
The reported failure showed retained events followed by newly sent messages,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC150
with an omitted interval of roughly eight to ten hours presented as continuous.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC151
Older pagination continued before the retained buffer, and a page refresh did
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC152
not recover the missing range. A refresh can reconnect to the existing
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC153
SharedWorker, so it does not prove that the engine restarted.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC154
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC155
The earlier SDK cache path could discard a limited-sync boundary token when a
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC156
timeline was reported unlimited despite cached events. It could also discard a
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC157
token when all incoming events were duplicates. Both cases can hide a real
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC158
discontinuity from older pagination. Initial pagination could also finish
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC159
against retained history before fresh sync arrived. These are concrete failure
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC160
paths, but none has been confirmed as the production cause. The durable gaps
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC161
preserve discontinuities when observed. Startup, subscription, and sync
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC162
diagnostics are still needed to identify where this particular interval was
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC163
lost.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC164
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC165
## Validation commands
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC166
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC167
Run native checks from `../macltok-history-repair` with the repository's Nix
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC168
toolchain.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC169
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC170
```sh
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC171
nix develop --command cargo test --all-targets
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC172
nix develop --command cargo test --all-targets --features sqlite
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC173
nix develop --command cargo clippy --all-targets -- -D warnings
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC174
nix develop --command cargo clippy --all-targets --features sqlite -- -D warnings
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC175
```
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC176
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC177
For the browser worker runtime, use the web devshell. The harness starts a local
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC178
homeserver and runs both worker targets in Firefox.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC179
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC180
```sh
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC181
nix develop .#web -c nu harness/web/run.nu
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC182
```
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC183
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC184
The flake checks include native and WebAssembly compilation with IndexedDB and
9eabb6b7587datagen2026-10-25 07:08 UTC185
the web worker runtime. The default native gate passed all 86 tests. The
9eabb6b7587datagen2026-10-25 07:08 UTC186
aggregate `nix flake check` run stopped during the SQLite and mirror build.
9eabb6b7587datagen2026-10-25 07:08 UTC187
The remaining aggregate gates were waived for this handoff and have not passed.
9eabb6b7587datagen2026-10-25 07:08 UTC188
The focused native history repair and engine suites passed all 11 tests with
9eabb6b7587datagen2026-10-25 07:08 UTC189
SQLite, mirror, and serde enabled against the published combined SDK revision.
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC190
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC191
Seven focused SDK recent-history tests and seven segmented-history tests pass.
9eabb6b7587datagen2026-10-25 07:08 UTC192
The SDK WebAssembly compile passes with IndexedDB enabled. SDK UI timeline
9eabb6b7587datagen2026-10-25 07:08 UTC193
checks pass with 123 unit tests. Separate SDK room checks pass with seven unit
9eabb6b7587datagen2026-10-25 07:08 UTC194
tests and 14 timed integration tests. Two headless Firefox tests pass for the
9eabb6b7587datagen2026-10-25 07:08 UTC195
IndexedDB shared gap storage contract with plaintext and encrypted stores. The
9eabb6b7587datagen2026-10-25 07:08 UTC196
full SQLite and mirror gate, aggregate WebAssembly gate, and browser worker
9eabb6b7587datagen2026-10-25 07:08 UTC197
runtime gate remain unverified. Complete worker restart and encrypted room
9eabb6b7587datagen2026-10-25 07:08 UTC198
recovery remain integration acceptance checks.