From a907f73cfd0fbecf0b52d7d901cde350f7438f8b Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 25 Oct 2026 09:48:27 -0500 Subject: [PATCH] test(server): require chat milestone drain to release idle activity Co-Authored-By: Paperclip --- ...26-10-25-chat-run-milestone-sleep-audit.md | 19 +++++++++++++++++++ .../__tests__/chat-run-milestone-work.test.ts | 10 ++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/doc/plans/2026-10-25-chat-run-milestone-sleep-audit.md b/doc/plans/2026-10-25-chat-run-milestone-sleep-audit.md index 7e26643cecc..71b6c6d417f 100644 --- a/doc/plans/2026-10-25-chat-run-milestone-sleep-audit.md +++ b/doc/plans/2026-10-25-chat-run-milestone-sleep-audit.md @@ -1044,3 +1044,22 @@ These dispositions do not finish the entire publication/configuration/recovery audit. In particular, endpoint and conversation reactivation, policy changes and alternate recovery receipts still need complete real-producer coverage before production registration and removal of the lifecycle scan. + + +## Shared drain repair acceptance + +The voice-recovery review exposed dirty hints retaining a settled intent hold +while dispatch is paused. The repair is owned by #17576 and propagated through +#17578's shared scoped-deadline worker into this slice. The milestone first-write +and failed-selection drain tests now also assert zero active work and a stable +idle snapshot, then process the retained notice after resume without a new write. +All 99 lifecycle/governed/voice recovery cases pass in +`/tmp/sleep28-propagated-drain-tests.log`; server types pass. Both Teams scheduled +consumer groups pass 24 cases in `/tmp/sleep28-teams-drain-consumers.log`. + +Broader acceptance found two tests relying on the old held counter to wait for +resume: report and retention flush helpers return as soon as active work is zero, +which is now correct during drain even before the retained retry fires. The run +in `/tmp/sleep28-shared-drain-consumers.log` has 36 passes and those two failures. +Confirm the saved deadline runs on resume, strengthen idle assertions, and repair +these tests at their owning parent PRs before claiming propagated acceptance. diff --git a/server/src/__tests__/chat-run-milestone-work.test.ts b/server/src/__tests__/chat-run-milestone-work.test.ts index 45aa4784f6d..d8bb45530ca 100644 --- a/server/src/__tests__/chat-run-milestone-work.test.ts +++ b/server/src/__tests__/chat-run-milestone-work.test.ts @@ -905,7 +905,9 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu it("retains the first committed wake during drain without periodic SQL", async () => { await start(); await flush(); enabled = false; await run(); await vi.advanceTimersByTimeAsync(1000); - expect(await publications()).toEqual([]); await quiet(); + await vi.waitFor(() => expect(idleWorkSnapshot().active).toBe(0)); + const drained = idleWorkSnapshot(); + expect(await publications()).toEqual([]); await quiet(); expect(idleWorkSnapshot()).toEqual(drained); enabled = true; await vi.advanceTimersByTimeAsync(30_000); await flush(); expect(await publications()).toHaveLength(1); await quiet(); }); @@ -948,7 +950,11 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu await run(); const select = vi.spyOn(db, "select").mockImplementationOnce(() => { throw new Error("temporary read failure"); }); await start(); select.mockRestore(); expect(errors.splice(0)).toHaveLength(1); - enabled = false; await quiet(); enabled = true; await vi.advanceTimersByTimeAsync(30_000); await flush(); + enabled = false; await vi.advanceTimersByTimeAsync(30_000); + await vi.waitFor(() => expect(idleWorkSnapshot().active).toBe(0)); + const drained = idleWorkSnapshot(); + await quiet(); expect(idleWorkSnapshot()).toEqual(drained); + enabled = true; await vi.advanceTimersByTimeAsync(30_000); await flush(); expect(await publications()).toHaveLength(1); await quiet(); }); it("retries a failed publication write without idle scans before its deadline", async () => {