Skip to content

fix(combos): scope cached quota to provider-wide inference limits - #4090

Draft
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:agent/combo-quota-scope-20260909
Draft

fix(combos): scope cached quota to provider-wide inference limits#4090
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:agent/combo-quota-scope-20260909

Conversation

@luvs01

@luvs01 luvs01 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Keep account-, model- and service-scoped quota reports from excluding an entire Combo provider before its account and credential selection runs. For example, an exhausted Gemini group must not discard an Antigravity target using Claude, and an exhausted active key must not stand for other keys in a pool.

Quota producers now opt into routing evidence explicitly by handing the reporting helper the inference-wide subset that applies to the current single configured API key. A producer that omits that projection stays display-only, so reporting through the credential-bound helper never confers provider-veto authority on its own. The routing cache retains that evidence privately, bound to the captured key and destination, and rechecks it against the current configuration. Immediate selection, bounded cooldown waiting and reset-window ordering use the same evidence. A changed key, OAuth normalization or custom credential header cannot reuse another credential's cap. Single-key spending caps remain effective; Synthetic search windows and legacy ZAI MCP monthly figures remain display data.

This is one runtime quota-selection correction: three runtime files, two existing test files, the transport contract and eight Combo guide locales. Existing account admission, explicit target eligibility and response-driven cooldown/retry rules still decide whether a selected route can actually serve the request. The separate editor save-eligibility interpretation of display reports needs its own follow-up and is not changed here.

The credential binding and scope metadata never enter quota-report JSON or logs. Please include them in the security review of this change.

Verification

Based on dev 8026405d9a527085b3c972dc8630abf8fe3b0441; current HEAD 602ad8d3da97fd639366a39df0638b63955c03bf, using Bun 1.4.2. The CI evidence below belongs to the earlier 44e428ce head; final full CI is deliberately deferred until this branch is rebased onto the dev revision that carries #4104, as the maintainer requested.

  • Producer-to-cache-to-Combo cases cover Synthetic search and ZAI MCP exclusions while the positive OpenRouter single-key spending-cap control remains enforced.
  • Independent review found a probed key could differ from an explicit Authorization header or the registry's actual OAuth mode. Four new regressions failed before those guards; the custom Kimi key-provider control passed. A further Anthropic x-api-key override case also failed before its correction.
  • Provider-quota, Combo and core/Lab suites passed 248 tests and 953 assertions before the final header-guard extension. The final focused selection passed 18 cases and 44 assertions, including real Antigravity/Anthropic quota producers, header overrides, key rotation, environment references, multiple keys, last-good refresh, reset-window scope and bounded waiting.
  • Typecheck, privacy scan and git diff --check passed after the final changes.
  • Documentation built 425 pages. The added quota-scope text was read back from all eight generated Combo guides.
  • test:changed selected 824 of 1153 files and reached the 900-second lane limit after 7,008 passes, five named failures and 16 skips, with two additional Bun worker crashes. It has no completed full-suite result. Both crashed routing suites passed when isolated (36 tests, 290 assertions), as did four of the five named failures. The remaining model-detour LRU case timed out at 32.7s in the combined rerun; matched single-case runs passed on both clean dev (31.2s) and this candidate (31.0s), with 2,053 assertions each. The full run remains incomplete rather than being relabeled green; full author cross-platform CI and current-head review remain required before readiness.

All quota responses, credentials and endpoints in the regressions are synthetic or isolated test transports. No live provider or real credential was used.

Current-head CI follow-up

  • Attempt 1 failed Windows 6/6 when the restore-preservation fixture's child Bun process exceeded its internal 45-second deadline. No setting-preservation assertion was reached. Job evidence (author run 34322578719, job 102372402651).
  • One failed-job rerun passed that same restore test in 7.05s. Attempt 2 instead failed the token-guardian fixture's afterEach: deleting its temporary directory returned EPERM after the helper's 50 attempts. The refresh/persist operation had completed; the log does not distinguish permissions from an open handle. Job evidence (author run 34322578719, job 102378149666).
  • Both fixtures and the cleanup helper are unchanged from the PR base. On this HEAD, an isolated Windows/Bun 1.4.2 run of those two cases passed 2 tests / 12 assertions, including cleanup, in 19.3s. No implementation or timeout adjustment was made in response.
  • Subsequent source inspection identified a concrete missing wait: hardenConfigDir() starts optional asynchronous Windows ACL work, but this fixture deletes its home without awaiting the existing flushConfigDirHardening(dir) API. The narrowly scoped fixture correction is in test(oauth): await guardian fixture ACL hardening before cleanup #4104 (one test file, 4 additions / 1 deletion; its complete file passed 10 tests / 31 assertions). The failed CI did not capture the owning process handle, so this remains the strongest identified cause rather than proof that every EPERM has that origin.
  • Full CI remains failed. No further CI retry was started; maintainer evaluation of the failed run and the separate fixture correction remains outstanding.

