@bobfrankston/rmfmail 1.1.101 → 1.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2874,16 +2874,16 @@ export class ImapManager extends EventEmitter {
2874
2874
  // owns deletion via a 30-min grace; defer to it.
2875
2875
  const someReceived = received.size > 0;
2876
2876
  if (batchSucceeded && someReceived) {
2877
+ // Mirror of the IMAP-path fix: never delete on a
2878
+ // partial batch — Gmail API has its own transient
2879
+ // miss modes (rate-limit retry losing a message,
2880
+ // /batch response parse error) that look exactly
2881
+ // like server-side expunge. Set-diff reconcile in
2882
+ // syncAccountViaApi owns deletion.
2877
2883
  for (const uid of uidsInFolder) {
2878
2884
  if (received.has(uid))
2879
2885
  continue;
2880
- try {
2881
- this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
2882
- this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (Gmail batch)");
2883
- counters.deleted++;
2884
- madeProgress = true;
2885
- }
2886
- catch { /* ignore */ }
2886
+ this.markPrefetchEmpty(accountId, folderId, uid);
2887
2887
  }
2888
2888
  }
2889
2889
  else if (batchSucceeded && !someReceived) {
@@ -3007,18 +3007,24 @@ export class ImapManager extends EventEmitter {
3007
3007
  // authoritative deletion path with a 30-min
3008
3008
  // grace window; prefetch defers to it.
3009
3009
  const someReceived = received.size > 0;
3010
- if (batchSucceeded && someReceived)
3010
+ if (batchSucceeded && someReceived) {
3011
+ // DO NOT DELETE missing UIDs. A partial response is
3012
+ // an iflow parser miss / mid-stream hiccup MUCH more
3013
+ // often than a real server expunge — and deleting on
3014
+ // that signal cost Bob a Bambu Labs verification
3015
+ // code (audit id 3785, 2026-05-20), plus dozens of
3016
+ // other valid messages over the day. Set-diff
3017
+ // reconcile in syncFolder is the authoritative
3018
+ // deletion path with a 30-min grace window; prefetch
3019
+ // defers to it. Mark the UIDs as prefetch-empty so
3020
+ // the TTL backoff (5min → 12h) retries them — that
3021
+ // path is non-destructive.
3011
3022
  for (const uid of chunk) {
3012
3023
  if (received.has(uid))
3013
3024
  continue;
3014
- try {
3015
- this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
3016
- this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (IMAP batch)");
3017
- counters.deleted++;
3018
- madeProgress = true;
3019
- }
3020
- catch { /* ignore */ }
3025
+ this.markPrefetchEmpty(accountId, folderId, uid);
3021
3026
  }
3027
+ }
3022
3028
  else if (batchSucceeded && !someReceived) {
3023
3029
  console.error(` [prefetch] ${accountId}/${folder.path}: chunk ${chunkStart}-${chunkStart + chunk.length - 1} returned 0/${chunk.length} bodies — NOT pruning (set-diff reconcile owns deletion). UIDs: ${chunk.slice(0, 5).join(",")}${chunk.length > 5 ? "..." : ""}`);
3024
3030
  for (const uid of chunk)