Skip to content

fix(subscription): 按来源和周期匹配 Codex 被动额度窗口 - #574

Merged
tbphp merged 3 commits into
mainfrom
fix/passive-quota-period-identity
Sep 6, 2026
Merged

fix(subscription): 按来源和周期匹配 Codex 被动额度窗口#574
tbphp merged 3 commits into
mainfrom
fix/passive-quota-period-identity

Conversation

@tbphp

@tbphp tbphp commented Sep 5, 2026

Copy link
Copy Markdown
Owner

关联 Issue / Related Issue

维护者反馈:普通 Codex 7d 额度耗尽后请求 Spark,刷新卡片时原 7d 被显示为错误的 5h,手动同步后恢复。未关联单独 Issue。

变更内容 / Change Content

  • Bug 修复 / Bug fix
  • 新功能 / New feature
  • 其他改动 / Other changes

本次迭代将原来的“同 ID 周期冲突则丢弃”补齐为 同一账号内按额度来源+实际周期唯一匹配,不涉及额度池调度方案。

上游依据与方案

已对照 OpenAI Codex 官方实现:

  • 主动查询的来源标识:普通 rate_limitcodex;专属窗口使用 additional_rate_limits[].metered_featurelimit_name 单独用于显示。
  • 响应头来源解析:头部命名空间对应来源 ID;下划线/连字符、大小写按同一规则规范化,Limit-Name 可缺省。
  • Active-Limit 的用途:用于选择本次限额错误对应的来源,不用于将所有普通额度头改绑到该来源。本 PR 不修改错误处理。

窗口新增可选 source_id,随主动观测经控制面序列化保存到既有 snapshot_json。被动数据按 source_id + window_seconds 唯一匹配已有窗口,保留卡片原有 ID、名称、归属和窗口集合。primary/secondary 不再作为 Codex 被动合并的身份依据。

例如:主动查询的 primary=普通7d,可以由响应头 secondary=普通7d 正确刷新;同响应中的 primary=普通5h 若没有对应窗口就忽略,绝不写入 Spark 5h。Spark 按自身命名空间独立更新,名称变化或没有 Limit-Name 不影响匹配。

保留的简单边界

  • 来源或正数周期缺失、来源/周期不对应、匹配不唯一:保留旧值,不退回名称或槽位猜测。
  • 同一响应中的其他可唯一匹配窗口正常更新;整条响应没有匹配时不推进同步时间,不写库或更新健康摘要。
  • 其他没有提供来源标识的渠道继续使用原有 ID 匹配及周期冲突保护。
  • 解析从已知字段后缀定位槽位,避免来源名称含 primary/secondary 时切错。

不硬编码 Spark/模型映射,不增加依赖、数据库表、主动同步请求、SSE 采集或后台任务;不修改 429、路由、调度与原有额度状态推导。

测试 / Validation

在原有周期冲突测试之外,补充:来源隔离、主动/被动槽位变化、名称变化/缺省、任意来源与周期、缺少匹配信息、歧义目标、控制面字段往返,以及落库后卡片窗口集合和健康摘要保持正确。

已执行:

  • 新匹配测试在旧解析/合并行为下失败,修改后通过。
  • 原样使用实际 Codex normalizer、观测类型、合并函数,并提取实际控制面 DTO/投影函数,在离线隔离模块中执行测试;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 的原因及未验证范围。
  • 范围聚焦,未修改 429、路由或调度。
  • 必要的修复说明与升级操作已记录于本 PR;无新增公开能力需要修改 README。
  • 提交和测试数据不包含真实凭据或敏感信息。
  • 已说明 JSON 兼容性及旧快照同步要求。

被动额度合并前校验已有窗口与样本的周期;同 ID 但周期冲突时丢弃整个窗口补丁,避免 7d 被改成 5h,同时污染用量、重置时间及健康摘要。

保留兼容窗口与缺少周期字段的增量更新行为;补充合并与持久化回归测试,不改变限流、额度池或路由调度。
Copilot AI lite review requested due to automatic review settings September 5, 2026 14:53
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Team

Run ID: fef2d5b2-c2fd-403e-8717-a0039e2033b8

📥 Commits

Reviewing files that changed from the base of the PR and between f46786e and 10f4ade.

📒 Files selected for processing (1)
  • internal/subscription/passive_quota_flush.go

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

配额窗口现在生成并传递 SourceID。被动窗口解析按已知字段后缀识别窗口。快照合并按来源和窗口周期进行唯一匹配。周期冲突、歧义目标和未识别窗口不会修改快照。刷新流程会丢弃冲突样本,且不推进新鲜度或健康投影。

Merge Risk: ⚪ Minimal · up to 10f4a

