Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/review-corpus-triage-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"review": patch
---

Corpus fixes from the triage of drift run 29724668102's stable misses (a ruler change: catch rates move because the ground truth moved, not the reviewer). The retention lifecycle trees replace `void pruneNotes(...)` with `pruneNotes(...).catch(() => {})` so the documented fire-and-forget trap actually delivers "a failing prune must not fail the save"; the old form left an unhandled rejection, making the reviewer's blocking flag on it technically correct, which forced a wrong verdict in every lifecycle-3 sample and injected a guaranteed noise finding into every lifecycle-1/2 run. The `retention-dedup-window-untested` spec gains an altLocation on the test file plus mechanism alternates: the reviewer's real dedup-coverage finding anchors there and could never match (replayed deterministic rate goes 0/6 to 4/6), while the three recorded arbiter fallback accepts for this spec were all the unrelated off-by-one finding; both recorded findings are pinned as matcher regression fixtures. `trial-dedup-eventual-consistency` now expects REQUEST_CHANGES (recorded fixture severity blocking at 0.8, the trial-amplified-default-limit pattern): the case simultaneously required catching a defect that defeats dedup for exactly its target traffic and an APPROVE verdict a blocking catch cannot produce. The lifecycle-3 void-prune trap spec gains a "swallow/empty catch" mechanism alternate to keep the must-not-flag guard effective against the new phrasing.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
"retention-test-asserts-nothing"
]
},
"diff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,10 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ const notes = await db.query(\"Note\", {userId});\n+ await db.deleteMulti(notes.map((note) => note.id));\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,22 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ void pruneNotes(db, note.userId);\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER - 1,\n+ pageSize: \"all\",\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"diff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,10 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ const notes = await db.query(\"Note\", {userId});\n+ await db.deleteMulti(notes.map((note) => note.id));\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,22 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ pruneNotes(db, note.userId).catch(() => {});\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER - 1,\n+ pageSize: \"all\",\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"live": {
"prContext": {
"title": "notes: cap stored notes per user and delete them with user data",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const MAX_NOTES_PER_USER = 200;
export const saveNote = async (db: Db, note: Note): Promise<void> => {
await db.put(note);
// Intentionally fire-and-forget: a failing prune must not fail the save.
void pruneNotes(db, note.userId);
pruneNotes(db, note.userId).catch(() => {});
};

export const pruneNotes = async (db: Db, userId: string): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"retention-dedup-prefix-collision"
]
},
"diff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,15 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ for (;;) {\n+ const notes = await db.query(\"Note\", {userId, pageSize: 100});\n+ if (notes.length === 0) {\n+ return;\n+ }\n+ await db.deleteMulti(notes.map((note) => note.id));\n+ }\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,44 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+/** Window inside which a repeated note is treated as a duplicate. */\n+export const DEDUP_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;\n+\n+const PRUNE_BATCH = 50;\n+\n+/** Duplicate key: normalised content prefix, cheap to compare. */\n+const dedupKey = (content: string): string =>\n+ content.toLowerCase().slice(0, 8);\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ const existing = await db.query(\"Note\", {\n+ userId: note.userId,\n+ pageSize: \"all\",\n+ });\n+ const since = note.createdAt - DEDUP_WINDOW_MS;\n+ const seen = new Set(\n+ existing\n+ .filter((stored) => stored.createdAt >= since)\n+ .map((stored) => dedupKey(stored.content)),\n+ );\n+ if (seen.has(dedupKey(note.content))) {\n+ return;\n+ }\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ void pruneNotes(db, note.userId);\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER + 1,\n+ pageSize: PRUNE_BATCH,\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"diff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,15 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ for (;;) {\n+ const notes = await db.query(\"Note\", {userId, pageSize: 100});\n+ if (notes.length === 0) {\n+ return;\n+ }\n+ await db.deleteMulti(notes.map((note) => note.id));\n+ }\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,44 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+/** Window inside which a repeated note is treated as a duplicate. */\n+export const DEDUP_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;\n+\n+const PRUNE_BATCH = 50;\n+\n+/** Duplicate key: normalised content prefix, cheap to compare. */\n+const dedupKey = (content: string): string =>\n+ content.toLowerCase().slice(0, 8);\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ const existing = await db.query(\"Note\", {\n+ userId: note.userId,\n+ pageSize: \"all\",\n+ });\n+ const since = note.createdAt - DEDUP_WINDOW_MS;\n+ const seen = new Set(\n+ existing\n+ .filter((stored) => stored.createdAt >= since)\n+ .map((stored) => dedupKey(stored.content)),\n+ );\n+ if (seen.has(dedupKey(note.content))) {\n+ return;\n+ }\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ pruneNotes(db, note.userId).catch(() => {});\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER + 1,\n+ pageSize: PRUNE_BATCH,\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"live": {
"prContext": {
"title": "notes: cap stored notes per user and delete them with user data",
Expand All @@ -106,15 +106,22 @@
{
"key": "retention-dedup-window-untested",
"path": "src/notes/retention.ts",
"altLocations": [
{
"path": "src/notes/retention.test.ts"
}
],
"mechanism": [
"window.*(untested|no test)|no test.*window",
"DEDUP_WINDOW",
"boundary"
"boundary",
"dedup.{0,60}(untested|not (covered|tested|exercised)|no test)",
"(test|coverage).{0,80}dedup"
]
}
],
"rereview": {
"priorDiff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,10 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ const notes = await db.query(\"Note\", {userId});\n+ await db.deleteMulti(notes.map((note) => note.id));\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,22 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ void pruneNotes(db, note.userId);\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER - 1,\n+ pageSize: \"all\",\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"priorDiff": "diff --git a/src/notes/purge-user-data.ts b/src/notes/purge-user-data.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/purge-user-data.ts\n@@ -0,0 +1,10 @@\n+import type {Db} from \"./db\";\n+\n+/** Part of account deletion: remove every stored note for the user. */\n+export const purgeUserNotes = async (\n+ db: Db,\n+ userId: string,\n+): Promise<void> => {\n+ const notes = await db.query(\"Note\", {userId});\n+ await db.deleteMulti(notes.map((note) => note.id));\n+};\ndiff --git a/src/notes/retention.test.ts b/src/notes/retention.test.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.test.ts\n@@ -0,0 +1,11 @@\n+import {memDb, note} from \"./testing\";\n+import {pruneNotes, saveNote} from \"./retention\";\n+\n+describe(\"pruneNotes\", () => {\n+ it(\"prunes old notes past the cap\", async () => {\n+ const db = memDb();\n+ await saveNote(db, note(\"u1\", \"first\"));\n+ await saveNote(db, note(\"u1\", \"second\"));\n+ await expect(pruneNotes(db, \"u1\")).resolves.toBeUndefined();\n+ });\n+});\ndiff --git a/src/notes/retention.ts b/src/notes/retention.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/notes/retention.ts\n@@ -0,0 +1,22 @@\n+import type {Db, Note} from \"./db\";\n+\n+/** Hard cap on stored notes per user; prune enforces it on every save. */\n+export const MAX_NOTES_PER_USER = 200;\n+\n+export const saveNote = async (db: Db, note: Note): Promise<void> => {\n+ await db.put(note);\n+ // Intentionally fire-and-forget: a failing prune must not fail the save.\n+ pruneNotes(db, note.userId).catch(() => {});\n+};\n+\n+export const pruneNotes = async (db: Db, userId: string): Promise<void> => {\n+ const stale = await db.query(\"Note\", {\n+ userId,\n+ orderDesc: \"createdAt\",\n+ offset: MAX_NOTES_PER_USER - 1,\n+ pageSize: \"all\",\n+ });\n+ if (stale.length > 0) {\n+ await db.deleteMulti(stale.map((note) => note.id));\n+ }\n+};\n",
"priorVerdict": "REQUEST_CHANGES",
"priorDepth": "full",
"priorThreads": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const saveNote = async (db: Db, note: Note): Promise<void> => {
}
await db.put(note);
// Intentionally fire-and-forget: a failing prune must not fail the save.
void pruneNotes(db, note.userId);
pruneNotes(db, note.userId).catch(() => {});
};

export const pruneNotes = async (db: Db, userId: string): Promise<void> => {
Expand Down
Loading
Loading