fix(ai): reconciled codex self-heal with shared-scoped blocks
- markUsageLimitReached persists codex blocks under the strategy blockScope
("shared" since 0e259bb640); the healthy-usage reconcile guard read only
unscoped state, so the self-heal from PR #4844 never fired on current main.
- Guard now resolves the ranking-strategy blockScope like selection does.
- Updated PR tests to write/read shared-scoped blocks.
This commit is contained in:
@@ -4372,10 +4372,9 @@ export class AuthStorage {
|
||||
/**
|
||||
* Self-heal a stale Codex usage-limit block: when a fresh live usage report
|
||||
* shows the account is allowed and below every limit, drop the persisted and
|
||||
* in-memory `openai-codex:oauth` block so the balancer re-includes it. Only
|
||||
* Codex — its blocks are unscoped (`codexRankingStrategy` defines no
|
||||
* `blockScope`), so clearing all blocks for the credential id is exact. A
|
||||
* future scoped Codex block would require revisiting this.
|
||||
* in-memory `openai-codex:oauth` blocks so the balancer re-includes it. Only
|
||||
* Codex — its ranking strategy uses the single model-independent `"shared"`
|
||||
* scope, so clearing every block for the credential id is exact.
|
||||
*/
|
||||
#isHealthyCodexUsageReport(report: UsageReport): boolean {
|
||||
const metadata = report.metadata;
|
||||
@@ -4392,7 +4391,10 @@ export class AuthStorage {
|
||||
const providerKey = this.#getProviderTypeKey(provider, "oauth");
|
||||
const credentialIndex = this.#getStoredCredentials(provider).findIndex(entry => entry.id === credentialId);
|
||||
if (credentialIndex < 0) return;
|
||||
const blockedUntilMs = this.#getCredentialBlockedUntil(provider, providerKey, credentialIndex);
|
||||
// Mirror selection: consult the same strategy scope `markUsageLimitReached`
|
||||
// persists under, else a scoped block is invisible here and never healed.
|
||||
const blockScope = this.#rankingStrategyResolver?.(provider)?.blockScope?.({});
|
||||
const blockedUntilMs = this.#getCredentialBlockedUntil(provider, providerKey, credentialIndex, blockScope);
|
||||
if (blockedUntilMs === undefined) return;
|
||||
this.#clearCredentialBlocks(provider, credentialId);
|
||||
logger.info("Cleared stale Codex usage-limit block after healthy live usage report", {
|
||||
|
||||
@@ -350,7 +350,7 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
store.upsertCredentialBlock({
|
||||
credentialId: blockedRow.id,
|
||||
providerKey: "openai-codex:oauth",
|
||||
blockScope: "",
|
||||
blockScope: "shared",
|
||||
blockedUntilMs: Date.now() + 6 * 24 * HOUR_MS,
|
||||
});
|
||||
|
||||
@@ -398,7 +398,7 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
|
||||
await authStorage.fetchUsageReports();
|
||||
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
|
||||
expect(authStorage.getGeneration()).toBeGreaterThan(generationBeforeFetch);
|
||||
|
||||
const reconciledSelectionCounts = await countApiKeySelections(
|
||||
@@ -483,19 +483,19 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
baseUrlResolver: provider => (provider === "openai-codex" ? inFlightBaseUrl : undefined),
|
||||
});
|
||||
await inFlightStarted.promise;
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
|
||||
|
||||
const markResult = await authStorage.markUsageLimitReached("openai-codex", blockedSessionId, {
|
||||
retryAfterMs: 6 * 24 * HOUR_MS,
|
||||
});
|
||||
|
||||
expect(markResult.switched).toBe(true);
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
|
||||
|
||||
inFlightUsage.resolve(blockedReport);
|
||||
await inFlightReports;
|
||||
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
|
||||
const selectionCounts = await countApiKeySelections(
|
||||
authStorage,
|
||||
"openai-codex",
|
||||
@@ -599,14 +599,14 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
});
|
||||
|
||||
expect(markResult.switched).toBe(true);
|
||||
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
|
||||
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
|
||||
await blockPersisted.promise;
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
|
||||
|
||||
await clientStorage.fetchUsageReports();
|
||||
|
||||
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
|
||||
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
|
||||
expect(await clientStorage.getApiKey("openai-codex", blockedSessionId)).toBe("api-acct-broker-blocked");
|
||||
} finally {
|
||||
clientStorage.close();
|
||||
@@ -637,7 +637,7 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
store.upsertCredentialBlock({
|
||||
credentialId: blockedRow.id,
|
||||
providerKey: "openai-codex:oauth",
|
||||
blockScope: "",
|
||||
blockScope: "shared",
|
||||
blockedUntilMs,
|
||||
});
|
||||
|
||||
@@ -674,7 +674,7 @@ describe("AuthStorage codex oauth ranking", () => {
|
||||
|
||||
await authStorage.fetchUsageReports();
|
||||
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBe(blockedUntilMs);
|
||||
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBe(blockedUntilMs);
|
||||
});
|
||||
|
||||
test("falls back to earliest-unblocking account when all exhausted", async () => {
|
||||
|
||||
Reference in New Issue
Block a user