Skip to content

fix(server,web): make projection rebuilds restore full history - #25

Merged
asfires merged 3 commits into
mainfrom
t3code/fix-bootstrap-replay-limit
Aug 13, 2026
Merged

asfires merged 3 commits into
mainfrom
t3code/fix-bootstrap-replay-limit

Conversation

@asfires

@asfires asfires commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Problem

Migration 045's wipe-and-replay rebuild lost data two different ways:

  1. Bootstrap replay was capped at 1,000 events. readFromSequence defaults its limit to 1,000, so the rebuild replayed only the first 1,000 of ~27k events, then live events stamped the cursors to head — every thread newer than the oldest three silently vanished, with no error.
  2. The turns projector destroyed checkpointless turns on revert. Its thread.reverted handler kept only turns with checkpoint evidence at or below the revert baseline. Turns recorded while checkpointing was unavailable have no checkpoint_turn_count, so the first revert deleted them — and since thread detail paginates by turns, their retained messages became unreachable, indistinguishable from data loss.

Fix

  • Bootstrap replays the full backlog (Number.MAX_SAFE_INTEGER).
  • The turns projector uses the same collectRevertedTurnIds denylist as the message/activity/plan projectors, so a revert only ever removes the turns it actually reverted. Regression test covers a checkpointless turn surviving a revert alongside a reverted post-baseline turn.
  • Migration 046 re-runs 045's wipe-and-replay so history rebuilds through the fixed projector; thread snapshot cache bumps v6→v7 so warm browser caches cold-load the rebuilt projections.

Note: the boot that runs migration 046 replays the full event history before the server starts listening (~30 min on the daily-driver DB).

Fable 5 via Claude Code

Migration 045 wipes all projection tables and resets the replay cursor to 0,
but bootstrap read the backlog with readFromSequence's default 1,000-event
limit. On a 27k-event store the rebuild silently stopped after the oldest
1,000 events, live events then stamped the cursor to the head, and nearly
every thread and project vanished from the UI with no error.

Pass Number.MAX_SAFE_INTEGER so bootstrap replays everything past the
cursor, matching readAll's behavior.

Fable 5 via Claude Code
The turns projector's thread.reverted handler kept only turns with
checkpoint evidence at or below the revert baseline. Turns recorded while
checkpointing was unavailable have no checkpoint_turn_count, so the first
revert on the thread deleted them — and since thread detail paginates by
turns, their retained messages became unreachable, indistinguishable from
data loss. Use the same collectRevertedTurnIds denylist as the message,
activity, and plan projectors so a revert only ever removes the turns it
actually reverted.

Fable 5 via Claude Code
Migration 046 re-runs 045's wipe-and-replay so the event history rebuilds
through the fixed turns projector, restoring turn rows the first rebuild
dropped. Bump the thread snapshot cache to v7 so warm browser caches
cold-load the rebuilt projections instead of resuming past them.

Fable 5 via Claude Code
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Aug 13, 2026
@asfires
asfires merged commit df49b7d into main Aug 13, 2026
6 of 10 checks passed
@asfires
asfires deleted the t3code/fix-bootstrap-replay-limit branch August 13, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant