feat(notes): compact the operation log and purge old tombstones
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m17s

- Background job at start and every 6 h: log entries older than 30 days
  (private notes: 7) are removed, documents.log_floor marks the boundary;
  clients behind it get resync and keep their queued changes
- Deleted blocks are purged 30 days after deleted_at via a "purge" log
  effect, so open editors drop them without reloading; a tombstone stays
  while a deleted child still points to it
- Text for a purged block becomes a new block at the end; inserts under a
  purged parent go to the top level (server and browser model)
- doc_op_results kept for 90 days; expired lease/session maps are swept
- Tests: storage, API, engine resync, simulation with compactions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 15:08:13 +01:00
1 parent aea4324c20
commit f26f286fb8
17 files changed
+653 -48

No files matched your search

+33
View File
@@ -196,3 +196,36 @@ func itoa(n int64) string {
b, _ := json.Marshal(n)
return string(b)
}
func TestDocSyncAfterCompaction(t *testing.T) {
srv, store, ts, owner, ownerTok := startRealtimeServer(t)
doc, err := store.CreateDocument(owner.ID, "", "Alt", "📄", "", []domain.DocumentBlock{
{ID: "b1", Type: "bullet", Content: "Bleibt"}, {ID: "b2", Type: "bullet", Content: "Weg"},
}, nil, false)
if err != nil {
t.Fatal(err)
}
_, live := syncDoc(t, ts, ownerTok, doc.ID, map[string]interface{}{
"clientId": "live", "lastSeq": 0, "ops": []collab.Op{{OpID: "d", Kind: collab.OpDelete, BlockID: "b2"}},
})
// 40 days later: log and tombstone are past their retention
st, err := srv.store.CompactDocs(time.Now().Add(40 * 24 * time.Hour))
if err != nil || st.Tombstones != 1 {
t.Fatalf("compaction: %+v %v", st, err)
}
// A client that was up to date only gets the purge entry
_, up := syncDoc(t, ts, ownerTok, doc.ID, map[string]interface{}{"clientId": "live", "lastSeq": live.HeadSeq})
if up.Resync || len(up.Ops) != 1 || up.Ops[0].Effect.Kind != collab.OpPurge {
t.Fatalf("up-to-date client: resync=%v ops=%+v", up.Resync, up.Ops)
}
// A client behind the compacted log must reload; its own changes are still applied
code, old := syncDoc(t, ts, ownerTok, doc.ID, map[string]interface{}{
"clientId": "old", "lastSeq": 1,
"ops": []collab.Op{{OpID: "late", Kind: collab.OpSetContent, BlockID: "b1", BaseVersion: 1, Content: strp("Spät")}},
})
if code != http.StatusOK || !old.Resync || len(old.Ops) != 0 || old.Results[0].Status != collab.StatusApplied {
t.Fatalf("stale client: %d resync=%v results=%+v", code, old.Resync, old.Results)
}
}