@remit/mailbox-service 0.0.98 → 0.0.100

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/mailbox-service",
3
- "version": "0.0.98",
3
+ "version": "0.0.100",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -381,10 +381,8 @@ describe("Address.flags.category overrides classification at sync time (issue #2
381
381
  );
382
382
 
383
383
  assert.equal(harness.messageUpdates.length, 1);
384
- assert.equal(
385
- harness.messageUpdates[0].input.category,
386
- MessageCategory.marketing,
387
- );
384
+ // Re-store writes only bodyStorageKey — category untouched on row + thread.
385
+ assert.equal(harness.messageUpdates[0].input.category, undefined);
388
386
  assert.equal(harness.message.category, MessageCategory.marketing);
389
387
  assert.equal(harness.rows[0].category, MessageCategory.marketing);
390
388
  });
@@ -422,10 +420,8 @@ describe("Address.flags.category overrides classification at sync time (issue #2
422
420
 
423
421
  assert.deepEqual(result.syncedMessageIds, ["m-1"]);
424
422
  assert.equal(harness.messageUpdates.length, 1);
425
- assert.equal(
426
- harness.messageUpdates[0].input.category,
427
- MessageCategory.marketing,
428
- );
423
+ // Re-store writes only bodyStorageKey — category untouched on row + thread.
424
+ assert.equal(harness.messageUpdates[0].input.category, undefined);
429
425
  assert.equal(harness.message.category, MessageCategory.marketing);
430
426
  assert.equal(harness.rows[0].category, MessageCategory.marketing);
431
427
  });
@@ -2,15 +2,13 @@
2
2
  * RFC 034 Decision 3.1: `Message.category` is written once and never mutated
3
3
  * after — RFC 030's message-list GSI sort key depends on it never churning.
4
4
  *
