From 362818418e4f73a5b0544be05e8341f604089dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 6 Oct 2026 20:29:56 +0200 Subject: [PATCH] test(coach): count the conversation writes that may stamp updatedAt The guard allowed every unnamed coachConversation write in a listed file, so a background write added beside the rename and the document attach would have passed. Each listed file now carries how many such writes it holds, and a changed count fails. --- ...ackfill-preserves-updated-at-guard.test.ts | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/__tests__/backfill-preserves-updated-at-guard.test.ts b/src/__tests__/backfill-preserves-updated-at-guard.test.ts index 9ee7b02ec..5f3238eb7 100644 --- a/src/__tests__/backfill-preserves-updated-at-guard.test.ts +++ b/src/__tests__/backfill-preserves-updated-at-guard.test.ts @@ -118,11 +118,17 @@ const BACKFILL_STAMPS: Record = { /** * `coachConversation` writes outside backfills that stamp `updatedAt` - * because the person did something in the conversation, keyed by file. + * because the person did something in the conversation, keyed by file, with + * how many such writes the file holds. The count matters: keyed by file + * alone, a new background write added beside the listed ones would pass + * unseen. */ -const COACH_ACTIVITY: Record = { - "src/lib/ai/coach/persistence.ts": - "a turn sets updatedAt to now explicitly; a rename and a document attach are the person acting on the thread", +const COACH_ACTIVITY: Record = { + "src/lib/ai/coach/persistence.ts": { + writes: 2, + reason: + "a rename and a document attach are the person acting on the thread (a turn names updatedAt explicitly)", + }, }; describe("backfills keep updatedAt", () => { @@ -180,13 +186,21 @@ describe("coach conversations move only on activity", () => { }); it("names updatedAt in every write that is not the person's own action", () => { - const offenders = writes - .filter((w) => !w.namesUpdatedAt && !(w.file in COACH_ACTIVITY)) - .map((w) => w.file); + const stamping = new Map(); + for (const w of writes.filter((w) => !w.namesUpdatedAt)) { + stamping.set(w.file, (stamping.get(w.file) ?? 0) + 1); + } + const offenders = [...stamping] + .filter(([file, n]) => n !== COACH_ACTIVITY[file]?.writes) + .map(([file, n]) => `${file}: ${n} stamping write(s)`); expect( - [...new Set(offenders)], - "a coachConversation write that is not activity must keep updatedAt (carry it, or write through raw SQL)", + offenders, + "a coachConversation write that is not activity must keep updatedAt (carry it, or write through raw SQL); a new activity write is listed with its count", ).toEqual([]); + // Every listed file is still there with its writes. + expect(Object.keys(COACH_ACTIVITY).filter((f) => !stamping.has(f))).toEqual( + [], + ); }); });