Repository navigation
fix(quota): record main-login responses under the observed main credential #6830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6bfd76b
dbc171d
18f8cc0
cecd192
5d96e50
4466135
faca1ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto"; | ||
| import { codexCredentialMutationEpoch } from "./credential-mutation-epoch"; | ||
| import type { StoredAccountQuota } from "./quota-types"; | ||
| import { truncateRetainedUtf8 } from "../lib/admission"; | ||
|
|
||
|
|
@@ -73,6 +74,48 @@ export function isMainQuotaWriterLive(writer: MainQuotaWriter): boolean { | |
| && writer.identityGeneration === mainAccountIdentityGeneration; | ||
| } | ||
|
|
||
| /** Proof that a dispatch used the observed main credential; process-local, never persisted. */ | ||
| export type MainQuotaDispatch = Readonly<{ | ||
| writer: MainQuotaWriter; | ||
| credentialGeneration: number; | ||
| credentialMutationEpoch: number; | ||
| configGeneration: number; | ||
| }>; | ||
|
|
||
| // WS quota frames publish through their observer; prelude quota can only come from those frames. | ||
| // A real HTTP fallback after a failed upgrade never invokes the observer and stays unclaimed. | ||
| const wsObservedMainDispatches = new WeakSet<MainQuotaDispatch>(); | ||
|
|
||
| export function claimMainQuotaDispatchForWs(dispatch: MainQuotaDispatch): void { | ||
| wsObservedMainDispatches.add(dispatch); | ||
| } | ||
|
|
||
| export function isMainQuotaDispatchWsClaimed(dispatch: MainQuotaDispatch): boolean { | ||
| return wsObservedMainDispatches.has(dispatch); | ||
| } | ||
|
|
||
| /** Give a replacement physical attempt its own quota ownership without recapturing credential fences. */ | ||
| export function renewMainQuotaDispatchForAttempt(dispatch: MainQuotaDispatch): MainQuotaDispatch { | ||
| return { ...dispatch }; | ||
| } | ||
|
|
||
| export function captureMainQuotaDispatch( | ||
| accessToken: string, accountId: string | undefined, configGeneration: number, | ||
| ): MainQuotaDispatch | undefined { | ||
| if (!accountId || !matchesMainQuotaCredential(accessToken, accountId)) return undefined; | ||
| const writer = captureMainQuotaWriter(accountId); | ||
| return writer ? { writer, credentialGeneration: mainQuotaCredentialGeneration, | ||
| credentialMutationEpoch: codexCredentialMutationEpoch(), configGeneration } : undefined; | ||
| } | ||
|
|
||
| export function isMainQuotaDispatchLive(dispatch: MainQuotaDispatch): boolean { | ||
| // Other OpenCodex-owned credential publications also advance this epoch; | ||
| // dropping a main quota update after any such publication is the intended safe direction. | ||
| return isMainQuotaWriterLive(dispatch.writer) | ||
| && dispatch.credentialGeneration === mainQuotaCredentialGeneration | ||
| && dispatch.credentialMutationEpoch === codexCredentialMutationEpoch(); | ||
| } | ||
|
|
||
|
Comment on lines
+110
to
+118
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,220p' src/codex/main-account-cache.ts
rg -n 'isMainQuotaDispatchLive|configGeneration|MainQuotaWriter|captureMainQuotaDispatch' src/codex src/server/responsesRepository: lidge-jun/opencodex Length of output: 12382 🏁 Script executed: set -o pipefail
printf '%s\n' '--- core-codex-account.ts ---'
nl -ba src/server/responses/core-codex-account.ts | sed -n '100,160p'
printf '%s\n' '--- passthrough-delivery.ts ---'
nl -ba src/server/responses/passthrough-delivery.ts | sed -n '515,565p'
printf '%s\n' '--- quota.ts ---'
nl -ba src/codex/quota.ts | sed -n '260,335p'
nl -ba src/codex/quota.ts | sed -n '580,650p'
printf '%s\n' '--- auth-context capture caller and generation references ---'
rg -n -F -- 'captureConfigGeneration' src/codex src/server
rg -n -F -- 'configGeneration' src/codex src/server tests || test "$?" -eq 1
nl -ba src/codex/auth-context.ts | sed -n '1620,1670p'
printf '%s\n' '--- generation mutation candidates ---'
rg -n -F -- 'configGeneration' src | grep -v 'captureConfigGeneration' || true
rg -n -F -- 'configuration' src/codex/main-account-cache.ts src/codex/auth-context.ts src/server/responses | head -80Repository: lidge-jun/opencodex Length of output: 25700 🏁 Script executed: set -o pipefail
printf '%s\n' '--- quota commit guard ---'
rg -n -F -- 'function mayCommitAccountQuota' src/codex/quota.ts
nl -ba src/codex/quota.ts | sed -n '1,90p'
printf '%s\n' '--- state-store-sweeper generation contract ---'
nl -ba src/lib/state-store-sweeper.ts | sed -n '1,215p'
printf '%s\n' '--- quota writer callers around generation guard ---'
rg -n -F -- 'mayCommitAccountQuota' src/codex src/server
rg -n -F -- 'captureConfigGeneration()' src/config src/lib | head -80Repository: lidge-jun/opencodex Length of output: 13695 🏁 Script executed: set -o pipefail
printf '%s\n' '--- quota reconciliation state ---'
rg -n -F -- 'lastReconciledGeneration' src/codex/quota.ts
rg -n -F -- 'liveAccountIds' src/codex/quota.ts
nl -ba src/codex/quota.ts | sed -n '760,930p'
printf '%s\n' '--- main account live-id sources ---'
rg -n -F -- 'MAIN_CODEX_ACCOUNT_ID' src/codex src/config src/server | head -120
rg -n -F -- 'registerStateStore' src/codex/quota.ts src/codex src/configRepository: lidge-jun/opencodex Length of output: 22170 🏁 Script executed: set -o pipefail
printf '%s\n' '--- generation context construction ---'
rg -n -F -- 'setGenerationContextBuilder' src
rg -n -F -- 'codexAccountIds:' src
rg -n -F -- 'GenerationContext' src | head -120Repository: lidge-jun/opencodex Length of output: 8228 🏁 Script executed: set -o pipefail
nl -ba src/lib/state-store-registrations.ts | sed -n '65,100p'
rg -n -F -- 'function listLiveCodexAccountIds' src
rg -n -F -- 'listLiveCodexAccountIds' srcRepository: lidge-jun/opencodex Length of output: 2812 🏁 Script executed: nl -ba src/codex/routing/health-store.ts | sed -n '1,155p'Repository: lidge-jun/opencodex Length of output: 8595 🏁 Script executed: rg -n -F -- 'function mergeAccountQuota' src/codex/quota.ts
nl -ba src/codex/quota.ts | sed -n '200,265p'
nl -ba src/codex/quota.ts | sed -n '293,330p'Repository: lidge-jun/opencodex Length of output: 5711 Fence main quota dispatches on the current configuration generation. When the canonical OpenAI provider is active, Suggested fix import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
import type { StoredAccountQuota } from "./quota-types";
import { truncateRetainedUtf8 } from "../lib/admission";
+import { captureConfigGeneration } from "../lib/state-store-sweeper";
export function isMainQuotaDispatchLive(dispatch: MainQuotaDispatch): boolean {
return isMainQuotaWriterLive(dispatch.writer)
- && dispatch.credentialGeneration === mainQuotaCredentialGeneration;
+ && dispatch.credentialGeneration === mainQuotaCredentialGeneration
+ && dispatch.configGeneration === captureConfigGeneration();
}🤖 Prompt for AI Agents |
||
| export function getObservedMainQuotaIdentityKey(): string | undefined { | ||
| return observedMainQuotaIdentityKey; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an upstream-rewriter plugin is enabled, this proof describes the headers before the physical send rather than the credential actually dispatched.
sendWithConnectionPolicysubsequently callsrewriteUpstream, whose plugin contract permits replacing the URL and changingauthorizationorchatgpt-account-id; the WebSocket dial has the same rewrite facility. A plugin that swaps either the destination or credential therefore leaves this proof live, allowing quota headers from a non-main dispatch to update__main__and potentially trigger its hard lock. Capture or revalidate the proof against the post-rewrite destination and headers at the physical dispatch boundary, and bind the HTTP/WS observer to that result.AGENTS.md reference: structure/AGENTS.md:L7-L10
Useful? React with 👍 / 👎.