atagen

macltok

public
Star0
Watch0
c9f0fd0ee1cccdd0048f121ba1c8ab1f12482f5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
docs/segmented-history.mdMarkdown164 lines8.3 KB

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 warnings

For 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.nu

The 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.