c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 1 | # Segmented history implementation |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 2 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 3 | The durable implementation is in the macltok `segmented-history` feature |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 4 | worktree. Its SDK dependency has been published at |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 5 | `49a9cd1aff19df1de7e46710cb6c984a1bddc30e` on the SDK `segmented-history` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 6 | bookmark. The macltok manifest and lockfile are being updated to that revision. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 7 | The macltok feature bookmark will also be named `segmented-history`. Malice has |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 8 | not yet been pinned to the macltok revision. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 9 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 10 | ## Current behavior |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 11 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 12 | Opening a room loads retained history and its gap rows, then fetches a bounded |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 13 | recent window from a fresh server position. The recent fetch is capped at four |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 14 | pages of 50 raw events. It preserves retained sections and their older cursors. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 15 | If it cannot establish continuity, it installs a durable gap between the |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 16 | sections. It stops when continuity is established or accessible history ends. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 17 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 18 | `HistoryRepairCommand::FillGap` takes a room ID and stable gap transaction ID. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 19 | It fetches up to four backward pages of 50 events and advances only that gap's |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 20 | saved cursor. Fill progress and events commit atomically. Concurrent history |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 21 | operations for the room return `RoomBusy`. An empty page with an advancing |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 22 | cursor keeps the gap. A repeated cursor pauses it. Accessible history ending |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 23 | before the retained boundary marks the interval unavailable. Only reaching the |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 24 | retained boundary joins sections. Already joined gaps are harmless to stale |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 25 | callers. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 26 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 27 | The cache stores gap identity, boundary evidence, cursor, and status alongside |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 28 | the encrypted linked event chunks. SQLite supports legacy token data and the |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 29 | new metadata. IndexedDB stores the optional encrypted metadata field. Room |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 30 | close and restart retain sections and fill cursors. Timeline snapshots and |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 31 | incremental updates project gaps as `TimelineItem::HistoryGap`. Gap rows retain |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 32 | their identity while status changes. Joined markers disappear and unavailable |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 33 | intervals remain visible. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 34 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 35 | The recent fetch can complete while live sync continues. Cache installation |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 36 | uses a candidate state and commits events, gap metadata, and cursor together. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 37 | Sync publication is serialized with gap installation. Existing loaded timelines |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 38 | receive incremental insertions that preserve retained item identities and |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 39 | pending sends. Loading history outside the current timeline window still needs |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 40 | a reset. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 41 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 42 | Each SDK room cache owns a coordinator task. Recent callers share an in-flight |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 43 | request. Cancelling one caller preserves the operation for other subscribers, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 44 | and cancelling all callers stops fetching before installation. Started commits |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 45 | finish before the coordinator accepts conflicting work. No mutex guard crosses |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 46 | the network request. The idle task holds only a weak cache reference, so it does |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 47 | not keep a room cache alive. The SDK UI calls `wait_for_history` as a fence, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 48 | then takes an atomic event and gap snapshot while replacing its cache |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 49 | subscription. The macltok watcher is stopped before its timeline reset is sent, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 50 | so old diffs cannot arrive after the reset. A mirror publication acknowledgement |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 51 | completes before the command response is released. The room actor starts recent |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 52 | loading in the background when the room opens, so opening does not wait for the |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 53 | network request. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 54 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 55 | Limited sync results persist explicit gap markers in the event cache. Later sync |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 56 | updates preserve those markers until the pagination chain reaches the retained |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 57 | boundary. The integration uses the room's live timeline flush and watcher reset |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 58 | path. It does not reseed the cache. Thread and pinned timelines remain open |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 59 | through this update path. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 60 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 61 | ## Public and wire shapes |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 62 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 63 | `HistoryRepairCommand::FillGap { room_id, gap_id }` is the supported targeted |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 64 | fill operation, sent through `ClientHandle::execute` as |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 65 | `Command::HistoryRepair`. It returns `CommandOutput::Done` on success. The older |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 66 | policy and staged repair command variants remain available for serde |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 67 | compatibility, but room opening no longer consults the saved policy. `Continue` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 68 | is invalid because continuation must identify its gap. `GetPolicy` and |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 69 | `SetPolicy` remain compatibility operations and do not control opening. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 70 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 71 | Targeted fill progress is published as a `HistoryRepairReport` with `gap_id` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 72 | set and `phase` set to `Bridging`. This report supplies the loading state while |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 73 | the fill runs. Recent loading has its own room keyed `RecentHistoryState` with |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 74 | `Idle`, `Loading`, `Loaded`, or `Failed` values. The mirror exposes that state in |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 75 | `recent_history`, separately from gap continuity and repair progress. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 76 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 77 | `TimelineItem::HistoryGap` carries a `HistoryGap` with `id`, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 78 | `older_boundary`, `newer_boundary`, `lifecycle`, `continuity`, and `failure`. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 79 | Lifecycle values are `Idle`, `Fetching`, `Paused`, `Failed`, and `Unavailable`. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 80 | Continuity values are `Unresolved`, `Verified`, and `AccessibleStart`. The gap |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 81 | ID is an owned transaction ID. Boundary values are optional owned event IDs. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 82 | Failure is an optional displayable string. `TimelineState.items` contains these |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 83 | items in order with event rows. Mirror snapshots serialize the same item shape, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 84 | and incremental timeline changes carry it through the normal reset or changed |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 85 | diff. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 86 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 87 | Serde enums use externally tagged variants. A command payload is shaped as |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 88 | follows, with the outer `Command` adding its `HistoryRepair` tag. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 89 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 90 | ```json |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 91 | { |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 92 | "FillGap": { |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 93 | "room_id": "!room:example.org", |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 94 | "gap_id": "opaque-gap-transaction-id" |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 95 | } |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 96 | } |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 97 | ``` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 98 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 99 | The final history repair publication acknowledgement is internal, skipped by |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 100 | serde, and not part of application wire data. The mirror applies the report |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 101 | before acknowledging it, so command completion follows publication. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 102 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 103 | ## Remaining publication and Malice work |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 104 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 105 | Publish the SDK revision on the user's fork, then publish the macltok revision |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 106 | that pins it. Update the Malice macltok dependency in both native and worker |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 107 | manifests and regenerate the applicable lockfiles against that same revision. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 108 | The current worktree has not recorded a final publishable commit or revision |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 109 | for these changes. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 110 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 111 | Malice should remove the saved policy and threshold controls from room opening. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 112 | Render each inline gap between its neighboring event sections. A fill should |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 113 | preserve the visible event anchor, composer draft, reply or edit state, pending |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 114 | sends, and encryption identity. Keep event-context windows independent from |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 115 | live room history. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 116 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 117 | ## Lost interval investigation |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 118 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 119 | The reported failure showed retained events followed by newly sent messages, |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 120 | with an omitted interval of roughly eight to ten hours presented as continuous. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 121 | Older pagination continued before the retained buffer, and a page refresh did |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 122 | not recover the missing range. A refresh can reconnect to the existing |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 123 | SharedWorker, so it does not prove that the engine restarted. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 124 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 125 | The earlier SDK cache path could discard a limited-sync boundary token when a |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 126 | timeline was reported unlimited despite cached events. It could also discard a |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 127 | token when all incoming events were duplicates. Both cases can hide a real |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 128 | discontinuity from older pagination. Initial pagination could also finish |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 129 | against retained history before fresh sync arrived. These are concrete failure |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 130 | paths, but none has been confirmed as the production cause. The durable gaps |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 131 | preserve discontinuities when observed. Startup, subscription, and sync |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 132 | diagnostics are still needed to identify where this particular interval was |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 133 | lost. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 134 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 135 | ## Validation commands |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 136 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 137 | Run native checks from `../macltok-history-repair` with the repository's Nix |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 138 | toolchain. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 139 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 140 | ```sh |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 141 | nix develop --command cargo test --all-targets |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 142 | nix develop --command cargo test --all-targets --features sqlite |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 143 | nix develop --command cargo clippy --all-targets -- -D warnings |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 144 | nix develop --command cargo clippy --all-targets --features sqlite -- -D warnings |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 145 | ``` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 146 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 147 | For the browser worker runtime, use the web devshell. The harness starts a local |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 148 | homeserver and runs both worker targets in Firefox. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 149 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 150 | ```sh |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 151 | nix develop .#web -c nu harness/web/run.nu |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 152 | ``` |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 153 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 154 | The flake checks include native and WebAssembly compilation with IndexedDB and |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 155 | the web worker runtime. `nix flake check` is the aggregate gate and remains |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 156 | pending. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 157 | |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 158 | Seven focused SDK recent-history tests and seven segmented-history tests pass. |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 159 | The SDK WebAssembly compile passes with IndexedDB enabled. The SDK UI timeline |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 160 | checks pass with 123 unit tests, seven room unit tests, and 14 timed integration |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 161 | tests. Two headless Firefox tests pass for the IndexedDB shared gap storage |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 162 | contract with plaintext and encrypted stores. The full flake gate remains |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 163 | pending. Complete worker restart and encrypted room recovery have not been |
c9f0fd0ee1cc | atagen | 2026-10-25 06:16 UTC | 164 | tested. |