5
- * `applyPostStoreSteps` computes a fresh classification on every pass and
6
- * folds it into the single Message update, with no check for whether the
7
- * message already carries a decided category. Two shipped paths re-enter it
8
- * on an already-classified message — the `NoSuchKey` fallback in
9
- * `fetchAndGetBody`, and `syncBodies(..., force: true)` — and both must leave
10
- * `category` untouched. Each test below feeds a re-entrant pass a body that
11
- * would classify differently from the message's existing category, so a
12
- * regression that drops the guard shows up even though header classification
13
- * is otherwise deterministic (issue #355).
5
+ * The re-entrant paths — `fetchAndGetBody`'s `NoSuchKey` fallback and
6
+ * `syncBodies(..., force: true)` — are now body-only re-stores (issue #1011):
7
+ * `applyPostStoreSteps(isReStore: true)` skips the entire decision pass and
8
+ * writes only `bodyStorageKey`, so the existing category on the row is never
9
+ * touched. Each test below feeds a re-store a body that would classify
10
+ * differently, so a regression that drops the isReStore skip shows up as the
11
+ * category field appearing in the update input (or changing on the row).
14
12
  */
15
13
 
16
14
  import assert from "node:assert/strict";
@@ -157,10 +155,11 @@ describe("Message.category survives a re-entrant classification pass", () => {
157
155
  );
158
156
 
159
157
  assert.equal(harness.messageUpdates.length, 1);
160
- assert.equal(
161
- harness.messageUpdates[0].input.category,
162
- MessageCategory.marketing,
163
- );
158
+ // Re-store writes only bodyStorageKey — category is untouched on the row.
159
+ assert.equal(harness.messageUpdates[0].input.category, undefined);
160
+ assert.deepEqual(Object.keys(harness.messageUpdates[0].input).sort(), [
161
+ "bodyStorageKey",
162
+ ]);
164
163
  assert.equal(harness.message.category, MessageCategory.marketing);
165
164
  });
166
165
 
@@ -196,10 +195,11 @@ describe("Message.category survives a re-entrant classification pass", () => {
196
195
 
197
196
  assert.deepEqual(result.syncedMessageIds, ["m-1"]);
198
197
  assert.equal(harness.messageUpdates.length, 1);
199
- assert.equal(
200
- harness.messageUpdates[0].input.category,
201
- MessageCategory.marketing,
202
- );
198
+ // Re-store writes only bodyStorageKey — category is untouched on the row.
199
+ assert.equal(harness.messageUpdates[0].input.category, undefined);
200
+ assert.deepEqual(Object.keys(harness.messageUpdates[0].input).sort(), [
201
+ "bodyStorageKey",
202
+ ]);
203
203
  assert.equal(harness.message.category, MessageCategory.marketing);
204
204
  });
205
205
 
@@ -1,20 +1,18 @@
1
1
  /**
2
- * RFC 039 Non-goals / issue #383: placement is meant to run once per message.
3
- * #378 (issue #355) guarded `Message.category` against the same two re-entrant
4
- * paths re-running `applyPostStoreSteps` on an already-processed message —
5
- * `fetchAndGetBody`'s `NoSuchKey` fallback and `syncBodies(..., force: true)`
6
- * — but did not guard `resolvePlacement`/`computePlacement`. Without a guard,
7
- * a message the provider originally junked and a user later rescued by hand
8
- * (never touched by Remit, so `movedByRemit` records nothing) gets
9
- * `classifyPlacement` re-evaluated against the same demote signals that
10
- * junked it in the first place, and can be silently moved right back.
2
+ * RFC 039 Non-goals / issues #383, #1011: placement is meant to run once per
3
+ * message. The two re-entrant paths — `fetchAndGetBody`'s `NoSuchKey` fallback
4
+ * and `syncBodies(..., force: true)` — are now marked as re-stores by
5
+ * `applyPostStoreSteps(isReStore: true)`, which skips the entire decision pass
6
+ * (placement, filters, classification) and writes only `bodyStorageKey`. This
7
+ * replaces the old per-field guards (`hasDecidedPlacement`,
8
+ * `hasDecidedCategory`, `hasClassifiedBody`) that each individual derivation
9
+ * carried.
11
10
  *
12
- * Each "already decided" test below presets `placementDecidedAt` on the
13
- * fixture (as a genuine first pass would have left it) and feeds the
14
- * re-entrant pass a body whose headers WOULD trigger a confident demote if
15
- * `classifyPlacement` ran fresh — so a regression that drops the guard shows
16
- * up as an unwanted move, not as a passing test relying on deterministic
17
- * heuristics happening to agree.
11
+ * The last test below previously asserted that a legacy row (movedByRemit: true,
12
+ * no placementDecidedAt) gets backfilled with placementDecidedAt on re-enter —
13
+ * that behavior is gone by design: re-stores never re-decide placement, so a
14
+ * pre-#1011 row that never got placementDecidedAt simply keeps not having it
15
+ * (and never gets moved by Remit either).
18
16
  */
19
17
 
20
18
  import assert from "node:assert/strict";
@@ -201,6 +199,11 @@ describe("placement survives a re-entrant computePlacement pass (issue #383)", (
201
199
  assert.equal(harness.message.mailboxId, MAILBOXES.inbox.mailboxId);
202
200
  assert.equal(harness.messageUpdates[0]?.input.movedByRemit, undefined);
203
201
  assert.equal(harness.messageUpdates[0]?.input.placementVerdict, undefined);
202
+ // The re-store wrote only bodyStorageKey — no placement fields.
203
+ assert.equal(
204
+ harness.messageUpdates[0]?.input.placementDecidedAt,
205
+ undefined,
206
+ );
204
207
  });
205
208
 
206
209
  it("keeps a user-rescued message in Inbox when syncBodies re-fetches with force", async () => {
@@ -279,16 +282,18 @@ describe("placement survives a re-entrant computePlacement pass (issue #383)", (
279
282
  );
280
283
  });
281
284
 
282
- it("continues to protect a message Remit itself already moved (movedByRemit: true), unchanged behavior", async () => {
285
+ it("a legacy row with movedByRemit: true and no placementDecidedAt is not re-decided on re-store", async () => {
283
286
  const harness = buildHarness(
284
287
  {
285
288
  messageId: "m-1",
286
289
  mailboxId: MAILBOXES.inbox.mailboxId,
287
290
  bodyStorageKey: "s3://bodies/m-1",
288
291
  movedByRemit: true,
289
- // No placementDecidedAt — a legacy row synced before issue #383's
290
- // guard existed. `classifyPlacement`'s own `movedByRemit` check
291
- // must still hold on its own.
292
+ // No placementDecidedAt and no category — a legacy row synced before
293
+ // the write-once guards existed. Under #1011 a re-store skips the
294
+ // decision pass entirely, so placement is never re-evaluated and the
295
+ // row's missing fields are never backfilled — the re-store writes only
296
+ // bodyStorageKey.
292
297
  },
293
298
  async () => {
294
299
  throw noSuchKeyError();
@@ -313,10 +318,20 @@ describe("placement survives a re-entrant computePlacement pass (issue #383)", (
313
318
  [],
314
319
  "movedByRemit must keep protecting a legacy row with no placementDecidedAt of its own",
315
320
  );
321
+ // The re-store wrote only bodyStorageKey — no placement fields were
322
+ // re-decided or backfilled.
316
323
  assert.equal(harness.message.mailboxId, MAILBOXES.inbox.mailboxId);
317
- assert.ok(
318
- typeof harness.messageUpdates[0]?.input.placementDecidedAt === "number",
319
- "the legacy row is backfilled with placementDecidedAt going forward, self-healing for future passes",
324
+ assert.equal(
325
+ harness.messageUpdates[0]?.input.placementDecidedAt,
326
+ undefined,
327
+ );
328
+ assert.equal(harness.messageUpdates[0]?.input.movedByRemit, undefined);
329
+ assert.equal(harness.messageUpdates[0]?.input.placementVerdict, undefined);
330
+ // The only field written is bodyStorageKey.
331
+ assert.equal(
332
+ Object.keys(harness.messageUpdates[0]?.input ?? {}).length,
333
+ 1,
334
+ "re-store must write only bodyStorageKey",
320
335
  );
321
336
  });
322
337
  });