diff --git a/doc/plans/2026-10-25-voice-drain-repair.md b/doc/plans/2026-10-25-voice-drain-repair.md index e7f761ae35b..da0ae09914d 100644 --- a/doc/plans/2026-10-25-voice-drain-repair.md +++ b/doc/plans/2026-10-25-voice-drain-repair.md @@ -50,3 +50,13 @@ then verifies the saved reply sends on resume without another producer. All later shared-worker consumers inherit this repair through the stack. Their combined-tip checks and PR checks still need fresh verification after propagation. + +## Retention resume assertion + +The combined-tip consumer run exposed a retention test that called flush after +resume and assumed a zero activity counter meant the retained retry had run. Its +helper checks active work only; zero is now also the correct paused state. The +owner test now advances the actual retained nextWakeAt, waits for row removal, +and checks disarm. Before resume it also proves a stable idle snapshot through a +minute of quiet. All 11 retention cases pass in /tmp/sleep24-drain-tests.log. +No production scheduling deadline or dispatch behavior changed for this repair. diff --git a/server/src/__tests__/voice-inbound-retention.test.ts b/server/src/__tests__/voice-inbound-retention.test.ts index ebafa59093c..7b07b3caac6 100644 --- a/server/src/__tests__/voice-inbound-retention.test.ts +++ b/server/src/__tests__/voice-inbound-retention.test.ts @@ -151,8 +151,13 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu }); it("keeps the first write during drain and runs it on resume", async () => { await start(); await flush(); enabled = false; - const call = await admission(); await quiet(60_000); expect(await row(call.id)).toBeDefined(); - enabled = true; await flush(); expect(await row(call.id)).toBeUndefined(); await quiet(); + const call = await admission(); await flush(); + const drained = idleWorkSnapshot(); + await quiet(60_000); expect(idleWorkSnapshot()).toEqual(drained); expect(await row(call.id)).toBeDefined(); + const resumeAt = coordinator!.nextWakeAt(); expect(resumeAt).not.toBeNull(); + enabled = true; await vi.advanceTimersByTimeAsync(Math.max(0, resumeAt! - Date.now())); + await vi.waitFor(async () => { await flush(); expect(await row(call.id)).toBeUndefined(); }); + expect(coordinator!.nextWakeAt()).toBeNull(); await quiet(); }); it("pages through more than one batch of history across companies", async () => { for (let i = 0; i < 104; i++) await admission();