@remit/mailbox-service 0.0.36 → 0.0.37

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.36",
3
+ "version": "0.0.37",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -228,14 +228,17 @@ describe("cosineSimilarity", () => {
228
228
  });
229
229
 
230
230
  describe("selectMoveWinner", () => {
231
- const filter = (filterId: string, ruleChangedAt: number): FilterItem =>
232
- ({ filterId, ruleChangedAt }) as FilterItem;
231
+ const filter = (
232
+ filterId: string,
233
+ actionChangedAt: number,
234
+ ruleChangedAt = actionChangedAt,
235
+ ): FilterItem => ({ filterId, actionChangedAt, ruleChangedAt }) as FilterItem;
233
236
 
234
237
  it("returns undefined with no candidates", () => {
235
238
  assert.equal(selectMoveWinner([]), undefined);
236
239
  });
237
240
 
238
- it("picks the most-recently-changed filter", () => {
241
+ it("picks the most-recently action-changed filter", () => {
239
242
  const winner = selectMoveWinner([
240
243
  filter("a", 100),
241
244
  filter("b", 300),
@@ -244,7 +247,7 @@ describe("selectMoveWinner", () => {
244
247
  assert.equal(winner?.filterId, "b");
245
248
  });
246
249
 
247
- it("tie-breaks on filterId when ruleChangedAt is identical", () => {
250
+ it("tie-breaks on filterId when actionChangedAt is identical", () => {
248
251
  const winner = selectMoveWinner([
249
252
  filter("a", 100),
250
253
  filter("c", 100),
@@ -252,6 +255,23 @@ describe("selectMoveWinner", () => {
252
255
  ]);
253
256
  assert.equal(winner?.filterId, "c");
254
257
  });
258
+
259
+ it("ignores a ruleChangedAt-only bump — extending scope/expiry alone must not promote a filter to move-winner (reader #384)", () => {
260
+ // filter A's predicate/action last changed at 10:00 (actionChangedAt).
261
+ // filter B was created at 09:00 and never had its predicate/action
262
+ // touched since, but a user extended its expiry at 11:00 — bumping only
263
+ // its ruleChangedAt (RFC 034 Decision 3.2 / #294), not what it matches or
264
+ // does. B must not out-rank A.
265
+ const filterA = filter("filter-a", 10_00);
266
+ const filterB = filter("filter-b", 9_00, 11_00);
267
+
268
+ const winner = selectMoveWinner([filterA, filterB]);
269
+ assert.equal(
270
+ winner?.filterId,
271
+ "filter-a",
272
+ "A still wins: its actionChangedAt (10:00) beats B's (09:00), even though B's ruleChangedAt (11:00) is later",
273
+ );
274
+ });
255
275
  });
256
276
 
257
277
  describe("buildMatchText", () => {
@@ -134,10 +134,14 @@ export const cosineSimilarity = (
134
134
  };
135
135
 
136
136
  /**
137
- * The move a message ends in when several filters matched: the most-recently
138
- * *changed* filter wins (RFC 034 Decision 3.2), tie-broken on `filterId` for the
139
- * unreachable identical-timestamp case. `ruleChangedAt` not `updatedAt` is
140
- * the signal, so a cosmetic rename never flips an exclusive move.
137
+ * The move a message ends in when several filters matched: the filter whose
138
+ * predicate or action was most recently *changed* wins (RFC 034 Decision 3.2),
139
+ * tie-broken on `filterId` for the unreachable identical-timestamp case.
140
+ * `actionChangedAt` not `ruleChangedAt` is the signal: `ruleChangedAt` also
141
+ * bumps on a scope/expiry-only edit (reader #266), which changes a filter's
142
+ * lifecycle, not what it matches or does, and must not reorder exclusive-move
143
+ * precedence (reader #384). Nor is it `updatedAt`, so a cosmetic rename never
144
+ * flips an exclusive move either.
141
145
  */
142
146
  export const selectMoveWinner = (
143
147
  candidates: readonly FilterItem[],
@@ -148,12 +152,12 @@ export const selectMoveWinner = (
148
152
  winner = candidate;
149
153
  continue;
150
154
  }
151
- if (candidate.ruleChangedAt > winner.ruleChangedAt) {
155
+ if (candidate.actionChangedAt > winner.actionChangedAt) {
152
156
  winner = candidate;
153
157
  continue;
154
158
  }
155
159
  if (
156
- candidate.ruleChangedAt === winner.ruleChangedAt &&
160
+ candidate.actionChangedAt === winner.actionChangedAt &&
157
161
  candidate.filterId > winner.filterId
158
162
  ) {
159
163
  winner = candidate;