Skip to content

fix(server): stop reverts destroying thread history; rebuild projections from events - #24

Merged
asfires merged 3 commits into
mainfrom
t3code/revert-retention-rebuild
Aug 12, 2026
Merged

asfires merged 3 commits into
mainfrom
t3code/revert-retention-rebuild

Conversation

@asfires

@asfires asfires commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Problem

Every Esc-retraction revert progressively destroyed the head of long threads. thread.reverted retention was an allowlist keyed on checkpointed turns: any message whose turn had no checkpoint at revert time (never diff-completed, evicted, or trimmed by a previous revert) was silently dropped, and the count-based fallback could only rescue turnId === null messages. Production evidence: thread d1b97649… went through 11 retractions in one day and its projection shrank to 75 rows starting mid-day, while the event log holds the full 2,804-event history.

Fix (3 commits)

  1. fix(server): retain thread history during projection replay — retention inverted to a denylist of actually-reverted turns (shared collectRevertedTurnIds: post-baseline checkpoints + retraction target + latest/active turn unless proven retained), applied to both the in-memory projector and the SQL projection pipeline, for messages, activities, and proposed plans. Worst-case failure mode is now a stray retained message instead of destroyed history.

    Also makes bootstrap replay pure: per-event attachment side effects are skipped during bootstrap (a from-zero replay would prune attachment files still referenced by later events — a hazard that already existed in miniature for crash-gap recovery) and replaced with a single final-state reconciliation pass at bootstrap end, before reactors/serving start. Bootstrap also switches from projector-major to event-major replay to match live ordering, so retention sees turn/session evidence as of each historical event.

  2. feat(server): rebuild projections from event history — migration 045 clears all event-derived projection tables and zeroes projection_state cursors, so the next boot replays the full event log (~27k events) through the fixed projector, restoring all revert-trimmed history in every thread. Table-guarded and idempotent per fork convention.

  3. fix(web): invalidate cache after projection rebuildStoredThreadSnapshot schemaVersion 5→6 so warm IndexedDB snapshots cold-load the rebuilt server truth (same pattern as fix(web): invalidate thread snapshot caches predating migration 044 #23/migration 044).

Verification

  • Focused suites: 73 passed / 1 skipped (denylist regressions incl. checkpointless-early-turns, sequential reverts, completed-retraction exclusion, from-zero attachment survival, migration guard/idempotence)
  • Web: 2,306 passed; server+web typecheck clean; lint clean
  • Full server suite: 2,466 passed; 3 failures are pre-existing on main (ActivityPayloadProjection payload-budget tests + dependent server.test.ts case — none of those files change in this PR)

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL labels Aug 12, 2026
@asfires
asfires merged commit 9c38dcc into main Aug 12, 2026
6 of 10 checks passed
@asfires
asfires deleted the t3code/revert-retention-rebuild branch August 12, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 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