From d28ff58b3a50a2c44b4f038c9548b4ea4bd64b44 Mon Sep 17 00:00:00 2001 From: macodev00 <273427913+macodev00@users.noreply.github.com> Date: Sun, 20 Sep 2026 08:33:16 +0000 Subject: [PATCH] fix(server): keep a failed usage probe marked when a sparse update lands A rate_limit_event after a failed or missing probe was rebuilt as a complete one-window list. Copy unavailable onto the merge the same way resetCredits is copied so the snapshot stays incomplete until a successful probe replaces it. --- .../src/provider/providerUsageLimits.test.ts | 39 +++++++++++++++++++ .../src/provider/providerUsageLimits.ts | 13 ++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/providerUsageLimits.test.ts b/apps/server/src/provider/providerUsageLimits.test.ts index 6e288ddd3a36..dfc055d1b84f 100644 --- a/apps/server/src/provider/providerUsageLimits.test.ts +++ b/apps/server/src/provider/providerUsageLimits.test.ts @@ -62,6 +62,35 @@ describe("applyUsageLimitsUpdate", () => { ).toBe(published); }); + it("keeps a failed probe marked when a sparse update lands on it", () => { + // A `rate_limit_event` names one window. Merged onto a failed probe it + // must not read as the full set, or a later failed probe keeps it as last-good. + const failed = { + checkedAt, + windows: [], + unavailable: { reason: "probeFailed" as const, message: "usage timed out" }, + }; + const next = applyUsageLimitsUpdate({ + previous: failed, + checkedAt: "2026-09-03T12:00:05.000Z", + update: { windows: [weekly] }, + }); + expect(next).toEqual({ + checkedAt: "2026-09-03T12:00:05.000Z", + windows: [weekly], + unavailable: failed.unavailable, + }); + expect( + applyUsageLimitsUpdate({ previous: next, checkedAt, update: { windows: [weekly] } }), + ).toBe(next); + }); + + it("marks a sparse update with no previous snapshot as probeFailed", () => { + expect( + applyUsageLimitsUpdate({ previous: undefined, checkedAt, update: { windows: [weekly] } }), + ).toEqual({ checkedAt, windows: [weekly], unavailable: { reason: "probeFailed" } }); + }); + it("preserves reset credits when a streamed window update changes usage", () => { const resetCredits = { availableCount: 2, nextExpiresAt: "2026-10-01T00:00:00.000Z" }; const next = applyUsageLimitsUpdate({ @@ -86,4 +115,14 @@ describe("resolveUsageLimitsAfterProbe", () => { expect(resolveUsageLimitsAfterProbe({ published, probed: unsupported })).toBe(unsupported); expect(resolveUsageLimitsAfterProbe({ published: undefined, probed: failed })).toBe(failed); }); + + it("does not keep a probeFailed merge as last-good through a later failed probe", () => { + const failed = { checkedAt, windows: [], unavailable: { reason: "probeFailed" as const } }; + const partial = applyUsageLimitsUpdate({ + previous: failed, + checkedAt: "2026-09-03T12:00:05.000Z", + update: { windows: [weekly] }, + }); + expect(resolveUsageLimitsAfterProbe({ published: partial, probed: failed })).toBe(failed); + }); }); diff --git a/apps/server/src/provider/providerUsageLimits.ts b/apps/server/src/provider/providerUsageLimits.ts index ea8d0d1d029f..f2db582ae7b9 100644 --- a/apps/server/src/provider/providerUsageLimits.ts +++ b/apps/server/src/provider/providerUsageLimits.ts @@ -56,6 +56,11 @@ export function makeUnavailableUsageLimits(input: { * * An `unsupported` snapshot stays unsupported: an account that cannot have * subscription windows will not start reporting them mid-turn. + * + * A `probeFailed` snapshot, or no snapshot at all, stays marked. The update + * is sparse by contract, so the windows it adds are not the full read; without + * the marker a lone weekly window would look complete and survive later failed + * probes as the last good set. */ export function applyUsageLimitsUpdate(input: { readonly previous: ServerProviderUsageLimits | undefined; @@ -88,12 +93,15 @@ export function applyUsageLimitsUpdate(input: { changed = true; } } - if (!changed && previous !== undefined && previous.unavailable === undefined) { + if (!changed && previous !== undefined) { return previous; } + const unavailable = + previous === undefined ? { reason: "probeFailed" as const } : previous.unavailable; return { ...makeUsageLimits({ checkedAt: input.checkedAt, windows: merged.values() }), ...(previous?.resetCredits !== undefined ? { resetCredits: previous.resetCredits } : {}), + ...(unavailable !== undefined ? { unavailable } : {}), }; } @@ -112,7 +120,8 @@ function usageWindowEquals(a: ServerProviderUsageWindow, b: ServerProviderUsageW * Choose what to publish after a status probe finishes. A probe that failed * this time must not wipe bars a previous probe or a turn already * established, so the last good snapshot stays; `unsupported` is - * authoritative and replaces them. + * authoritative and replaces them. A snapshot still marked `probeFailed` by a + * sparse merge is not last good and gives way to the probe's own result. * * A successful probe replaces the published windows outright, including any * runtime update that landed while it was running. That is a deliberate