atagen

macltok

public
Star0
Watch0

Commit 06c73391cbae

tarball

atagen <boss@atagen.co>2026-10-25 10:26 UTC

2 files changed+9-10
history: lean on the non-replacing flush fence

Timeline::flush_history now applies what the room cache already queued instead of replacing the timeline, so completions keep receipts, the read marker and row ids. A gap that has already joined now answers NotFound from the sdk, so only an Unavailable first answer is free of a page.
Modified file: docs/segmented-history.md+6 additions−4 deletions
@@ -36,7 +36,8 @@ with phase `Bridging` as the fill starts and once more as it settles.
3636 when accessible history ends first, `Cancelled`
3737 - `Paused` with a `failure`, and the gap row goes `Failed`
3838
39-a gap that is already joined or unavailable answers at once and counts no page.
39+a gap that is already unavailable answers at once and counts no page. one that
40+has already joined is gone, so filling it answers `EventNotFound`.
4041
4142 ## one operation per room
4243
@@ -54,9 +55,10 @@ gaps appear in `TimelineState.items` as `TimelineItem::HistoryGap`, between the
5455 sections they separate. a row keeps its id while its lifecycle changes, and is
5556 removed once the gap joins. joined gaps never appear.
5657
57-history lands as `TimelineChanged` diffs, not a `TimelineReset`. the sdk
58-currently replays the whole live timeline when it applies committed history,
59-so those diffs can span the timeline until it gets a non-replacing fence.
58+history lands as incremental `TimelineChanged` diffs, never a `TimelineReset`.
59+before answering, the room fences on `Timeline::flush_history`, which applies
60+what the cache already queued without replacing the timeline, so receipts, the
61+read marker and row ids survive.
6062
6163 a command's answer comes after its outcome is published, and after the mirror
6264 has applied it when one is attached.
Modified file: src/engine/repair.rs+3 additions−6 deletions
@@ -307,14 +307,11 @@ async fn fill_gap(
307307 HistoryGapFillOutcome::Busy => return Err(room_busy()),
308308 HistoryGapFillOutcome::Updated { gap, events } => (gap, events),
309309 };
310- // the sdk answers an already joined or unavailable gap without asking the server.
311- // an empty first page that settles the gap looks the same and goes uncounted too
310+ // the sdk answers an already unavailable gap without asking the server, and a joined
311+ // one with NotFound. an empty first page that hits the end looks the same, uncounted
312312 let settled_before = report.pages == 0
313313 && events == 0
314- && matches!(
315- gap.status,
316- HistoryGapStatus::Joined | HistoryGapStatus::Unavailable
317- );
314+ && gap.status == HistoryGapStatus::Unavailable;
318315 if !settled_before {
319316 report.pages += 1;
320317 committed = true;