feat(PROJ-45): IMAP per-folder UID-tracking, UIDVALIDITY-check + reindex OCR protection

- scheduler.go: BUG-1 fix — preserve stored uid_validity when server returns 0
- scheduler.go: BUG-2 fix — replace inline switch with DecideResync() call
- scheduler.go: SetAuditLogger wired; imap_uidvalidity_reset audit event
- cmd_reindex.go: read existing attachment_text before IndexSync to prevent
  Manticore REPLACE INTO from wiping OCR text written by the OCR worker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-05-11 14:56:28 +02:00
parent 4151b6f8c5
commit 799c828548
4 changed files with 35 additions and 9 deletions
+18 -8
View File
@@ -302,13 +302,16 @@ func (s *Scheduler) syncFolder(
serverUIDValidity = selectData.UIDValidity
}
effectiveLastUID := state.LastUID
// BUG-2 fix: use DecideResync instead of inline switch to keep the
// branching logic in one place and avoid drift with the unit-tested path.
decision := DecideResync(state.LastUID, state.UIDValidity, serverUIDValidity)
effectiveLastUID := decision.EffectiveStart
switch {
case serverUIDValidity == 0:
if serverUIDValidity == 0 {
log.Warn("uidvalidity zero from server, keeping stored last_uid",
"folder", folder, "stored_uid_validity", state.UIDValidity)
case state.UIDValidity != 0 && state.UIDValidity != serverUIDValidity:
}
if decision.UIDValidReset {
// Mailbox was rebuilt server-side — UIDs restart at 1. We must
// resync the entire folder from scratch. PROJ-32 message-id
// deduplication prevents duplicate writes.
@@ -327,7 +330,6 @@ func (s *Scheduler) syncFolder(
acc.ID, folder, state.UIDValidity, serverUIDValidity),
})
}
effectiveLastUID = 0
}
var uids []imapv2.UID
@@ -359,14 +361,22 @@ func (s *Scheduler) syncFolder(
maxUID uint32 = effectiveLastUID
)
// PROJ-45: even when there are 0 new UIDs, persist the (possibly new)
// uid_validity so a later UIDVALIDITY change can still be detected.
// BUG-1 fix: when the server returns UIDVALIDITY=0 (misbehaving server),
// keep the stored valid value so future mismatch detection still works.
// Writing 0 would silently disable the UIDVALIDITY guard for this folder.
persistedValidity := serverUIDValidity
if serverUIDValidity == 0 {
persistedValidity = state.UIDValidity
}
// PROJ-45: even when there are 0 new UIDs, persist the uid_validity
// so a later UIDVALIDITY change can still be detected.
defer func() {
newState := FolderState{
AccountID: acc.ID,
Folder: folder,
LastUID: maxUID,
UIDValidity: serverUIDValidity,
UIDValidity: persistedValidity,
}
if err := s.store.UpsertFolderState(ctx, newState); err != nil {
log.Warn("imap scheduler: persist folder state failed",