Publication contract follow-up

At maintainer request, 602ad8d3d removes the defaulted inference projection. keyReport() now publishes routing evidence only when the producer passes its inference-only copy, and returns before resolving a binding otherwise. OpenRouter keeps the single-key spending-cap control, Synthetic keeps its search-window exclusion, legacy Z.ai keeps its MCP exclusion, and MiniMax stays display-only. The remaining producers pass their reports explicitly, so their behavior is unchanged and their scope decision is now recorded at the call site.

A new regression puts an exhausted MiniMax Token Plan behind a single-key key-auth provider in a failover Combo: the ownership binding would resolve, and the combo still selects MiniMax. Provider-quota passed 159 tests / 564 assertions, Combos passed 80 tests / 362 assertions, and typecheck and privacy scan are clean.

Review disposition

CodeRabbit proposed using MiniMax's displayed Token Plan depletion as key-scoped routing evidence. The source-backed disposition keeps it display-only: a plan-duration/countdown row does not prove all-resource inference unavailability, and MiniMax documents purchased-credit fallback after plan exhaustion. Binding such a row to a key would establish ownership but not the missing scope. The existing single-key OpenRouter spending-cap control remains enforced.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • The focused code review checked credential identity, quota scope and retained enforcement.
  • Full CI and maintainer review are complete.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.
  • I pushed my PR to the latest dev commit.
  • I resolved all correct Codex and CodeRabbit findings.
  • My PR is ready for review.

Current management state

This branch carries the requested publication-contract change on top of the reviewed prerequisite head. The maintainer explicitly requested waiting for #4104 to land in dev before rebasing and running final CI. #4104 is now ready at e16fa26 with 26/26 author CI jobs passing; its upstream execution approval and landing are still pending. No obsolete-head CI retry was dispatched here. Credential ownership and inference-wide exhaustion remain separate conditions; the response to that review is recorded above.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@luvs01

luvs01 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review current head 44e428ce46412dd07f0b76f3a5bd9eea5c64bf39, particularly the producer's inference scope, current credential/destination binding, effective OAuth normalization, header overrides, and both Combo eligibility and reset-window consumers. The description records the focused results and incomplete local changed-suite run; full author CI is linked there.

@github-actions github-actions Bot added the bug Something isn't working label Sep 9, 2026
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR metadata ==='
gh pr view 4090 --repo lidge-jun/opencodex --json number,title,headRefOid,baseRefOid,files,commits,url

echo '=== Changed files ==='
gh pr diff 4090 --repo lidge-jun/opencodex --name-only

echo '=== Diff ==='
gh pr diff 4090 --repo lidge-jun/opencodex --patch

echo '=== Candidate quota/combo files ==='
fd -t f . | rg -i '(combo|quota|routing|provider|credential|oauth)'

echo '=== Structural outline of changed TypeScript files ==='
while IFS= read -r f; do
  case "$f" in
    *.ts|*.tsx)
      echo "--- $f ---"
      ast-grep outline "$f" --items all --type class,function,interface,type --view expanded || true
      ;;
  esac
done < <(gh pr diff 4090 --repo lidge-jun/opencodex --name-only)

Length of output: 119859


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Outlines ==='
for f in src/providers/quota-routing-cache.ts src/providers/quota.ts src/combos/resolve.ts src/providers/key-store.ts src/providers/registry.ts src/router.ts; do
  echo "--- $f ---"
  ast-grep outline "$f" --items all --view expanded || true
done

echo '=== Routing cache ==='
cat -n src/providers/quota-routing-cache.ts

