@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,229 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type {
|
|
4
|
-
FilterItem,
|
|
5
|
-
IFilterAnchorRepository,
|
|
6
|
-
IFilterRepository,
|
|
7
|
-
IMessageLabelRepository,
|
|
8
|
-
} from "@remit/data-ports";
|
|
9
|
-
import { FilterMatchOperator, FilterState } from "@remit/domain-enums";
|
|
10
|
-
import { _resetEmbeddingCapabilityForTest } from "@remit/search-service/capability";
|
|
11
|
-
import type { PlacementMoveService } from "../placement-move.js";
|
|
12
|
-
import { buildFilterConfig, type FilterConfigDeps } from "./filter-config.js";
|
|
13
|
-
import { type FilterMessage, NO_ACTION } from "./match.js";
|
|
14
|
-
import { _resetMessageEmbedderForTest } from "./message-embedder.js";
|
|
15
|
-
import { FilterPipeline, type MessageEmbedder } from "./pipeline.js";
|
|
16
|
-
|
|
17
|
-
const anchorOnlyFilter = (destinationMailboxId: string): FilterItem =>
|
|
18
|
-
({
|
|
19
|
-
filterId: "flt-semantic",
|
|
20
|
-
accountConfigId: "cfg-1",
|
|
21
|
-
name: "receipts",
|
|
22
|
-
scope: "Standing",
|
|
23
|
-
state: FilterState.Active,
|
|
24
|
-
hasAnchor: true,
|
|
25
|
-
ruleChangedAt: 1,
|
|
26
|
-
matchOperator: FilterMatchOperator.Or,
|
|
27
|
-
literalClauses: [],
|
|
28
|
-
actionLabelId: NO_ACTION,
|
|
29
|
-
actionMailboxId: destinationMailboxId,
|
|
30
|
-
createdAt: 1,
|
|
31
|
-
updatedAt: 1,
|
|
32
|
-
}) as unknown as FilterItem;
|
|
33
|
-
|
|
34
|
-
const CURRENT_MODEL = "test-model@3";
|
|
35
|
-
|
|
36
|
-
const repos = (filter: FilterItem): FilterConfigDeps => ({
|
|
37
|
-
filterService: {
|
|
38
|
-
listByAccountAndState: async () => [filter],
|
|
39
|
-
refreshExpiry: async (f: FilterItem) => f,
|
|
40
|
-
} as unknown as IFilterRepository,
|
|
41
|
-
filterAnchorService: {
|
|
42
|
-
get: async () => ({
|
|
43
|
-
anchorEmbedding: [1, 0, 0],
|
|
44
|
-
anchorEmbeddingId: CURRENT_MODEL,
|
|
45
|
-
}),
|
|
46
|
-
} as unknown as IFilterAnchorRepository,
|
|
47
|
-
messageLabelService: {} as unknown as IMessageLabelRepository,
|
|
48
|
-
placementMoveService: {} as unknown as PlacementMoveService,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const message: FilterMessage = {
|
|
52
|
-
from: "billing@stripe.com",
|
|
53
|
-
fromName: "Stripe",
|
|
54
|
-
subject: "Your receipt",
|
|
55
|
-
text: "Thanks for your payment",
|
|
56
|
-
listId: "",
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
describe("buildFilterConfig", () => {
|
|
60
|
-
it("returns undefined without a placement mover — no move path, filters stay off", () => {
|
|
61
|
-
const deps = repos(anchorOnlyFilter("mbx-archive"));
|
|
62
|
-
deps.placementMoveService = undefined;
|
|
63
|
-
|
|
64
|
-
assert.equal(buildFilterConfig(deps), undefined);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("wires an embedder into the config", () => {
|
|
68
|
-
const config = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
69
|
-
|
|
70
|
-
assert.ok(config);
|
|
71
|
-
assert.ok(config.embedder);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("lights up a semantic anchor-only filter on the body-sync pass", async () => {
|
|
75
|
-
let embedCalls = 0;
|
|
76
|
-
const embedder: MessageEmbedder = {
|
|
77
|
-
embed: async () => {
|
|
78
|
-
embedCalls += 1;
|
|
79
|
-
return [1, 0, 0];
|
|
80
|
-
},
|
|
81
|
-
embeddingId: CURRENT_MODEL,
|
|
82
|
-
};
|
|
83
|
-
const config = buildFilterConfig(
|
|
84
|
-
repos(anchorOnlyFilter("mbx-archive")),
|
|
85
|
-
embedder,
|
|
86
|
-
);
|
|
87
|
-
assert.ok(config);
|
|
88
|
-
|
|
89
|
-
const decision = await new FilterPipeline(config, {
|
|
90
|
-
info: () => {},
|
|
91
|
-
}).evaluate("cfg-1", "m-1", message);
|
|
92
|
-
|
|
93
|
-
assert.deepEqual(decision.move, {
|
|
94
|
-
destinationMailboxId: "mbx-archive",
|
|
95
|
-
filterId: "flt-semantic",
|
|
96
|
-
});
|
|
97
|
-
assert.equal(embedCalls, 1);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("does not match when the message embedding diverges from the anchor", async () => {
|
|
101
|
-
const embedder: MessageEmbedder = {
|
|
102
|
-
embed: async () => [0, 1, 0],
|
|
103
|
-
embeddingId: CURRENT_MODEL,
|
|
104
|
-
};
|
|
105
|
-
const config = buildFilterConfig(
|
|
106
|
-
repos(anchorOnlyFilter("mbx-archive")),
|
|
107
|
-
embedder,
|
|
108
|
-
);
|
|
109
|
-
assert.ok(config);
|
|
110
|
-
|
|
111
|
-
const decision = await new FilterPipeline(config, {
|
|
112
|
-
info: () => {},
|
|
113
|
-
}).evaluate("cfg-1", "m-2", message);
|
|
114
|
-
|
|
115
|
-
assert.equal(decision.move, undefined);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// An instance with semantic search off (issue #1068). `off` selects an embedder
|
|
120
|
-
// that throws by design, and the throw lands in the pipeline's per-filter catch:
|
|
121
|
-
// one error-level `filter_anchor_match_failed` for every semantic filter on
|
|
122
|
-
// every message it syncs. The pipeline already has a designed skip for a
|
|
123
|
-
// missing embedder, so the config passes none.
|
|
124
|
-
describe("buildFilterConfig with semantic search off", () => {
|
|
125
|
-
const withProviderOff = async (body: () => Promise<void>): Promise<void> => {
|
|
126
|
-
const previous = process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
127
|
-
process.env.SEARCH_EMBEDDING_PROVIDER = "off";
|
|
128
|
-
_resetMessageEmbedderForTest();
|
|
129
|
-
try {
|
|
130
|
-
await body();
|
|
131
|
-
} finally {
|
|
132
|
-
if (previous === undefined) {
|
|
133
|
-
delete process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
134
|
-
} else {
|
|
135
|
-
process.env.SEARCH_EMBEDDING_PROVIDER = previous;
|
|
136
|
-
}
|
|
137
|
-
_resetMessageEmbedderForTest();
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
it("wires no embedder", async () => {
|
|
142
|
-
await withProviderOff(async () => {
|
|
143
|
-
const config = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
144
|
-
|
|
145
|
-
assert.ok(config);
|
|
146
|
-
assert.equal(config.embedder, undefined);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("skips the semantic filter without logging an error", async () => {
|
|
151
|
-
await withProviderOff(async () => {
|
|
152
|
-
const config = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
153
|
-
assert.ok(config);
|
|
154
|
-
|
|
155
|
-
const errors: unknown[] = [];
|
|
156
|
-
const decision = await new FilterPipeline(config, {
|
|
157
|
-
info: () => {},
|
|
158
|
-
debug: () => {},
|
|
159
|
-
warn: () => {},
|
|
160
|
-
error: (fields) => errors.push(fields.alert),
|
|
161
|
-
}).evaluate("cfg-1", "m-off", message);
|
|
162
|
-
|
|
163
|
-
assert.deepEqual(decision.labels, []);
|
|
164
|
-
assert.equal(decision.move, undefined);
|
|
165
|
-
assert.deepEqual(errors, []);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// The self-host `local` profile asks for the Transformers.js embedder, but the
|
|
171
|
-
// backend and imap-worker images ship without `@huggingface/transformers`
|
|
172
|
-
// (npm-scripts/docker-bundle.mjs). The first embed throws ERR_MODULE_NOT_FOUND,
|
|
173
|
-
// which is the same capability absence `off` states and must take the same
|
|
174
|
-
// designed skip — not an error-level `filter_anchor_match_failed` per semantic
|
|
175
|
-
// filter per message on a user-facing GET.
|
|
176
|
-
describe("buildFilterConfig when this deployment carries no embedder", () => {
|
|
177
|
-
const moduleNotFound = (): Error =>
|
|
178
|
-
Object.assign(
|
|
179
|
-
new Error("Cannot find package '@huggingface/transformers'"),
|
|
180
|
-
{ code: "ERR_MODULE_NOT_FOUND" },
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
it("skips the semantic filter, logs no error, and stops wiring an embedder", async () => {
|
|
184
|
-
const previous = process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
185
|
-
process.env.SEARCH_EMBEDDING_PROVIDER = "local";
|
|
186
|
-
_resetMessageEmbedderForTest();
|
|
187
|
-
_resetEmbeddingCapabilityForTest();
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const unavailable: MessageEmbedder = {
|
|
191
|
-
embed: async () => {
|
|
192
|
-
throw moduleNotFound();
|
|
193
|
-
},
|
|
194
|
-
embeddingId: CURRENT_MODEL,
|
|
195
|
-
};
|
|
196
|
-
const config = buildFilterConfig(
|
|
197
|
-
repos(anchorOnlyFilter("mbx-archive")),
|
|
198
|
-
unavailable,
|
|
199
|
-
);
|
|
200
|
-
assert.ok(config);
|
|
201
|
-
|
|
202
|
-
const errors: unknown[] = [];
|
|
203
|
-
const decision = await new FilterPipeline(config, {
|
|
204
|
-
info: () => {},
|
|
205
|
-
debug: () => {},
|
|
206
|
-
warn: () => {},
|
|
207
|
-
error: (fields) => errors.push(fields.alert),
|
|
208
|
-
}).evaluate("cfg-1", "m-local", message);
|
|
209
|
-
|
|
210
|
-
assert.deepEqual(decision.labels, []);
|
|
211
|
-
assert.equal(decision.move, undefined);
|
|
212
|
-
assert.deepEqual(errors, []);
|
|
213
|
-
|
|
214
|
-
// Probed once, not per filter or per message: every later config on this
|
|
215
|
-
// process wires no embedder at all, so nothing else attempts the embed.
|
|
216
|
-
const next = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
217
|
-
assert.ok(next);
|
|
218
|
-
assert.equal(next.embedder, undefined);
|
|
219
|
-
} finally {
|
|
220
|
-
if (previous === undefined) {
|
|
221
|
-
delete process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
222
|
-
} else {
|
|
223
|
-
process.env.SEARCH_EMBEDDING_PROVIDER = previous;
|
|
224
|
-
}
|
|
225
|
-
_resetMessageEmbedderForTest();
|
|
226
|
-
_resetEmbeddingCapabilityForTest();
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IFilterAnchorRepository,
|
|
3
|
-
IFilterRepository,
|
|
4
|
-
IMessageLabelRepository,
|
|
5
|
-
} from "@remit/data-ports";
|
|
6
|
-
import { isEmbeddingCapabilityUnavailable } from "@remit/search-service/capability";
|
|
7
|
-
import {
|
|
8
|
-
EMBEDDING_PROVIDER_OFF,
|
|
9
|
-
readEmbeddingProviderFromEnv,
|
|
10
|
-
} from "@remit/search-service/from-env";
|
|
11
|
-
import type { PlacementMoveService } from "../placement-move.js";
|
|
12
|
-
import { getMessageEmbedder } from "./message-embedder.js";
|
|
13
|
-
import type { FilterConfig, MessageEmbedder } from "./pipeline.js";
|
|
14
|
-
|
|
15
|
-
export interface FilterConfigDeps {
|
|
16
|
-
filterService: IFilterRepository;
|
|
17
|
-
filterAnchorService: IFilterAnchorRepository;
|
|
18
|
-
messageLabelService: IMessageLabelRepository;
|
|
19
|
-
placementMoveService?: PlacementMoveService;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The env-selected embedder, or none on an instance that cannot embed.
|
|
24
|
-
*
|
|
25
|
-
* `off` embeds nothing by design, and its embedder says so by throwing
|
|
26
|
-
* (EmbeddingDisabledError). The pipeline has a designed skip for a *missing*
|
|
27
|
-
* embedder — semantic filters are passed over and logged at debug — but a
|
|
28
|
-
* present embedder that throws lands in the per-filter catch instead, which is
|
|
29
|
-
* an error-level `filter_anchor_match_failed` for every semantic filter on
|
|
30
|
-
* every synced message. Same outcome, a log nobody can read.
|
|
31
|
-
*
|
|
32
|
-
* `local` on the self-host profiles reaches the same place by a different road:
|
|
33
|
-
* the backend and imap-worker images ship without `@huggingface/transformers`,
|
|
34
|
-
* so the first embed throws ERR_MODULE_NOT_FOUND. The pipeline classifies that
|
|
35
|
-
* one throw through `@remit/search-service/capability` and remembers it, and
|
|
36
|
-
* from then on the config here wires no embedder either — one probe per process,
|
|
37
|
-
* not one per filter per message.
|
|
38
|
-
*/
|
|
39
|
-
const embedderFromEnv = (): MessageEmbedder | undefined =>
|
|
40
|
-
readEmbeddingProviderFromEnv() === EMBEDDING_PROVIDER_OFF ||
|
|
41
|
-
isEmbeddingCapabilityUnavailable()
|
|
42
|
-
? undefined
|
|
43
|
-
: getMessageEmbedder();
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Assemble the filter config a body-materializing pass runs (RFC 034), shared by
|
|
47
|
-
* the imap-worker's sync path and the backend's read-path backfill so both embed
|
|
48
|
-
* under the same model that produced the anchors — a semantic (anchor-only)
|
|
49
|
-
* filter is evaluated on either path instead of silently skipped.
|
|
50
|
-
*
|
|
51
|
-
* Absent the placement mover there is no move path, so filters stay off — a
|
|
52
|
-
* matched filter's actions reuse the same enqueue plumbing the placement mover
|
|
53
|
-
* owns.
|
|
54
|
-
*/
|
|
55
|
-
export const buildFilterConfig = (
|
|
56
|
-
deps: FilterConfigDeps,
|
|
57
|
-
embedder?: MessageEmbedder,
|
|
58
|
-
): FilterConfig | undefined => {
|
|
59
|
-
const { placementMoveService } = deps;
|
|
60
|
-
if (!placementMoveService) return undefined;
|
|
61
|
-
const resolved = embedder ?? embedderFromEnv();
|
|
62
|
-
return {
|
|
63
|
-
filterService: deps.filterService,
|
|
64
|
-
filterAnchorService: deps.filterAnchorService,
|
|
65
|
-
messageLabelService: deps.messageLabelService,
|
|
66
|
-
placementMoveService,
|
|
67
|
-
...(resolved ? { embedder: resolved } : {}),
|
|
68
|
-
};
|
|
69
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import { simpleParser } from "mailparser";
|
|
4
|
-
import { extractListId, normalizeListId } from "./list-id.js";
|
|
5
|
-
|
|
6
|
-
const parse = async (lines: string[]) =>
|
|
7
|
-
simpleParser(Buffer.from(lines.join("\r\n")));
|
|
8
|
-
|
|
9
|
-
describe("normalizeListId", () => {
|
|
10
|
-
it("extracts the bracketed identifier and folds case", () => {
|
|
11
|
-
assert.equal(
|
|
12
|
-
normalizeListId("Weekly News <Weekly.News.Example.COM>"),
|
|
13
|
-
"weekly.news.example.com",
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("keeps a bare value and folds case", () => {
|
|
18
|
-
assert.equal(
|
|
19
|
-
normalizeListId(" Weekly.News.Example.COM "),
|
|
20
|
-
"weekly.news.example.com",
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("normalizes an empty value to the empty string", () => {
|
|
25
|
-
assert.equal(normalizeListId(" "), "");
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("extractListId", () => {
|
|
30
|
-
it("reads and normalizes a List-Id header", async () => {
|
|
31
|
-
const parsed = await parse([
|
|
32
|
-
"From: list@example.com",
|
|
33
|
-
"Subject: hi",
|
|
34
|
-
"List-Id: Weekly News <weekly.news.example.com>",
|
|
35
|
-
"",
|
|
36
|
-
"body",
|
|
37
|
-
]);
|
|
38
|
-
assert.equal(extractListId(parsed), "weekly.news.example.com");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("returns the empty string when there is no List-Id header", async () => {
|
|
42
|
-
const parsed = await parse([
|
|
43
|
-
"From: alice@example.com",
|
|
44
|
-
"Subject: hi",
|
|
45
|
-
"",
|
|
46
|
-
"body",
|
|
47
|
-
]);
|
|
48
|
-
assert.equal(extractListId(parsed), "");
|
|
49
|
-
});
|
|
50
|
-
});
|