@bobfrankston/mailx-imap 0.1.136 → 0.1.139

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.
Files changed (2) hide show
  1. package/index.js +29 -1
  2. package/package.json +7 -7
package/index.js CHANGED
@@ -2687,7 +2687,35 @@ export class ImapManager extends EventEmitter {
2687
2687
  else {
2688
2688
  const toDelete = localUids.filter(uid => !serverUids.has(uid));
2689
2689
  const RECONCILE_DELETE_THRESHOLD = 0.5; // refuse to delete >50% in one pass
2690
- if (localUids.length > 0 && toDelete.length / localUids.length > RECONCILE_DELETE_THRESHOLD) {
2690
+ const overThreshold = localUids.length > 0 && toDelete.length / localUids.length > RECONCILE_DELETE_THRESHOLD;
2691
+ // Over-threshold self-heal. The IMAP path has had an
2692
+ // "authoritative listing" carve-out since 2026-05-12; this
2693
+ // Gmail path never did, so stale local rows a server-side
2694
+ // delete left behind were refused FOREVER — 11 ghost gmail
2695
+ // drafts re-refused every cycle (Bob 2026-07-30), 105/106 on
2696
+ // Eleanor's machine (v1.2.188 notes). A non-truncated listing
2697
+ // already is the server's full truth; for a bounded candidate
2698
+ // set, DOUBLE-CONFIRM with a second independent listing — two
2699
+ // complete listings agreeing on absence is positive evidence,
2700
+ // not a transient. Beyond the cap keep refusing (rebuild is
2701
+ // the escape hatch for corruption-scale divergence).
2702
+ let confirmed = !overThreshold;
2703
+ if (overThreshold && toDelete.length <= 200 && !serverUidsArr._truncated) {
2704
+ try {
2705
+ const second = await api.getUids(folder.path);
2706
+ const secondSet = new Set(second);
2707
+ confirmed = !second._truncated
2708
+ && second.length === serverUidsArr.length
2709
+ && toDelete.every(uid => !secondSet.has(uid));
2710
+ if (confirmed) {
2711
+ console.log(` [api] ${accountId}/${folder.path}: over-threshold reconcile DOUBLE-CONFIRMED (${toDelete.length}/${localUids.length} absent in two independent listings) — deleting`);
2712
+ }
2713
+ }
2714
+ catch {
2715
+ confirmed = false;
2716
+ }
2717
+ }
2718
+ if (!confirmed) {
2691
2719
  console.log(` [api] ${accountId}/${folder.path}: reconcile refused — would delete ${toDelete.length}/${localUids.length} (${Math.round(toDelete.length / localUids.length * 100)}%) — probably a sync bug, skipping`);
2692
2720
  }
2693
2721
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.136",
3
+ "version": "0.1.139",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -11,11 +11,11 @@
11
11
  "dependencies": {
12
12
  "@bobfrankston/mailx-types": "^0.1.38",
13
13
  "@bobfrankston/mailx-settings": "^0.1.48",
14
- "@bobfrankston/mailx-store": "^0.1.77",
15
- "@bobfrankston/iflow-direct": "^0.1.59",
14
+ "@bobfrankston/mailx-store": "^0.1.78",
15
+ "@bobfrankston/iflow-direct": "^0.1.63",
16
16
  "@bobfrankston/tcp-transport": "^0.1.8",
17
17
  "@bobfrankston/smtp-direct": "^0.1.9",
18
- "@bobfrankston/mailx-sync": "^0.1.27",
18
+ "@bobfrankston/mailx-sync": "^0.1.29",
19
19
  "@bobfrankston/oauthsupport": "^1.0.34"
20
20
  },
21
21
  "repository": {
@@ -39,11 +39,11 @@
39
39
  "dependencies": {
40
40
  "@bobfrankston/mailx-types": "^0.1.38",
41
41
  "@bobfrankston/mailx-settings": "^0.1.48",
42
- "@bobfrankston/mailx-store": "^0.1.77",
43
- "@bobfrankston/iflow-direct": "^0.1.59",
42
+ "@bobfrankston/mailx-store": "^0.1.78",
43
+ "@bobfrankston/iflow-direct": "^0.1.63",
44
44
  "@bobfrankston/tcp-transport": "^0.1.8",
45
45
  "@bobfrankston/smtp-direct": "^0.1.9",
46
- "@bobfrankston/mailx-sync": "^0.1.27",
46
+ "@bobfrankston/mailx-sync": "^0.1.29",
47
47
  "@bobfrankston/oauthsupport": "^1.0.34"
48
48
  }
49
49
  }