Wesley Liddick
a2bc133747
Merge pull request #4163 from bestony/fix/openai-ws-ingress-lifecycle
...
fix(openai-ws): bound ingress session lifecycle
2026-07-13 16:07:53 +08:00
Wesley Liddick
4bc7486c3b
Merge pull request #4161 from fengshao1227/fix/remove-payment-channels-endpoint
...
fix(payment): 删除泄露内部 AI 渠道配置的废弃接口
2026-07-13 15:39:32 +08:00
Bestony@Homelab
c8cfc93632
fix(openai-ws): bound ingress session lifecycle
2026-07-13 15:32:42 +08:00
Wesley Liddick
b4aa3eb023
Merge pull request #4148 from feeeei/fix/retry_count
...
fix: 池模式同账号重试次数配置对 Anthropic/Gemini/通用转发路径生效
2026-07-13 15:32:40 +08:00
li
03ccb2a08e
fix(payment): 删除泄露内部 AI 渠道配置的废弃接口
...
Fixes #4160
2026-07-13 15:22:39 +08:00
shaw
8315defe8e
Merge branch 'main' into feat/grok-video-edit-extension
2026-07-13 14:50:18 +08:00
feeeei
c7c933776d
fix: 池模式同账号重试次数配置对 Anthropic/Gemini/通用转发路径生效
...
e643fc38 引入 pool_mode_retry_count 账号配置时,只覆盖了 OpenAI 族
handler 的内联重试循环;走 HandleFailoverError 的 Anthropic/
Antigravity/Gemini/通用转发路径仍硬编码同账号重试 3 次,配置不生效。
为 HandleFailoverError 增加 retryLimit 参数,由调用方传入
account.GetPoolModeRetryCount();未配置账号默认仍为 3 次,行为不变。
2026-07-13 12:57:00 +08:00
yan9651688
3605a316af
Keep usage ranges consistent across API and dashboards
...
Expose the active weekly subscription window through /v1/usage, calculate offsets with the same normalized page size used by queries, and keep user-facing date ranges on the browser's local calendar date.
Constraint: Preserve existing response fields and avoid new dependencies
Rejected: Keep duplicate inline date formatters | a shared local-date utility prevents the same UTC regression in both views
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Offset and Limit based on the same normalized page size
Tested: go test ./internal/pkg/pagination ./internal/handler; go vet ./internal/pkg/pagination ./internal/handler; frontend 923 tests; pnpm typecheck; pnpm lint:check; pnpm build
Not-tested: Live API request against a deployed subscription
Related: #4121
2026-07-13 11:35:46 +08:00
chenjian
909b96edd2
feat: support Grok video edits and extensions
2026-07-13 10:50:42 +08:00
Wesley Liddick
b73d8c3efe
Merge pull request #4009 from heathermhuang/codex/fix-recent-grok-issues
...
fix: expand Grok API, CLI, billing, and setup support
2026-07-13 10:36:11 +08:00
Heatherm Huang
8a22dc7347
fix(grok): diagnose unavailable models by platform
2026-07-13 10:11:34 +08:00
Heatherm Huang
3375b4ed2b
fix(grok): route OAuth subscriptions through CLI proxy
2026-07-13 10:11:33 +08:00
shaw
7cbb36f278
feat(billing): Codex alpha/search 网页搜索按次计费
...
- alpha/search 成功请求(上游 2xx)按次计费落 usage_logs(billing_mode=per_request),
上游错误透传/failover 不计费;使用 mandatory 池提交,池满同步兜底不丢扣费
- 单价默认 0.01 USD/次(官方 $10/1000 次),分组新增 web_search_price_per_call
覆盖价(0=免费,负数/留空=默认价),实际扣费叠加分组费率倍数(与 token 口径一致)
- 分组字段全链路:ent schema + 迁移 174 + 端口快照 v15 + admin 创建/更新 + DTO
- 前端分组表单(openai 平台)新增单次价格配置,实时预览应用当前倍率后的单次价格
- 该端点鉴权维持仅 OpenAI 分组(非 OpenAI 分组 404)
2026-07-13 09:54:51 +08:00
shaw
8d51364c3d
Merge remote-tracking branch 'origin/main' into feat/grok-prompt-cache-identity
...
# Conflicts:
# backend/internal/handler/endpoint.go
# backend/internal/service/openai_gateway_grok_test.go
2026-07-13 09:12:44 +08:00
Wesley Liddick
33b1d772f7
Merge pull request #4063 from yardbirds0/fix/codex-alpha-search
...
fix: 转发 Codex alpha/search 独立搜索端点
2026-07-13 08:58:01 +08:00
Wesley Liddick
841481e051
Merge pull request #3971 from yardbirds0/codex/fix-remote-compaction-v2
...
fix: 保留 remote_compaction_v2 原生 Responses 请求链路
2026-07-13 08:54:53 +08:00
superman2003
7050070aa3
fix(grok): route cacheable chat requests via responses
2026-07-12 11:50:56 +08:00
jjaw
06af8115f7
修复 compact 心跳 writer 生命周期
2026-07-12 04:53:41 +08:00
Tian Lee
52071d391b
fix(openai): 转发 Codex alpha/search 独立搜索端点
2026-07-11 20:31:33 +08:00
Tian Lee
84bb7d0709
fix: 保留 remote_compaction_v2 原生 Responses 链路
2026-07-11 12:51:53 +08:00
li
bc3cb29027
fix(handler): 补齐 opsCaptureWriter 全部委托方法的 nil 守卫
2026-07-10 21:03:40 +08:00
li
89a551b964
fix(handler): opsCaptureWriter 释放后访问 nil panic
...
Fixes #3961
2026-07-10 20:56:31 +08:00
Lyonle
f2966530c5
feat(openai): 支持用户级 Fast/Flex 策略
2026-07-10 15:16:09 +08:00
li
dda8f78733
fix(admin): GetUserBreakdown 使用 ParseUsageRequestType 解析 request_type
...
Fixes #3920
2026-07-10 11:43:44 +08:00
shaw
ae9a01d852
fix(compact): 二轮审计加固——心跳字节不得污染 failover 判定与并发写安全
...
对首轮修复的对抗式审计发现并修复以下问题:
1. failover 判定污染(真实回归风险):handler 以 "Forward 前后
c.Writer.Size() 是否变化" 判定响应是否已写出并据此放弃换号。心跳注释
字节会使该判定恒真,compact 请求一旦在上游等待期间发过心跳,上游
429/5xx 将不再 failover。新增
OpenAICompactKeepaliveAdjustedWrittenSize(扣除心跳字节、互斥锁下
一致读取、仅心跳字节归一化为未写哨兵 -1),快照、failover 比较与
openAIForwardErrorAlreadyCommunicated 三处判定统一改用该口径;无心跳
请求完全等价于原 c.Writer.Size()。
2. 并发写竞争:心跳 goroutine 与未被显式拦截的写回路径(Forward 内部
本地拒绝等)存在 ResponseWriter 数据竞争。StartOpenAICompactSSE-
Keepalive 现将 c.Writer 替换为 openAICompactKeepaliveWriter:写侧
方法(Header/Write/WriteString/WriteHeader/WriteHeaderNow/Flush)
先在互斥锁下停拍,读侧(Status/Size/Written)仅加锁不停拍——任何
请求侧响应构造与心跳从构造上互斥,热路径状态读取不误杀心跳。
3. 语义拦截补齐:rejectIfCyberSessionBlocked(在用户槽位长等待之后
执行的直接 c.JSON)与 writeOpenAIFastPolicyBlockedResponse 在心跳
提交后降级为 response.failed 终止事件;未提交时先停拍再写 JSON,
状态码语义不变。失败事件 errType 参数化(permission_error 等)。
4. reconstruct 混合形态:done 事件存在但 compaction 只在
output_item.added 中时也要补入;done 已含 compaction 时跳过 added,
避免无 id 可去重时收集两份(Codex 要求恰好一个)。
5. 观测性:logOpenAIRemoteCompactOutcome 对心跳提交后的失败(wire 200)
以 GetOpsStreamError 纠正 outcome,不再误记 succeeded。
新增 5 个测试:failover 口径不变式、包装器停拍语义(-race)、fast
policy 提交前后两态、混合 done/added 形态(含去重)。
Refs #3887 #3777
2026-07-10 10:18:16 +08:00
shaw
2cffe1cf5f
fix(compact): SSE→JSON 保留 raw output_item.done 并为 unary 等待补下游心跳(修复 #3887)
...
#3887 报告 v0.1.149(已含 #3880 桥接修复)remote compact 仍失败且持续
计费。核实为 #3880 明示的两个遗留:
1. 上游对 unary compact 返回 SSE 且 compaction item 只出现在 raw
response.output_item.done、终态 completed.response.output 为空
(#3777 实录形态)时,reconstructResponseOutputFromSSE 只累加
text/function_call/reasoning 三类 delta,compaction item 被丢弃,
桥接合成 0 个 output_item.done,Codex 报 "expected exactly one
compaction output item, got 0" 后盲目重试,每次重试重新消耗上游
compact 配额。
2. 桥接为全缓冲写回:上游 unary 完成前(大上下文可达数分钟,且上游
处理期间不发送任何字节)下游连响应头都收不到,反向代理
(Nginx/Cloudflare Tunnel)空闲超时掐断连接同样触发盲重连
(同类问题见 #2243/#2976)。
修复:
- reconstructResponseOutputFromSSE 优先以 raw JSON 逐字节收集
output_item.done item(协议上的最终完整形态),不经窄结构体,
encrypted_content/summary/opaque 等 compact 专属字段全部保留;
无 done 事件时退回收集 output_item.added 中的 compaction 类 item;
两者皆无才回到原 delta 重建。path-based v1 JSON 写回同样受益。
- 新增 openAICompactSSEKeepalive:body-signal 客户端流式 compact 在
上游等待期间按 gateway.stream_keepalive_interval 向下游写 SSE 注释
行心跳(eventsource 解析层直接忽略)。首拍延迟一个间隔,快速失败
仍走 JSON+状态码;首拍后状态码固化为 200,桥接/错误链路
(writeOpenAICompactSSEBridge、errorResponse、
handleStreamingAwareError、ensureForwardErrorResponse、
writeOpenAINonStreamingProtocolError)统一降级为 response.failed
终止事件并标记 ops 流内错误。
- API-key 账号的 compact 上游请求也强制 accept: application/json
(#3777 期望行为 4;透传白名单原会放行客户端的 text/event-stream)。
测试:#3777 实录形态经 handleSSEToJSON / 透传 / path-based 三条链路
的修补断言;raw-done 优先不与 delta 重复;added 回退门控;心跳提交、
提交后 2xx 续写、提交后失败降级、未提交行为不变;-race 通过。
Fixes #3887
Refs #3777 #3875 #3880
2026-07-10 09:56:56 +08:00
Wesley Liddick
dd1a116f48
Merge pull request #3881 from Wei-Shaw/feat/version-rollback
...
feat: 版本徽章新增近3个历史版本在线回退与手动回退指引
2026-07-09 22:39:41 +08:00
shaw
1c2e6503c6
feat: 版本徽章新增近3个历史版本在线回退与手动回退指引
...
后端:
- UpdateService 新增 ListRollbackVersions/RollbackToVersion,回退目标强制
限定为比当前版本更旧的近3个正式版本(排除当前/更新/预发布/草稿),
越界返回 400 ROLLBACK_VERSION_NOT_ALLOWED;下载复用既有 HTTPS 域名
白名单 + checksum 校验 + 原子换二进制管线
- GitHubReleaseClient 新增 FetchRecentReleases(releases 列表 API)
- 新增 GET /admin/system/rollback-versions;POST /admin/system/rollback
支持可选 {"version"} body,无 body 保持原 .backup 本地回退行为不变
- 端点均在 /admin 组 adminAuth(IsAdmin+TokenVersion)保护内
前端:
- VersionBadge「已是最新版本」状态下新增版本回退面板:radio 卡片选择
近3个版本,手动回退方式按部署形态 tab 切换(脚本部署 curl 命令 /
Docker 镜像 tag 指引),支持一键复制;release 构建提供一键回退并
复用重启流程;源码构建仅提示不支持在线回退
- 下拉根元素重置 whitespace-normal,修复 sidebar-brand 的
white-space:nowrap 继承导致的长文本溢出裁切
测试:服务层过滤/排序/上限/非法目标拒绝、handler 双模式与鉴权错误映射、
GitHub client 列表拉取、前端 API 请求体行为共 16 个新增用例
2026-07-09 22:30:04 +08:00
shaw
16c0613b21
fix(compact): body-signal 客户端流式请求的响应合成回 SSE
...
v0.1.147 的 body-signal 提升(#3804)把 Codex remote compact v2 的流式
/responses 请求改写为上游 unary /responses/compact(JSON),但把 JSON
文档原样写回给了按 Responses SSE 协议消费的客户端。Codex 只从
response.output_item.done 收集 item 且必须收到 response.completed,
收到 JSON 后报 "stream disconnected before completion: stream closed
before response.completed" 无限重连,每次重连再白烧一次上游 compact
配额(#3875,v0.1.146 无提升逻辑故正常)。
修复保留提升的全部收益(requireCompact 调度过滤、compact 模型映射、
白名单归一化、上游 unary JSON),补上协议转换缺失的响应半程:
- handler 在 body-signal 提升时记录原始 body 的 stream:true 意图
(MarkOpenAICompactClientStream);path-based 请求不标记,Codex v1
unary 协议与链式 sub2api 的 JSON 写回行为保持不变。
- 四个非流式写回点(handleNonStreamingResponse / handleSSEToJSON /
handleNonStreamingResponsePassthrough / handlePassthroughSSEToJSON)
对已标记的 2xx JSON 响应经 writeOpenAICompactSSEBridge 合成最小
Responses SSE:每个 output[] item 一条 response.output_item.done
(原始字段逐字节保留),最后一条 response.completed 携带完整
response 对象。
- 按 codex-rs 解析器硬约束兜底:先 json.Compact 防 pretty JSON 换行
破坏 SSE 帧;response.id 缺失时注入 resp_*(必填 string);usage
缺 input_tokens/output_tokens/total_tokens 整数字段时整体删除,
避免整条 completed 事件解析失败。
- 非 2xx 一律保持 JSON 原样,Codex 依赖 HTTP 状态码走重试链路。
新增 9 个 service 测试 + 4 个 handler 测试覆盖合成形态、id/usage 兜底、
path-based 不受影响、主链路/透传链路/上游 SSE 提取再合成三条端到端。
Fixes #3875
Refs #3777
2026-07-09 21:40:51 +08:00
shaw
7918b1a9c5
fix: 落实 #3867-#3870 合并审计的全部跟进项
...
透传规则(跟进 #3868/#3870,refs #3857):
- CC/Messages 4 条协议转换路径改用共享 helper,走语义状态推断 +
body 归一化,使按错误码配置的透传规则也能命中(原先传 0 恒不命中)
- helper 增加 platform 参数:本服务同时承载 openai/grok 平台账号,
规则须按 account.Platform 匹配,消除硬编码平台错配
- /v1/responses 两条路径命中透传规则时补记 ops 上游错误事件,
对齐 CC/Messages 与 antigravity 先例,消除监控盲区
用户角色管理(跟进 #3869):
- 补齐 EN 语言包缺失的 admin.users.form.roleLabel
- 新增"最后一个管理员不可降级"守卫,覆盖跨管理员互降致零 admin 锁死
- 角色变更/创建管理员落审计日志(含操作者 actor_admin_id)
2026-07-09 20:06:09 +08:00
Birditch and Claude Opus 4.8
b062b36646
feat(admin): 用量记录页新增"用户 Token 排行"面板
...
- 在 /admin/usage 新增按用户聚合的 Token 排行表(输入/输出/缓存/总 Token、请求数、费用)
- 支持按列排序、邮箱搜索、Top N (20/50/100/200),点击行下钻到该用户
- 复用现有筛选(用户/时间/模型/分组等)与既有 KPI 卡片、逐条日志
- 后端对 /admin/dashboard/user-breakdown 做加法扩展(向后兼容):
- UserBreakdownItem 增加 input_tokens/output_tokens/cache_tokens
- 新增 sort_by(白名单排序,防注入,缺省仍按 actual_cost)
- 新增 sort_by 转发单元测试;更新 UsageView.spec 稳定桩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-09 06:04:22 -04:00
Birditch and Claude Opus 4.8
64fdc11ec4
feat(admin): 用户创建/编辑支持选择与修改角色 (user/admin)
...
- 创建用户时可指定角色,缺省仍为 user,不再硬编码为普通用户
- 编辑用户时可在 user/admin 之间切换角色
- 后端对角色做 admin/user 合法性校验
- 防锁死保护:管理员不能把自己降级为普通用户(与既有"不能禁用/删除 admin"保护一致;降级其他管理员仍允许)
- 前端创建/编辑弹窗新增角色下拉,复用既有 i18n 文案
- 新增角色创建/更新/非法值/防降级单元测试
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-09 06:04:06 -04:00
Wesley Liddick
0438057c0b
Merge pull request #3816 from Vibeone/fix/ops-inband-sse-error-logging
...
fix(ops): 记录固化 200 SSE 流上的就地错误,修复流内限流不进错误看板
2026-07-09 16:35:29 +08:00
Wesley Liddick
c842c3166c
Merge pull request #3847 from heathermhuang/codex/grok-45-official-support
...
Add official Grok 4.5 support
2026-07-09 16:07:42 +08:00
Wesley Liddick
9ba0fb3084
Merge pull request #3775 from heathermhuang/codex/grok-media-pricing-labels
...
fix: add Grok video pricing controls
2026-07-09 15:03:38 +08:00
Wesley Liddick
b6d2df24d8
Merge pull request #3800 from Ge-limin/feat/codex-models-manifest
...
feat: Codex 客户端模型清单(manifest)透传接口
2026-07-09 14:40:23 +08:00
Wesley Liddick
dfff28ab05
Merge pull request #3843 from InCerryGit/fix/issue-3540-lenient-json-limit
...
fix(gateway): cap lenient JSON normalization
2026-07-09 14:39:55 +08:00
Wesley Liddick
9392d1fc43
Merge pull request #3841 from fengshao1227/fix/html-escape-site-name-and-sanitize-doc-url
...
fix(security): HTML-escape site_name 并对 doc_url 统一应用 sanitizeUrl
2026-07-09 14:39:26 +08:00
Heatherm Huang
cccba9a82e
Add official Grok 4.5 support
2026-07-09 14:26:10 +08:00
InCerry
53a5c45bd8
fix(gateway): cap lenient json normalization
...
Fixes #3540
2026-07-09 11:15:52 +08:00
li
bfb827b879
fix(security): HTML-escape site_name 并对 doc_url 统一应用 sanitizeUrl
...
Refs #3839 (第 8、12 点)
2026-07-09 10:03:49 +08:00
wucm667
e0d149d511
feat(api-key): show last used IP
2026-07-08 15:26:54 +08:00
Heatherm Huang
4d702e3234
fix: split Grok image and video pricing
2026-07-08 13:50:49 +08:00
Eyre921
5aba53d542
fix(ops): 记录固化 200 SSE 流上的就地错误,修复流内限流不进错误看板
...
流式请求一旦 flush 了 keepalive ping,HTTP 状态码即固化为 200;此后
出现的错误(等待并发槽位超时后回退的限流、Wait 后二次计费校验失败、
流开始后才无可用账号等)只能就地以 SSE error 帧回传。而 ops_error_logger
以 status>=400 为采集触发条件,这类挂在 200 流上的失败此前会在错误看板里
完全隐形——客户端能收到 rate_limit_error,但网关侧没有任何错误记录可供排障。
- service: 新增 OpsStreamError 上下文 + MarkOpsStreamError/GetOpsStreamError,
采用「首个标记生效」保留根因错误,避免被后续通用兜底帧覆盖。
- handler: handleStreamingAwareError 在 streamStarted 分支标记流内错误。
- handler: OpsErrorLoggerMiddleware 在 status<400 且无上游错误上下文时,
据标记补记一条错误日志;分级用 IntendedStatus(如并发限流 429),
StatusCode 仍记 wire 的 200。上游透传错误已由 upstream-context 分支落库,
故此路径不重复记录。
- 补充单测覆盖补记、no-op、skip_monitoring 跳过与首个标记生效。
2026-07-08 02:51:51 +00:00
Wesley Liddick
6f43986c37
Merge pull request #3811 from jianjianai/hotfix/admin-scheduler-score-opt-in
...
fix(admin): 管理员账号列表默认关闭调度权值计算以降低负载
2026-07-08 10:22:10 +08:00
shaw
a56eb5b4dc
fix(compact): body-signal 提升上移到 handler 层并对齐 path-based 链路
...
合并 main 解决拆分冲突后,将原先 Forward 内的 body-signal 提升重构到
handler 的 compact 归一化入口之前,修复原方案的四个问题:
- reqStream 未重推导:body-signal 原始请求带 stream:true,Forward 级提升
后 compact 上游返回 JSON(Accept: application/json)却被流式 handler
解析,"stream ended before a terminal event" 会触发最多
max_account_switches 次换号 failover,且每次都白烧一次上游 compact 配额;
handler 级提升让白名单归一化先删除 stream,reqStream 自然为 false。
- requireCompact 调度过滤失效:原方案 path 改写发生在 requireCompact 判定
之后,调度器不会过滤不支持 compact 的账号;现在改写先于该判定。
- passthrough / Grok / chat-completions 桥接分支位于 Forward 检测点之前,
passthrough 账号完全无法命中;handler 级改写对所有分支生效。
- body 归一化口径不一致:body-signal 现在与 path-based 一样走白名单归一化
(prompt_cache_key 等一并删除),而非仅依赖 OAuth 黑名单转换。
检测函数导出为 HasCompactionTriggerInInput 供 handler 使用,保留原 PR 的
7 个单测;新增 6 个 handler 级回归测试(提升、codex 别名路由、尾斜杠、
子路径不误伤、path-based 无双重后缀、普通请求不受影响)。
Refs #3777
2026-07-08 10:04:14 +08:00
shaw
bb5d2e84a1
refactor(handler): 纯移动拆分 setting_handler.go(3957→468行)
2026-07-08 08:49:22 +08:00
jjaw
6ae5fc31b3
fix(admin): gate scheduler score calculation
2026-07-08 06:58:35 +08:00
liminge and Claude Fable 5
13e773ef5e
feat: Codex 客户端模型清单(manifest)透传接口
...
背景:Codex CLI / Codex App 会从 provider 的 GET {base_url}/models?client_version=...
(自定义 provider 模式)或 GET /backend-api/codex/models(chatgpt_base_url 模式)
刷新模型选单,期望 ChatGPT Codex manifest 格式({"models":[{slug,...}]})。
sub2api 此前只提供 OpenAI 兼容格式的 /v1/models,Codex 客户端解析失败后静默
回落到本地缓存,导致指向 sub2api 的 Codex 客户端模型选单永久冻结在切换
provider 当天的状态,新模型(如 gpt-5.6 系列)永远不会出现。
方案:新增 manifest 透传——用组内可调度 OAuth 账号的凭据向
chatgpt.com/backend-api/codex/models 实时转发请求,响应体与 ETag 原样透传。
不在网关侧解析或维护模型清单:manifest schema 随 Codex 客户端版本演进,
透传保证网关无需跟进 schema 变化,且返回的始终是账号真实的模型权限
(区别于静态 DefaultModels 的"理论列表")。
路由:
- GET /backend-api/codex/models(新增)
- GET /v1/models 带 client_version 查询参数且组平台为 openai 时分发到
manifest 透传(client_version 是 Codex 客户端的天然指纹,普通 OpenAI
客户端不携带);其余请求保持原有 OpenAI 格式行为不变。
上游失败时按 fast-fail 返回错误,不伪造列表;Codex 客户端自身会回落缓存。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-07 19:49:40 +08:00
shaw
80a229bce5
fix(batch-image): 修复审计发现的计费死锁、状态机与队列原子性缺陷
...
修复 PR #3768 批量图像 MVP 合并后审计报告中的全部问题:
结算与计费(高危):
- 所有 SETTLEMENT_* 失败(超冻结/计数非法/manifest 冲突/定价缺失/扣费失败)
统一计入 retry_count 并在耗尽时释放冻结转 failed,消灭 settling 无限
requeue 导致的冻结余额永久锁死
- 耗尽出口的释放指纹统一为 RequestHash,与 processor/Cancel/recovery 一致;
release 遇同 request id 指纹冲突视为幂等成功,治愈历史毒消息
- 管理端校验 hold_multiplier >= discount_multiplier,定价快照对存量脏数据钳制
- 释放前校验 per-job hold claim(dedup+归档表),杜绝幻影释放
索引对账(高危):
- provider 输出与提交 custom_id 集对账:未知条目丢弃并记事件,
漏项补 PROVIDER_RESULT_MISSING 失败行,保证 success+fail == item_count
提交与恢复(高危):
- 提交前转 uploading 并在 provider.Submit 期间心跳刷新 updated_at;
恢复扫描改为原子复核(FailStaleUnsubmittedBatchImageJob),
消灭慢提交被误杀退款而上游任务照常计费的孤儿场景
- 上游任务创建成功但本地状态推进失败时,尽力取消上游并清理输入
- recovery 释放失败时入队交由 worker releaseTerminalHold 兜底重试
队列与并发(中危):
- Enqueue(SetNX+LPush)与 Reserve(BRPop+ZAdd)均改为 Lua 原子脚本,
消灭崩溃窗口导致 job 脱离队列、被 7 天 inflight 键锁死
- 锁冲突按 LockConflictDelay 重新入队(原直接丢弃需等 10 分钟 stale 恢复)
- 处理期间心跳:active zset 续期(ZAddXX 防幽灵成员)+ 锁 TTL 续期
- ReplaceBatchImageItemsForJob 增加 indexing 状态守卫,防掉队 worker 重写账目
存量回归(中危):
- image-only 定价条目(仅图片价无 token 价)恢复 token 计费 fail-closed,
不再按 $0 计费;图片计费路径不受影响
- 鉴权余额门槛恢复 balance <= 0 语义,MinimumBalanceReserve 不再作硬 403
加固:
- ZIP max_items 钳制到管理员上限;Submit 补齐 Platform==Gemini 校验;
gemini downloadUri 跟随前做 host 白名单校验
- 批量客户端改用共享 httpclient(拨号/TLS/响应头超时有界)
- 审计点名的忽略错误(MarkDownloaded/SettlementFailed/AppendEvent 等)改为记日志
2026-07-07 18:53:56 +08:00