@remit/imap-worker 0.0.59 → 0.0.61
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/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type PlacementMoveService,
|
|
16
16
|
} from "@remit/mailbox-service";
|
|
17
17
|
import { buildFilterConfig, type FilterConfigDeps } from "./filter-config.js";
|
|
18
|
+
import { _resetMessageEmbedderForTest } from "./message-embedder.js";
|
|
18
19
|
|
|
19
20
|
const anchorOnlyFilter = (destinationMailboxId: string): FilterItem =>
|
|
20
21
|
({
|
|
@@ -117,3 +118,54 @@ describe("buildFilterConfig", () => {
|
|
|
117
118
|
assert.equal(decision.move, undefined);
|
|
118
119
|
});
|
|
119
120
|
});
|
|
121
|
+
|
|
122
|
+
// An instance with semantic search off (issue #1068). `off` selects an embedder
|
|
123
|
+
// that throws by design, and the throw lands in the pipeline's per-filter catch:
|
|
124
|
+
// one error-level `filter_anchor_match_failed` for every semantic filter on
|
|
125
|
+
// every message it syncs. The pipeline already has a designed skip for a
|
|
126
|
+
// missing embedder, so the config passes none.
|
|
127
|
+
describe("buildFilterConfig with semantic search off", () => {
|
|
128
|
+
const withProviderOff = async (body: () => Promise<void>): Promise<void> => {
|
|
129
|
+
const previous = process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
130
|
+
process.env.SEARCH_EMBEDDING_PROVIDER = "off";
|
|
131
|
+
_resetMessageEmbedderForTest();
|
|
132
|
+
try {
|
|
133
|
+
await body();
|
|
134
|
+
} finally {
|
|
135
|
+
if (previous === undefined) {
|
|
136
|
+
delete process.env.SEARCH_EMBEDDING_PROVIDER;
|
|
137
|
+
} else {
|
|
138
|
+
process.env.SEARCH_EMBEDDING_PROVIDER = previous;
|
|
139
|
+
}
|
|
140
|
+
_resetMessageEmbedderForTest();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
it("wires no embedder", async () => {
|
|
145
|
+
await withProviderOff(async () => {
|
|
146
|
+
const config = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
147
|
+
|
|
148
|
+
assert.ok(config);
|
|
149
|
+
assert.equal(config.embedder, undefined);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("skips the semantic filter without logging an error", async () => {
|
|
154
|
+
await withProviderOff(async () => {
|
|
155
|
+
const config = buildFilterConfig(repos(anchorOnlyFilter("mbx-archive")));
|
|
156
|
+
assert.ok(config);
|
|
157
|
+
|
|
158
|
+
const errors: unknown[] = [];
|
|
159
|
+
const decision = await new FilterPipeline(config, {
|
|
160
|
+
info: () => {},
|
|
161
|
+
debug: () => {},
|
|
162
|
+
warn: () => {},
|
|
163
|
+
error: (fields) => errors.push(fields.alert),
|
|
164
|
+
}).evaluate("cfg-1", "m-off", message);
|
|
165
|
+
|
|
166
|
+
assert.deepEqual(decision.labels, []);
|
|
167
|
+
assert.equal(decision.move, undefined);
|
|
168
|
+
assert.deepEqual(errors, []);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
package/src/filter-config.ts
CHANGED
|
@@ -8,6 +8,10 @@ import type {
|
|
|
8
8
|
MessageEmbedder,
|
|
9
9
|
PlacementMoveService,
|
|
10
10
|
} from "@remit/mailbox-service";
|
|
11
|
+
import {
|
|
12
|
+
EMBEDDING_PROVIDER_OFF,
|
|
13
|
+
readEmbeddingProviderFromEnv,
|
|
14
|
+
} from "@remit/search-service/from-env";
|
|
11
15
|
import { getMessageEmbedder } from "./message-embedder.js";
|
|
12
16
|
|
|
13
17
|
export interface FilterConfigDeps {
|
|
@@ -17,6 +21,21 @@ export interface FilterConfigDeps {
|
|
|
17
21
|
placementMoveService?: PlacementMoveService;
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The env-selected embedder, or none on an instance with semantic search off.
|
|
26
|
+
*
|
|
27
|
+
* `off` embeds nothing by design, and its embedder says so by throwing
|
|
28
|
+
* (EmbeddingDisabledError). The pipeline has a designed skip for a *missing*
|
|
29
|
+
* embedder — semantic filters are passed over and logged at debug — but a
|
|
30
|
+
* present embedder that throws lands in the per-filter catch instead, which is
|
|
31
|
+
* an error-level `filter_anchor_match_failed` for every semantic filter on
|
|
32
|
+
* every synced message. Same outcome, a log nobody can read.
|
|
33
|
+
*/
|
|
34
|
+
const embedderFromEnv = (): MessageEmbedder | undefined =>
|
|
35
|
+
readEmbeddingProviderFromEnv() === EMBEDDING_PROVIDER_OFF
|
|
36
|
+
? undefined
|
|
37
|
+
: getMessageEmbedder();
|
|
38
|
+
|
|
20
39
|
/**
|
|
21
40
|
* Assemble the index-time filter config the body-sync pass runs (RFC 034). The
|
|
22
41
|
* embedder is provisioned from env exactly as the backend read-path and
|
|
@@ -33,11 +52,12 @@ export const buildFilterConfig = (
|
|
|
33
52
|
): FilterConfig | undefined => {
|
|
34
53
|
const { placementMoveService } = deps;
|
|
35
54
|
if (!placementMoveService) return undefined;
|
|
55
|
+
const resolved = embedder ?? embedderFromEnv();
|
|
36
56
|
return {
|
|
37
57
|
filterService: deps.filterService,
|
|
38
58
|
filterAnchorService: deps.filterAnchorService,
|
|
39
59
|
messageLabelService: deps.messageLabelService,
|
|
40
60
|
placementMoveService,
|
|
41
|
-
embedder:
|
|
61
|
+
...(resolved ? { embedder: resolved } : {}),
|
|
42
62
|
};
|
|
43
63
|
};
|
|
@@ -158,6 +158,8 @@ export const syncMessageBody = async (
|
|
|
158
158
|
mailboxSpecialUse: mailboxSpecialUseRepository,
|
|
159
159
|
quarantine: quarantineRepository,
|
|
160
160
|
flagQueue: flagQueueService,
|
|
161
|
+
calendarSuggestion: calendarSuggestionService,
|
|
162
|
+
calendarUnitOfWork,
|
|
161
163
|
} = await getClient();
|
|
162
164
|
|
|
163
165
|
const account = await accountService.get(accountId);
|
|
@@ -274,6 +276,9 @@ export const syncMessageBody = async (
|
|
|
274
276
|
attempts: receiveCount,
|
|
275
277
|
},
|
|
276
278
|
{ flagQueueService },
|
|
279
|
+
// An invitation becomes a card the first time the message is seen,
|
|
280
|
+
// alongside the filters that run on the same pass (issue #1033).
|
|
281
|
+
{ calendarSuggestionService, calendarUnitOfWork, filterService },
|
|
277
282
|
);
|
|
278
283
|
|
|
279
284
|
// Guard at the openBox choke point (epic #1281 invariants 3 & 5). The
|