echo '=== Combo resolution ==='
cat -n src/combos/resolve.ts

echo '=== Key-store and registry functions used by binding ==='
rg -n -C 12 'function (resolveProviderApiKey|providerUsesKeyAuthOverride)|export (function|const) (resolveProviderApiKey|providerUsesKeyAuthOverride)' \
  src/providers/key-store.ts
rg -n -C 12 'function getProviderRegistryEntry|export (function|const) getProviderRegistryEntry|authKind' \
  src/providers/registry.ts

echo '=== Quota producer and cache publication flow ==='
rg -n -C 14 'function (keyReport|maybeFetchProviderQuota)|export async function fetchProviderQuotaReports|replaceCachedProviderQuotas|routingEvidence|providerQuotaBeforePublishForTests' \
  src/providers/quota.ts

echo '=== All keyReport call sites ==='
rg -n -C 3 '\bkeyReport\s*\(' src/providers/quota.ts

echo '=== Effective routed provider configuration ==='
rg -n -C 12 'function routedProviderConfig|export (function|const) routedProviderConfig' src/router.ts
rg -n -C 8 '\broutedProviderConfig\s*\(' src --glob '*.ts'

echo '=== Combo cooldown/wait consumers ==='
rg -n -C 10 'pickComboTargetWithWait|targetProviderIsUsable|resetWindowIndex|getCachedProviderRoutingQuota' \
  src/combos --glob '*.ts'

echo '=== Quota cache reader consumers ==='
rg -n -C 4 'getCachedProviderQuota|getCachedProviderRoutingQuota|replaceCachedProviderQuotas' src tests --glob '*.ts'

Length of output: 230200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== routedProviderConfig complete implementation ==='
sed -n '295,430p' src/router.ts | cat -n

echo '=== API-key selection capture and pool selection ==='
cat -n src/providers/api-key-selection-capture.ts
sed -n '1,180p' src/providers/api-key-selection.ts | cat -n
rg -n -C 10 'apiKeyPool|_apiKeyAttempt|captureProviderApiKeySelection' src/providers --glob '*.ts'

echo '=== Quota readers and dispatch selection ==='
sed -n '400,610p' src/providers/quota.ts | cat -n
sed -n '850,1100p' src/providers/quota.ts | cat -n
sed -n '2280,2470p' src/providers/quota.ts | cat -n
sed -n '2890,2985p' src/providers/quota.ts | cat -n

echo '=== Cache key and refresh merge details ==='
sed -n '165,190p' src/providers/quota.ts | cat -n
sed -n '3060,3158p' src/providers/quota.ts | cat -n

echo '=== Provider config and apiKeyPool types ==='
rg -n -C 10 'interface OcxProviderConfig|type OcxProviderConfig|apiKeyPool|_apiKeyAttempt' src/types --glob '*.ts'

echo '=== Existing pool and binding tests ==='
rg -n -C 12 'apiKeyPool|routing quota scope|setProviderQuotaBeforePublishForTests|Authorization override|x-api-key' \
  tests/providers/provider-quota.test.ts tests/codex-integration/combos.test.ts tests/providers/api-key-selection-capture.test.ts

echo '=== Calls to report() in quota producers after this change ==='
rg -n -C 3 '\breport\s*\(' src/providers/quota.ts

Length of output: 160537


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Direct-report producer implementations ==='
for r in '585,635' '900,1035' '1080,1175'; do
  echo "--- src/providers/quota.ts:$r ---"
  sed -n "$r"p src/providers/quota.ts | nl -ba -v "${r%,*}"
done

echo '=== Key quota reader names and relevant direct-report providers ==='
rg -n -C 8 'function (isCanonicalMinimaxBaseUrl|isCanonicalDeepInfraBaseUrl|isCanonicalNeuralwattBaseUrl)|fetch(Minimax|DeepInfra|Neuralwatt)Quota|keyQuotaReaderForProvider' src/providers/quota.ts

echo '=== Existing Combo behavior tests for Minimax, DeepInfra, Neuralwatt, and direct reports ==='
rg -n -i -C 8 '(minimax|deepinfra|neuralwatt).*(combo|quota|exhaust|reset)|(?:combo|quota|exhaust|reset).*(minimax|deepinfra|neuralwatt)' tests src --glob '*.test.ts'

