fix(subscription): 按来源和周期匹配 Codex 被动额度窗口 - #574
Conversation
被动额度合并前校验已有窗口与样本的周期;同 ID 但周期冲突时丢弃整个窗口补丁,避免 7d 被改成 5h,同时污染用量、重置时间及健康摘要。 保留兼容窗口与缺少周期字段的增量更新行为;补充合并与持久化回归测试,不改变限流、额度池或路由调度。
|
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 (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthrough配额窗口现在生成并传递 Merge Risk: ⚪ Minimal · up to Codex 被动额度现在仅在来源和实际周期唯一匹配时更新既有窗口,冲突或不确定数据会被保留而不会影响新鲜度或健康状态。当前变更具备针对性覆盖,未见剩余可操作的合并风险。 🚥 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.
🟢 Approval recommended
The functional change is small, targeted, and covered by new regression tests; remaining feedback is limited to comment/doc clarity.
Pull request overview
This PR fixes a passive-quota merge bug where a passive observation could incorrectly overwrite an existing quota window’s window_seconds (and related fields) when the upstream slot ID matches but the quota period differs (e.g., “primary” 7d being overwritten by “primary” 5h).
Changes:
- Skip merging a passive patch when both stored and patch windows provide
window_secondsand they conflict, preventing window repurposing. - Treat “only conflict/unmatched windows” responses as non-matching so they don’t advance
observed_at_msor update stored snapshots/health projection. - Add regression tests covering period conflicts, compatible updates alongside conflicts, and ensuring conflicting-only samples are acknowledged without persisting changes.
File summaries
| File | Description |
|---|---|
| internal/subscription/passive_quota_flush.go | Add period-conflict guard in passive merge and ensure non-matching/conflicting-only samples don’t advance freshness. |
| internal/subscription/passive_quota_period_test.go | Add unit/regression tests for conflict handling and flush behavior when conflicts occur. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // 未命中已有窗口或周期冲突的样本不能推进同步时间。 | ||
| // 窗口创建和周期变更仍由主动观测负责。 |
There was a problem hiding this comment.
已结合仓库规范复核,此翻译建议不采纳。CONTRIBUTING.md 的“代码风格 / Code style”明确要求注释优先使用简体中文;新增中文注释符合项目约定,无需为了邻近历史英文注释而反向统一语言。
当前注释也已随来源+周期匹配方案更新,准确说明未唯一匹配的样本不推进同步时间。因此无需修改代码。
处理状态:已尝试解决本线程,但操作被工具平台拦截;重新读取后确认它仍为未解决,未将其报告为已关闭。
主动同步保留 metered_feature,响应头按命名空间生成相同来源标识;不依赖模型名称或 Limit-Name。 按同一账号内的来源与实际周期唯一匹配已有窗口,允许 primary/secondary 槽位变化,但保留卡片 ID 与展示元数据。缺少来源/周期或存在歧义时不覆盖;其他渠道继续使用原有 ID 匹配。 补充解析、跨槽位合并、来源隔离、控制面序列化及持久化健康投影测试。无 429、路由或调度改动;旧快照需主动同步一次补齐来源。
明确无变化时保持原始字节,仅在窗口数据变化时重新编码;不修改任何运行逻辑。
|
本轮 review 核查与处理结果(提交
本次只改 1 处函数注释(+3/-5),没有修改额度匹配、429 或调度行为。已验证 PR 已推送更新,保持可评审状态,未合并。 |
* fix(ui): 接受订阅额度窗口的 source_id 字段 修复 #574 新增 source_id 后前端严格投影拒绝成功响应的问题。补齐可选 DTO 字段、白名单及字符串投影,保持其他未知字段校验,不修改后端额度匹配或限流行为。 新增前后端额度字段契约回归测试,避免后端新增字段却遗漏前端白名单和 DTO。 * chore(ui): 移除额度字段修复中的跨语言契约测试 * fix(subscription): 按 Active-Limit 判定被动额度通用组的归属 上游的 X-Codex-Primary/Secondary-* 报告的是本次请求实际计费到的额度, 而不是固定的普通账号额度。请求 Spark 等专属额度时这一组装的是该额度的 副本,按 codex 收下会用它覆盖同周期的普通窗口,把用尽的 7d 刷成满额。 改为由 X-Codex-Active-Limit 决定该组归属:premium/codex 归普通额度, 指向专属额度时改绑到该来源,上游已单独报告该来源则让位避免歧义。 缺少该头且响应里已有别的来源时按副本丢弃。 * fix(subscription): 按窗口去重被动额度副本而非丢弃整组 Active-Limit 指向专属额度时,通用组只有与独立命名空间来源和周期都相同 的窗口才是重复报告。原先只要该来源存在任意窗口就丢掉整组,会漏掉另一 个周期的刷新。改为解析后按来源加周期逐窗口去重。 * fix(subscription): 限定被动额度去重只比较独立命名空间 通用组内部的 primary 与 secondary 是两份数据而非互为副本,周期相同时 原实现让它们互相判定为重复并双双丢弃。去重范围收窄为通用窗口与独立 命名空间窗口之间。
关联 Issue / Related Issue
维护者反馈:普通 Codex 7d 额度耗尽后请求 Spark,刷新卡片时原 7d 被显示为错误的 5h,手动同步后恢复。未关联单独 Issue。
变更内容 / Change Content
本次迭代将原来的“同 ID 周期冲突则丢弃”补齐为 同一账号内按额度来源+实际周期唯一匹配,不涉及额度池调度方案。
上游依据与方案
已对照 OpenAI Codex 官方实现:
rate_limit为codex;专属窗口使用additional_rate_limits[].metered_feature,limit_name单独用于显示。Limit-Name可缺省。窗口新增可选
source_id,随主动观测经控制面序列化保存到既有snapshot_json。被动数据按source_id + window_seconds唯一匹配已有窗口,保留卡片原有 ID、名称、归属和窗口集合。primary/secondary不再作为 Codex 被动合并的身份依据。例如:主动查询的
primary=普通7d,可以由响应头secondary=普通7d正确刷新;同响应中的primary=普通5h若没有对应窗口就忽略,绝不写入 Spark 5h。Spark 按自身命名空间独立更新,名称变化或没有Limit-Name不影响匹配。保留的简单边界
primary/secondary时切错。不硬编码 Spark/模型映射,不增加依赖、数据库表、主动同步请求、SSE 采集或后台任务;不修改 429、路由、调度与原有额度状态推导。
测试 / Validation
在原有周期冲突测试之外,补充:来源隔离、主动/被动槽位变化、名称变化/缺省、任意来源与周期、缺少匹配信息、歧义目标、控制面字段往返,以及落库后卡片窗口集合和健康摘要保持正确。
已执行:
go test -race -count=1 ./internal/...、go vet ./internal/...通过。gofmt,上传内容与本地已验证文件通过 Git blob SHA 核对。本地验证限制:环境只有 Go 1.23.2,仓库要求 Go 1.27.0,容器 DNS 无法下载完整仓库及依赖。因此上述为隔离核心逻辑测试,不代表完整仓库测试通过;
make check、完整包测试和 GORM 持久化集成测试以本提交的仓库 CI 为准。没有使用维护者账号的真实凭据或采集现场原始响应,测试采用协议结构的合成样本。兼容性与人工验收
source_id是可选的 JSON 元数据,保留现有窗口 ID,无数据库迁移、配置或前端交互变更。升级后请主动同步 Codex 账号一次:旧快照没有来源标识,不能可靠匹配,因此在成功同步补齐前保留旧额度而不接受猜测性被动覆盖;已被旧逻辑污染的快照也由这次同步恢复。
验收:同步出普通 7d 与 Spark 独立窗口后分别请求,再刷新卡片,确认有数据的对应窗口分别更新、原有 7d 不变成 5h、普通未匹配 5h 不写入 Spark。不同账号仍按原有 credential ID / identity generation 隔离。
自查清单 / Checklist
make check的原因及未验证范围。