@remit/mailbox-service 0.0.111 → 0.0.113
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.
- package/dist/account-credentials.d.ts +83 -0
- package/dist/account-credentials.js +104 -0
- package/dist/attribute-mapper.d.ts +36 -0
- package/dist/attribute-mapper.js +119 -0
- package/dist/body-parse.d.ts +28 -0
- package/{src/body-parse.ts → dist/body-parse.js} +18 -26
- package/dist/body-part-mapper.d.ts +92 -0
- package/dist/body-part-mapper.js +301 -0
- package/dist/body-sync-queue.d.ts +59 -0
- package/dist/body-sync-queue.js +56 -0
- package/dist/body-sync-terminal.d.ts +58 -0
- package/dist/body-sync-terminal.js +95 -0
- package/dist/body-sync.d.ts +351 -0
- package/dist/body-sync.js +1204 -0
- package/dist/calendar-parts.d.ts +23 -0
- package/dist/calendar-parts.js +18 -0
- package/dist/classification-backfill.d.ts +91 -0
- package/dist/classification-backfill.js +180 -0
- package/dist/classify-message.d.ts +31 -0
- package/dist/classify-message.js +89 -0
- package/dist/connection-factory.d.ts +54 -0
- package/dist/connection-factory.js +54 -0
- package/dist/connection-test.d.ts +41 -0
- package/dist/connection-test.js +152 -0
- package/dist/external-move.d.ts +94 -0
- package/dist/external-move.js +173 -0
- package/dist/filter-operations.d.ts +42 -0
- package/dist/filter-operations.js +211 -0
- package/dist/filters/filter-config.d.ts +14 -0
- package/dist/filters/filter-config.js +10 -0
- package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
- package/dist/filters/list-id.js +26 -0
- package/dist/filters/match.d.ts +69 -0
- package/dist/filters/match.js +165 -0
- package/dist/filters/pipeline.d.ts +64 -0
- package/dist/filters/pipeline.js +100 -0
- package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +19 -77
- package/dist/flag-push-terminal.js +63 -0
- package/dist/flag-push.d.ts +69 -0
- package/dist/flag-push.js +111 -0
- package/dist/flag-queue.d.ts +175 -0
- package/dist/flag-queue.js +291 -0
- package/dist/folder-operations.d.ts +39 -0
- package/dist/folder-operations.js +112 -0
- package/dist/folder-placement.d.ts +13 -0
- package/dist/folder-placement.js +23 -0
- package/dist/heuristics/authenticationResults.d.ts +10 -0
- package/dist/heuristics/authenticationResults.js +187 -0
- package/dist/heuristics/classifyByHeaders.d.ts +82 -0
- package/dist/heuristics/classifyByHeaders.js +365 -0
- package/dist/heuristics/classifyPlacement.d.ts +67 -0
- package/dist/heuristics/classifyPlacement.js +153 -0
- package/dist/heuristics/machineSenders.d.ts +21 -0
- package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
- package/dist/heuristics/senderMismatch.d.ts +81 -0
- package/dist/heuristics/senderMismatch.js +192 -0
- package/dist/heuristics/socialDomains.d.ts +14 -0
- package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
- package/dist/heuristics/transactionalDomains.d.ts +18 -0
- package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
- package/dist/imapflow-connection.d.ts +377 -0
- package/dist/imapflow-connection.js +1147 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +65 -0
- package/dist/list-id-backfill.d.ts +83 -0
- package/dist/list-id-backfill.js +132 -0
- package/dist/mailbox-cursor-rebuild.d.ts +74 -0
- package/dist/mailbox-cursor-rebuild.js +71 -0
- package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
- package/dist/mailbox-cursor.js +102 -0
- package/dist/mailbox-intent.d.ts +41 -0
- package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +39 -63
- package/dist/mailbox-management.d.ts +240 -0
- package/dist/mailbox-management.js +576 -0
- package/dist/mailbox-presence.d.ts +52 -0
- package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
- package/dist/mailbox-queue.d.ts +126 -0
- package/dist/mailbox-queue.js +214 -0
- package/dist/mailbox-sync.d.ts +93 -0
- package/dist/mailbox-sync.js +490 -0
- package/dist/mailbox-upstream.d.ts +38 -0
- package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
- package/dist/message-category.d.ts +51 -0
- package/{src/message-category.ts → dist/message-category.js} +28 -71
- package/dist/message-move.d.ts +285 -0
- package/dist/message-move.js +858 -0
- package/dist/message-parser.d.ts +14 -0
- package/dist/message-parser.js +17 -0
- package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
- package/dist/message-presence.js +29 -0
- package/dist/message-read.d.ts +51 -0
- package/dist/message-read.js +31 -0
- package/dist/message-sync.d.ts +345 -0
- package/dist/message-sync.js +1358 -0
- package/dist/mime-walker.d.ts +102 -0
- package/dist/mime-walker.js +198 -0
- package/dist/organize-job.d.ts +20 -0
- package/dist/organize-job.js +37 -0
- package/dist/outbox-attachment-content.d.ts +37 -0
- package/dist/outbox-attachment-content.js +50 -0
- package/dist/outbox-attachment-filename.d.ts +19 -0
- package/dist/outbox-attachment-filename.js +64 -0
- package/dist/outbox-attachment.d.ts +148 -0
- package/dist/outbox-attachment.js +273 -0
- package/dist/outbox-queue.d.ts +73 -0
- package/dist/outbox-queue.js +284 -0
- package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
- package/dist/outbox-status.js +18 -0
- package/dist/ownership.d.ts +24 -0
- package/dist/ownership.js +83 -0
- package/dist/placement-move-terminal.d.ts +67 -0
- package/dist/placement-move-terminal.js +88 -0
- package/dist/placement-move.d.ts +83 -0
- package/dist/placement-move.js +216 -0
- package/{src/placement-settled.ts → dist/placement-settled.d.ts} +19 -50
- package/dist/placement-settled.js +75 -0
- package/dist/quarantine.d.ts +95 -0
- package/dist/quarantine.js +131 -0
- package/dist/refusal.d.ts +31 -0
- package/dist/refusal.js +3 -0
- package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -101
- package/dist/restore-source-placement.js +79 -0
- package/dist/search-text-backfill.d.ts +15 -0
- package/dist/search-text-backfill.js +33 -0
- package/dist/snippet.d.ts +61 -0
- package/dist/snippet.js +129 -0
- package/dist/spam-report.d.ts +86 -0
- package/dist/spam-report.js +241 -0
- package/dist/sync-warning.d.ts +21 -0
- package/dist/sync-warning.js +26 -0
- package/dist/sync-watermarks.d.ts +138 -0
- package/dist/sync-watermarks.js +205 -0
- package/dist/test-helpers/fake-imap.d.ts +151 -0
- package/dist/test-helpers/fake-imap.js +508 -0
- package/dist/test-helpers/folder-roles.d.ts +34 -0
- package/dist/test-helpers/folder-roles.js +47 -0
- package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
- package/dist/test-helpers/mime-tree-generator.js +329 -0
- package/dist/test-helpers/test-store.d.ts +69 -0
- package/dist/test-helpers/test-store.js +193 -0
- package/dist/text/normalizer.d.ts +20 -0
- package/dist/text/normalizer.js +102 -0
- package/dist/thread-message-rows.d.ts +69 -0
- package/dist/thread-message-rows.js +40 -0
- package/dist/thread-root.d.ts +7 -0
- package/dist/thread-root.js +25 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +19 -0
- package/package.json +86 -73
- package/src/account-credentials.ts +0 -210
- package/src/adversarial-mime.e2e.test.ts +0 -307
- package/src/attribute-mapper.test.ts +0 -104
- package/src/attribute-mapper.ts +0 -148
- package/src/body-parse.test.ts +0 -55
- package/src/body-part-mapper.property.test.ts +0 -354
- package/src/body-part-mapper.ts +0 -360
- package/src/body-sync-auto-read-writeonce.test.ts +0 -219
- package/src/body-sync-calendar-suggestion.test.ts +0 -433
- package/src/body-sync-category-denormalize.test.ts +0 -352
- package/src/body-sync-category-override.test.ts +0 -501
- package/src/body-sync-category-writeonce.test.ts +0 -234
- package/src/body-sync-filter.test.ts +0 -237
- package/src/body-sync-message-gone.test.ts +0 -139
- package/src/body-sync-placement-flags.test.ts +0 -347
- package/src/body-sync-placement-writeonce.test.ts +0 -337
- package/src/body-sync-quarantine.test.ts +0 -276
- package/src/body-sync-queue.test.ts +0 -124
- package/src/body-sync-queue.ts +0 -131
- package/src/body-sync-resync.test.ts +0 -605
- package/src/body-sync-terminal.test.ts +0 -310
- package/src/body-sync-terminal.ts +0 -176
- package/src/body-sync-unclassifiable-once.test.ts +0 -260
- package/src/body-sync-unsubscribed-auto-read.test.ts +0 -176
- package/src/body-sync.ts +0 -1970
- package/src/calendar-parts.ts +0 -41
- package/src/classification-backfill.test.ts +0 -615
- package/src/classification-backfill.ts +0 -363
- package/src/classify-message.test.ts +0 -205
- package/src/classify-message.ts +0 -116
- package/src/connection-factory.ts +0 -102
- package/src/connection-test.ts +0 -208
- package/src/credentials.test.ts +0 -181
- package/src/external-move.test.ts +0 -369
- package/src/external-move.ts +0 -272
- package/src/filters/anchor-drift.ts +0 -55
- package/src/filters/filter-config.test.ts +0 -229
- package/src/filters/filter-config.ts +0 -69
- package/src/filters/list-id.test.ts +0 -50
- package/src/filters/match.test.ts +0 -429
- package/src/filters/match.ts +0 -274
- package/src/filters/message-embedder.test.ts +0 -39
- package/src/filters/message-embedder.ts +0 -33
- package/src/filters/pipeline.test.ts +0 -330
- package/src/filters/pipeline.ts +0 -284
- package/src/flag-push-terminal.test.ts +0 -209
- package/src/flag-push.test.ts +0 -408
- package/src/flag-push.ts +0 -198
- package/src/flag-queue-stars.test.ts +0 -171
- package/src/flag-queue.ts +0 -509
- package/src/folder-placement.ts +0 -56
- package/src/heuristics/authenticationResults.test.ts +0 -189
- package/src/heuristics/authenticationResults.ts +0 -212
- package/src/heuristics/classifyByHeaders.test.ts +0 -889
- package/src/heuristics/classifyByHeaders.ts +0 -467
- package/src/heuristics/classifyPlacement.test.ts +0 -366
- package/src/heuristics/classifyPlacement.ts +0 -211
- package/src/heuristics/classifyRealMail.test.ts +0 -242
- package/src/heuristics/machineSenders.test.ts +0 -101
- package/src/heuristics/senderMismatch.test.ts +0 -346
- package/src/heuristics/senderMismatch.ts +0 -259
- package/src/imapflow-connect-retry.test.ts +0 -134
- package/src/imapflow-connection.e2e.test.ts +0 -335
- package/src/imapflow-connection.integ.test.ts +0 -316
- package/src/imapflow-connection.test.ts +0 -469
- package/src/imapflow-connection.ts +0 -1475
- package/src/index.ts +0 -350
- package/src/list-id-backfill.test.ts +0 -573
- package/src/list-id-backfill.ts +0 -274
- package/src/mailbox-cursor-rebuild.test.ts +0 -171
- package/src/mailbox-cursor-rebuild.ts +0 -146
- package/src/mailbox-cursor.test.ts +0 -249
- package/src/mailbox-delete-intent.test.ts +0 -246
- package/src/mailbox-management.test.ts +0 -356
- package/src/mailbox-management.ts +0 -839
- package/src/mailbox-presence.test.ts +0 -72
- package/src/mailbox-queue.ts +0 -415
- package/src/mailbox-rename-intent.test.ts +0 -389
- package/src/mailbox-rename-subtree.test.ts +0 -664
- package/src/mailbox-sync.test.ts +0 -848
- package/src/mailbox-sync.ts +0 -724
- package/src/message-delete-junk-reconcile.test.ts +0 -117
- package/src/message-delete-no-trash.test.ts +0 -245
- package/src/message-delete-unsettled-placement.test.ts +0 -396
- package/src/message-move-copy.test.ts +0 -562
- package/src/message-move-empty-trash.test.ts +0 -326
- package/src/message-move-same-mailbox.test.ts +0 -139
- package/src/message-move-unsettled-placement.test.ts +0 -378
- package/src/message-move.ts +0 -1348
- package/src/message-parser.ts +0 -26
- package/src/message-presence.test.ts +0 -50
- package/src/message-sync-address-spoof.test.ts +0 -293
- package/src/message-sync-category.test.ts +0 -142
- package/src/message-sync-changedsince.test.ts +0 -840
- package/src/message-sync-flags.test.ts +0 -158
- package/src/message-sync-junk-harvest.test.ts +0 -244
- package/src/message-sync-quarantine.test.ts +0 -416
- package/src/message-sync-repoint.test.ts +0 -419
- package/src/message-sync.ts +0 -1963
- package/src/mime-walker.test.ts +0 -311
- package/src/mime-walker.ts +0 -305
- package/src/outbox-attachment-content.test.ts +0 -153
- package/src/outbox-attachment-content.ts +0 -104
- package/src/outbox-attachment-filename.test.ts +0 -132
- package/src/outbox-attachment-filename.ts +0 -76
- package/src/outbox-attachment.test.ts +0 -738
- package/src/outbox-attachment.ts +0 -536
- package/src/outbox-failed-recovery.test.ts +0 -370
- package/src/outbox-queue-settled-row.test.ts +0 -140
- package/src/outbox-queue.test.ts +0 -291
- package/src/outbox-queue.ts +0 -508
- package/src/pass-through-unit-of-work.ts +0 -17
- package/src/placement-move-terminal.test.ts +0 -300
- package/src/placement-move-terminal.ts +0 -148
- package/src/placement-move-unsettled.test.ts +0 -275
- package/src/placement-move.ts +0 -336
- package/src/placement-settled.test.ts +0 -147
- package/src/placement-transition-race.test.ts +0 -482
- package/src/quarantine.test.ts +0 -191
- package/src/quarantine.ts +0 -228
- package/src/snippet.test.ts +0 -213
- package/src/snippet.ts +0 -152
- package/src/spam-report.test.ts +0 -801
- package/src/spam-report.ts +0 -357
- package/src/stale-message-reconcile.test.ts +0 -82
- package/src/stale-message-reconcile.ts +0 -55
- package/src/sync-watermarks.test.ts +0 -409
- package/src/sync-watermarks.ts +0 -319
- package/src/system-flag-wire-format.test.ts +0 -77
- package/src/test-helpers/folder-roles.ts +0 -66
- package/src/test-helpers/isolated-mailbox.ts +0 -44
- package/src/test-helpers/mailfuzz-connection.ts +0 -27
- package/src/test-helpers/mime-tree-generator.ts +0 -461
- package/src/text/normalizer.test.ts +0 -223
- package/src/text/normalizer.ts +0 -140
- package/src/thread-message-rows.ts +0 -77
- package/src/types/mailparser-augment.d.ts +0 -15
- package/src/types/natural-porter-stemmer.d.ts +0 -50
- package/src/types/stopword.d.ts +0 -69
- package/src/types.ts +0 -371
- package/test/fixtures/mime-adversarial-e2e/01-octet-stream-pdf.eml +0 -21
- package/test/fixtures/mime-adversarial-e2e/02-nested-related-cid-image.eml +0 -31
- package/test/fixtures/mime-adversarial-e2e/03-calendar-invite.eml +0 -33
- package/test/fixtures/mime-adversarial-e2e/04-forwarded-rfc822-with-attachments.eml +0 -42
- package/test/fixtures/mime-adversarial-e2e/05-tnef-winmail.eml +0 -25
- package/test/fixtures/mime-adversarial-e2e/06-8bit-non-ascii.eml +0 -15
- package/test/fixtures/mime-adversarial-e2e/07-quoted-printable-soft-breaks.eml +0 -17
- package/test/fixtures/mime-adversarial-e2e/08-alternative-empty-text.eml +0 -18
- package/tsconfig.json +0 -8
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type {
|
|
4
|
-
MessageItem,
|
|
5
|
-
PlacementPredicate,
|
|
6
|
-
PlacementTransitionInput,
|
|
7
|
-
} from "@remit/data-ports";
|
|
8
|
-
import {
|
|
9
|
-
MessageMutation,
|
|
10
|
-
MessageStatus,
|
|
11
|
-
MessageSyncStatus,
|
|
12
|
-
} from "@remit/domain-enums";
|
|
13
|
-
import type { PlacementMoveLogger } from "./placement-move.js";
|
|
14
|
-
import {
|
|
15
|
-
type ResolveExhaustedPlacementMoveDeps,
|
|
16
|
-
resolveExhaustedPlacementMoveFailure,
|
|
17
|
-
} from "./placement-move-terminal.js";
|
|
18
|
-
import type { IImapConnection } from "./types.js";
|
|
19
|
-
|
|
20
|
-
interface LogEntry {
|
|
21
|
-
obj: Record<string, unknown>;
|
|
22
|
-
msg: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface Transition {
|
|
26
|
-
messageId: string;
|
|
27
|
-
expected: PlacementPredicate;
|
|
28
|
-
next: PlacementTransitionInput;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface Recorder {
|
|
32
|
-
deps: ResolveExhaustedPlacementMoveDeps;
|
|
33
|
-
infos: LogEntry[];
|
|
34
|
-
errors: LogEntry[];
|
|
35
|
-
markerDeletes: string[];
|
|
36
|
-
deletedMessages: string[];
|
|
37
|
-
deletedThreadMessages: Array<{
|
|
38
|
-
accountConfigId: string;
|
|
39
|
-
threadMessageId: string;
|
|
40
|
-
}>;
|
|
41
|
-
transitions: Transition[];
|
|
42
|
-
threadUpdates: string[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const buildRecorder = (
|
|
46
|
-
threadMessages: Array<{ accountConfigId: string; threadMessageId: string }>,
|
|
47
|
-
transitionWins = true,
|
|
48
|
-
): Recorder => {
|
|
49
|
-
const infos: LogEntry[] = [];
|
|
50
|
-
const errors: LogEntry[] = [];
|
|
51
|
-
const markerDeletes: string[] = [];
|
|
52
|
-
const deletedMessages: string[] = [];
|
|
53
|
-
const deletedThreadMessages: Array<{
|
|
54
|
-
accountConfigId: string;
|
|
55
|
-
threadMessageId: string;
|
|
56
|
-
}> = [];
|
|
57
|
-
const transitions: Transition[] = [];
|
|
58
|
-
const threadUpdates: string[] = [];
|
|
59
|
-
|
|
60
|
-
const log: PlacementMoveLogger = {
|
|
61
|
-
info: (obj, msg) => infos.push({ obj, msg }),
|
|
62
|
-
error: (obj, msg) => errors.push({ obj, msg }),
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const deps: ResolveExhaustedPlacementMoveDeps = {
|
|
66
|
-
markerService: {
|
|
67
|
-
delete: async (id: string) => {
|
|
68
|
-
markerDeletes.push(id);
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
messageService: {
|
|
72
|
-
delete: async (id: string) => {
|
|
73
|
-
deletedMessages.push(id);
|
|
74
|
-
},
|
|
75
|
-
transitionPlacement: async (
|
|
76
|
-
messageId: string,
|
|
77
|
-
expected: PlacementPredicate,
|
|
78
|
-
next: PlacementTransitionInput,
|
|
79
|
-
) => {
|
|
80
|
-
transitions.push({ messageId, expected, next });
|
|
81
|
-
return transitionWins ? ({ messageId } as MessageItem) : undefined;
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
threadMessageService: {
|
|
85
|
-
findAllByMessageId: async () => threadMessages as never,
|
|
86
|
-
deleteMany: async (
|
|
87
|
-
keys: Array<{ accountConfigId: string; threadMessageId: string }>,
|
|
88
|
-
) => {
|
|
89
|
-
deletedThreadMessages.push(...keys);
|
|
90
|
-
},
|
|
91
|
-
update: async (_accountConfigId: string, threadMessageId: string) => {
|
|
92
|
-
threadUpdates.push(threadMessageId);
|
|
93
|
-
return undefined as never;
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
log,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
deps,
|
|
101
|
-
infos,
|
|
102
|
-
errors,
|
|
103
|
-
markerDeletes,
|
|
104
|
-
deletedMessages,
|
|
105
|
-
deletedThreadMessages,
|
|
106
|
-
transitions,
|
|
107
|
-
threadUpdates,
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const buildConnection = (
|
|
112
|
-
present: Set<number>,
|
|
113
|
-
fetchDrops: Set<number> = new Set(),
|
|
114
|
-
): IImapConnection =>
|
|
115
|
-
({
|
|
116
|
-
openBox: async () => ({}) as never,
|
|
117
|
-
fetchMessages: async (uids: number[]) =>
|
|
118
|
-
uids
|
|
119
|
-
.filter((uid) => present.has(uid) && !fetchDrops.has(uid))
|
|
120
|
-
.map((uid) => ({ uid }) as unknown as never),
|
|
121
|
-
search: async (criteria: unknown[]) => {
|
|
122
|
-
const [, value] = (criteria as Array<[string, string]>)[0];
|
|
123
|
-
const uid = Number(value);
|
|
124
|
-
return present.has(uid) ? [uid] : [];
|
|
125
|
-
},
|
|
126
|
-
}) as unknown as IImapConnection;
|
|
127
|
-
|
|
128
|
-
describe("resolveExhaustedPlacementMoveFailure — the two terminal outcomes (mirrors #1270 for placement moves)", () => {
|
|
129
|
-
it("RECONCILED (expected): the message is gone from its pending-move source — marker dropped, stale row reconciled, no alarm", async () => {
|
|
130
|
-
const rec = buildRecorder([
|
|
131
|
-
{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-gone" },
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
const result = await resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
135
|
-
accountId: "acc-1",
|
|
136
|
-
accountConfigId: "cfg-1",
|
|
137
|
-
messageId: "msg-gone",
|
|
138
|
-
uid: 101,
|
|
139
|
-
sourceMailboxId: "mbx-junk",
|
|
140
|
-
sourceMailboxPath: "Junk",
|
|
141
|
-
getConnection: async () => buildConnection(new Set()),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
assert.equal(result.outcome, "reconciled");
|
|
145
|
-
assert.deepEqual(rec.markerDeletes, ["msg-gone"]);
|
|
146
|
-
assert.deepEqual(rec.deletedMessages, ["msg-gone"]);
|
|
147
|
-
assert.deepEqual(rec.deletedThreadMessages, [
|
|
148
|
-
{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-gone" },
|
|
149
|
-
]);
|
|
150
|
-
assert.equal(
|
|
151
|
-
rec.errors.length,
|
|
152
|
-
0,
|
|
153
|
-
"no alarm for the expected/routine outcome",
|
|
154
|
-
);
|
|
155
|
-
assert.ok(
|
|
156
|
-
rec.infos.some(
|
|
157
|
-
(e) => e.obj.metric === "placement_move_stale_row_reconciled",
|
|
158
|
-
),
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("BROKEN: the message is still at the source — the row goes back to the source pair rather than being left `moving` forever", async () => {
|
|
163
|
-
const rec = buildRecorder([
|
|
164
|
-
{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-broken" },
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
const result = await resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
168
|
-
accountId: "acc-1",
|
|
169
|
-
accountConfigId: "cfg-1",
|
|
170
|
-
messageId: "msg-broken",
|
|
171
|
-
uid: 202,
|
|
172
|
-
sourceMailboxId: "mbx-inbox",
|
|
173
|
-
sourceMailboxPath: "INBOX",
|
|
174
|
-
getConnection: async () => buildConnection(new Set([202])),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
assert.equal(result.outcome, "broken");
|
|
178
|
-
assert.deepEqual(rec.transitions, [
|
|
179
|
-
{
|
|
180
|
-
messageId: "msg-broken",
|
|
181
|
-
expected: {
|
|
182
|
-
status: [MessageStatus.moving, MessageStatus.deleting],
|
|
183
|
-
},
|
|
184
|
-
next: {
|
|
185
|
-
mailboxId: "mbx-inbox",
|
|
186
|
-
uid: 202,
|
|
187
|
-
status: MessageStatus.active,
|
|
188
|
-
syncStatus: MessageSyncStatus.synced,
|
|
189
|
-
// Remit's own classification filing, not something the user asked
|
|
190
|
-
// for, so no per-message give-up marker — the alert is the surface.
|
|
191
|
-
abandonedMutation: MessageMutation.none,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
]);
|
|
195
|
-
assert.deepEqual(rec.threadUpdates, ["tm-msg-broken"]);
|
|
196
|
-
// The marker predicted a move that has now been given up on; leaving it
|
|
197
|
-
// would keep a folder-count prediction nothing will ever settle.
|
|
198
|
-
assert.deepEqual(rec.markerDeletes, ["msg-broken"]);
|
|
199
|
-
assert.deepEqual(rec.deletedMessages, []);
|
|
200
|
-
assert.ok(
|
|
201
|
-
rec.errors.some((e) => e.obj.alert === "placement_move_failed"),
|
|
202
|
-
"the operator signal is the alert, not a stuck row",
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// The marker is the operator's evidence that this move still owed IMAP a
|
|
207
|
-
// push. Dropping it first meant a restore that threw left the row stuck
|
|
208
|
-
// `moving` with nothing left to say why.
|
|
209
|
-
it("BROKEN: a restore that throws leaves the marker standing", async () => {
|
|
210
|
-
const rec = buildRecorder([
|
|
211
|
-
{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-broken" },
|
|
212
|
-
]);
|
|
213
|
-
rec.deps.messageService.transitionPlacement = async () => {
|
|
214
|
-
throw new Error("ProvisionedThroughputExceeded");
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
await assert.rejects(
|
|
218
|
-
() =>
|
|
219
|
-
resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
220
|
-
accountId: "acc-1",
|
|
221
|
-
accountConfigId: "cfg-1",
|
|
222
|
-
messageId: "msg-broken",
|
|
223
|
-
uid: 202,
|
|
224
|
-
sourceMailboxId: "mbx-inbox",
|
|
225
|
-
sourceMailboxPath: "INBOX",
|
|
226
|
-
getConnection: async () => buildConnection(new Set([202])),
|
|
227
|
-
}),
|
|
228
|
-
/ProvisionedThroughputExceeded/,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
assert.deepEqual(
|
|
232
|
-
rec.markerDeletes,
|
|
233
|
-
[],
|
|
234
|
-
"the record of the owed push outlives a failed restore",
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("BROKEN with another lane already settled: the restore loses its predicate and writes nothing", async () => {
|
|
239
|
-
const rec = buildRecorder(
|
|
240
|
-
[{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-raced" }],
|
|
241
|
-
false,
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
const result = await resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
245
|
-
accountId: "acc-1",
|
|
246
|
-
accountConfigId: "cfg-1",
|
|
247
|
-
messageId: "msg-raced",
|
|
248
|
-
uid: 404,
|
|
249
|
-
sourceMailboxId: "mbx-inbox",
|
|
250
|
-
sourceMailboxPath: "INBOX",
|
|
251
|
-
getConnection: async () => buildConnection(new Set([404])),
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
assert.equal(result.outcome, "broken");
|
|
255
|
-
assert.equal(rec.transitions.length, 1);
|
|
256
|
-
assert.deepEqual(
|
|
257
|
-
rec.threadUpdates,
|
|
258
|
-
[],
|
|
259
|
-
"a lost predicate leaves the listing rows to whoever won",
|
|
260
|
-
);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("a dropped FETCH row is not absence: the message is still at the source — local rows survive", async () => {
|
|
264
|
-
const rec = buildRecorder([
|
|
265
|
-
{ accountConfigId: "cfg-1", threadMessageId: "tm-msg-live" },
|
|
266
|
-
]);
|
|
267
|
-
|
|
268
|
-
const result = await resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
269
|
-
accountId: "acc-1",
|
|
270
|
-
accountConfigId: "cfg-1",
|
|
271
|
-
messageId: "msg-live",
|
|
272
|
-
uid: 303,
|
|
273
|
-
sourceMailboxId: "mbx-inbox",
|
|
274
|
-
sourceMailboxPath: "INBOX",
|
|
275
|
-
getConnection: async () =>
|
|
276
|
-
buildConnection(new Set([303]), new Set([303])),
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
assert.equal(result.outcome, "broken");
|
|
280
|
-
assert.deepEqual(rec.deletedMessages, []);
|
|
281
|
-
assert.deepEqual(rec.deletedThreadMessages, []);
|
|
282
|
-
assert.ok(rec.errors.some((e) => e.obj.alert === "placement_move_failed"));
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("never throws — both outcomes are terminal, the caller always acks", async () => {
|
|
286
|
-
const rec = buildRecorder([]);
|
|
287
|
-
|
|
288
|
-
await assert.doesNotReject(() =>
|
|
289
|
-
resolveExhaustedPlacementMoveFailure(rec.deps, {
|
|
290
|
-
accountId: "acc-1",
|
|
291
|
-
accountConfigId: "cfg-1",
|
|
292
|
-
messageId: "msg-x",
|
|
293
|
-
uid: 1,
|
|
294
|
-
sourceMailboxId: "mbx-inbox",
|
|
295
|
-
sourceMailboxPath: "INBOX",
|
|
296
|
-
getConnection: async () => buildConnection(new Set([1])),
|
|
297
|
-
}),
|
|
298
|
-
);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { MessageMutation, MessageSyncStatus } from "@remit/domain-enums";
|
|
2
|
-
import { isMessageGoneFromOpenMailbox } from "./message-presence.js";
|
|
3
|
-
import type { PlacementMoveLogger } from "./placement-move.js";
|
|
4
|
-
import {
|
|
5
|
-
type RestoreSourcePlacementDeps,
|
|
6
|
-
restoreSourcePlacement,
|
|
7
|
-
} from "./restore-source-placement.js";
|
|
8
|
-
import {
|
|
9
|
-
reconcileStaleMessage,
|
|
10
|
-
type StaleMessageReconcileDeps,
|
|
11
|
-
} from "./stale-message-reconcile.js";
|
|
12
|
-
import type { IImapConnection } from "./types.js";
|
|
13
|
-
|
|
14
|
-
export interface ResolveExhaustedPlacementMoveDeps {
|
|
15
|
-
messageService: StaleMessageReconcileDeps["messageService"] &
|
|
16
|
-
RestoreSourcePlacementDeps["messageService"];
|
|
17
|
-
threadMessageService: StaleMessageReconcileDeps["threadMessageService"] &
|
|
18
|
-
RestoreSourcePlacementDeps["threadMessageService"];
|
|
19
|
-
markerService: { delete(messageId: string): Promise<void> };
|
|
20
|
-
log: PlacementMoveLogger;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ResolveExhaustedPlacementMoveInput {
|
|
24
|
-
accountId: string;
|
|
25
|
-
accountConfigId: string;
|
|
26
|
-
messageId: string;
|
|
27
|
-
uid: number;
|
|
28
|
-
sourceMailboxId: string;
|
|
29
|
-
sourceMailboxPath: string;
|
|
30
|
-
getConnection: () => Promise<IImapConnection>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type PlacementMoveTerminalOutcome = "reconciled" | "broken";
|
|
34
|
-
|
|
35
|
-
export interface ResolveExhaustedPlacementMoveResult {
|
|
36
|
-
outcome: PlacementMoveTerminalOutcome;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Resolve a PLACEMENT_MOVE_PUSH failure that has exhausted the placement-move
|
|
41
|
-
* queue's redelivery budget into exactly one of two terminal outcomes,
|
|
42
|
-
* mirroring `resolveExhaustedBodySyncFailures` (#1270) for the same failure
|
|
43
|
-
* taxonomy (epic #1281 invariant 3) — no third, softer outcome.
|
|
44
|
-
*
|
|
45
|
-
* 1. RECONCILED (expected) — the message no longer exists at its pending-move
|
|
46
|
-
* source on IMAP, confirmed by {@link isMessageGoneFromOpenMailbox} rather
|
|
47
|
-
* than by a FETCH coming back empty. Per invariant 2, an external delete
|
|
48
|
-
* supersedes the marker entirely: the marker is dropped and the stale
|
|
49
|
-
* Message/ThreadMessage rows are deleted via
|
|
50
|
-
* {@link reconcileStaleMessage}. This is also the
|
|
51
|
-
* outcome for the (rarer, functionally indistinguishable from here) case
|
|
52
|
-
* where a foreign client moved the message elsewhere — either way, our
|
|
53
|
-
* prediction no longer holds, and the marker cannot be honoured. Metric
|
|
54
|
-
* only, no alarm — routine.
|
|
55
|
-
* 2. BROKEN — the message still exists at the source, but the move keeps
|
|
56
|
-
* failing. This indicates broken code or a broken account (issue #1271),
|
|
57
|
-
* not a transient blip. The row is put back on the source pair the server
|
|
58
|
-
* has just confirmed, exactly as the move and delete resolvers do, and the
|
|
59
|
-
* marker goes with it: an auto-file whose push gave up must not leave
|
|
60
|
-
* `moving` plus the destination folder plus the source's uid standing
|
|
61
|
-
* forever, because that pair refuses every later delete and move with a
|
|
62
|
-
* 409, blocks the sighting repair, and nothing else clears it. The failure
|
|
63
|
-
* is an operator's to read, so it lives in the `alert`-shaped log entry and
|
|
64
|
-
* the alarm on it — not in a row the user's own mail is stuck behind.
|
|
65
|
-
* Never re-thrown (terminal — the caller acks either way, since retrying a
|
|
66
|
-
* stale or permanently-broken move can never succeed).
|
|
67
|
-
*
|
|
68
|
-
* An operator reading `placement_move_failed` should know one case where the
|
|
69
|
-
* message is not actually at the source: a message another client expunged
|
|
70
|
-
* mid-session can answer an empty FETCH while the server still lists its UID
|
|
71
|
-
* in SEARCH, until it is allowed to send the untagged EXPUNGE. That message
|
|
72
|
-
* lands in BROKEN, and BROKEN is terminal — the alert stands until someone
|
|
73
|
-
* clears it, and the row goes back to a source the message may already have
|
|
74
|
-
* left. The reverse mistake deletes live mail, so the cost is paid
|
|
75
|
-
* deliberately: a stale alert over a row the next sync re-points is
|
|
76
|
-
* recoverable, a deleted message is not.
|
|
77
|
-
*/
|
|
78
|
-
export const resolveExhaustedPlacementMoveFailure = async (
|
|
79
|
-
deps: ResolveExhaustedPlacementMoveDeps,
|
|
80
|
-
input: ResolveExhaustedPlacementMoveInput,
|
|
81
|
-
): Promise<ResolveExhaustedPlacementMoveResult> => {
|
|
82
|
-
const {
|
|
83
|
-
accountId,
|
|
84
|
-
accountConfigId,
|
|
85
|
-
messageId,
|
|
86
|
-
uid,
|
|
87
|
-
sourceMailboxId,
|
|
88
|
-
sourceMailboxPath,
|
|
89
|
-
getConnection,
|
|
90
|
-
} = input;
|
|
91
|
-
|
|
92
|
-
const connection = await getConnection();
|
|
93
|
-
await connection.openBox(sourceMailboxPath);
|
|
94
|
-
|
|
95
|
-
if (await isMessageGoneFromOpenMailbox(connection, uid)) {
|
|
96
|
-
await deps.markerService.delete(messageId);
|
|
97
|
-
const { threadMessagesDeleted } = await reconcileStaleMessage(
|
|
98
|
-
deps,
|
|
99
|
-
accountConfigId,
|
|
100
|
-
messageId,
|
|
101
|
-
);
|
|
102
|
-
deps.log.info(
|
|
103
|
-
{
|
|
104
|
-
metric: "placement_move_stale_row_reconciled",
|
|
105
|
-
accountId,
|
|
106
|
-
accountConfigId,
|
|
107
|
-
messageId,
|
|
108
|
-
uid,
|
|
109
|
-
sourceMailboxPath,
|
|
110
|
-
threadMessagesDeleted,
|
|
111
|
-
},
|
|
112
|
-
"Message no longer at its pending-move source after retry exhaustion (external delete or move); marker dropped, stale row reconciled",
|
|
113
|
-
);
|
|
114
|
-
return { outcome: "reconciled" };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// The row first, the marker second. The marker is the operator's evidence
|
|
118
|
-
// that this move still owed IMAP a push; dropping it before the restore
|
|
119
|
-
// means a restore that throws leaves the row stuck `moving` with nothing
|
|
120
|
-
// left to say why.
|
|
121
|
-
await restoreSourcePlacement(deps, {
|
|
122
|
-
accountConfigId,
|
|
123
|
-
messageId,
|
|
124
|
-
sourceMailboxId,
|
|
125
|
-
uid,
|
|
126
|
-
// No give-up marker: this move is Remit's own classification filing, not
|
|
127
|
-
// something the user asked for, so a per-message "your move failed" would
|
|
128
|
-
// report a failure against an intent nobody formed. The alert below is the
|
|
129
|
-
// whole surface, and it is an operator's.
|
|
130
|
-
syncStatus: MessageSyncStatus.synced,
|
|
131
|
-
abandonedMutation: MessageMutation.none,
|
|
132
|
-
});
|
|
133
|
-
await deps.markerService.delete(messageId);
|
|
134
|
-
|
|
135
|
-
deps.log.error(
|
|
136
|
-
{
|
|
137
|
-
alert: "placement_move_failed",
|
|
138
|
-
accountId,
|
|
139
|
-
accountConfigId,
|
|
140
|
-
messageId,
|
|
141
|
-
uid,
|
|
142
|
-
sourceMailboxId,
|
|
143
|
-
sourceMailboxPath,
|
|
144
|
-
},
|
|
145
|
-
"Placement move could not be pushed to IMAP after retry exhaustion; message still exists at its source — row restored to the source pair, marker dropped, alert left for operator investigation",
|
|
146
|
-
);
|
|
147
|
-
return { outcome: "broken" };
|
|
148
|
-
};
|