Codex 被动额度现在仅在来源和实际周期唯一匹配时更新既有窗口,冲突或不确定数据会被保留而不会影响新鲜度或健康状态。当前变更具备针对性覆盖,未见剩余可操作的合并风险。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了本次变更的核心内容,即按来源和周期匹配 Codex 被动额度窗口。标题简洁、明确,并与代码和测试变更一致。
Description check ✅ Passed 描述覆盖了关联 Issue、变更内容、测试验证、自查清单、兼容性和升级影响。描述明确说明未执行完整 make check 的原因及未验证范围,也说明了旧快照需要主动同步。虽然没有填写具体 Issue 编号,但已明确说明未关联单独 Issue,整体信息完整。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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_seconds and they conflict, preventing window repurposing.
  • Treat “only conflict/unmatched windows” responses as non-matching so they don’t advance observed_at_ms or 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.

Comment on lines +86 to +87
// 未命中已有窗口或周期冲突的样本不能推进同步时间。
// 窗口创建和周期变更仍由主动观测负责。

@tbphp tbphp Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已结合仓库规范复核,此翻译建议不采纳。CONTRIBUTING.md 的“代码风格 / Code style”明确要求注释优先使用简体中文;新增中文注释符合项目约定,无需为了邻近历史英文注释而反向统一语言。

当前注释也已随来源+周期匹配方案更新,准确说明未唯一匹配的样本不推进同步时间。因此无需修改代码。

处理状态:已尝试解决本线程,但操作被工具平台拦截;重新读取后确认它仍为未解决,未将其报告为已关闭。

Comment thread internal/subscription/passive_quota_flush.go Outdated
Comment thread internal/subscription/passive_quota_flush.go
主动同步保留 metered_feature,响应头按命名空间生成相同来源标识;不依赖模型名称或 Limit-Name。
按同一账号内的来源与实际周期唯一匹配已有窗口,允许 primary/secondary 槽位变化,但保留卡片 ID 与展示元数据。缺少来源/周期或存在歧义时不覆盖;其他渠道继续使用原有 ID 匹配。
补充解析、跨槽位合并、来源隔离、控制面序列化及持久化健康投影测试。无 429、路由或调度改动;旧快照需主动同步一次补齐来源。
@tbphp tbphp changed the title fix(subscription): 修复被动额度更新将 7d 窗口覆盖为 5h fix(subscription): 按来源和周期匹配 Codex 被动额度窗口 Sep 5, 2026
@tbphp tbphp self-assigned this Sep 5, 2026
@tbphp tbphp added the bug Something isn't working label Sep 5, 2026
@tbphp tbphp added this to the v2.0.0 milestone Sep 5, 2026
明确无变化时保持原始字节,仅在窗口数据变化时重新编码;不修改任何运行逻辑。

tbphp commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

本轮 review 核查与处理结果(提交 10f4ade8fc0572dab4f5d41db6eb1631c4336e4a):

  • 已修正 mergePassiveQuotaSnapshot 的文档说明:无变化时保持原始 raw 字节,仅数据变化时重新编码。对应线程已回复并解决。
  • 两条要求将中文注释改为英文的建议均已回复说明不采纳:仓库 CONTRIBUTING.md 明确要求注释优先使用简体中文,不做无关语言重写。其中一条已解决;另一条的解决操作被工具平台拦截,回读确认仍未关闭(首条语言建议,原评论 ID 3940991660),并已在线程内记录实际状态。
  • CodeRabbit 最新 review 没有新增可执行的代码意见;docstring coverage 为提示性指标,不为达到比例给测试函数堆砌重复注释。

本次只改 1 处函数注释(+3/-5),没有修改额度匹配、429 或调度行为。已验证 gofmt、Go 语法、空白检查及修改前后全部 1199 个非注释 token 一致,上传 blob SHA 与本地验证文件一致。上一功能提交 f46786e 的仓库 CI 已通过;本次提交的 CI #534 已启动,当前仍在运行。本地 Go 1.23.2 与项目 Go 1.27.0 不匹配且无法下载依赖,因此未在本地执行完整 make check

PR 已推送更新,保持可评审状态,未合并。

Repository owner deleted a comment from chatgpt-codex-connector Bot Sep 6, 2026
@tbphp
tbphp merged commit 2aa5002 into main Sep 6, 2026
11 checks passed
@tbphp
tbphp deleted the fix/passive-quota-period-identity branch September 6, 2026 01:29
tbphp added a commit that referenced this pull request Sep 6, 2026
* 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 是两份数据而非互为副本,周期相同时
原实现让它们互相判定为重复并双双丢弃。去重范围收窄为通用窗口与独立
命名空间窗口之间。
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.

2 participants