feat: refine admin controls and overload protection

Refine settings navigation, selective pricing sync, model deletion, and admin interactions. Consolidate unreleased migrations and keep Fast policy scoped to groups. Increase confirmed capacity-rejection feedback without changing ordinary failure recovery.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-09-19 06:41:03 +08:00
co-authored by Codex
parent 16ae8c7afa
commit 4c7e4e08f6
112 changed files with 1466 additions and 982 deletions
+16 -12
View File
@@ -267,7 +267,8 @@ OpenAI 明确返回 `server_is_overloaded`、`slow_down` 或模型容量不足
尚未交付输出的前提下,先做最多 3 次同账号指数退避,再通过现有调度换号。默认间隔从 500ms 开始,
上游 `Retry-After` 参与退避计算,单次等待不超过 8 秒;重试同时受请求总尝试次数和截止时间约束。
`server_is_overloaded`、`slow_down` 等可计分的结构化错误按已发送的失败尝试计入 Smart 账号
健康分。失败率使用账号级平滑与时间衰减,影响后续普通选路,已有可用账号的
健康分。已确认容量拒绝的平滑权重为 0.4,其他可计分失败与成功样本保持 0.2。
失败率使用账号级平滑与时间衰减,影响后续普通选路,已有可用账号的
会话亲和仍优先。容量不足不触发 Provider 全局熔断,也不作为账号额度耗尽;启用账号自动冻结时,
达到容量失败阈值会另外写入临时冷却。
客户端错误兼容由 API 编码出口统一处理:最终交付的 `server_is_overloaded`、`slow_down` 错误码
@@ -857,7 +858,13 @@ PostgreSQL 或 Redis。
分组是 Provider-neutral 的账号集合;一个组可包含任意 Provider 账号,一个账号也可属于多个组。
分组详情和列表返回 `disableFast`,创建时省略默认为 `false`,更新时省略或 `null` 保留现值。
Client Key 绑定的任一分组开启此限制(包括已禁用分组)时,该 Key 的 OpenAI Responses 请求关闭 Fast;
未绑定分组的 Key 只受全局限制,不按最终所选账号的分组判断。
未绑定分组的 Key 不限制 Fast,不按最终所选账号的分组判断。
关闭 Fast 只将顶层 `service_tier` 的 `priority`(含 `fast` 别名)改为显式 `default`,继续处理请求;
不改变 `flex`、`ultrafast`、缺失值、默认档、嵌套字段或其他 Provider。
HTTP 和每个 WebSocket `response.create` 均使用请求开始时的分组策略,同一请求重试保持该策略;
HTTP 请求头及新建 WS 的握手提示按当时的最终出站档位构造;复用 WS 时不重发握手头,
每个 `response.create` 仍独立应用档位策略,请求档位统计与本地费用估算使用各帧的最终出站档位。
| 方法 | 路由 | 主要 query/body | 说明 |
| --- | --- | --- | --- |
@@ -971,7 +978,6 @@ HTTP 返回 `429`,`error.code` 为 `key_daily_budget_exceeded` 或 `key_weekly
```text
openaiClientProfile
disableFast
requestLocationEnabled
requestLocation
modelMappings
@@ -998,13 +1004,6 @@ accountAutoFreezeProbeModel
accountAutoFreezeAdaptiveConcurrency
```
`disableFast` 默认 `false`,更新时省略或 `null` 保留现值。全局开启时,所有 Key 的 OpenAI Responses 请求关闭 Fast;
全局关闭时仍应用 Key 绑定分组的限制。关闭 Fast 只将顶层 `service_tier` 的 `priority`(含 `fast` 别名)
改为显式 `default`,继续处理请求;不改变 `flex`、`ultrafast`、缺失值、默认档、嵌套字段或其他 Provider。
HTTP 和每个 WebSocket `response.create` 均使用请求开始时的配置,同一请求重试保持该配置;
HTTP 请求头及新建 WS 的握手提示按当时的最终出站档位构造;复用 WS 时不重发握手头,
每个 `response.create` 仍独立应用档位策略,请求档位统计与本地费用估算使用各帧的最终出站档位。
`requestLocationEnabled` 是必填布尔值,默认 `false`:关闭时不覆盖客户端原有位置和时区;开启时使用已保存的
`requestLocation`。关闭不会清空自定义值,代理自定义位置仍优先。
`requestLocation` 是必填的完整对象 `{ country, region, city, timezone }`,不接受 `null`;初始保存值为
@@ -1038,7 +1037,7 @@ HTTP 请求头及新建 WS 的握手提示按当时的最终出站档位构造
| `GET` | `/api/admin/settings/pricing` | 返回 `{ defaults, synced, overrides, syncedAt }` |
| `POST` | `/api/admin/settings/pricing/update` | `{ provider, models, change }`,成功返回 `{ saved: true }` |
| `POST` | `/api/admin/settings/pricing/sync/preview` | 无 body;返回 `{ prices, skipped }`,不写入配置 |
| `POST` | `/api/admin/settings/pricing/sync` | 原样提交确认的 `{ prices, skipped }`;成功返回 `{ saved: true }` |
| `POST` | `/api/admin/settings/pricing/sync` | `{ preview: { prices, skipped }, models: { openai: ["gpt-5.4"] } }`;成功返回 `{ saved: true }` |
价目使用 `Provider → 精确上游模型 ID → { multiplierBps, bands }` 的映射。优先级为人工覆盖、已同步价目、
内置价目;按档位合并,不从客户端模型别名或响应模型猜测价格。`syncedAt` 为 ISO 时间或 `null`。
@@ -1060,12 +1059,17 @@ OpenAI 长上下文为输入超过 272000 Token,xAI 为输入达到 200000 Tok
配置,未提供档位重新继承来源;内置和同步均未登记的模型必须包含 `standard`。
- `{ "action": "multiplier", "multiplierBps": 20000 }`:设置目标倍率,保留已有人工单价;重复提交不连续相乘。
- `{ "action": "reset" }`:移除人工单价与倍率,恢复同步价或内置价;仅有人工价格的模型恢复为未配置。
- `{ "action": "delete" }`:删除非内置模型的同步价目与人工配置;批量包含任何内置模型时整批返回 400,
即使该内置模型已有人工覆盖也不能删除。删除不影响历史账单,之后可重新添加或选中同步导入。
一批更新原子提交并写审计,不覆盖未选中的模型。未知字段、错误类型和非法价格字符串等 JSON 合同错误
返回 422;Provider、模型 ID、批量数量、倍率上限和不支持的档位等业务校验错误返回 400。
models.dev 同步只导入可表示为当前文本 Token 计价的 OpenAI/xAI 模型;不完整价格、其他输出模态及
不匹配的上下文梯度在 `skipped` 中返回 `provider/model`。确认会重新抓取价目;若与预览不同返回 400,
需重新预览。来源不可用返回 502,已有价目保持不变。同步只更新来源层,始终保留人工单价与倍率。
需重新预览。来源不可用返回 502,已有价目保持不变。`preview` 必须原样提交,`models` 按 Provider
指定 1~10000 个待同步模型;空选择或未登记的模型返回 400。同步只更新选中模型的来源层,未选中模型
及所有人工单价与倍率保持不变。选中的已同步模型若不再出现在来源价目中,则移除其来源层,恢复内置价格;
没有内置价目的模型变为未配置。
### OpenAI 上游客户端身份
+2 -2
View File
@@ -133,8 +133,8 @@ service;客户端原生对象保存在有界进程缓存中,与套餐 Redis
`RuntimeSnapshot → RoutingPlan → AttemptContext` 冻结传递,关闭时保留客户端原有字段;
Provider 在选定账号后应用代理位置覆盖。请求期间不额外查询全局设置,配置发布不改变已开始请求的全局值。
Fast 限制由同一快照链路冻结:全局开关与 Client Key 所有绑定分组的开关取逻辑或;
禁用分组仍贡献限制,无分组 Key 只受全局开关影响,不按所选账号的分组重新解释。
Fast 限制由同一快照链路冻结:Client Key 所有绑定分组的开关取逻辑或;
禁用分组仍贡献限制,无分组 Key 不限制 Fast,不按所选账号的分组重新解释。
OpenAI Provider 在独立编码请求上、生成上游头与观测前统一应用顶层档位覆盖,HTTP、WS 与重试共用。
该策略不改变选号、会话亲和或共享原始请求;每个新 WS 请求重新取得当前策略。
WS 路由提示属于握手,连接复用时不重发;档位变化不重建连接或打断 `previous_response_id` 续写,
-266
View File
@@ -1,266 +0,0 @@
# 上游客户端身份配置实施计划
状态:实现与本地验收完成,真实上游账号验收尚未覆盖。当前实现位于 `feat/upstream-client-profiles` 独立工作区。
代码核对基线:`3328a435`。
## 1. 目标与已确定的规则
为 OpenAI 上游请求提供 macOS、Linux、Windows 与 Desktop、CLI 组合的六套客户端身份预设,
支持自定义,并允许管理员在 Client Key 中单独选择,覆盖通用设置。
配置只保留两层:
```text
Key 有独立配置 → 使用 Key 配置
Key 跟随通用设置 → 使用通用设置
```
- 新安装的通用设置默认使用 **macOS · Desktop · 自动最新**,管理员可以修改。
- 新建及升级前已有的 Key 默认「跟随通用设置」。
- 内置默认值只用于初始化通用设置,不参与运行时配置优先级。
- 修改通用设置后,跟随它的 Key 在新请求中自动生效;独立配置的 Key 保持自己的选择。
- Key 切回「跟随通用设置」时清除独立覆盖,不保留参与运行时合并的隐藏字段。
- 独立覆盖以完整配置为单位,不逐字段混合 Key 与通用设置,避免组合出不一致的身份。
本文中的客户端身份指网关发给上游的 User-Agent、originator、平台和版本等信息。
该功能不改变接入客户端的最低版本门禁、客户端安装方式或网关宿主操作系统。
## 2. 当前实现与可复用入口
| 现有能力 | 代码入口 | 本次需要补齐的部分 |
| --- | --- | --- |
| 启动时的 Desktop 身份配置,默认 macOS | [OpenAI 配置](../../backend/crates/providers/openai/src/config.rs) | 将用户选择纳入持久化通用设置,支持客户端类型与平台选择 |
| 共享请求画像、Desktop 发布检查及版本元组更新 | [请求画像](../../backend/crates/providers/openai/src/transport/profile/mod.rs) | 按类型与平台解析版本,支持固定版本以及每请求独立身份 |
| 模型、账号及 Desktop 专属请求的头部组装 | [请求头](../../backend/crates/providers/openai/src/transport/headers.rs) | 按调用场景消费解析后的身份,保留不同接口的协议边界 |
| 通用设置管理 | [管理模型](../../backend/crates/gateway-admin/src/model/settings.rs)、[持久化](../../backend/crates/gateway-store/src/postgres/runtime_settings.rs) | 保存全局选择,复用事务、审计及配置发布 |
| Key 管理 | [管理模型](../../backend/crates/gateway-admin/src/model/client_keys.rs)、[管理用例](../../backend/crates/gateway-admin/src/use_case/client_keys.rs) | 保存可空的独立覆盖,并提供实际生效配置的投影 |
| 请求策略冻结与发布 | [运行快照](../../backend/crates/gateway-core/src/routing/snapshot.rs)、[快照发布](../../backend/crates/gateway-core/src/runtime/mod.rs) | 携带配置选择,保持同一请求及重试的一致性 |
| 上游连接池已有身份区分与精确续写;模型目录已有缓存隔离 | [WebSocket](../../backend/crates/providers/openai/src/transport/websocket/)、[账号目录](../../backend/crates/providers/openai/src/credential/catalog.rs) | 复用现有机制,核对新增身份字段是否完整进入隔离维度 |
| 设置页及首页身份展示 | [设置页](../../frontend/src/views/settings/)、[身份卡片](../../frontend/src/views/dashboard/components/WireProfileCard.vue) | 增加配置入口,明确全局展示与 Key 实际生效值的区别 |
目前画像类型包含 Desktop 专属字段,自动更新通过同一 Desktop 制品取得应用版本、Core 版本及构建号。
因此,六套配置需要按官方身份语义扩展,不能仅替换现有 UA 的操作系统名称。
## 3. 预设、自定义与版本策略
### 3.1 六套预设
| 平台 | Desktop | CLI |
| --- | --- | --- |
| macOS | macOS Desktop | macOS CLI |
| Linux | Linux Desktop | Linux CLI |
| Windows | Windows Desktop | Windows CLI |
平台与客户端类型决定预设;系统版本、CPU 架构及终端信息是预设参数,不再展开成额外的顶级选项。
预设值由后端统一提供,前端不维护另一份默认值和 UA 拼接规则。
实施前逐项核对官方源码或官方制品,记录版本、提交或制品标识、请求格式和更新来源。
尤其需要核实 Linux Desktop 的可用制品及身份依据,不能因为有六个组合就宣称六套均已获得官方验证。
没有可靠官方依据的组合保留自定义配置入口,标明核验状态;不伪造官方发布版本或自动更新来源。
### 3.2 自定义
通用设置和 Key 独立配置共用一套编辑模型:选择预设后,可以调整客户端标识、系统版本、
架构、终端及适用的版本字段,并预览最终请求身份。
- 首期将自定义内容保存在对应通用设置或 Key 中;命名模板库、共享模板引用及模板管理不属于本次范围。
- 后端按客户端类型生成 UA 与相关身份头;不增加任意请求头透传或任意发布源 URL 配置。
- 校验必填项、字段长度、HTTP 头合法性、控制字符及客户端类型对应的版本格式。
- Desktop 与 CLI 使用各自适用的字段;CLI 不携带由 Desktop 默认值遗留的应用版本后缀。
- 自定义值与「官方核验」状态分别展示,用户填写的值不能自行获得官方核验标记。
- 认证信息、账号 ID、Cookie、请求位置及数据驻留仍由现有职责管理,不纳入身份自定义。
### 3.3 自动最新与固定版本
| 模式 | 行为 |
| --- | --- |
| 自动最新(默认) | 跟随该客户端、平台及适用架构对应的最新已核验稳定发布;后台更新,不在业务请求中访问发布源 |
| 固定版本 | 使用保存的版本;后台发布检查不能覆盖固定值 |
自动最新只更新版本资料,不覆盖用户的平台、架构、终端等选择。手动修改版本时转为固定版本,
避免保存后又被后台任务覆盖。恢复自动最新后,重新采用对应发布源的有效版本。
- Desktop 应用版本、内嵌 Core 版本和构建号作为同一制品的完整元组更新;不拼接独立 CLI 的最新版本。
- CLI 使用对应官方 CLI 发布信息,不借用 Desktop 发布号。
- 不假设不同平台的 Desktop 同步发布或共享构建号。
- 更新失败保留同一预设上次成功的版本,并展示检查失败及检查时间;不得切到其他平台或客户端。
- 首次离线启动可使用同一预设随版本附带的已核验版本资料,并标明未完成最新检查。
这些资料属于版本解析器的种子数据,不是第三层用户配置。
- 没有有效发布源或启动资料的组合不能进入可用的「自动最新」状态;提供原因与固定自定义入口。
- Redis 中的发布缓存至少区分客户端类型、平台和影响制品选择的架构,不再仅凭 Provider 区分。
缓存失效和重启后的恢复也不得覆盖用户的固定配置。
## 4. 配置合同与职责
### 4.1 持久化与管理 API
拟增加的概念字段如下,最终名称在实施时与现有 API 风格统一:
| 位置 | 字段 | 语义 |
| --- | --- | --- |
| 通用设置 | `openaiClientProfile` | 必须存在的完整配置选择,包含预设或自定义参数、版本模式 |
| Client Key | `openaiClientProfileOverride` | `null` 表示跟随通用设置;对象表示完整独立配置 |
| 只读投影 | `effectiveOpenaiClientProfile` | 返回当前解析结果、配置来源、版本来源及检查状态,不作为另一份可写配置 |
新建 Key 省略覆盖字段等同于 `null`;更新 Key 省略字段保留已有覆盖,显式 `null` 才清除。
旧调用方更新通用设置时省略新增字段,应保留现值,不能重置管理员已保存的选择。
通用设置不接受以 `null` 制造第三层回退的语义。
配置由 PostgreSQL 保存。管理写入沿用现有事务、脱敏审计、revision 和快照发布流程。
官方发布结果继续作为 Provider 的派生缓存,不将每次版本检查写成用户配置或推进全局配置 revision。
职责依照 [系统架构](../architecture.md):
- Admin 负责全局与 Key 的管理规则和继承选择,复用同一解析入口生成生效投影。
- Core 负责请求策略的携带与冻结,不依赖具体 Provider,也不解释 UA、官方制品或请求头。
- OpenAI Provider 负责预设、官方版本解析、字段语义校验及上游头部生成,通过现有端口风格提供能力。
- Store 实现配置、迁移与缓存存取,不解释客户端身份的业务规则。
- API 负责 wire 校验及投影,前端负责编辑与展示,不重复推导最终身份。
### 4.2 生效范围
本次配置作用于 OpenAI Provider。Key 同时授权其他 Provider 时,不把 OpenAI 身份套用到 xAI。
由 Client Key 发起的 OpenAI 模型请求及其相关目录读取,消费该 Key 的有效身份;
HTTP/SSE、WebSocket 以及 Images、Search 等入口按实际调用链逐一核对覆盖。
OAuth 与 API Key 上游认证模式继续共用适用的身份规则,认证字段仍按各自协议生成。
后台令牌刷新、额度查询、账号管理及 Desktop 专属接口没有 Client Key 上下文,不能被某个 Key 的选择污染。
实施时按官方接口要求划分共用身份与 Desktop 专属身份,保留必要的独立请求格式;
全局选为 CLI 时也不能让 Desktop 专属接口意外携带缺失或错误的应用版本。
### 4.3 请求一致性与资源隔离
本节以接入现有机制为主。当前鉴权已持有 `RuntimeSnapshot`,重试通过原路由计划传递冻结策略;
WebSocket 连接池已将 `originator`、`user-agent` 等纳入连接身份,且已有
[画像更新后保留精确续写所属连接的测试](../../backend/crates/providers/openai/tests/transport/websocket_pool.rs)。
实施沿用这些快照、重试和续写机制,补齐新增配置的数据传递与必要隔离维度。
- 将通用与 Key 的身份选择接入已有配置快照,沿现有请求上下文传入 Provider,
同一逻辑请求的重试与换号继续使用冻结的选择。
- 自动发布版本目前属于 Provider 的共享运行时画像,`response_headers` 在组装头部时读取
`self.profile.snapshot()`,尚未由 `RuntimeSnapshot` 冻结。
为满足跨重试也使用同一解析版本的目标,在已有请求生命周期内保存首次解析结果并复用;
这部分与用户配置的快照继承分别验证,不改动重试决策和状态机。
- 禁止通过临时修改共享 `CodexWireProfileState` 实现 Key 覆盖;并发 Key 使用独立的不可变请求身份。
- 同一入站 WebSocket 的每个 `response.create` 按现有规则获取新策略。
普通连接获取复用现有身份匹配规则;精确续写继续使用持有响应状态的原连接,
保留已有的画像更新例外,不能因版本刷新强制断开或重建这类连接。
- 检查新增字段是否已被现有连接身份和模型目录缓存键覆盖,仅补齐影响协议结果的缺失维度。
Key 授权边界沿用现有机制;身份切换继续遵循原有续写和重放限制,避免上下文丢失或越界重放。
- 「当前生效」投影显示解析结果;请求观测使用本次冻结的摘要,不能事后用最新全局值替代。
## 5. 管理端交互
### 通用设置
新增「上游客户端身份」配置区,包含平台、客户端类型、预设/自定义、版本策略以及相应字段。
默认展示 macOS Desktop 自动最新;提供最终 UA、相关版本、发布检查状态及字段错误提示。
保存后通过现有设置发布机制对新请求生效,无需重启。
### Key 创建与编辑
默认选择「跟随通用设置」,同时展示当前通用配置摘要。
选择「独立配置」后使用与通用设置相同的编辑组件;首次切换可用当前通用值填充表单草稿,
只有保存后才形成独立配置。摘要明确标出「通用设置」或「Key 独立配置」。
### 现有展示
首页身份卡片标明展示的是通用配置,不将其描述成所有 Key 的唯一身份。
Key 详情展示该 Key 当前有效身份;复用后端投影,不在浏览器拼装 UA 或解析版本。
复用现有基础组件及 [管理端主题](../theme.md),提供保存失败、版本检查失败、无可用发布源等状态。
## 6. 升级与迁移
- 按 [迁移规则](../../backend/migrations/README.md) 新增编号迁移,更新冻结清单,不修改历史迁移。
- 新安装在初始化阶段写入通用默认配置;存量 Key 的覆盖初始化为 `null`。
- 升级需识别既有 `openai.wire_profile` 自定义值,首次导入通用配置时保留有效的环境与身份选择。
标准配置映射到 macOS Desktop 自动最新;显式自定义版本映射到固定自定义,并在升级说明交代。
- SQL 迁移不负责读取部署文件;必要的一次性导入由启动组装流程经正式管理/存储端口完成,
在设置尚未初始化时原子写入。初始化标记避免重启或更新重新覆盖数据库中的设置。
- 实施前核实配置加载器能否区分显式自定义与默认填充值,必要时在加载阶段保留该来源信息。
- 初始化完成后,相关用户选择以数据库为唯一权威;清理旧配置的运行时覆盖路径并同步配置样例。
其他仍有效的 Provider 配置字段继续归原 owner 管理。
- 备份与恢复包含通用配置和 Key 覆盖;发布缓存仍可重建,不成为配置恢复的前置依赖。
## 7. 实施顺序
1. **核对官方依据与接口范围。** 完成六个组合的来源矩阵,明确可自动更新的组合、
Desktop 专属接口、CLI 格式及旧配置来源识别方式,记录无法核验的项目。
2. **定义合同并打通持久化。** 确定完整配置对象、继承语义及只读投影;实现新增迁移、
一次性初始化、设置与 Key 管理 API,复用审计和快照发布。
3. **扩展 Provider 解析。** 复用现有 Desktop 版本核验,补充 CLI 及平台来源、固定版本、
自定义校验和分维度缓存;确保官方资料与用户配置分别存储。
4. **接入实际请求链路。** 复用现有快照与请求上下文携带有效选择,补齐解析版本的请求级复用,
覆盖相关传输与目录入口;复用连接池身份匹配与精确续写规则,仅补齐必要的隔离维度。
5. **实现设置与 Key 界面。** 复用一套编辑组件和服务端投影,调整首页身份卡片含义,
完成浏览器交互验收。
6. **验证并同步正式文档。** 完成下列矩阵;在行为可用后就地更新 API、架构、部署和用户入口的必要说明,
不提前将本计划中的拟定合同写成已发布能力。
## 8. 验收条件
| 范围 | 必须覆盖的场景与结果 |
| --- | --- |
| 默认与继承 | 新安装采用约定默认;新旧 Key 跟随;通用修改只影响跟随者;独立配置不受影响;显式清除后恢复继承 |
| API 更新语义 | 省略覆盖字段保留原值,显式 `null` 清除;旧设置调用不重置新配置;非法配置不留下部分提交 |
| 预设与自定义 | 六个组合均有明确支持状态;已核验预设与对应官方证据一致;自定义可保存、重启恢复并生成合法头部 |
| 版本解析 | 自动最新按来源更新;固定值不被刷新;Desktop 元组一致;检查失败保留同组合旧版本;无来源状态如实显示 |
| 并发与冻结 | 两个不同配置的 Key 并发请求不串用身份;配置或发布刷新期间,已开始请求及其重试保持一致 |
| 上游传输 | 在可用的真实测试环境检查 HTTP/SSE、WebSocket 及受影响接口发出的身份与结果;API Key 认证不混入 OAuth 字段 |
| 缓存与续写 | 不同有效身份不误用目录结果或连接;配置切换后的新请求应用新身份;旧 continuation 无上下文丢失或越界重放 |
| 管理与后台 | Key 覆盖不影响其他 Key、后台任务及 Desktop 专属操作;全局 CLI 与混合 Provider 授权场景可用 |
| 升级与恢复 | 标准配置、自定义配置、重复启动、数据库备份恢复和发布缓存缺失均按约定处理;历史迁移校验不变 |
| 界面 | 创建/编辑/清除覆盖、保存与加载失败、自动与固定切换、来源及核验状态正确;检查窄屏、深浅色和键盘操作 |
后端补充能验证上述真实行为的用例,按 [贡献与审查](../../CONTRIBUTING.md#验证) 和
[架构验收入口](../architecture.md#12-修改与验收) 执行适用的 Rustfmt、Clippy、测试、架构边界与迁移检查。
数据库与 Redis 使用专用测试实例,因环境缺失跳过的测试明确记录为缺口。
前端执行 `pnpm --dir frontend format:check`、`pnpm --dir frontend build`,并提供实际浏览器运行证据;
不为本功能引入项目当前不维护的独立前端测试体系。所有变更执行 `git diff --check`。
构建、单元测试或模拟上游通过不能替代真实协议验收;测试账号和上游环境不足时列明未覆盖组合。
## 9. 已核对的官方依据与实现边界
- CLI UA 与 originator:官方 `openai/codex` 提交 `4d205c7a4dc36b719679a0356a45b23133732265`,
`codex-rs/login/src/auth/default_client.rs`。CLI 不添加 Desktop 应用后缀。
- CLI 版本:`https://registry.npmjs.org/@openai%2Fcodex/latest`,2026-09-18 返回 `0.155.0`,
darwin/linux/win32 × arm64/x64 的依赖版本均配套。实现验证包名、稳定 SemVer 和六个依赖,逐制品缓存。
- macOS Desktop:沿用官方 appcast 与同一 ZIP 的内嵌 Core 核验路径,支持 arm64。
- Windows Desktop:分别读取官方 `ChatGPT-x64.msix`、`ChatGPT-arm64.msix`,从 `app.asar/package.json`
获取应用版本和构建号,从同包 `codex.exe` 核验架构和 Core 版本;MSIX 清单版本不作为应用版本。
- Linux Desktop:依据[官方安装说明](https://learn.chatgpt.com/docs/linux/linux-app),分别读取 amd64/arm64 DEB,
按 XZ 索引跳读 tar,核验同包 `app.asar/package.json` 与 ELF Core。Windows/Linux 的 Range 请求锁定 ETag,
未变更时只检查一个字节,变更后完整核验版本元组;独立缓存、检查失败保留已核验版本。
身份预览采用独立只读接口,由后端解析全局或所提交的完整覆盖;Key 列表保存并返回覆盖配置,
编辑器按预览结果显示当前生效身份,不在前端生成 UA。
## 10. 本地验收记录
2026-09-18,独立 PostgreSQL 18、Redis 8 与 `127.0.0.1:28080` 测试实例,使用合成账号与 Key。
- `RUST_MIN_STACK=16777216 cargo +1.97.0 test --manifest-path backend/Cargo.toml --test main --locked`
全部 9 个测试套件通过,合计 2420 项,PostgreSQL/Redis 使用上述专用实例。
覆盖配置继承、首次导入、旧调用保留字段、清除覆盖、跨重试版本冻结、并发请求身份隔离、制品缓存隔离,
以及 Windows/Linux 归档解析、架构不符、损坏/截断、ETag 变化、XZ 跨块跳读和发布回退。
- `cargo +1.97.0 fmt --all --manifest-path backend/Cargo.toml -- --check`、严格 Clippy(所有 target/feature)通过。
- `pnpm --dir frontend format:check`、`pnpm --dir frontend build`、`git diff --check` 通过。
- `backend/migrations/.frozen-sha256` 全部校验通过,历史迁移未修改。
- Chrome 实测通用设置切换并保存 Linux CLI、创建默认继承的 Key、保存 Windows CLI 固定版本覆盖、
重载回显与清除覆盖后恢复继承;检查 1920 px 与 390 px、深浅色、Key 弹窗和键盘关闭下拉框。
新增表单宽度与相邻设置对齐,文案仅保留短标签与必要状态,浏览器无错误或警告。
- Chrome 实测 Windows/Linux Desktop 自动模式与各自 UA,Windows 保存后重载回显;验收后恢复原 MacOS Desktop 通用配置。
下拉显示 `MacOS`,内部枚举仍为 `macos`,UA 的系统类型仍为 `Mac OS`。
- 官方发布检查实际成功:macOS Desktop `26.915.31029` / Core `0.155.0-alpha.9`,CLI `0.155.0`。
Windows 两种架构为 Desktop `26.908.70816` / Core `0.154.0-alpha.6.2` / Build `9275`;
Linux 两种架构为 Desktop `26.915.31029` / Core `0.155.0-alpha.9` / Build `9771`。
四个官方包首次核验耗时约 17~30 秒,重复检查约 67~74 毫秒,未重新扫描制品。
- `cargo audit --file backend/Cargo.lock` 未通过:已有固定依赖 `rustls 0.23.36` 命中
`RUSTSEC-2026-0285`,另有原有 `chacha20`、`wnaf` 撤回警告;新增归档依赖未报告漏洞。
本次未改变受官方 TLS 指纹约束的依赖版本。
验证边界:没有真实上游账号,真实认证下的 HTTP/SSE、WebSocket、Images/Search 未进行端到端验收;
本地传输测试不能替代该验收。Windows/Linux Desktop 的 x86_64、arm64 官方制品解析和未变化复用均已实测,真实账号请求仍未覆盖。
完整备份恢复、生产部署环境及发布源故障下的界面交互尚未实测。