@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.
Files changed (298) hide show
  1. package/dist/account-credentials.d.ts +83 -0
  2. package/dist/account-credentials.js +104 -0
  3. package/dist/attribute-mapper.d.ts +36 -0
  4. package/dist/attribute-mapper.js +119 -0
  5. package/dist/body-parse.d.ts +28 -0
  6. package/{src/body-parse.ts → dist/body-parse.js} +18 -26
  7. package/dist/body-part-mapper.d.ts +92 -0
  8. package/dist/body-part-mapper.js +301 -0
  9. package/dist/body-sync-queue.d.ts +59 -0
  10. package/dist/body-sync-queue.js +56 -0
  11. package/dist/body-sync-terminal.d.ts +58 -0
  12. package/dist/body-sync-terminal.js +95 -0
  13. package/dist/body-sync.d.ts +351 -0
  14. package/dist/body-sync.js +1204 -0
  15. package/dist/calendar-parts.d.ts +23 -0
  16. package/dist/calendar-parts.js +18 -0
  17. package/dist/classification-backfill.d.ts +91 -0
  18. package/dist/classification-backfill.js +180 -0
  19. package/dist/classify-message.d.ts +31 -0
  20. package/dist/classify-message.js +89 -0
  21. package/dist/connection-factory.d.ts +54 -0
  22. package/dist/connection-factory.js +54 -0
  23. package/dist/connection-test.d.ts +41 -0
  24. package/dist/connection-test.js +152 -0
  25. package/dist/external-move.d.ts +94 -0
  26. package/dist/external-move.js +173 -0
  27. package/dist/filter-operations.d.ts +42 -0
  28. package/dist/filter-operations.js +211 -0
  29. package/dist/filters/filter-config.d.ts +14 -0
  30. package/dist/filters/filter-config.js +10 -0
  31. package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
  32. package/dist/filters/list-id.js +26 -0
  33. package/dist/filters/match.d.ts +69 -0
  34. package/dist/filters/match.js +165 -0
  35. package/dist/filters/pipeline.d.ts +64 -0
  36. package/dist/filters/pipeline.js +100 -0
  37. package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +19 -77
  38. package/dist/flag-push-terminal.js +63 -0
  39. package/dist/flag-push.d.ts +69 -0
  40. package/dist/flag-push.js +111 -0
  41. package/dist/flag-queue.d.ts +175 -0
  42. package/dist/flag-queue.js +291 -0
  43. package/dist/folder-operations.d.ts +39 -0
  44. package/dist/folder-operations.js +112 -0
  45. package/dist/folder-placement.d.ts +13 -0
  46. package/dist/folder-placement.js +23 -0
  47. package/dist/heuristics/authenticationResults.d.ts +10 -0
  48. package/dist/heuristics/authenticationResults.js +187 -0
  49. package/dist/heuristics/classifyByHeaders.d.ts +82 -0
  50. package/dist/heuristics/classifyByHeaders.js +365 -0
  51. package/dist/heuristics/classifyPlacement.d.ts +67 -0
  52. package/dist/heuristics/classifyPlacement.js +153 -0
  53. package/dist/heuristics/machineSenders.d.ts +21 -0
  54. package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
  55. package/dist/heuristics/senderMismatch.d.ts +81 -0
  56. package/dist/heuristics/senderMismatch.js +192 -0
  57. package/dist/heuristics/socialDomains.d.ts +14 -0
  58. package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
  59. package/dist/heuristics/transactionalDomains.d.ts +18 -0
  60. package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
  61. package/dist/imapflow-connection.d.ts +377 -0
  62. package/dist/imapflow-connection.js +1147 -0
  63. package/dist/index.d.ts +63 -0
  64. package/dist/index.js +65 -0
  65. package/dist/list-id-backfill.d.ts +83 -0
  66. package/dist/list-id-backfill.js +132 -0
  67. package/dist/mailbox-cursor-rebuild.d.ts +74 -0
  68. package/dist/mailbox-cursor-rebuild.js +71 -0
  69. package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
  70. package/dist/mailbox-cursor.js +102 -0
  71. package/dist/mailbox-intent.d.ts +41 -0
  72. package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +39 -63
  73. package/dist/mailbox-management.d.ts +240 -0
  74. package/dist/mailbox-management.js +576 -0
  75. package/dist/mailbox-presence.d.ts +52 -0
  76. package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
  77. package/dist/mailbox-queue.d.ts +126 -0
  78. package/dist/mailbox-queue.js +214 -0
  79. package/dist/mailbox-sync.d.ts +93 -0
  80. package/dist/mailbox-sync.js +490 -0
  81. package/dist/mailbox-upstream.d.ts +38 -0
  82. package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
  83. package/dist/message-category.d.ts +51 -0
  84. package/{src/message-category.ts → dist/message-category.js} +28 -71
  85. package/dist/message-move.d.ts +285 -0
  86. package/dist/message-move.js +858 -0
  87. package/dist/message-parser.d.ts +14 -0
  88. package/dist/message-parser.js +17 -0
  89. package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
  90. package/dist/message-presence.js +29 -0
  91. package/dist/message-read.d.ts +51 -0
  92. package/dist/message-read.js +31 -0
  93. package/dist/message-sync.d.ts +345 -0
  94. package/dist/message-sync.js +1358 -0
  95. package/dist/mime-walker.d.ts +102 -0
  96. package/dist/mime-walker.js +198 -0
  97. package/dist/organize-job.d.ts +20 -0
  98. package/dist/organize-job.js +37 -0
  99. package/dist/outbox-attachment-content.d.ts +37 -0
  100. package/dist/outbox-attachment-content.js +50 -0
  101. package/dist/outbox-attachment-filename.d.ts +19 -0
  102. package/dist/outbox-attachment-filename.js +64 -0
  103. package/dist/outbox-attachment.d.ts +148 -0
  104. package/dist/outbox-attachment.js +273 -0
  105. package/dist/outbox-queue.d.ts +73 -0
  106. package/dist/outbox-queue.js +284 -0
  107. package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
  108. package/dist/outbox-status.js +18 -0
  109. package/dist/ownership.d.ts +24 -0
  110. package/dist/ownership.js +83 -0
  111. package/dist/placement-move-terminal.d.ts +67 -0
  112. package/dist/placement-move-terminal.js +88 -0
  113. package/dist/placement-move.d.ts +83 -0
  114. package/dist/placement-move.js +216 -0
  115. package/{src/placement-settled.ts → dist/placement-settled.d.ts} +19 -50
  116. package/dist/placement-settled.js +75 -0
  117. package/dist/quarantine.d.ts +95 -0
  118. package/dist/quarantine.js +131 -0
  119. package/dist/refusal.d.ts +31 -0
  120. package/dist/refusal.js +3 -0
  121. package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -101
  122. package/dist/restore-source-placement.js +79 -0
  123. package/dist/search-text-backfill.d.ts +15 -0
  124. package/dist/search-text-backfill.js +33 -0
  125. package/dist/snippet.d.ts +61 -0
  126. package/dist/snippet.js +129 -0
  127. package/dist/spam-report.d.ts +86 -0
  128. package/dist/spam-report.js +241 -0
  129. package/dist/sync-warning.d.ts +21 -0
  130. package/dist/sync-warning.js +26 -0
  131. package/dist/sync-watermarks.d.ts +138 -0
  132. package/dist/sync-watermarks.js +205 -0
  133. package/dist/test-helpers/fake-imap.d.ts +151 -0
  134. package/dist/test-helpers/fake-imap.js +508 -0
  135. package/dist/test-helpers/folder-roles.d.ts +34 -0
  136. package/dist/test-helpers/folder-roles.js +47 -0
  137. package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
  138. package/dist/test-helpers/mime-tree-generator.js +329 -0
  139. package/dist/test-helpers/test-store.d.ts +69 -0
  140. package/dist/test-helpers/test-store.js +193 -0
  141. package/dist/text/normalizer.d.ts +20 -0
  142. package/dist/text/normalizer.js +102 -0
  143. package/dist/thread-message-rows.d.ts +69 -0
  144. package/dist/thread-message-rows.js +40 -0
  145. package/dist/thread-root.d.ts +7 -0
  146. package/dist/thread-root.js +25 -0
  147. package/dist/types.d.ts +343 -0
  148. package/dist/types.js +19 -0
  149. package/package.json +86 -73
  150. package/src/account-credentials.ts +0 -210
  151. package/src/adversarial-mime.e2e.test.ts +0 -307
  152. package/src/attribute-mapper.test.ts +0 -104
  153. package/src/attribute-mapper.ts +0 -148
  154. package/src/body-parse.test.ts +0 -55
  155. package/src/body-part-mapper.property.test.ts +0 -354
  156. package/src/body-part-mapper.ts +0 -360
  157. package/src/body-sync-auto-read-writeonce.test.ts +0 -219
  158. package/src/body-sync-calendar-suggestion.test.ts +0 -433
  159. package/src/body-sync-category-denormalize.test.ts +0 -352
  160. package/src/body-sync-category-override.test.ts +0 -501
  161. package/src/body-sync-category-writeonce.test.ts +0 -234
  162. package/src/body-sync-filter.test.ts +0 -237
  163. package/src/body-sync-message-gone.test.ts +0 -139
  164. package/src/body-sync-placement-flags.test.ts +0 -347
  165. package/src/body-sync-placement-writeonce.test.ts +0 -337
  166. package/src/body-sync-quarantine.test.ts +0 -276
  167. package/src/body-sync-queue.test.ts +0 -124
  168. package/src/body-sync-queue.ts +0 -131
  169. package/src/body-sync-resync.test.ts +0 -605
  170. package/src/body-sync-terminal.test.ts +0 -310
  171. package/src/body-sync-terminal.ts +0 -176
  172. package/src/body-sync-unclassifiable-once.test.ts +0 -260
  173. package/src/body-sync-unsubscribed-auto-read.test.ts +0 -176
  174. package/src/body-sync.ts +0 -1970
  175. package/src/calendar-parts.ts +0 -41
  176. package/src/classification-backfill.test.ts +0 -615
  177. package/src/classification-backfill.ts +0 -363
  178. package/src/classify-message.test.ts +0 -205
  179. package/src/classify-message.ts +0 -116
  180. package/src/connection-factory.ts +0 -102
  181. package/src/connection-test.ts +0 -208
  182. package/src/credentials.test.ts +0 -181
  183. package/src/external-move.test.ts +0 -369
  184. package/src/external-move.ts +0 -272
  185. package/src/filters/anchor-drift.ts +0 -55
  186. package/src/filters/filter-config.test.ts +0 -229
  187. package/src/filters/filter-config.ts +0 -69
  188. package/src/filters/list-id.test.ts +0 -50
  189. package/src/filters/match.test.ts +0 -429
  190. package/src/filters/match.ts +0 -274
  191. package/src/filters/message-embedder.test.ts +0 -39
  192. package/src/filters/message-embedder.ts +0 -33
  193. package/src/filters/pipeline.test.ts +0 -330
  194. package/src/filters/pipeline.ts +0 -284
  195. package/src/flag-push-terminal.test.ts +0 -209
  196. package/src/flag-push.test.ts +0 -408
  197. package/src/flag-push.ts +0 -198
  198. package/src/flag-queue-stars.test.ts +0 -171
  199. package/src/flag-queue.ts +0 -509
  200. package/src/folder-placement.ts +0 -56
  201. package/src/heuristics/authenticationResults.test.ts +0 -189
  202. package/src/heuristics/authenticationResults.ts +0 -212
  203. package/src/heuristics/classifyByHeaders.test.ts +0 -889
  204. package/src/heuristics/classifyByHeaders.ts +0 -467
  205. package/src/heuristics/classifyPlacement.test.ts +0 -366
  206. package/src/heuristics/classifyPlacement.ts +0 -211
  207. package/src/heuristics/classifyRealMail.test.ts +0 -242
  208. package/src/heuristics/machineSenders.test.ts +0 -101
  209. package/src/heuristics/senderMismatch.test.ts +0 -346
  210. package/src/heuristics/senderMismatch.ts +0 -259
  211. package/src/imapflow-connect-retry.test.ts +0 -134
  212. package/src/imapflow-connection.e2e.test.ts +0 -335
  213. package/src/imapflow-connection.integ.test.ts +0 -316
  214. package/src/imapflow-connection.test.ts +0 -469
  215. package/src/imapflow-connection.ts +0 -1475
  216. package/src/index.ts +0 -350
  217. package/src/list-id-backfill.test.ts +0 -573
  218. package/src/list-id-backfill.ts +0 -274
  219. package/src/mailbox-cursor-rebuild.test.ts +0 -171
  220. package/src/mailbox-cursor-rebuild.ts +0 -146
  221. package/src/mailbox-cursor.test.ts +0 -249
  222. package/src/mailbox-delete-intent.test.ts +0 -246
  223. package/src/mailbox-management.test.ts +0 -356
  224. package/src/mailbox-management.ts +0 -839
  225. package/src/mailbox-presence.test.ts +0 -72
  226. package/src/mailbox-queue.ts +0 -415
  227. package/src/mailbox-rename-intent.test.ts +0 -389
  228. package/src/mailbox-rename-subtree.test.ts +0 -664
  229. package/src/mailbox-sync.test.ts +0 -848
  230. package/src/mailbox-sync.ts +0 -724
  231. package/src/message-delete-junk-reconcile.test.ts +0 -117
  232. package/src/message-delete-no-trash.test.ts +0 -245
  233. package/src/message-delete-unsettled-placement.test.ts +0 -396
  234. package/src/message-move-copy.test.ts +0 -562
  235. package/src/message-move-empty-trash.test.ts +0 -326
  236. package/src/message-move-same-mailbox.test.ts +0 -139
  237. package/src/message-move-unsettled-placement.test.ts +0 -378
  238. package/src/message-move.ts +0 -1348
  239. package/src/message-parser.ts +0 -26
  240. package/src/message-presence.test.ts +0 -50
  241. package/src/message-sync-address-spoof.test.ts +0 -293
  242. package/src/message-sync-category.test.ts +0 -142
  243. package/src/message-sync-changedsince.test.ts +0 -840
  244. package/src/message-sync-flags.test.ts +0 -158
  245. package/src/message-sync-junk-harvest.test.ts +0 -244
  246. package/src/message-sync-quarantine.test.ts +0 -416
  247. package/src/message-sync-repoint.test.ts +0 -419
  248. package/src/message-sync.ts +0 -1963
  249. package/src/mime-walker.test.ts +0 -311
  250. package/src/mime-walker.ts +0 -305
  251. package/src/outbox-attachment-content.test.ts +0 -153
  252. package/src/outbox-attachment-content.ts +0 -104
  253. package/src/outbox-attachment-filename.test.ts +0 -132
  254. package/src/outbox-attachment-filename.ts +0 -76
  255. package/src/outbox-attachment.test.ts +0 -738
  256. package/src/outbox-attachment.ts +0 -536
  257. package/src/outbox-failed-recovery.test.ts +0 -370
  258. package/src/outbox-queue-settled-row.test.ts +0 -140
  259. package/src/outbox-queue.test.ts +0 -291
  260. package/src/outbox-queue.ts +0 -508
  261. package/src/pass-through-unit-of-work.ts +0 -17
  262. package/src/placement-move-terminal.test.ts +0 -300
  263. package/src/placement-move-terminal.ts +0 -148
  264. package/src/placement-move-unsettled.test.ts +0 -275
  265. package/src/placement-move.ts +0 -336
  266. package/src/placement-settled.test.ts +0 -147
  267. package/src/placement-transition-race.test.ts +0 -482
  268. package/src/quarantine.test.ts +0 -191
  269. package/src/quarantine.ts +0 -228
  270. package/src/snippet.test.ts +0 -213
  271. package/src/snippet.ts +0 -152
  272. package/src/spam-report.test.ts +0 -801
  273. package/src/spam-report.ts +0 -357
  274. package/src/stale-message-reconcile.test.ts +0 -82
  275. package/src/stale-message-reconcile.ts +0 -55
  276. package/src/sync-watermarks.test.ts +0 -409
  277. package/src/sync-watermarks.ts +0 -319
  278. package/src/system-flag-wire-format.test.ts +0 -77
  279. package/src/test-helpers/folder-roles.ts +0 -66
  280. package/src/test-helpers/isolated-mailbox.ts +0 -44
  281. package/src/test-helpers/mailfuzz-connection.ts +0 -27
  282. package/src/test-helpers/mime-tree-generator.ts +0 -461
  283. package/src/text/normalizer.test.ts +0 -223
  284. package/src/text/normalizer.ts +0 -140
  285. package/src/thread-message-rows.ts +0 -77
  286. package/src/types/mailparser-augment.d.ts +0 -15
  287. package/src/types/natural-porter-stemmer.d.ts +0 -50
  288. package/src/types/stopword.d.ts +0 -69
  289. package/src/types.ts +0 -371
  290. package/test/fixtures/mime-adversarial-e2e/01-octet-stream-pdf.eml +0 -21
  291. package/test/fixtures/mime-adversarial-e2e/02-nested-related-cid-image.eml +0 -31
  292. package/test/fixtures/mime-adversarial-e2e/03-calendar-invite.eml +0 -33
  293. package/test/fixtures/mime-adversarial-e2e/04-forwarded-rfc822-with-attachments.eml +0 -42
  294. package/test/fixtures/mime-adversarial-e2e/05-tnef-winmail.eml +0 -25
  295. package/test/fixtures/mime-adversarial-e2e/06-8bit-non-ascii.eml +0 -15
  296. package/test/fixtures/mime-adversarial-e2e/07-quoted-printable-soft-breaks.eml +0 -17
  297. package/test/fixtures/mime-adversarial-e2e/08-alternative-empty-text.eml +0 -18
  298. 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
- });