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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user