fix(subscription): 修复被动额度契约缺失与来源误归属 - #575
Conversation
修复 #574 新增 source_id 后前端严格投影拒绝成功响应的问题。补齐可选 DTO 字段、白名单及字符串投影,保持其他未知字段校验,不修改后端额度匹配或限流行为。 新增前后端额度字段契约回归测试,避免后端新增字段却遗漏前端白名单和 DTO。
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: QUIET Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughCodex 被动配额窗口现在根据命名空间和 Merge Risk: ⚪ Minimal · up to Codex passive quota windows now retain their source identity across backend and frontend handling, preventing metered quota updates from overwriting account quotas. The supplied coverage shows source-specific refreshes and period-aware deduplication behave as intended, with no material merge-readiness risk remaining. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
🟡 Changes recommended
The new Go contract test reflects the provider snapshot struct rather than the Control API response type, which can miss API-only fields and weaken the intended frontend/backend contract guarantee.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a frontend/backend contract mismatch that caused the UI to show “sync failed” even when the quota sync API returned success (code: 0, state: fresh): the backend added quota_windows[].source_id, but the frontend DTO and strict whitelist rejected it during projection.
Changes:
- Allow
source_idin the quota window whitelist and project it into the parsed quota window DTO. - Add
source_id?: stringtoCredentialQuotaWindowDtoso the Control API types match the response. - Add a Go “frontend contract” test to ensure future backend quota window field additions require updating the frontend whitelist/DTO together.
File summaries
| File | Description |
|---|---|
| web/src/app/resources/credentials.ts | Adds source_id to the strict allowed-field list and projects it into quota window parsing. |
| web/src/api/control/types.ts | Extends CredentialQuotaWindowDto with optional source_id. |
| internal/webui/credential_quota_contract_test.go | Adds a contract test intended to keep frontend whitelist/DTO aligned with backend quota window JSON fields. |
Review details
Suppressed comments (1)
internal/webui/credential_quota_contract_test.go:31
- After switching the import to
internal/control, reflect the actual serialized quota window type (control.ObservationQuotaWindow) instead of the provider snapshot struct, so the test covers the real web contract (includingobserved_usage).
windowType := reflect.TypeOf(providerobservation.QuotaWindow{})
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "reflect" | ||
| "regexp" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| providerobservation "gpt-load/internal/subscription/providers/observation" | ||
| ) |
上游的 X-Codex-Primary/Secondary-* 报告的是本次请求实际计费到的额度, 而不是固定的普通账号额度。请求 Spark 等专属额度时这一组装的是该额度的 副本,按 codex 收下会用它覆盖同周期的普通窗口,把用尽的 7d 刷成满额。 改为由 X-Codex-Active-Limit 决定该组归属:premium/codex 归普通额度, 指向专属额度时改绑到该来源,上游已单独报告该来源则让位避免歧义。 缺少该头且响应里已有别的来源时按副本丢弃。
There was a problem hiding this comment.
🔵 Needs a closer look
It includes behavior-changing backend quota parsing logic (beyond the described frontend DTO fix), so it needs careful human review of scope, correctness, and rollout impact.
Review details
- Files reviewed: 8/8 changed files
- Comments generated: 1
- Review effort level: Lite
| // passiveQuotaNamespaceFields are the header suffixes that belong to a limit | ||
| // namespace as a whole rather than to one of its windows. | ||
| var passiveQuotaNamespaceFields = []string{"limit-reached", "limit-name", "allowed"} | ||
| // namespace as a whole rather than to one of its windows. active-limit only | ||
| // ever appears on the response as a whole, which parses as the empty namespace. | ||
| var passiveQuotaNamespaceFields = []string{"limit-reached", "limit-name", "allowed", "active-limit"} | ||
|
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Team
Run ID: 5ca080a8-3e54-46e9-a511-b781222bd7cf
📒 Files selected for processing (6)
internal/subscription/passive_quota_source_flush_test.gointernal/subscription/passive_quota_source_test.gointernal/subscription/providers/codex/observation.gointernal/subscription/providers/codex/observation_test.gointernal/subscription/providers/codex/passive_quota_active_limit_test.gointernal/subscription/providers/codex/quota_source_test.go
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.
Active-Limit 指向专属额度时,通用组只有与独立命名空间来源和周期都相同 的窗口才是重复报告。原先只要该来源存在任意窗口就丢掉整组,会漏掉另一 个周期的刷新。改为解析后按来源加周期逐窗口去重。
通用组内部的 primary 与 secondary 是两份数据而非互为副本,周期相同时 原实现让它们互相判定为重复并双双丢弃。去重范围收窄为通用窗口与独立 命名空间窗口之间。
关联 Issue / Related Issue
Follow-up to #574。#574 上线后暴露两个问题:
code: 0、state: fresh,但账号卡片提示"无法同步账号信息"。变更内容 / Change Content
问题 1(前端契约遗漏):#574 新增后端
quota_windows[].source_id,遗漏前端 DTO 和严格字段白名单,导致projectQuotaWindow()拒绝成功响应并抛出InvalidResponseError。仅补CredentialQuotaWindowDto与quotaWindowFields接受可选source_id,沿用现有projectString()校验。问题 2(被动额度来源归属错误):
X-Codex-Primary/Secondary-*这组无命名空间的响应头,语义是"本次请求实际计费到的额度",不是固定的普通账号额度。请求 Spark 时该组装的是 Spark 自身的窗口;#574 把这组无条件标成source_id=codex,导致来源+周期精确匹配到了错误目标(Spark 7d 覆盖普通 7d)。修复为按
X-Codex-Active-Limit判定该组归属:premium/codex→ 普通账号额度codex_bengalfox)→ 改绑到该来源(来源, 周期)逐窗口去重,避免误删另一周期的正常刷新不硬编码 Spark 模型映射,不新增依赖、数据库表,不修改 429、路由或调度。
测试 / Validation
新增/修改:
source_id存在时保留,不存在时兼容旧响应,其他未知字段仍拒绝。已执行:
make check全部通过(含-race、CPA、数据库合同、windows-encryption-acl)。git --no-pager diff --check通过。兼容性与人工验收
无数据库或配置迁移,不需要删除或重新导入账号。部署须包含重新构建后的前端。
人工验收:
自查清单 / Checklist
make check并全部通过。