echo '=== Static credential headers in registry ==='
rg -n -i -C 6 'staticHeaders|authorization|x-api-key|x-goog-api-key' src/providers/registry.ts

echo '=== Exact changed-source line references ==='
nl -ba src/providers/quota.ts | sed -n '905,950p'
nl -ba src/providers/quota-routing-cache.ts | sed -n '20,82p'
nl -ba src/combos/resolve.ts | sed -n '60,150p'

echo '=== Current PR check results ==='
gh pr checks 4090 --repo lidge-jun/opencodex || true

Length of output: 1713


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Direct-report producer implementations ==='
for r in '585,635' '900,1035' '1080,1175'; do
  echo "--- src/providers/quota.ts:$r ---"
  sed -n "$r"p src/providers/quota.ts | cat -n
done

echo '=== Key quota reader names and relevant direct-report providers ==='
rg -n -C 8 'function (isCanonicalMinimaxBaseUrl|isCanonicalDeepInfraBaseUrl|isCanonicalNeuralwattBaseUrl)|fetch(Minimax|DeepInfra|Neuralwatt)Quota|keyQuotaReaderForProvider' src/providers/quota.ts

echo '=== Existing Combo behavior tests for Minimax, DeepInfra, Neuralwatt, and direct reports ==='
rg -n -i -C 8 '(minimax|deepinfra|neuralwatt).*(combo|quota|exhaust|reset)|(?:combo|quota|exhaust|reset).*(minimax|deepinfra|neuralwatt)' tests src --glob '*.test.ts' || true

echo '=== Static credential headers in registry ==='
rg -n -i -C 6 'staticHeaders|authorization|x-api-key|x-goog-api-key' src/providers/registry.ts

echo '=== Exact changed-source line references ==='
sed -n '905,950p' src/providers/quota.ts | cat -n
sed -n '20,82p' src/providers/quota-routing-cache.ts | cat -n
sed -n '60,150p' src/combos/resolve.ts | cat -n

echo '=== Current PR check results ==='
gh pr checks 4090 --repo lidge-jun/opencodex || true

Length of output: 60495


@luvs01 I found one routing regression.

P1 — Keep the MiniMax Token Plan quota as routing evidence

In src/providers/quota.ts Line 938, fetchMinimaxQuota() returns report(...) instead of keyReport(...).

This probe:

  • Requires the active API key.
  • Sends that key to the canonical MiniMax endpoint.
  • Computes a utilization percentage from the Token Plan total and remaining values.
  • Represents the Token Plan quota that inference requests consume.

However, src/providers/quota-routing-cache.ts Line 50 only retains routing evidence that keyReport() stored in routingEvidence. Therefore, getCachedProviderRoutingQuota() returns null for MiniMax. src/combos/resolve.ts then treats an exhausted MiniMax provider as eligible for both normal Combo selection and reset-window ordering.

This changes the existing behavior. A MiniMax Token Plan at 100% can be selected first and fail only after dispatch.

Use the credential-bound producer path here:

return keyReport(provider, "minimax:token-plan-remains", {
  customWindows: [{ label, percent }],
  updatedAt: Date.now(),
}, config, apiKey);

Add a regression test that returns remains_time: 0 with a positive plan total. The test should verify that a single-key MiniMax Combo target is skipped for its fallback. It should also verify that a changed key, a multi-key pool, or a credential header override makes the snapshot inapplicable.

The credential binding, OAuth normalization, configured-header handling, and both Combo consumers otherwise match the stated design. The current GitHub resolve-pr check is still pending.

