From 199bd18125f1ed7c2d754baeec35081d0c50fafb Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:41:36 +0900 Subject: [PATCH 1/2] fix(routing): tolerate malformed historical attempts --- src/routing/analytics.ts | 15 ++++++++------- tests/routing-analytics.test.ts | 26 +++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/routing/analytics.ts b/src/routing/analytics.ts index bcf8f75ed..9e7b5a069 100644 --- a/src/routing/analytics.ts +++ b/src/routing/analytics.ts @@ -11,7 +11,7 @@ * sample for the full history. */ -import type { PersistedUsageEntry, PersistedUsageAttempt } from "../usage/log"; +import type { PersistedUsageEntry } from "../usage/log"; import { estimateRequestCost, serviceTierContext } from "../usage/cost"; import { openRequestHistoryIndex, requestHistoryDb } from "./history/indexer"; @@ -144,14 +144,15 @@ function parseEntry(rowJson: string): PersistedUsageEntry | null { } } -function attemptsOf(entry: PersistedUsageEntry | null): PersistedUsageAttempt[] | undefined { - return entry?.attempts; -} - function cooldownTriggering(entry: PersistedUsageEntry | null, status: number): boolean { if (status === 429) return true; - const attempts = attemptsOf(entry) ?? []; - return attempts.some(attempt => attempt.recoveryKinds.some(kind => COOLDOWN_RECOVERY_KINDS.has(kind))); + if (!Array.isArray(entry?.attempts)) return false; + return entry.attempts.some((attempt: unknown) => { + if (!attempt || typeof attempt !== "object") return false; + const recoveryKinds = (attempt as { recoveryKinds?: unknown }).recoveryKinds; + return Array.isArray(recoveryKinds) + && recoveryKinds.some(kind => typeof kind === "string" && COOLDOWN_RECOVERY_KINDS.has(kind)); + }); } function successCostUsd( diff --git a/tests/routing-analytics.test.ts b/tests/routing-analytics.test.ts index bc824bbe4..8231d23a3 100644 --- a/tests/routing-analytics.test.ts +++ b/tests/routing-analytics.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; -import { mkdtempSync, rmSync } from "node:fs"; +import { appendFileSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { handleManagementAPI } from "../src/server/management-api"; @@ -7,6 +7,7 @@ import { ManagementRequest } from "./helpers/management-auth"; import { appendUsageEntry, resetUsageReadCacheForTests, + usageLogPath, type PersistedUsageEntry, } from "../src/usage/log"; import { closeRequestHistoryIndex } from "../src/routing/history/indexer"; @@ -224,6 +225,29 @@ describe("routing analytics (RI-03)", () => { expect(result.cooldownTriggeringFailures).toBe(1); }); + test("ignores malformed attempts in historical failure rows", async () => { + appendUsageEntry(entry("baseline", { timestamp: 1, status: 200, durationMs: 10 })); + const historicalRows = [ + { + ...entry("missing-recovery-kinds", { timestamp: 2, status: 503, durationMs: 20 }), + attempts: [{ ordinal: 1 }], + }, + { + ...entry("malformed-attempts", { timestamp: 3, status: 503, durationMs: 30 }), + attempts: { recoveryKinds: ["rate-limit-429"] }, + }, + { + ...entry("malformed-recovery-kinds", { timestamp: 4, status: 503, durationMs: 40 }), + attempts: [null, { recoveryKinds: "rate-limit-429" }, { recoveryKinds: [null, 42, "unknown"] }], + }, + ]; + appendFileSync(usageLogPath(), `${historicalRows.map(row => JSON.stringify(row)).join("\n")}\n`); + + const result = await computeRoutingAnalytics({}); + expect(result.totalRequests).toBe(4); + expect(result.cooldownTriggeringFailures).toBe(0); + }); + test("routing analytics API returns 400 for invalid from/to/limit", async () => { const cases = [ { query: "from=abc", code: "invalid_from" }, From 36cffcef6c4ace2ccd70db2c8e04fedcc2de9f1f Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sat, 8 Aug 2026 18:56:06 +0900 Subject: [PATCH 2/2] test(routing): lock malformed 429 classification --- tests/routing-analytics.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/routing-analytics.test.ts b/tests/routing-analytics.test.ts index 8231d23a3..171a18594 100644 --- a/tests/routing-analytics.test.ts +++ b/tests/routing-analytics.test.ts @@ -225,7 +225,7 @@ describe("routing analytics (RI-03)", () => { expect(result.cooldownTriggeringFailures).toBe(1); }); - test("ignores malformed attempts in historical failure rows", async () => { + test("ignores malformed attempts while preserving explicit 429 classification", async () => { appendUsageEntry(entry("baseline", { timestamp: 1, status: 200, durationMs: 10 })); const historicalRows = [ { @@ -240,12 +240,16 @@ describe("routing analytics (RI-03)", () => { ...entry("malformed-recovery-kinds", { timestamp: 4, status: 503, durationMs: 40 }), attempts: [null, { recoveryKinds: "rate-limit-429" }, { recoveryKinds: [null, 42, "unknown"] }], }, + { + ...entry("malformed-429", { timestamp: 5, status: 429, durationMs: 50 }), + attempts: { recoveryKinds: ["unknown"] }, + }, ]; appendFileSync(usageLogPath(), `${historicalRows.map(row => JSON.stringify(row)).join("\n")}\n`); const result = await computeRoutingAnalytics({}); - expect(result.totalRequests).toBe(4); - expect(result.cooldownTriggeringFailures).toBe(0); + expect(result.totalRequests).toBe(5); + expect(result.cooldownTriggeringFailures).toBe(1); }); test("routing analytics API returns 400 for invalid from/to/limit", async () => {