From bc876740f59ea68950e39a7922c536351c47e9b1 Mon Sep 17 00:00:00 2001 From: Pedro Sakamoto <71204668+2jjj@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:45:20 -0300 Subject: [PATCH] fix(db): reset budget counters before validating on a fresh window (#9241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log --- .../9241-registered-keys-window-reset.md | 1 + src/lib/db/registeredKeys.ts | 6 ++++ tests/unit/db-registeredKeys-crud.test.ts | 32 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 changelog.d/fixes/9241-registered-keys-window-reset.md diff --git a/changelog.d/fixes/9241-registered-keys-window-reset.md b/changelog.d/fixes/9241-registered-keys-window-reset.md new file mode 100644 index 0000000000..deeb1e4baa --- /dev/null +++ b/changelog.d/fixes/9241-registered-keys-window-reset.md @@ -0,0 +1 @@ +- **fix(db):** `validateRegisteredKey` no longer rejects the first request of a fresh budget window when the previous window's usage already met the daily/hourly budget — the reset `UPDATE` zeroed the counters in the DB but the budget check still read the stale pre-reset snapshot, so the reset is now mirrored into the row before checking ([#9241](https://github.com/diegosouzapw/OmniRoute/pull/9241)) diff --git a/src/lib/db/registeredKeys.ts b/src/lib/db/registeredKeys.ts index e56fd8851b..2b0d0ddaf4 100644 --- a/src/lib/db/registeredKeys.ts +++ b/src/lib/db/registeredKeys.ts @@ -384,6 +384,8 @@ export function validateRegisteredKey(rawKey: string): RegisteredKey | null { const today = nowDay(); const hour = nowHour(); if (row.last_reset_day !== today || row.last_reset_hour !== hour) { + const dailyReset = row.last_reset_day !== today; + const hourlyReset = row.last_reset_hour !== hour; db.prepare( ` UPDATE registered_keys @@ -393,6 +395,10 @@ export function validateRegisteredKey(rawKey: string): RegisteredKey | null { WHERE id = ? ` ).run(today, hour, today, hour, row.id); + if (dailyReset) row.daily_used = 0; + if (hourlyReset) row.hourly_used = 0; + row.last_reset_day = today; + row.last_reset_hour = hour; } // Budget check diff --git a/tests/unit/db-registeredKeys-crud.test.ts b/tests/unit/db-registeredKeys-crud.test.ts index a9a3894062..06718ff56d 100644 --- a/tests/unit/db-registeredKeys-crud.test.ts +++ b/tests/unit/db-registeredKeys-crud.test.ts @@ -213,6 +213,38 @@ test("validateRegisteredKey respects budget limits", async () => { assert.equal(rk.validateRegisteredKey(created.rawKey), null); }); +test("validateRegisteredKey resets budget counters on a fresh window", async () => { + await resetStorage(); + const issued = rk.issueRegisteredKey({ + name: "Window Reset", + dailyBudget: 3, + }); + assert.ok("rawKey" in issued); + if (!("rawKey" in issued)) return; + + const db = core.getDbInstance(); + const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000) + .toISOString() + .slice(0, 10); + const previousHour = new Date(Date.now() - 60 * 60 * 1000) + .toISOString() + .slice(0, 13); + + rk.incrementRegisteredKeyUsage(issued.id); + rk.incrementRegisteredKeyUsage(issued.id); + rk.incrementRegisteredKeyUsage(issued.id); + db.prepare( + `UPDATE registered_keys SET daily_used = ?, hourly_used = ?, last_reset_day = ?, last_reset_hour = ? WHERE id = ?`, + ).run(3, 3, yesterday, previousHour, issued.id); + + // First validation of the new window must be accepted (not rejected against the + // stale pre-reset counters) and must return the freshly-reset counters. + const validated = rk.validateRegisteredKey(issued.rawKey); + assert.ok(validated !== null); + assert.equal(validated.dailyUsed, 0); + assert.equal(validated.hourlyUsed, 0); +}); + // ──────────────── checkQuota ──────────────── test("checkQuota returns allowed true when no limits set", async () => {