@bobfrankston/rmfmail 1.2.205 → 1.2.206

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.
@@ -2710,7 +2710,32 @@ export class ImapManager extends EventEmitter {
2710
2710
  } else {
2711
2711
  const toDelete = localUids.filter(uid => !serverUids.has(uid));
2712
2712
  const RECONCILE_DELETE_THRESHOLD = 0.5; // refuse to delete >50% in one pass
2713
- if (localUids.length > 0 && toDelete.length / localUids.length > RECONCILE_DELETE_THRESHOLD) {
2713
+ const overThreshold = localUids.length > 0 && toDelete.length / localUids.length > RECONCILE_DELETE_THRESHOLD;
2714
+ // Over-threshold self-heal. The IMAP path has had an
2715
+ // "authoritative listing" carve-out since 2026-05-12; this
2716
+ // Gmail path never did, so stale local rows a server-side
2717
+ // delete left behind were refused FOREVER — 11 ghost gmail
2718
+ // drafts re-refused every cycle (Bob 2026-07-30), 105/106 on
2719
+ // Eleanor's machine (v1.2.188 notes). A non-truncated listing
2720
+ // already is the server's full truth; for a bounded candidate
2721
+ // set, DOUBLE-CONFIRM with a second independent listing — two
2722
+ // complete listings agreeing on absence is positive evidence,
2723
+ // not a transient. Beyond the cap keep refusing (rebuild is
2724
+ // the escape hatch for corruption-scale divergence).
2725
+ let confirmed = !overThreshold;
2726
+ if (overThreshold && toDelete.length <= 200 && !(serverUidsArr as any)._truncated) {
2727
+ try {
2728
+ const second = await api.getUids(folder.path);
2729
+ const secondSet = new Set(second);
2730
+ confirmed = !(second as any)._truncated
2731
+ && second.length === serverUidsArr.length
2732
+ && toDelete.every(uid => !secondSet.has(uid));
2733
+ if (confirmed) {
2734
+ console.log(` [api] ${accountId}/${folder.path}: over-threshold reconcile DOUBLE-CONFIRMED (${toDelete.length}/${localUids.length} absent in two independent listings) — deleting`);
2735
+ }
2736
+ } catch { confirmed = false; }
2737
+ }
2738
+ if (!confirmed) {
2714
2739
  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`);
2715
2740
  } else {
2716
2741
  for (const uid of toDelete) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.137",
3
+ "version": "0.1.139",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bobfrankston/mailx-imap",
9
- "version": "0.1.137",
9
+ "version": "0.1.139",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/iflow-direct": "^0.1.27",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.137",
3
+ "version": "0.1.139",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",