diff --git a/docs/audit-2026-07-26.md b/docs/audit-2026-07-26.md deleted file mode 100644 index 43420a461..000000000 --- a/docs/audit-2026-07-26.md +++ /dev/null @@ -1,321 +0,0 @@ -# Codex Proxy RS 全仓审计报告(2026-07-26) - -> 审计对象:`main` 分支当前工作树(含 3 处未提交改动)。基线:`cargo clippy --workspace --all-targets` 零告警、`cargo test --workspace` 约 1182 通过 / 6 ignored 全绿。 -> 方法:两轮多 agent 并行审读(14 路子系统 + 4 路盲区补审,合计 56 个子 agent),每条 P0/P1 发现由独立 agent 亲读代码对抗核验,P2/P3 批量核验。头部结论由本人二次抽验代码确认。 -> 判定基线:`/v1/responses` 透明代理红线(不改写数据体、差异下沉限 `providers/xai` 内 raw Map surgery、不二次写枚举)、明文存储密钥为有意设计(不计入问题)。 - ---- - -## 0. 总览与结论 - -代码工程底子扎实——分层契约(Bundle/initialize/WorkerContribution)完全落地、组合根仅 129 行、敏感数据 Debug 全线脱敏、Redis 侧 13 类 key 全部带 TTL、DB 约束密度教科书级、WS 池语义与既定基线 99% 一致。**但存在若干纯配置即可触发的启动/升级砖机、一处结构性数据失真(健康指标恒 0)、以及一条 OpenAI 路径的字节透传回归。** - -> **关于红线定性的修订(2026-07-26)**:本报告初版把 xAI 响应侧的两条发现(A3 断流、A4 错误 redact)定为「透明代理红线违规 P0」,**该定性错误、已撤销**。xAI/Grok 与 Codex 是两套 wire 协议,xAI provider 请求侧翻译 Codex→Grok、响应侧把 Grok 流**重新合成**为 Responses 事件——客户端拿到的是合成 wire 而非 Grok 原始字节,字节透明在物理上不成立,也不在红线约束范围内(红线约束的是 OpenAI 同协议 passthrough 路径)。**随后对照用户指定基线 grok2api 逐行核实**:两条都不是红线违规,但**都是相对 grok2api 基线的真实回归**——A3(翻译层在正常上游变体上断流,基线全部 tolerate)定 **P1**;A4(基线转发上游错误原文,本项 redact)定 **P2 裁决点**(redact 可能是有意加的风控)。详见 §1。 - -两轮合计约 145 条原始发现,对抗核验 + 定性修订后 **6 条被推翻/撤销**,其余确认或降级;去重合并后约 108 个不同问题。有效严重度分布(去重后近似): - -| severity | 数量 | 性质 | -|---|---|---| -| **P0** | 0 | — | -| **P1** | ~11 | 启动/升级砖机、数据失真、关停 panic、凭据误废、OpenAI WS 字节重序列化(唯一真红线)、xAI A3 断流回归 | -| **P2** | ~49 | 正确性缺陷、性能债、架构错位、安全加固缺口、xAI 翻译形态、A4 错误透传回归 | -| **P3** | ~50 | 死代码、冗余、文档漂移、小清理 | - -**必须尽快处理的三件事(✅ 全部已修,2026-07-26,commit 6deecdeb / a9b68728 / 4b41c8fa)**: -1. **CORS 启动 panic(P1,✅)**——`allow_headers(Any)+credentials` 改显式 header 白名单,补非空 origin 预检装配测试。 -2. **2.x → 3.x 一键更新砖机(P1,✅)**——跨 major 版本不再进入 has_update(带说明 warning),perform_update 在拉取 release 前显式拒绝跨 major target。 -3. **store 健康指标结构性恒 0(P1,✅)**——用量事实谓词下沉到逐聚合 FILTER:结果计数覆盖全部请求,token/延迟/成本聚合仍限已交付成功响应;真 PG 回归测试锁定失败计数。 - -> 同日完成性能批 14 项(见 §「性能与效率」,commit c8a09e21..42c41c1d)并经 20-agent 对抗审查,三处审查发现已在 3b6d6353 修正(dashboard 并发降为两批、被动 quota 恢复最新状态复核、迁移 0002 去冗余索引)。 - -> 唯一的透明代理红线项是 **OpenAI WS 上游字节被 serde 重序列化**(P1,§1),因为 OpenAI 路径本就该字节透明。 - ---- - -## 1. 透明代理红线专章(最高优先) - -红线定义见 `[[transparent-proxy-redline]]`:`/v1/responses` 可读不可改数据体(账号/风控例外);xAI 差异下沉仅限 `providers/xai` 内 raw Map surgery;不二次写枚举。 - -> **前提(勿再拿错尺子量 xAI)**:红线的「字节透传/不改数据体」约束的是 **OpenAI 路径**——Codex 客户端与 Codex 上游同一套 Responses wire,双向都不该改。**xAI 路径不适用**:Grok 与 Codex 是两套协议,xAI provider 请求侧必须翻译 Codex→Grok、响应侧必须把 Grok 流**重新合成**为 Responses 事件(`canonical.rs:214-244` 下发的是 `rewrite_stream_event` 转换后的合成 wire,不是 Grok 原始字节)。因此对 xAI 谈「字节透明」无意义。红线对 xAI 的真实约束只有两条:① 翻译要用**合法形态**(raw Map surgery + 未知字段随行,而非 `json!` 白名单重组);② 不二次写枚举。以下先列唯一的 OpenAI 真红线项,再列 xAI 翻译形态项,最后单列已撤销的两条并说明为什么。 - -### ✅ P1(已修复 2026-07-26) 【唯一真红线】OpenAI WS 上游路径客户端字节被 serde_json 重打印 - -> **修复**:`provider.rs` 现对 HTTP SSE 与 WebSocket **两条**上游都启用 `with_raw_sse_passthrough()`。WS 的 body 由 reducer 以 `encode_sse_event(&event, raw)` 逐字节内嵌上游原文,`push_frames` 抽出的 `data` 即上游原始 JSON,下游按字节转发,不再经 serde 往返。回归测试 `websocket_raw_passthrough_should_preserve_upstream_number_bytes`(openai canonical):上游 `1e3` 原样保留、未被重序列化为 `1000.0`。全量 clippy + 测试绿。 -`backend/crates/providers/openai/src/provider.rs:822` - -`with_raw_sse_passthrough()` 只在 `transport == HttpSse` 时启用(L822-824);**WS 路径**(warmup、exact continuation、显式 `use_websocket`、continuation 快路径)走 `json_with_sse_metadata(raw_sse_frame=None)`,下游 `response.rs:48` 回退 `wire.data().to_string()` 重序列化。workspace serde_json 仅开 `preserve_order`、无 `arbitrary_precision`,Value 往返会改字节:`"1e3"→"1000.0"`、超 u64 整数降 f64 丢精度、`\uXXXX` 转义解开、重复键坍缩。2026-07-24 的 `ab8dffba` 只修了 HTTP 半边。**修法**:WS 衍生流同样启用 raw 透传(reducer 产出的 SSE 帧字节即上游原文)。 - -### 🟡 P2-redline xAI 历史项转换用白名单重组而非 raw Map surgery -`backend/crates/providers/xai/src/transport/request.rs:2215`(原报 P1,核验降 P2) - -`normalize_*_input` 系列用 `json!` 白名单重新合成历史项,未列入白名单的字段(`message.status`、`function_call.id`、`web_search_call.*` 等)静默蒸发。红线明确合法形态仅限「在原 Map 上删/改必要字段、未知字段随行」。账号绑定 id 的剥离本可用现有 `ACCOUNT_BOUND_FIELDS` 机制在原 Map 上完成。**修法**:改为 `item.clone()` 上做删除/覆写手术,其余键值原样保留。 - -### 🟡 P2-redline `sanitize_history_json_value` 递归剥 `phase` 键并丢弃所有 null -`backend/crates/providers/xai/src/transport/request.rs:2703` - -在任意嵌套深度剥离名为 `phase` 的键、丢弃所有显式 `null`。若工具 schema(`mcp_list_tools.tools[].input_schema.properties.phase`)或语义化 `null`(`{"query": null}`)恰好命中,重放历史即被静默改写。风控剥离应限定在已知的 Grok 内部注入位置。 - -### 🟡 二次写枚举清单(红线③) -- **`GrokCatalogReasoningEffort`**(`xai/src/transport/catalog.rs:595`):wire 镜像 typed 枚举且**无 `#[serde(other)]`**——上游任一模型新增一个 effort 值 → 整轮目录解析失败 → Codex `/v1/models` 目录塌缩为默认 grok-4.5,套餐 seed cache TTL 过期后无法重建。既是二次枚举,又是 fail-closed 断更。**修法**:改受校验的字符串透传,或加 `#[serde(other)] Unknown` 且未知值跳过。 -- **账号可用性 7 态**(`gateway-admin/src/model/accounts.rs:14`):被 4 个镜像枚举 + ≥6 处手写字符串映射重复定义,新增状态需改 5 个 crate 约 10 个映射点。应以 `gateway-core::AccountAvailability::as_str/parse` 为唯一编解码点。 - -### 迁移遗留项 B8 现状 -`backend/crates/providers/openai/src/transport/request.rs:131`:`client_metadata` 通用回退**已移除**(符合计划),但 body 顶层 camelCase 别名(`turnState` 等)**仍被提取且优先于 header**——计划为仅 header。属残留,P3。 - -### ⟲ 重定性:xAI A3、A4 —— 非红线,但确认是相对 grok2api 基线的回归 - -> 初版把这两条挂在「透明代理红线」下并定 P0。经复读代码,**红线对 xAI 不适用**(xai 是翻译层,见本章前提)。随后对照用户指定的行为基线 **grok2api**(`/home/zyy/Codes/grok2api`,architecture.md 亦声明「xAI/Grok 行为以 grok2api 验证结果为基准」)逐行核实,结论:两条都不是红线违规,但**都是相对基线的真实回归**。 -> -> grok2api 流式主循环 `streamConverter.handle`(`backend/internal/infra/provider/conversation/stream.go:195`)对上游异常**极度容忍,唯一会中断流的是向客户端写失败(断连)**:JSON 解析失败 `return nil`(L203)、字段缺失/空 delta 用 `_ = json.Unmarshal` 给零值照发、重复/乱序 index 不校验、已 finished 再来帧 `if c.finished { return nil }`(L196)、未知事件类型 switch `default: return nil`。错误事件 `error`/`response.failed` 经 `streamErrorValue`(stream.go:454)提取 `response.error`/`error`/`message` **原文转发给客户端**。 - -- **A3(`canonical.rs:255` 强 schema 校验断流)→ P1「翻译层健壮性回归」【✅ 已修复 2026-07-26】**: - - **修复**:`decode` 循环现对**非终态内容事件**的 `Protocol` 类错误容忍——跳过该事件的 canonical 提取、wire 原样转发,流不断(对齐 grok2api);`rewrite_stream_event` 转换失败改为跳过该帧而非断流。**终态事件(completed/incomplete)保留 fail-closed**(用量/计费校验失败仍断流,`decoder_should_fail_closed_for_non_integer_provider_cost` 仍绿);真·上游错误(response.failed/error,非 Protocol 类别)仍按终态传播。回归测试 `decoder_should_tolerate_content_field_error_without_breaking_stream`:空 delta 不再断流、wire 仍转发、终态仍解出。 - - **纠正初版事实**:`decode_event` 的 `_ => Ok(())`(L291)**容忍未知事件类型且原样转发**(L239-243)。初版「未来所有非 `response.*` 事件族被吞掉/未知事件断流」的说法**错误、已删**。 - - **真实内核(基线背书)**:codex-proxy-rs 在 **grok2api 会容忍的正常上游变体**上断整条客户端流——空 delta(`required_text` L589)、重复 index(`add_content` L550)、已完成再来帧(L255)、kind 不符(L563)等,都会 `?` 上抛打断已开始的流,对 Codex 客户端表现为响应被截断。基线在这些场景全部 tolerate,因此这是**确认的回归**,非可接受的 fail-closed。 - - **修法**:把 `decode_event` 的 `Err(protocol_error_marker())` 路径降级为「跳过该事件的 canonical 提取、wire 原样转发」,仅客户端写失败/SSE 帧解码失败才断流——即对齐 grok2api 的容忍语义。 -- **A4(上游 error 正文 redact)→ P2「错误透传回归」【✅ 已裁决并修复 2026-07-26,commit 2e0bee60】**: - - **基线**:grok2api 把上游错误 `message`/`code` **原文转发**给客户端(`streamErrorValue`)。codex-proxy-rs 经 `redact_sensitive_context` 只留改写过的 code + 静态文案。故这是相对基线的回归。 - - **四方裁决依据(2026-07-26 补充核实)**:grok-build(xAI 官方 CLI)对结构化 JSON 错误**原文展示**(280/200 字符截断 + 内部服务名改写护栏);codex(下游客户端)不仅展示 message,还**依赖 error.code 分致命/可重试、依赖 message 中 "try again in Ns" 定重试间隔**——redact 会把致命错误变成盲重试;grok2api 流内错误零脱敏转发,但其 fixture 证实 Grok 429 文案内嵌 **team UUID**(`console_test.go:271`),账号指纹风险真实存在。 - - **裁决与实现**:转发结构化 `message`/`code`/`type` 走 OpenAI 侧同款 `ClientVisibleUpstreamError` 通道;message 先经 `scrub_account_fingerprints`(UUID→`[redacted]`、控制字符归一空格)——即红线「账号/风控例外」的精确用法;非结构化正文不透出;非 2xx HTTP 路径保持收敛语义(与 grok2api 重试段一致)。实现:`xai/transport/canonical.rs` `upstream_event_error`。 - -**✅ 红线完好清单**(勿重复审计):OpenAI HTTP SSE 端到端 raw 字节保真(`push_frames` 保留原始帧、解析失败仍转发 raw、`[DONE]` 网关唯一追加);gateway-core 对 body 仅只读能力探测、全 crate 无生产路径改写 provider 数据;双 transport body 同源(HTTP 序列化 raw map / WS = `{"type":"response.create"}` + 同一 map 逐字段含未知字段按插入序透传);`preserve_order` 已在全部 7 个业务 crate 显式声明;WS 剥帧严格限于上游私有控制帧 `codex.rate_limits` / `response.metadata`;reasoning_replay 跨账号消毒逐字相同。 - ---- - -## 2. 正确性与可用性 - -### 🟠 P1 CORS 配置任一 origin 即启动 panic -`backend/crates/gateway-api/src/lib.rs:153`(三路独立命中 + 最小复现实锤) - -`!cors_allowed_origins.is_empty()` 时构造 `CorsLayer::new()...allow_headers(Any).allow_credentials(true)`。tower-http 0.7.0 `ensure_usable_cors_rules` 在 credentials=true 且 headers 为 wildcard 时 `assert!` panic;axum 0.8.9 `Router::layer` 立即求值,**panic 发生在 `initialize()` 内**。`deploy/config.example.yaml:49` 公开暴露该配置项,`validate_origin` 精细校验给了「配置可用」的错觉,实则填入任一合法 origin → 启动即 panic → Compose `restart: unless-stopped` 无限重启。测试只覆盖空数组。**修法**:`.allow_headers(Any)` 换显式白名单(`authorization`/`content-type`/`x-api-key`/`x-request-id`)或 `AllowHeaders::mirror_request()`,补非空 origin 装配测试。(本人已核 L140-155 属实。) - -### 🟠 P1 管理员登录限流退化为全局单桶 —— 零成本管理面 DoS -`backend/crates/gateway-api/src/admin/auth.rs:217` - -login handler 取 `Option>`,**全仓无任何中间件注入该 Extension**(grep 仅 3 处命中且均在 auth.rs 自身),故 `source` 恒为 `"unknown"`。限流键即 source(`use_case/auth.rs:141`、`redis/mod.rs:123`),所有访客失败落进同一个桶。任意未认证者对 `/api/admin/auth/login` 发 5 次错密码,15 分钟内合法管理员登录一律 429,每 15 分钟 5 请求即可无限续锁。测试(`tests/admin/auth.rs:54` 传 `"127.0.0.1"`)证明设计意图是按来源隔离。IP 本可得(`serve.rs:126` 已 `into_make_service_with_connect_info`)。**修法**:admin router 加中间件从 `ConnectInfo` 构造并 insert `AdminLoginSource`,unknown 与已知来源分桶。 - -### 🟠 P1 store 健康指标结构性恒 0 —— 仪表盘成功率永远 100% -`backend/crates/gateway-store/src/postgres/observability.rs:1657` - -`request_metrics` 用 FILTER 算 `failure_count`/`cancelled`/`incomplete`/`caller_error_count`(L1656-1661),但 L1695 无条件追加 `push_completed_usage_fact_filter`(`and outcome='succeeded' and downstream_committed_at is not null and client_status_code between 200 and 399`,L2252)——WHERE 已把结局钉死为成功,**这些失败计数在集合上不可能命中**。`request_metric_series` 同样。下游 admin `service_failure_count = failure_count - caller_error_count` 推导 success_rate 与趋势 errors 序列。只要范围内有失败请求,Dashboard/Usage 成功率仍显示 100%、errors 曲线恒 0——健康观测完全失真。**修法**:把健康类计数从完成态过滤中解耦(用量/成本聚合保留过滤,请求结局计数改在未过滤子集上统计)。(本人已核 SQL 与 filter 函数属实。) - -> ⚠️ 关联但**已推翻**:`diagnostics()` 与 `TrendPoint.success_rate` 两条「成功数虚高/分母不一致」发现经核验为 REFUTED——因为它们的数据源同样被完成态过滤器钉死,重算值与精确值**恒等**,不存在虚高。但这反过来印证:整套观测的失败维度都被这个 filter 结构性抹平了。 - -### gateway-core 执行链簇(coordinator.rs) -- 🟠 **P1 重试后终态把已持久化的 `sent` 发送屏障降级覆盖为 `not_sent`**(`coordinator.rs:984`):`finish_provider_error` 用 `current_send_state()`,attempt1 已 `mark_send_state(Sent)` 但 current 被 take 不归还,attempt2 遇 `NoEligibleAccount` 时读回 `NotSent`,`finalize_model_request` 无条件覆盖该列 → 最终行 `send_state='not_sent'` 且 `status_code=429` 自相矛盾,破坏发送屏障审计事实。**修法**:维护请求级发送高水位,终态取 `max(高水位, error.send_state())`。 -- 🟡 **P2 网关 10min deadline 到期被归因为 Provider Timeout 熔断失败**(L387):长生成请求(>10min)属网关预算到期而非上游故障,却累积 provider 失败;多条长流同时到期可打开熔断,30s 内该 Provider 全部新请求被拒。`downstream_committed_at.is_some()` 时不应计入熔断。 -- 🟡 **P2 同账号凭据恢复后 `required_account` 永久钉死**(L824):401→OAuth 恢复→原账号重放,若再遇本可换号的错误(429/Sent),即使还有可调度账号和 32 次预算也立即终态。xAI `with_same_account_retry` 真实可触发。 -- 🟢 **P3**:`reset_uncommitted_observations` 漏清 `provider_processing_ms`(L906,废弃 attempt 耗时泄漏进终态);`finish_success` 无观测时凭空合成 `status_code=200`(L948);`EmptyRoutingPlan` 被复用为内部不变量破坏错误且 attempts 封顶守卫不落终态(L440)。 - -### admin 用例 -- 🟡 **P2 `enrich_billing` 对非法 provider_kind 硬失败**(`use_case/observability.rs:513`):一条来自已下线/回退 Provider 的脏 billing 记录使整个 usage 列表与 dashboard 500。同作者的 `recover_standard_costs` 已按可能事实容错——两路不一致。改为 `continue` 保留原 billing。 -- 🟡 **P2 `AccountAvailability::Invalid` 归一化为 `AccountStatus::Active`**(`use_case/accounts.rs:484`):被调度排除的账号在目录显示活跃且计入活跃汇总,管理员无法从状态列发现。应给 `AccountStatus` 增 Invalid 值单列。 -- 🟢 **P3**:`session_ttl_minutes` 校验上限过松,巨值经 `Duration::minutes`/`DateTime` 加法触发 panic(`lib.rs:102`);仪表盘自然日硬编码 UTC+8 且 `china_day_start` 两 crate 双实现(`observability.rs:836`);账号 label 更新(v2 `/accounts/update`)被删未恢复(前端亦无调用点)。 - -### gateway-host 关停与自更新 -- 🟠 **P1 `WorkerSupervisor::shutdown` 超时分支对已 join 的 handle 二次 await → tokio panic**(`workers/mod.rs:127`):第一轮 timeout 内顺序 join,超时后对**全量** handle 再次 `await`。tokio 1.53.1 对已消费 handle 再 poll 会 `panic!("JoinHandle polled after completion")`。触发条件:任一 worker 30s 内未退出且它不是 BTreeMap 序第一个。现有测试恰好让挂死任务排第一而侥幸通过。**修法**:改用 `JoinSet` 或第一轮 join 成功即移出列表。 -- 🟠 **P1 `drain_timeout` 无法约束 axum 优雅关闭**(`serve.rs:129`):`with_graceful_shutdown` 无超时等待所有在飞连接,`wait_until_idle(drain_timeout)` 只在其返回后执行;而 HTTP SSE 响应体不观察 cancellation token。上游持续流式/慢客户端不读 body 时 `axum::serve` 永不返回,drain_timeout 形同虚设,后续 `workers.shutdown` 也无限期不执行,最终被 SIGKILL。**修法**:shutdown 信号后对 serve future 施加 `drain_timeout` 上界,或让 SSE unfold `select` on cancellation。 -- 🟢 **P3**:非 docker 自重启端口竞态(新进程固定延迟 1.2s,老进程未放端口即 bind 失败死亡,`process.rs:44`);`restart()` 不获取 operation_lock 可与 update 并发(`mod.rs:491`);`ConnectionTracker::wait_until_idle` Notify 丢失唤醒竞态(`serve.rs:47`);preview 更新通道无效(`/releases/latest` 永不返回 prerelease,`release.rs:124`);更新包下载 120s 总超时慢链路必败(`download.rs:29`)。 - -### OpenAI 凭据(refresh / catalog) -- ✅ **P1(已修复 2026-07-26,确认为重构回归) token endpoint 429/5xx/超时 → 账号永久 Invalid**(`credential/refresh.rs:208`):原 `classify_refresh_failure(_status, body)` **忽略状态码**,非 `invalid_grant` 一律 `Transport`→`persist_terminal(Invalid)`,无自动恢复。经三方核验(当前代码 / v2.5.3 git / 官方 codex-rs)确认为重构丢失的退避机制。**修复三步**:① Transport/Unavailable 两臂改 `defer_refresh`(瞬态保持 Ready、退避重试);② `classify_refresh_failure` 改按状态码先分流(5xx/429→Transport,仅 4xx 据正文分类,对齐官方"只 401/明确 expired 才 Permanent");③ **Redis 指数退避**恢复 v2.5.3 语义——`ProviderCredentialStatePort` 加 `record/clear_refresh_backoff`(Redis INCR+PEXPIRE 30min 窗口),`provider_refresh_backoff_at`(5s×3ⁿ cap 5min+抖动),defer 时 increment、全成功 clear 归零。回归测试 3 个(指数增长/成功清零/5xx 仍瞬态)。删除死变体 `Ambiguous`。全量绿 + 对抗复核 clean。 -- ✅ **P1(已修复 2026-07-26,同上) 刷新成功后 JWKS 暂不可用 → 永久 Invalid 且丢弃新 RT**(`refresh.rs:328`):`identity Unavailable`(JWKS 抖动)改为 `defer_refresh`(该方法文档注释本就是"签名边界不可用时以相同 credential 推进退避"——原代码接错线到终态)。保留旧凭据退避重试;若上游确已轮换 RT,下轮 invalid_grant→Expired 是可接受正确终态(对抗复核确认非死循环、非不可恢复丢失)。 -- 🟡 **P2 quota 周期同步单账号解析失败用 `?` 中止整批**(`credential/quota.rs:535`):一个账号 usage JSON 字段漂移即系统性推迟其后所有耗尽账号恢复 30 分钟,每轮重复。改 per-account continue。 -- 🟡 **P2 跨套餐目录 union 任一元数据差异即 `ConflictingModelFacts`**(`credential/catalog.rs:374`):混合套餐部署下上游对同 slug 按套餐差异化任一展示字段 → `refresh_catalogs` 每 5 分钟稳定失败、快照永久停留旧 generation。冲突判定应收窄到影响路由/能力的字段。 -- 🟡 **P2 OpenAI provider 丢弃全部目录展示事实**(`provider.rs:1535`):不构造 `ModelPresentation`,Codex 发现合同回退到编造默认值(visibility 恒 list、reasoning levels 空、context_window null);同时 `catalog.rs` 大量白名单字段成为无消费者的镜像结构且仍承担严格校验拒绝风险。 - -### xAI 传输 / 凭据 -- 🟠 **P1 批量导入验证中途失败丢弃已轮换的 refresh token**(`xai/src/admin.rs:327`):`prepare_import` 逐条真实执行 RT exchange,任一后续条目失败(含重复 subject 检查)使整批 Err,先前已轮换 RT 的 prepared 全部丢弃——源文件旧 RT 已被上游消费,重导即 `invalid_grant`,凭据不可恢复报废。导入文件含重复账号即稳定触发。**修法**:先做无副作用校验再逐条验证,或改逐条部分成功语义。 -- 🟡 **P2**:`web_search`/`x_search` prompt-cache 路由注入在混合工具请求下可被上游真实调用(`request.rs:328`,客户端收到未声明的 server-side 调用);选择器热路径逐账号串行 Redis 往返(`selector.rs:79`,N 账号 ≈2N 往返计入 TTFB);Provider 级目录同步全有全无(`credential/catalog.rs:1044`,单套餐失败拖垮全目录)。 -- 🟢 **P3**:`GrokRefreshFailure::Banned` 无构造者路径不可达;JWKS wire `deny_unknown_fields`(上游加任一标准 JWK 字段即全量拒验,`oidc_verifier.rs:358`);套餐目录 cache decode 失败当硬错误而非 miss(坏缓存阻塞 admin models() 直至 TTL)。 - -### OpenAI 传输杂项 -- 🟡 **P2 错误消息含 `"unauthorized"` 子串即判 `CredentialExpired`**(`transport/diagnostics.rs:497`):任何 4xx/5xx 只要 message/body 回显该字样即误判凭据过期 → 触发换号重放(payload 已送达可能重复副作用)。应限定在 401/403 状态或 error.code 字段。 -- 🟢 **P3**:`x-reasoning-included` 响应头忽略值,`"false"` 也置 true(`response_meta.rs:100`);Retry-After 仅解析整数秒,HTTP-date 形式丢失(`client.rs:490`);raw 透传模式下 wire 构造失败静默丢弃原始帧(`canonical.rs:285`)。 - ---- - -## 3. 性能与热路径 - -按预估收益排序。均为 provider 内 / 存储层改动,不触数据体。 - -| # | 严重度 | 问题 | 位置 | 预估收益 | -|---|---|---|---|---| -| 1 | P2 | WS 帧 reducer 对同一帧重复全量 JSON 解析约 7-9 次,每次分配完整 Value 树 | `openai/transport/websocket/exchange/reducer.rs:31` | 100 并发流 × 30 帧/s 约省半个 CPU 核;每帧解析降 7×,顶部解析一次传 `&Value` 即可 | -| 2 | P2 | WS 审计 artifact 在审计未启用时也每请求深拷贝整个请求 body(MB 级会话历史) | `openai/transport/client_sse.rs:179` | 生产环境(不设审计目录)纯浪费,位于 TTFB 前;`OnceLock` 缓存判定后跳过构造 | -| 3 | P2 | 客户端 WS `response.create` 解码对完整 body 做序列化+再解析往返 | `gateway-api/openai/responses/websocket.rs:552` | MB 级 body 约 10ms 直接加在请求启动路径;拆出接受 `Map` 的内核 | -| 4 | P2 | TTFB 关键路径串行 4 次 DB 往返,insert + 首次 attempt 操作同一行可合并 | `gateway-core/engine/coordinator.rs:617` | 跨机房(RTT 5-20ms)每请求首 token 省 1 RTT / 20-80ms | -| 5 | P2 | `SseEventDecoder` 无扫描游标,大帧跨 chunk 累积 O(n²) 重扫 | `gateway-protocol/openai/sse.rs:84` | 10MB base64 帧按 16KB chunk 到达需扫 ~3.2GB×2,秒级 CPU 尖峰;记扫描偏移增量续扫 | -| 6 | P2 | retention 每小时按 `completed_at` 删 `model_requests`,该列**无索引**且单事务无上限批删 | `gateway-store/postgres/retention.rs:87` | 百万行后每小时全表扫 + 潜在百万行长事务锁竞争;加 partial index + 分批 `ctid` 删除 | -| 7 | P2 | `dashboard_summary` 顺序 await 13 个查询,同范围对 `model_requests` 重复聚合扫描 ≥6 次 | `gateway-store/postgres/observability.rs:757` | 最常刷新页端到端 = 13 RTT 之和;`try_join!` 并发 + group by 合并把扫描降到 2 | -| 8 | P2 | PgPool 全默认(max=10、acquire 30s),无容量/超时预算且不可配置 | `gateway-store/postgres/mod.rs:39` | 并发略高即成瓶颈、尾延迟暴涨而非快速失败;`StoreConfig` 加 pool 段,acquire 压到 2-5s | -| 9 | P3 | WS 发送 payload 为前插 `type` 深拷贝整个 body Map | `openai/transport/protocol/websocket.rs:170` | MB 级 body 每请求一次多余深拷贝 | -| 10 | P3 | provider 冷流每 chunk 两次 tokio Mutex 加锁并克隆 turn_state 字符串 | `openai/src/provider.rs:878` | 长流每帧锁竞争 + 分配 | -| 11 | P3 | 每事件新建 deadline 计时器(futures-timer Delay 全局锁) | `gateway-core/engine/coordinator.rs:1234` | 帧循环内重复创建计时器 | -| 12 | P3 | continuation 附着 session state 时 `clone`+`make_mut` 强制整份 payload 深拷贝 | `gateway-core/engine/execution.rs:871` | 每续写请求一次全量深拷贝 | -| 13 | P3 | 被动限流头同步每响应 3-4 次串行 PG 往返 | `openai/credential/quota.rs:629` | 每响应握手后额外 DB 往返 | -| 14 | P3 | xAI 推理流每 chunk 做 Bytes→Vec 拷贝 | `xai/transport/network.rs:412` | 长流逐 chunk 多余拷贝 | - -**✅ 性能完好**:reqwest Client 进程级 `OnceLock` 复用(HTTP/2 keepalive 明确);API Key `last_used` 按 1 秒窗口异步合批 `unnest` UPDATE,认证热路径零同步写;全链路无 unbounded channel(WS 下行缓冲有界 16 + 断线立即释放 slot);Operation payload 以 `Arc` 共享、attempt 级 clone 零拷贝;HTTP SSE 下行 raw 直通零重建。 - ---- - -## 4. 架构与依赖一致性 - -**依赖 DAG 实测全部成立**(manifest + 源码两级):gateway-core 零 HTTP/DB/Redis/provider 依赖、gateway-protocol 零 workspace 依赖、两 provider 互不依赖、gateway-admin 无 axum/sqlx/redis/reqwest。Bundle/initialize/WorkerContribution 契约完全落地。以下是错位与文档漂移: - -- 🟡 **P2 architecture 测试只覆盖 2/9 crate,§2 六条依赖规则零机器强制**(`docs/architecture.md:45`):全仓仅 `gateway-api/tests/architecture.rs` 与 `apps/gateway/tests/main.rs` 两处,且各自只查自己;`gateway-api` forbidden 列表漏 `gateway-host`;无任何测试验证「provider 互不依赖」「core 不依赖 HTTP/DB」「生产 src 禁测试挂载」。文档「各 crate 强制验证」是假承诺,且下一条违规已实证。**修法**:在组合根加 workspace 级 DAG 测试(解析各 Cargo.toml + 递归扫描 src 断言无 `#[cfg(test)]`)。 -- 🟡 **P2 provider-xai 生产 src 内嵌 `#[cfg(test)] mod tests`**(`xai/src/transport/profile.rs:259`):全仓生产 src 唯一一处,违反 §11「生产 src 禁止测试挂载」;这 2 个测试 `cargo test` 永不执行(`clippy --all-targets` 只编译不运行)= 假覆盖;`tests/transport/` 缺 `profile.rs` 镜像。迁到 `tests/transport/profile.rs`。 -- 🟡 **P2 gateway-store 自持调度策略常量**(`gateway-store/src/lib.rs:305`):`ProviderCircuitPolicy::default()`(阈值 3 次/熔断 30s)直接改变调度行为,却既不在 RuntimeSnapshot 冻结策略也不在启动配置,藏在存储适配层 Default impl,多副本无法经 config_revision 协调。同类:`CONTINUATION_TTL=4h`、账号 lease 10min/OAuth refresh lease 5min。**修法**:熔断参数上浮到 core 默认值或 runtime_settings。 -- 🟡 **P2 admin `DefaultXaiService` 是 `DefaultOpenAiService` 逐字子集复制**(`use_case/xai.rs:79`):约 200 行重复编排且已漂移(xai 无 details/rotate),`initialize` 硬编码同时 require openai+xai(无法单 Provider 部署)。抽参数化通用 `ProviderCredentialService`。 -- 🟢 **P3**:worker 实际由 store/core/provider **四方**贡献、HTTP server 与 worker 运行时在 gateway-host 而非 apps/gateway(`architecture.md:170` 描述漂移);workspace 清单继承不一致(core/store/api/protocol 写死版本号、protocol 手抄整段 lints 存在漂移风险);`architecture.md:176` 声称 host bundle 贡献 update/system worker,实际 gateway-host 不贡献任何 WorkerContribution。 - ---- - -## 5. 多副本部署语义 - -architecture.md 宣称「单进程、可多副本部署」。逐机制核验结论: - -**✅ 多副本安全**:`client_admission` 限流/并发完全下沉 Redis Lua 原子脚本且用 Redis TIME(RPM/并发精确不放大);`provider_circuit`/session affinity/native continuation/credential cooldown 全部 Redis 原子下沉;RuntimeSnapshot 双通道收敛(Redis pub/sub + 5s Postgres revision 对账兜底,不一致窗口 ≈5s,fail-closed suspend)。 - -**⚠️ 多副本须知**(应写入部署文档): -- 🟡 **P2 Worker leader lease 是「每周期互斥锁」而非跨周期 leader 选举**(`gateway-host/src/workers/mod.rs:521`):`run_leased_cycle` 周期结束立即 `release_guard`,各副本定时器相位独立 → 无并发双跑,但「每 interval 恰好一次」失效,quota 同步/appcast 轮询/retention/目录刷新对上游/DB 调用**最高 N 倍**。**具体受害**:xAI 30 分钟配额最小刷新间隔靠进程内 `Mutex` 节流,副本交替获锁时各自 map 互不可见,节流被击穿 → 增加风控暴露。**修法**:有节流语义的任务把 `last_refresh_at` 下沉 Redis,或改真 leader(lease 跨周期持有 + 续租)。 -- 🟡 **P2 自更新只替换处理请求的那个副本的本地二进制**(`system_update/swap.rs:11`):多副本触发 update 后只有一台升级,其余停留旧版本(API 行为/迁移预期/前端 dist 不一致);rollback 同理只回一台。**修法**:文档明确自更新仅限单副本,或检测到多 lease owner 时拒绝 update。 -- 🟢 **P3**:ConnectionLocal WS 续写跨副本必然降级为全量 transcript 重放(应建议 LB 会话粘性);副本被硬杀时在握 worker lease 依赖 15 分钟 TTL 兜底,期间全 fleet 同任务停摆;Catalog ETag daemon 每副本各一份无 lease,同一上游变更引发 N 倍全量抓取。 - ---- - -## 6. 安全加固(明文存储为有意设计,不计入) - -**✅ 基线正确**:`/v1` client key 走常数时间比较(`constant_time_equal`)、四种失败收敛同一响应;归档解包拒绝 absolute/ParentDir/symlink/hardlink(无 zip-slip);session token / admin key / client key / Argon2 salt 全用 `OsRng`;密码 Argon2;xAI 网络边界有 DNS rebinding 防护 + DoH 兜底;OIDC 验证 ES256 单算法 fail-closed。以下是缺口: - -- 🟡 **P2 自更新无签名/attestation 验证,且二进制目录对运行用户可写**(`system_update/mod.rs:331`):完整性根只有同源 checksums.txt(防损坏不防篡改);发布侧其实已产出 `actions/attest` artifact attestation + keyless cosign 镜像签名(`release.yml:384/528`),**消费侧一个都没用**。信任根 = 「谁能往那个 GitHub Release 写 asset」(含 `CPR_UPDATE_REPOSITORY` 被改、CI 令牌失陷);一旦成立即持久化 RCE(`replace_release_files` 覆盖运行中可执行文件 + `restart: unless-stopped`)。**修法**:verify_checksum 后加离线可验证签名校验(内置公钥验签的 checksums.txt,或校验 GitHub attestation 的 Sigstore 证书链绑定 repo/workflow identity),extract 前完成。 -- 🟡 **P2 管理员密码首次创建后无法轮换,且无会话批量吊销**(`gateway-admin/src/lib.rs:234`):`ensure_default_admin` 幂等创建(已存在哈希则静默不动、返回 bool 被丢弃),管理面 39 个路由无任何改密端点,`AuthStore` 无「按 admin_user_id 清全部 session」。口令泄露后产品内无路可走,必须直连 DB 改哈希;既有会话不可失效,攻击者 cookie 可用到 TTL(示例 1440min)到期。**修法**:加 `POST /api/admin/settings/password`(验旧密码 + 复用强度策略),成功后吊销该用户全部会话;`ensure_default_admin` 已存在哈希时 warn 一行。 -- 🟢 **P3**:OpenAI 上游 `base_url` 只校验 scheme,允许明文 http 与任意内网主机(与 xAI 严格钉死不一致,`openai/config.rs:135`);session cookie 无条件带 `Secure` 但无文档/开关说明必须 TLS(HTTP 部署会登录成功却拿不到会话,`admin/auth.rs:22`);`security-scan.yml` 只覆盖依赖漏洞,缺 secret scanning,容器扫描不在 PR 上跑;release 资产选择兜底分支会挑中任意平台匹配资产且远端资产名直接当路径拼接(`release.rs:166`)。 - ---- - -## 7. 升级兼容与发布链路 - -### 🟠 P1 2.x → 3.x 一键更新砖机(含 4.0 前向风险) -`system_update/release.rs:262` - -已逐字复核:v2.5.3 的 `version_is_newer` 与现行**完全相同**(纯 semver、无 major gate);两代资产契约逐字兼容(同一 `releases/latest` 端点、同一命名 `codex-proxy-rs_{ver}_{os}_{arch}.tar.gz`、同样 checksums.txt 与解包布局)。因此存活的 2.5.3 部署**今天就显示「3.0.10 可更新」**,点击后下载/校验/替换/重启全部成功,然后 3.x 二进制解析 2.x 布局 config.yaml 失败(`deny_unknown_fields` + 必填段)→ `InvalidDocument` 启动即死;即使手工迁移配置,2.x 库的 `0001`(不同 checksum)+`0002` 仍让 sqlx 拒绝启动(`postgres/mod.rs:43` `MIGRATOR.run` 失败即启动失败,**无 VersionMismatch 兜底**)。README/deploy/README/release notes 三处均无 clean-break 声明。docker 需 force-recreate 回退、binary 需手工恢复 `.backup`,rollback API 因进程起不来而不可用。**且 3.x updater 同样无门禁,4.0 clean break 时全部 3.x 部署复现。** - -**修法**(三件):① 现行 `detail_from_release`/`perform_update` 加 same-major 门禁(`latest.major != current.major` → `has_update=false` + `unsupported_reason`),为 4.0 兜底;② 每个 3.x GitHub Release body 首行加醒目警告(2.x UI 把 `release.body` 作为更新说明展示,是唯一能在点击前触达存量用户的通道);③ README/deploy/README 明确 3.0 是 clean break。 - -### 🟡 P2 迁移前向演进无约束 —— 下次原地改写 0001 = 全 fleet 砖机 -`docs/architecture.md:147` - -`git log --follow 0001_initial.sql` 共 5 次改写,全在 v3.0.0 之前;v3.0.0..HEAD 迁移目录零提交(3.x 内部升级目前安全)。但 0001 文件头无「发布后禁改」声明、无 CI 校验其内容不变。sqlx 按 `(version, checksum)` 对账:3.x 发布后若沿用「原地改写 0001」的习惯做任何 schema 变更,所有存量部署下次自更新即 VersionMismatch。**修法**:0001 文件头 + architecture.md 写死「v3.0.0 后 0001 冻结,变更必须新增编号迁移」;加廉价 CI guard 断言 0001 的 sha256 等于冻结值。 - -### 其余发布链路 -- 🟡 **P2 Windows 自更新必然失败**(`system_update/archive.rs:24`):`platforms.yaml` 为 windows 发布 zip,`select_archive` 会选中 `.zip`,但 `extract_release` 无条件走 `GzDecoder + tar` → 「failed to read release archive」。失败发生在下载 + checksum 之后。加 zip 分支或显式 `update_supported=false`。 -- 🟡 **P2 compose `stop_grace_period=30s` < 串行 drain(30s)+worker(30s) 预算**(`deploy/compose.yaml:80`):长流吃满 drain 时 worker shutdown 被 SIGKILL,请求停留 running 等 stale recovery。提到 ≥70s 或两超时共享总预算。 -- 🟢 **P3**:`CPR_UPDATE_CHANNEL=preview` 是死配置(`/releases/latest` 永不返回 prerelease);Dockerfile `release-asset`/`release-asset-builder` 两 stage 零引用;`version.yaml` 允许 `+build` 元数据但 `+` 是非法 Docker tag 字符会让 publish 失败。 - -**✅ 发布完好**:`deploy/config.example.yaml` 所有键与代码 `deny_unknown_fields` 零漂移;`release/publish` 原子推送 + release.yml 双重校验 version 一致性;3.x 内自更新端到端可用;全部 actions 按 SHA pin;CI 有 cargo-audit 周扫 + Trivy 容器 SARIF。 - ---- - -## 8. 死代码与冗余(全部已确认零生产调用者) - -> workspace 级 `deny(warnings)` 且生产源码零 `#[allow(dead_code)]`——crate 内私有死代码被编译器结构性拦截,以下全部存在于 **pub API 面**(编译器不查跨 crate pub 可达性),日常卫生极好。 - -| 类别 | 项 | 位置 | -|---|---|---| -| 死模块 | `history.rs` 整个模块(112 行含 SQL,`PgHistoryRepository`/`find_model_request_history`),§5 明说不新增 continuation 查询面却留此 | `gateway-store/postgres/history.rs` | -| 死 trait 方法 | client_keys 本地 repo trait 6 个方法(含成段 SQL,`update_provider_account_observation` 是无 fence 整行覆写枪,误用会绕过 revision 清空 quota) | `gateway-store/postgres/client_keys.rs:313`、`provider_accounts.rs:657` | -| 死 API 簇 | provider-openai `CollectedResponse` 枚举等 5 项(被删非流式收集 IR 残骸) | `openai/transport/protocol/responses.rs:371` | -| 死 API 簇 | gateway-core 8 个 pub 项(`canonical_facts_mut`/`try_batch`/`commit_requirement` 等) | `gateway-core/event.rs:720` | -| 死 API 簇 | provider-xai `prepare_cycle_if_due`/`has_due_refresh`/`list_due_refresh`/`synchronize` 等四组 | `xai/credential/catalog.rs:1015`、`refresh.rs:342` | -| 死变体 | `ConflictKind` 7 变体中 4 个从未构造;openai WS 4 个错误/原因枚举变体;`GrokRefreshFailure::Banned`;`GrokCatalogReasoningEffort` 未用;admin 5 个错误码常量 + `into_data`/`into_context` | 分散 | -| 死用量簇 | gateway-protocol `extract_sse_usage` + `UsageExtraction` 变体 + `response_failed_sse_event`(仅测试可达) | `gateway-protocol/openai/events.rs:109` | -| 未用依赖 | `gateway-host/uuid`、`provider-openai/zeroize`、`gateway-core/chrono`(Cargo.toml 声明未用) | 三处 Cargo.toml | -| 冗余 | openai/xai 两 provider 复制粘贴换算函数簇(`account_from_record` 等 6 个,~250 行,已漂移:openai 版 billing 完整性多校验两项)——上移到 gateway-admin/gateway-protocol | `xai/src/admin.rs:817` | -| 冗余 | xai/openai canonical 解码器重复 helper(已漂移,A3/A4 修复要改两处)——下沉 gateway-protocol | `xai/transport/canonical.rs:583` | -| 冗余 | `valid_secret_header` 同 crate 逐字两份;`normalizeVersion`/`errorMessage` 前端各两份;`china_day_start` 两 crate 双实现 | 分散 | - ---- - -## 9. 前端(Vue3 + TS,约 20k 行) - -**✅ 良好**:请求竞态防护系统化(`requestSequence`/`requestId` 守卫 + `onScopeDispose` 失效在途);资源清理纪律好(timer/RAF/SSE/EventSource 全清理,无泄漏轮询);唯一 `v-html` 经 marked + DOMPurify;变更操作统一 `expectedConfigRevision` 乐观并发 + 409 自动 reload。 - -- 🟡 **P2 API 层零类型**(`frontend/src/api/request.ts:83`):`request()` 返回 `any`,全仓派生 wire 类型全部塌缩;`Awaited>` 制造「已有类型」假象比显式 any 更危险。后端 wire 改名(当前工作树正改 `openai/models.rs` 即此类)前端零编译反馈。**修法**:`request`,按前端实际消费字段补最小接口(只声明用到的字段 + 开放索引,不做全量 wire 镜像)。 -- 🟢 **P3**:usage 页 `OpsErrorPanel` v-show 常驻未进错误页签也全量拉取;诊断维度切换后过期请求失败仍弹无关 toast;使用记录详情无并发防护与 loading;趋势图色调靠后端中文 label 子串匹配(文案即协议)。 - ---- - -## 10. 测试与工程门禁 - -**✅ 良好**:9/9 crate 统一 `autotests=false` + 单 `[[test]] main` target 镜像生产模块树(约 1170 测试函数);WS 池语义 25 个测试覆盖完整;PG 每测试 UUID schema 隔离真实跑 0001;透明性有字节级断言(raw SSE frame 逐字节相等、SSE/WS 同 JSON 断言);CI 严谨(clippy `-D warnings`、actionlint、actions SHA pin、cargo-audit 周扫 + Trivy)。缺口: - -- 🟡 **P2**:架构测试强制力只覆盖 2/9 crate(见 §4);gateway-api WebSocket 会话循环(722 行)零测试,双 transport 只有 HTTP 侧有交付语义断言,恰是已知 WS/HTTP 分歧带;PG/Redis 集成测试环境变量缺失时静默跳过,96+ store 测试可无声假绿(CI 侧 env 名漂移即整体跳过而门禁仍绿)——建议 `CI` 环境下缺 env 直接 panic(fail closed)。 -- 🟢 **P3**:architecture 测试用弱字符串/计数断言可被无意绕过;文档终态门禁与 CI 实际门禁漂移且无 justfile/Makefile 统一本地入口;frontend 零测试(CI 前端门禁仅 eslint + tsc)。 - ---- - -## 11. 文档合同漂移 - -- 🟢 **P3**:`docs/api.md:25` 称管理请求「必须携带非空 x-request-id」,实际缺失时服务端自动生成(对客户端不成立);`README.md:94` 宣称 Client Key 可「设置模型范围」,该功能全仓不存在;`docs/api.md:39` 稳定业务码清单缺登录限流 42901 与凭据类 40102/40103;`GET /v1/models?client_version=` 会切换 Codex 目录合同(`{"models":[...]}`)但文档未记载双响应形态(正是当前工作树未提交改动引入的行为)。 -- ⚠️ `GET /v1/models` 的 `Query` 提取失败会在鉴权前返回 axum 明文 400,脱离 OpenAI 错误合同(工作树新增代码,`openai/models.rs:31`)。 - ---- - -## 12. 迁移遗留项对账(对 `[[multi-provider-migration-audit]]` 的 4 项收尾) - -| 遗留项 | 现状 | 证据 | -|---|---|---| -| **A3** xAI 断流回归 | ✅ **已修复 2026-07-26**:内容事件 Protocol 错误容忍+wire 转发,终态保留 fail-closed,附回归测试 | `xai/transport/canonical.rs:255`(§1) | -| **A4** xAI 错误正文吞噬 | ✅ **已裁决并修复(2e0bee60)**:转发结构化 message/code/type 走 ClientVisibleUpstreamError 通道 + UUID 账号指纹脱敏 | `xai/transport/canonical.rs`(§1) | -| **B8** 上下文 body 别名 + client_metadata 回退 | 🟡 **半修** | client_metadata 回退已移除,body 顶层 camelCase 别名仍优先于 header(`request.rs:131`,P3) | -| **CodexCatalogVisibility 缺 serde(other)** | ✅ **已修** | `openai/transport/catalog.rs:159` 已带 `#[serde(other)] Unknown` + 测试(`tests/transport/catalog.rs:185`) | -| **C6** 指纹自动覆写 codex_version | ⚖️ **裁决点**(未修但文档已改写为设计) | `architecture.md:13` 已将「官方版本检查原子更新版本字段」写为有意设计,与 C6「指纹应钉死」直接冲突。OpenAI `profile.rs:102` + xAI `profile.rs:171` 均每 24h 轮询 npm 自动改出站版本指纹,无 pin 开关 | - -**C6 需你裁决**:维持 C6(两 provider 加「版本跟随开关」,默认 pin 配置值,release 检查仅上报不覆写)还是正式确认自动跟版为设计并关闭 C6。 - ---- - -## 13. 修复优先级建议 - -> P0 已清零(初版的两条 xAI P0 经定性修订撤销/降级,见 §1)。 - -**P1(砖机/数据失真/凭据误废/真红线,本周)** -1. CORS panic → 显式 header 白名单 + 非空 origin 测试 -2. 2.x→3.x 砖机 → same-major 门禁 + Release body 警告 + clean-break 文档 -3. store 健康指标恒 0 → 结局计数从完成态过滤解耦 -4. admin 登录限流 → 中间件注入 `AdminLoginSource` -5. host shutdown 二次 await panic → `JoinSet` -6. host drain 无界 → serve future 加 `drain_timeout` 上界 -7. OpenAI token 刷新瞬态失败 → 按状态码分类,429/5xx/超时走 defer 而非 Invalid(两条) -8. ~~OpenAI WS 字节重序列化(唯一真红线)→ WS 衍生流启用 raw 透传~~ ✅ **已修复 2026-07-26** -9. core 重试后 send_state 降级覆盖 → 请求级发送高水位 -10. xAI 批量导入 RT 丢失 → 无副作用校验前置 -11. ~~xAI A3 断流回归 → decode_event Err 路径降级为跳过事件保流,对齐 grok2api 容忍语义~~ ✅ **已修复 2026-07-26** - -**P2(正确性/性能/架构/安全/xAI 翻译,迭代)**:xAI 请求侧白名单重组改 raw surgery、~~xAI A4 错误透传~~(✅ 已裁决修复:对齐基线转发 + UUID 脱敏,2e0bee60)、`GrokCatalogReasoningEffort` 去枚举、WS reducer 单次解析、retention 加索引 + 分批、PgPool 可配置、dashboard 并发查询、自更新签名验证、admin 改密端点、Windows 更新修复、迁移冻结 CI guard、architecture 测试补全、多副本节拍文档。 - -**P3**:死代码清理(一次性删 history.rs / 各死 API 簇 / 未用依赖)、冗余下沉、文档合同同步、前端 API 类型化。 - ---- - -## 附录 A:被推翻/撤销的发现(透明度) - -对抗核验推翻 4 条 + 定性修订撤销 2 条: -1. **admin `diagnostics()` 成功数虚高** — REFUTED:数据源同样被完成态过滤钉死,重算值与精确值恒等。 -2. **`TrendPoint.success_rate` 分母不一致** — REFUTED:同上,两公式数值恒等。 -3. **前端双击复制误弹「Key 不可用」** — REFUTED:按钮 revealing 时 disabled,第二次点击不触发,仅防御性瑕疵。 -4. **OpenAI 目录进程内快照无 TTL 跨副本发散** — REFUTED:RuntimeSnapshot 有 5s revision 对账兜底 + 编译期 generation 前后比对防 TOCTOU。 -5. **xAI A4 错误正文吞噬「红线违规」** — 撤销:`redact_sensitive_context` 属红线明示的「风控剥离」例外,疑为 by-design(见 §1)。 -6. **xAI A3「未知事件断流」** — 部分撤销:`_ => Ok(())`(canonical.rs:291)容忍并转发未知事件,该说法错误;剩余「已知事件字段校验断流」降级为 P2 健壮性(见 §1)。 - -## 附录 B:有意设计声明 - -- **密钥/凭据明文存储** 为有意设计(DB 明文、配置明文),全程未作为问题上报。 -- **指纹自动跟版(C6)** 已被 `architecture.md` 写为设计,本报告仅列为裁决点,不单方判违规。 -- **admin 路径重命名** 为迁移期已授权变更,前端已适配,未回滚。 - ---- - -*报告生成:2026-07-26 · 方法:双轮 56-agent 并行审计 + 对抗核验 · 全部 file:line 基于当前工作树 · 头部结论经二次代码抽验*