From e555f7b44e6a8754c9e4439d005993eb6583b9fa Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:41:42 +0900 Subject: [PATCH 1/2] fix(routing): keep unbound quota evidence unknown --- .../docs/reference/configuration/routing.md | 15 ++++---- src/router.ts | 20 ---------- .../management/routing-profile-routes.ts | 22 ----------- tests/quota-scoring.test.ts | 38 +++++++++++++++++-- 4 files changed, 43 insertions(+), 52 deletions(-) diff --git a/docs-site/src/content/docs/reference/configuration/routing.md b/docs-site/src/content/docs/reference/configuration/routing.md index 862dd9867..44d012132 100644 --- a/docs-site/src/content/docs/reference/configuration/routing.md +++ b/docs-site/src/content/docs/reference/configuration/routing.md @@ -153,13 +153,14 @@ CLI: `ocx route policy list [--json]`, `ocx route policy show [--json]`, an Dry-run evaluates candidates without sending any upstream request. Quota evidence (`optimize.quota`, `require.minQuotaHeadroom`, `unknownEvidence.quota`) comes from -the local Codex pool and Anthropic account quota caches, which are keyed by account. In **Pool** mode -the canonical `openai` provider preserves its existing account selection, then reads quota for the -selected account; **Direct** mode reads quota only from the current (caller/main) account. For other -providers (e.g. Anthropic), runtime candidates use the provider's active account. Quota evidence -never changes account selection, session affinity, cooldowns, or switching behavior — it only feeds -policy scoring. To see quota-aware behavior in a dry-run, supply account refs through the dry-run/API -candidate evidence: `candidates[].codexAccountId` (Codex pool, provider `openai`) or +account-keyed Codex and Anthropic quota caches. A runtime candidate receives cached quota only when +the evidence already identifies the account. Unbound canonical `openai` and Anthropic candidates +remain unknown during policy evaluation because Pool selection, Direct caller identity, provider +rotation, and thread affinity are resolved after the policy chooses a provider/model; a process-active +account is not used as a substitute. +Quota evidence never changes account selection, session affinity, cooldowns, or switching behavior — +it only feeds policy scoring. To see quota-aware behavior in a dry-run, supply account refs through +the dry-run/API candidate evidence: `candidates[].codexAccountId` (Codex pool, provider `openai`) or `candidates[].accountRef` (Anthropic) derives the matching cached account quota; an explicit `candidates[].quota` object is echoed as given. diff --git a/src/router.ts b/src/router.ts index 599ec9bb9..ec1d5bb91 100644 --- a/src/router.ts +++ b/src/router.ts @@ -22,8 +22,6 @@ import { import { decodeRoutedModelId, encodeRoutedModelId } from "./providers/slug-codec"; import { getStaleCached } from "./codex/model-cache"; import { codexAccountNamespaceEntries } from "./codex/account-namespaces"; -import { getEffectiveActiveCodexAccountId } from "./codex/routing"; -import { getAccountSet } from "./oauth/store"; import { buildRouteDecisionTrace, type RouteDecisionKind, @@ -513,24 +511,6 @@ function routeModelInternal( quota: quotaEvidenceForCandidate({ provider: candidate.provider, model: candidate.model, - ...(candidate.provider === OPENAI_CODEX_PROVIDER_ID - && providerCodexAccountMode( - OPENAI_CODEX_PROVIDER_ID, - config.providers[OPENAI_CODEX_PROVIDER_ID], - ) === "pool" - ? (() => { - const codexAccountId = getEffectiveActiveCodexAccountId(config); - return { - codexAccountId, - codexAccountPlan: codexAccountId - ? config.codexAccounts?.find(account => account.id === codexAccountId)?.plan - : undefined, - }; - })() - : {}), - accountRef: candidate.provider === "anthropic" - ? getAccountSet("anthropic")?.activeAccountId - : undefined, }), cost: costEvidenceForCandidate({ provider: candidate.provider, diff --git a/src/server/management/routing-profile-routes.ts b/src/server/management/routing-profile-routes.ts index 36687c01b..08406b003 100644 --- a/src/server/management/routing-profile-routes.ts +++ b/src/server/management/routing-profile-routes.ts @@ -20,10 +20,6 @@ import { candidateCapabilityEvidence } from "../../routing/capability"; import { policyCandidateHealthEvidence } from "../../routing/health"; import { quotaEvidenceForCandidate } from "../../routing/quota"; import { costEvidenceForCandidate } from "../../routing/cost"; -import { providerCodexAccountMode } from "../../providers/registry"; -import { getEffectiveActiveCodexAccountId } from "../../codex/routing"; -import { getAccountSet } from "../../oauth/store"; -import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers"; import { saveConfigPreservingClaudeCode } from "../../config"; import { reconcileLiveStateStores } from "../../lib/state-store-registrations"; import { isPlainRecord } from "./shared"; @@ -115,24 +111,6 @@ function assembleCandidateEvidence( quota: quotaEvidenceForCandidate({ provider: candidate.provider, model: candidate.model, - ...(candidate.provider === OPENAI_CODEX_PROVIDER_ID - && providerCodexAccountMode( - OPENAI_CODEX_PROVIDER_ID, - config.providers[OPENAI_CODEX_PROVIDER_ID], - ) === "pool" - ? (() => { - const codexAccountId = getEffectiveActiveCodexAccountId(config); - return { - codexAccountId, - codexAccountPlan: codexAccountId - ? config.codexAccounts?.find(account => account.id === codexAccountId)?.plan - : undefined, - }; - })() - : {}), - accountRef: candidate.provider === "anthropic" - ? getAccountSet("anthropic")?.activeAccountId - : undefined, }), cost: costEvidenceForCandidate({ provider: candidate.provider, diff --git a/tests/quota-scoring.test.ts b/tests/quota-scoring.test.ts index 30a637d84..3e7634979 100644 --- a/tests/quota-scoring.test.ts +++ b/tests/quota-scoring.test.ts @@ -7,6 +7,7 @@ import { setCachedProviderAccountQuotaForTests, clearAccountQuotaCache } from ". import { quotaEvidenceForCandidate, quotaScore } from "../src/routing/quota"; import { evaluatePolicyProfile, QUOTA_UNKNOWN_PENALTY_SCORE } from "../src/routing/evaluator"; import { routeModel } from "../src/router"; +import { getAccountSet, saveCredential } from "../src/oauth/store"; import { closeRequestHistoryIndex } from "../src/routing/history/indexer"; import type { OcxConfig } from "../src/types"; @@ -195,7 +196,7 @@ describe("quota-aware scoring (RI-07)", () => { expect(penalized.candidates[0]!.score!.components.quota).toBe(QUOTA_UNKNOWN_PENALTY_SCORE); }); - test("execution path passes the active codex account into quota evidence", async () => { + test("execution path does not invent Codex quota evidence from the active pool account", async () => { updateAccountQuota("pool-a", 30, 1_800_000_000_000, 20, 1_900_000_000_000); const cfg = config({ codexAccounts: [{ id: "pool-a", email: "pool-a@example.test", isMain: false }], @@ -205,8 +206,39 @@ describe("quota-aware scoring (RI-07)", () => { }, }); const route = routeModel(cfg, "policy/quotaRoute"); - expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(true); - expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeCloseTo(0.7, 2); + expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false); + expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined(); + }); + + test("execution path does not invent Anthropic quota evidence from the active account", async () => { + await saveCredential("anthropic", { + access: "access-a", + refresh: "refresh-a", + expires: Date.now() + 3_600_000, + accountId: "uuid-a", + email: "a@example.test", + }); + const activeId = getAccountSet("anthropic")!.activeAccountId; + setCachedProviderAccountQuotaForTests("anthropic", activeId, { + fiveHourPercent: 40, + updatedAt: Date.now(), + }); + const cfg = config({ + providers: { + anthropic: { + adapter: "anthropic", + baseUrl: "https://api.anthropic.com", + authMode: "oauth", + models: ["claude-sonnet-5"], + }, + }, + routingProfiles: { + quotaRoute: { candidates: [{ provider: "anthropic", model: "claude-sonnet-5" }] }, + }, + }); + const route = routeModel(cfg, "policy/quotaRoute"); + expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false); + expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined(); }); test("exact account selectors and pool strategies remain authoritative", () => { From 6eff3f6a58654fc95fd0dc8495550c906b668506 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:10:20 +0900 Subject: [PATCH 2/2] docs(routing): clarify API account evidence --- .../src/content/docs/reference/configuration/routing.md | 9 +++++---- tests/quota-scoring.test.ts | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs-site/src/content/docs/reference/configuration/routing.md b/docs-site/src/content/docs/reference/configuration/routing.md index 44d012132..d51e152ad 100644 --- a/docs-site/src/content/docs/reference/configuration/routing.md +++ b/docs-site/src/content/docs/reference/configuration/routing.md @@ -159,10 +159,11 @@ remain unknown during policy evaluation because Pool selection, Direct caller id rotation, and thread affinity are resolved after the policy chooses a provider/model; a process-active account is not used as a substitute. Quota evidence never changes account selection, session affinity, cooldowns, or switching behavior — -it only feeds policy scoring. To see quota-aware behavior in a dry-run, supply account refs through -the dry-run/API candidate evidence: `candidates[].codexAccountId` (Codex pool, provider `openai`) or -`candidates[].accountRef` (Anthropic) derives the matching cached account quota; an explicit -`candidates[].quota` object is echoed as given. +it only feeds policy scoring. To see quota-aware behavior in an API dry-run, supply account refs in +the candidate evidence sent to `POST /api/routing-profiles/dry-run`: +`candidates[].codexAccountId` (Codex pool, provider `openai`) or `candidates[].accountRef` +(Anthropic) derives the matching cached account quota; an explicit `candidates[].quota` object is +echoed as given. The CLI dry-run cannot supply these per-candidate account fields. ### Combos vs policy profiles diff --git a/tests/quota-scoring.test.ts b/tests/quota-scoring.test.ts index 3e7634979..7eae3e474 100644 --- a/tests/quota-scoring.test.ts +++ b/tests/quota-scoring.test.ts @@ -206,6 +206,7 @@ describe("quota-aware scoring (RI-07)", () => { }, }); const route = routeModel(cfg, "policy/quotaRoute"); + expect(route.routeDecision!.candidates[0]!.accountRef).toBeUndefined(); expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false); expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined(); }); @@ -237,6 +238,7 @@ describe("quota-aware scoring (RI-07)", () => { }, }); const route = routeModel(cfg, "policy/quotaRoute"); + expect(route.routeDecision!.candidates[0]!.accountRef).toBeUndefined(); expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false); expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined(); });