Segmented history implementation
The durable implementation is in the macltok segmented-history feature
worktree. Its SDK dependency has been published at
49a9cd1aff19df1de7e46710cb6c984a1bddc30e on the SDK segmented-history
bookmark. The macltok manifest and lockfile are being updated to that revision.
The macltok feature bookmark will also be named segmented-history. Malice has
not yet been pinned to the macltok revision.
Current behavior
Opening a room loads retained history and its gap rows, then fetches a bounded recent window from a fresh server position. The recent fetch is capped at four pages of 50 raw events. It preserves retained sections and their older cursors. If it cannot establish continuity, it installs a durable gap between the sections. It stops when continuity is established or accessible history ends.
HistoryRepairCommand::FillGap takes a room ID and stable gap transaction ID.
It fetches up to four backward pages of 50 events and advances only that gap’s
saved cursor. Fill progress and events commit atomically. Concurrent history
operations for the room return RoomBusy. An empty page with an advancing
cursor keeps the gap. A repeated cursor pauses it. Accessible history ending
before the retained boundary marks the interval unavailable. Only reaching the
retained boundary joins sections. Already joined gaps are harmless to stale
callers.
The cache stores gap identity, boundary evidence, cursor, and status alongside
the encrypted linked event chunks. SQLite supports legacy token data and the
new metadata. IndexedDB stores the optional encrypted metadata field. Room
close and restart retain sections and fill cursors. Timeline snapshots and
incremental updates project gaps as TimelineItem::HistoryGap. Gap rows retain
their identity while status changes. Joined markers disappear and unavailable
intervals remain visible.
The recent fetch can complete while live sync continues. Cache installation uses a candidate state and commits events, gap metadata, and cursor together. Sync publication is serialized with gap installation. Existing loaded timelines receive incremental insertions that preserve retained item identities and pending sends. Loading history outside the current timeline window still needs a reset.
Each SDK room cache owns a coordinator task. Recent callers share an in-flight
request. Cancelling one caller preserves the operation for other subscribers,
and cancelling all callers stops fetching before installation. Started commits
finish before the coordinator accepts conflicting work. No mutex guard crosses
the network request. The idle task holds only a weak cache reference, so it does
not keep a room cache alive. The SDK UI calls wait_for_history as a fence,
then takes an atomic event and gap snapshot while replacing its cache
subscription. The macltok watcher is stopped before its timeline reset is sent,
so old diffs cannot arrive after the reset. A mirror publication acknowledgement
completes before the command response is released. The room actor starts recent
loading in the background when the room opens, so opening does not wait for the
network request.
Limited sync results persist explicit gap markers in the event cache. Later sync updates preserve those markers until the pagination chain reaches the retained boundary. The integration uses the room’s live timeline flush and watcher reset path. It does not reseed the cache. Thread and pinned timelines remain open through this update path.
Public and wire shapes
HistoryRepairCommand::FillGap { room_id, gap_id } is the supported targeted
fill operation, sent through ClientHandle::execute as
Command::HistoryRepair. It returns CommandOutput::Done on success. The older
policy and staged repair command variants remain available for serde
compatibility, but room opening no longer consults the saved policy. Continue
is invalid because continuation must identify its gap. GetPolicy and
SetPolicy remain compatibility operations and do not control opening.
Targeted fill progress is published as a HistoryRepairReport with gap_id
set and phase set to Bridging. This report supplies the loading state while
the fill runs. Recent loading has its own room keyed RecentHistoryState with
Idle, Loading, Loaded, or Failed values. The mirror exposes that state in
recent_history, separately from gap continuity and repair progress.
TimelineItem::HistoryGap carries a HistoryGap with id,
older_boundary, newer_boundary, lifecycle, continuity, and failure.
Lifecycle values are Idle, Fetching, Paused, Failed, and Unavailable.
Continuity values are Unresolved, Verified, and AccessibleStart. The gap
ID is an owned transaction ID. Boundary values are optional owned event IDs.
Failure is an optional displayable string. TimelineState.items contains these
items in order with event rows. Mirror snapshots serialize the same item shape,
and incremental timeline changes carry it through the normal reset or changed
diff.
Serde enums use externally tagged variants. A command payload is shaped as
follows, with the outer Command adding its HistoryRepair tag.
{
"FillGap": {
"room_id": "!room:example.org",
"gap_id": "opaque-gap-transaction-id"
}
}The final history repair publication acknowledgement is internal, skipped by serde, and not part of application wire data. The mirror applies the report before acknowledging it, so command completion follows publication.
Remaining publication and Malice work
Publish the SDK revision on the user’s fork, then publish the macltok revision that pins it. Update the Malice macltok dependency in both native and worker manifests and regenerate the applicable lockfiles against that same revision. The current worktree has not recorded a final publishable commit or revision for these changes.
Malice should remove the saved policy and threshold controls from room opening. Render each inline gap between its neighboring event sections. A fill should preserve the visible event anchor, composer draft, reply or edit state, pending sends, and encryption identity. Keep event-context windows independent from live room history.
Lost interval investigation
The reported failure showed retained events followed by newly sent messages, with an omitted interval of roughly eight to ten hours presented as continuous. Older pagination continued before the retained buffer, and a page refresh did not recover the missing range. A refresh can reconnect to the existing SharedWorker, so it does not prove that the engine restarted.
The earlier SDK cache path could discard a limited-sync boundary token when a timeline was reported unlimited despite cached events. It could also discard a token when all incoming events were duplicates. Both cases can hide a real discontinuity from older pagination. Initial pagination could also finish against retained history before fresh sync arrived. These are concrete failure paths, but none has been confirmed as the production cause. The durable gaps preserve discontinuities when observed. Startup, subscription, and sync diagnostics are still needed to identify where this particular interval was lost.
Validation commands
Run native checks from ../macltok-history-repair with the repository’s Nix
toolchain.
nix develop --command cargo test --all-targets
nix develop --command cargo test --all-targets --features sqlite
nix develop --command cargo clippy --all-targets -- -D warnings
nix develop --command cargo clippy --all-targets --features sqlite -- -D warningsFor the browser worker runtime, use the web devshell. The harness starts a local homeserver and runs both worker targets in Firefox.
nix develop .#web -c nu harness/web/run.nuThe flake checks include native and WebAssembly compilation with IndexedDB and
the web worker runtime. nix flake check is the aggregate gate and remains
pending.
Seven focused SDK recent-history tests and seven segmented-history tests pass. The SDK WebAssembly compile passes with IndexedDB enabled. The SDK UI timeline checks pass with 123 unit tests, seven room unit tests, and 14 timed integration tests. Two headless Firefox tests pass for the IndexedDB shared gap storage contract with plaintext and encrypted stores. The full flake gate remains pending. Complete worker restart and encrypted room recovery have not been tested.