You are interacting with an AI system.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (0/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ All CI tests are green on my local testing.
  • ⬜ I pushed my PR to the latest dev commit.
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

This PR stays in draft until every box above is ticked.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 66 / 80

이 PR은 Combo가 “제공자 전체 할당량 캐시”로 타깃을 미리 버릴 때, 그 증거가 지금 설정된 단일 API 키의 모델 추론에만 적용되는 경우에만 쓰이게 고칩니다. 지금 dev HEAD 8026405d9src/combos/resolve.tsgetCachedProviderQuota(provider)로 소진 여부를 보고, OpenAI forward만 isCanonicalOpenAiForwardProvider로 예외 처리합니다. 그래서 예를 들어 Antigravity의 Gemini 그룹이 비면 Claude 타깃까지 통째로 빠지거나, 풀의 활성 키 한도가 다른 키를 대표하는 식이 됩니다. 이번 변경은 src/providers/quota.tskeyReport가 프로브한 자격증명+어댑터+baseUrl 바인딩(sha256, 로그/리포트 JSON에는 안 넣음)을 WeakMap으로 붙이고, quota-routing-cache.tsgetCachedProviderRoutingQuota가 그 증거만 읽게 합니다. OAuth/forward 정규화, 다중 키, Authorization/x-api-key/x-goog-api-key 헤더 덮어쓰기, Synthetic 검색창·ZAI legacy MCP 월간은 display-only로 남기고 사전 제외/reset-window 순위에는 안 씁니다. Combo 가이드 8개 로케일과 structure/04_transports-and-sidecars.md에도 같은 경계를 적습니다. types/config 분할 캠페인과 무관하고, 계정 선택·응답 기반 cooldown/retry는 그대로입니다.

라인 src/providers/quota-routing-cache.ts · providerQuotaRoutingBinding - 자격증명 원문은 캐시에 안 남기고 다이제스트만 씁니다. 보안 리뷰 포인트로 PR 본문이 이미 표시한 부분이라, 머지 전에 “리포트 JSON/로그에 binding이 새지 않는지”만 한 번 더 확인하면 됩니다.
라인 src/combos/resolve.ts · targetProviderIsUsable - OpenAI forward 전용 예외를 제거하고 routing quota만 봅니다. forward/OAuth는 binding이 null이라 사전 veto가 안 걸리고, 기존처럼 계정 선택이 실제 한도를 봅니다. 방향은 맞습니다.
라인 src/providers/quota.ts · keyReport / inferenceQuota - Synthetic은 customWindows(search)를 추론 증거에서 빼고, ZAI legacy는 monthly를 뺍니다. 테스트가 OpenRouter 단일키 spending cap은 유지·헤더 override·키 교체·다중 키를 잠급니다.
경로 draft + CI - 아직 draft이고 readiness 체크리스트가 비어 있습니다. 작성자 CI는 test:changed가 900초 한도에 걸려 전체 스위트 미완이고, 워커 크래시/타임아웃을 격리 재실행으로만 설명합니다. ready 전에 이 브랜치 exact-head Cross-platform이 그린지 확인하세요.
경로 후속 범위 - PR이 말하는 “에디터 save-eligibility가 display report를 읽는 해석”은 이번 범위 밖입니다. 랜딩 후 별도 이슈로 남길지 정하세요.

메인테이너의 판단이 필요한 지점

  • credential binding 다이제스트를 WeakMap-only로 두는 보안 모델이 충분한지
  • save-eligibility 후속을 바로 열지, 이 PR만 먼저 넣을지
  • draft를 ready로 올리기 전에 요구할 exact-head CI 범위(changed vs full)

너의 추천
exact-head CI가 그린 뒤에 ready로 올리고 머지하세요. Combo 사전 제외가 잘못된 키/그룹 증거를 쓰는 실사용 버그라 2.49 백로그에 가치가 큽니다. draft 상태로 머지하지 말고, #4092와 경로가 달라 병렬 랜딩해도 됩니다.

이 댓글은 grok-bot이 작성했습니다

@luvs01

luvs01 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai The MiniMax Token Plan row intentionally remains display-only in this change; I am not adopting the proposed unconditional keyReport() conversion.

The current parser derives a displayed share from remains_time plus a plan-duration total. It does not establish that the key has exhausted every usable inference resource. MiniMax's current Token Plan documentation explains that the same Subscription Key can use purchased Credits after the plan's 5-hour or weekly allocation is exhausted. A depleted plan alone therefore does not prove that dispatch with that credential is unavailable.

Binding a snapshot to the correct key would establish ownership, but would not establish this missing scope. Treating the countdown row as a whole-provider veto would reproduce the class of false exclusion this PR fixes. The existing producer does not expose a documented, exhausted all-resource balance or a corresponding resource-selection policy, so the runtime leaves this row for display and lets actual dispatch/admission decide availability. The positive single-key OpenRouter spending-cap control remains covered and enforced. MiniMax could gain routing evidence later if an authoritative response and resource policy establish an applicable inference-wide limit.

The current HEAD remains 44e428ce46412dd07f0b76f3a5bd9eea5c64bf39; no additional implementation or CI retry was made for this disposition. The description separately records both CI attempts and their isolated cleanup/timeout checks.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🤖 web_search executed:

MiniMax Coding Plan documentation "After reaching the usage limit" Subscription Key purchased Credits after 5-hour weekly allocation exhausted

Length of output: 13269


@luvs01 Thanks for the clarification. I verified the current MiniMax documentation.

The documentation states that, after the 5-hour or weekly Token Plan limit, the same Subscription Key can automatically use purchased Credits when available. Therefore, a remains_time: 0 Token Plan row does not prove that all inference resources for that credential are unavailable.

I withdraw the MiniMax finding. src/providers/quota.ts Line 938 must keep this row on the display-only report(...) path. Changing it to keyReport(...) would allow src/providers/quota-routing-cache.ts to create a pre-dispatch Combo veto from incomplete scope evidence. That would reintroduce the false-exclusion behavior this PR corrects.

A future routing-evidence change needs both of these conditions:

  1. An authoritative MiniMax signal for an exhausted inference-wide resource.
  2. A documented resource-selection policy that proves no fallback resource can serve the request.

The OpenRouter single-key spending-cap control remains the appropriate positive enforcement case.


✏️ Learnings added
Learnt from: luvs01
URL: https://github.com/lidge-jun/opencodex/pull/4090

Timestamp: 2026-09-09T09:23:20.387Z
Learning: In `src/providers/quota.ts`, MiniMax Token Plan reports are display-only. A depleted 5-hour or weekly Token Plan allocation does not prove inference unavailability because the same MiniMax Subscription Key can use purchased Credits. Do not publish this report as credential-bound Combo routing evidence unless MiniMax provides an authoritative inference-wide exhaustion signal and a resource-selection policy.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@Ingwannu

Ingwannu commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Follow-up on the recorded CI blocker, without reclassifying a failed run as green: I have independently reviewed and approved the narrow Windows ACL-cleanup correction in #4104. It awaits integration; please use the resulting dev revision when rebasing this PR, then run the final full suite rather than repeatedly retrying 44e428c.

The quota-scope change itself remains unapproved in this pass. In particular, keep credential ownership and inference-wide exhaustion as separate requirements; a display-only countdown is not made provider-wide merely by binding it to a key. #4105 remains dependent on this runtime decision and should not ship the editor interpretation first.

@luvs01

luvs01 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Ownership and inference-wide scope are already two separate requirements

@Ingwannu Thank you — that is the right constraint, and I want to show where it is enforced rather than just assert agreement, because the two predicates are deliberately independent in this change and neither one alone can veto a provider.

Ownership is providerQuotaRoutingBinding() in src/providers/quota-routing-cache.ts. It returns null unless the provider is authMode: "key" with a resolvable credential, and it additionally refuses a registry-owned oauth or forward entry that has not actually been overridden to key auth, and any provider carrying a static authorization, x-api-key or x-goog-api-key header whose semantics belong to the adapter rather than to the probed key. When it does return, it is a SHA-256 over name, adapter, baseUrl and credential, kept private and never emitted in quota-report JSON or logs. getCachedProviderRoutingQuota() then also returns null when apiKeyPool holds more than one key, since an active-key report cannot speak for the other keys the dispatcher may select.

Inference-wide scope is a separate per-producer opt-in, keyReport(..., inferenceQuota) in src/providers/quota.ts. The default is the full display quota, but a producer that knows some of its rows are not inference constraints passes a reduced copy. Synthetic strips customWindows because search.hourly does not constrain model inference. Legacy ZAI strips monthlyPercent and monthlyResetAt because that monthly figure also carries MCP usage. The display report keeps those rows unchanged; only the routing copy loses them.

That is exactly why a display-only countdown does not become provider-wide by being bound to a key. Binding it would at most produce a valid binding value. The row still never enters inferenceQuota, so getCachedProviderRoutingQuota() has no inference-wide evidence to return and targetProviderIsUsable() cannot exclude the provider. Both predicates must hold, and satisfying ownership alone changes nothing. This is the same reasoning behind the MiniMax disposition: a plan-duration row does not prove all-resource inference unavailability, and MiniMax documents purchased-credit fallback after plan exhaustion, so I kept it display-only. The positive control is the OpenRouter single-key spending cap, which does satisfy both requirements and remains enforced.

If you would prefer the scope decision to be structurally explicit rather than a defaulted parameter — for example an explicit per-producer declaration that must name why each retained window is inference-wide — I will restructure it that way. I would rather change the shape now than have the reviewer infer the invariant from a default argument.

On sequencing: #4104 is now rebased onto dev c15a98caa at head f7f275f123780d38c1c96e7514db563f934f58f8, preserving the newer guardian terminal-verdict coverage, with author CI at 26/26. Its upstream runs need your execution approval again. I am deliberately not rebasing this PR or retrying 44e428ce until that actually lands in dev, as you asked; when it does I will rebase onto the resulting dev revision and run the full suite once rather than retrying the old head. #4105 stays behind this runtime decision and will not ship the editor interpretation first.

@Ingwannu

Copy link
Copy Markdown
Owner

Thanks for separating ownership from inference scope. I checked the current 44e428c implementation: providerQuotaRoutingBinding and the multi-key guard are independent of keyReport's inferenceQuota parameter, as you described.

Yes, please take the structurally explicit option you offered: make a producer deliberately opt into routing evidence and provide its inference-only projection, with omission remaining display-only rather than defaulting inferenceQuota to the full display quota. That avoids a future quota producer inheriting provider-veto authority merely by calling the reporting helper.

Keep the confirmed single-key spending-limit positive control, Synthetic search-window exclusion, legacy Z.ai MCP exclusion, and MiniMax display-only behavior. The tests should show that a credential-bound display-only report still cannot rank/veto the provider; ownership by itself is not the scope decision. This is a small publication-contract change, not a request for a broader quota rewrite. #4104's latest e16fa26 runs are now execution-approved; keep the stated landing/rebase sequence.

@luvs01

luvs01 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Routing evidence is now an explicit producer opt-in

Done in 602ad8d3da97fd639366a39df0638b63955c03bf. keyReport() no longer defaults inferenceQuota to the display quota. The projection is a separate optional argument, and a report that omits it stays display-only: the helper returns before it even resolves a binding, so nothing reaches the private evidence map. A new quota producer that calls the credential-bound helper therefore publishes a report and nothing else, and ownership on its own remains outside the scope decision.

Each existing producer now states its own scope at the call site:

  • OpenRouter passes its full report. The per-key spending cap stops every request that credential can make, so the positive control is unchanged.
  • Synthetic still passes a copy without customWindows, and legacy Z.ai still passes a copy without monthlyPercent/monthlyResetAt.
  • MiniMax still publishes through report() and gains nothing from this change.
  • A6 API, opencode-go, Cline, Ollama Cloud, modern Z.ai, Kimi and Command Code pass their reports explicitly, with a short note where the reason is not obvious from the rows. Their behavior is unchanged; the difference is that the decision is now written down instead of inherited.

The inline object literals at four of those call sites are hoisted into a named quota so the same value can be handed to both parameters. That is the only structural churn.

Verification

A new regression covers exactly the case you asked for. MiniMax is configured as a single-key key-auth provider in a failover Combo and returns an exhausted Token Plan (remains_time: 0 against a reported total, so the display row is 100%). providerQuotaRoutingBinding() would resolve for that credential, yet pickComboTarget() still selects MiniMax: a credential-bound display-only report cannot rank or veto the provider.

bun test tests/providers/provider-quota.test.ts passes 159 tests / 564 assertions, bun test tests/codex-integration/combos.test.ts passes 80 tests / 362 assertions, and bun run typecheck and bun run privacy:scan are clean. structure/04_transports-and-sidecars.md now records the publication contract and the decision-log entry follows it.

Per your sequence I have not rebased: this commit sits on 44e428ce. Once #4104 lands on dev I will rebase onto that revision and run the full suite there rather than retrying this base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants