docs: record approved thinking contracts and implementation plan

This commit is contained in:
2026-09-09 00:48:31 -04:00
parent 6a090541be
commit 2553fc7f34
2 changed files with 695 additions and 0 deletions
@@ -0,0 +1,325 @@
# 1.3.4:推理意图的可满足性与测试证据契约
> 日期:2026-09-09。状态:**已通过独立审查并获人类正式批准;进入实施计划阶段,编码须先完成计划审查**。
> 用户已批准合并处理 #25/#26/#21,及 D1(未知 AUTO 尽力+告警)、D2(受管推理与 raw 冲突拒绝)、D3(下游显式迁移 namespace/salt)。下文细则供正式设计审查,不重新悬置已决方向。
> 本轮只修订本文件;未改生产代码/测试、未提交、未启动子代理、未执行真实付费调用。既有审查历史与剩余证据门见 §11。
## 1. 目标、非目标与事实源
本批修复共同问题:库声明的推理意图、实际发送参数和验证证据应一致;无法确认的事实不得变成成功声明。
| 范围 | 交付 | 明确不做 |
| --- | --- | --- |
| #21 | 已登记 AUTO 成员检查;移除默认 provider 偷选 medium;冲突守卫;能力证据审计与迁移说明 | 新推理 DSL、通用 overlay 系统、预算型推理、模型名猜测、无证据批量加 AUTO |
| #25 | 测试侧窄归因函数、逐轮留证、未覆盖汇总;纯本地断言离线化 | 改四分类、整类异常 skip、重跑到绿、扩大生产遥测、自动豁免发布覆盖 |
| #26 | 真实 client→治理→emitter→recorder 守护无推理路径,附变异证据 | 新端口、替换 reasoning_applies、遥测 schema 改造 |
| 后续批次 | #19#23 归 1.3.5#22 归 1.3.6#24 独立设计 | 调用上下文 API、deadline、429 算法、对冲或取消结算重构 |
规范依据:CLAUDE.md、brainstormingstructured-logging skill、ARCHITECTURE D11、§4.55.167.57.8、docs-convention。
旧设计:`2026-09-04-reasoning-effort-design.md` §36812;旧实验:`findings/2026-08-02-thinking-switch-and-reasoning-tokens.md``2026-08-25-thinking-observability-regression.md`
Issue 原文:`/tmp/polygateway-issue-triage/open-issues.json`。旧文档的“26 模型”“仅 reasoning_tokens 可靠”“下游尚未迁移”不是本轮验证结论。
## 2. 当前代码审计
| 证据 | 事实与根因 |
| --- | --- |
| `thinking.py::_settle_tier` | `effort is AUTO or effort in supported_efforts` 无条件放行 AUTO;形态与模型能力不能共同约束它 |
| `providers.py::ThinkingWire` | 空 on_base 当前文案混同协议形态与“任意模型默认推理”;需分开 |
| `DEFAULT_PROFILES[minimax]` | on_base 当前为 reasoning_effort=mediumM3 不是仍必然不推理;问题是 applied_effort=auto 却发 medium |
| `openai_compat.py::_build_payload` | resolution 后依次浅层 update extra_bodyoverlayraw 可改写或新增推理控制,成功档位与最终字节失配 |
| `client.py::_guard_thinking``middleware/cache.py` | 工厂仅解析源级意图;缓存命中早于 transport,请求级、全量注入及同版本策略变化都可能回放旧语义 |
| `middleware/telemetry.py` | 成功尝试、失败尝试、缓存命中、终态失败的档位来源不同,不能统称“成功记实际档” |
| `embedding.py::_emit``ocr.py::_emit` | 当前正确传 False;只测 emitter 不覆盖调用点,成功响应默认 None 还会掩盖变异 |
| `test_thinking_live.py``test_embed_probe.py` | 前者按异常类别 skip、用正文子串识别 model_not_found;后者捕全部 PolyGatewayError 当“不支持”,都可能遮蔽库回归 |
本轮核对 `origin/main..HEAD` 为既有 `758a127``6a09054`,保留历史提交、不重写;其外因判据按 §6 窄修正。
当前 `reference/` 只有 cherry-studio、litellm、new-api、vercel-ai-sdkGovDoc-SaaS、Video-Tree-TRM5、CHSAnalyzer 工作区均缺失,**未核验三项目现行配置**。迁移示例不是下游迁移完成证据。
## 3. 备选方案与已批方向
| 方案 | 收益 | 代价/结论 |
| --- | --- | --- |
| Asupported_efforts 包含 AUTO 的语义能力 | 不新增模型字段,统一可满足性判据 | 部分旧 True 配置报错,须审计清单并迁移;**用户已选 A** |
| B:新增模型默认推理三态 | 默认开启与未知可分别表达 | 多维护一套事实,易与能力清单漂移;不选 |
| Cprovider/模型把 AUTO 映射固定档 | 保持旧配置字节 | 库代选付费档位,模型事实塞进 provider;用户不选 |
| 决策 | 已批准 | 不采纳的备选及理由 |
| --- | --- | --- |
| D1 | 未登记 AUTO 保留尽力注入+warning,不保证开启;已登记一律成员检查 | 不改成未知即拒绝;也不把未知当能力已验证 |
| D2 | 有受管意图时拒绝 raw 推理冲突;无受管意图保留 raw | 不保留双来源互相覆盖,不从最终 payload 反向猜档位 |
| D3 | 下游显式换 namespacesalt 隔离语义变化 | 不加自动 revision/包版本,不让所有 chat 擅自冷启动,不把能力解析搬进缓存 |
Issue #25 选“离线硬契约+有限可执行归因+显式未覆盖”;#26 选真实客户端与轻量 recorder 为日常测试、临时 SQLite 为持久化锚点,不要求远程 PG。
## 4. 推理语义与最终请求体契约
### 4.1 词汇与决策矩阵
`supported_efforts` 表示模型在**当前登记的 OpenAI 兼容 wire** 下、有依据可执行的语义集合,不是上游字符串枚举的逐字镜像。
`AUTO` = 要求开启、不指定强度;不是 Python None(不表态),不是库任选一档,也不是上游必须接收 `auto` 字面值。
`on_base=None` 是开启形态未知;`on_base={}` 是该协议不加开启字节,**能否据此满足已登记模型 AUTO 另查清单**。非空开关也不豁免成员检查。
请求档>源级 reasoning_effortenable_thinking 糖(True→AUTO、False→NONE);None 不覆盖源级表态,源上两字段矛盾的既有构造校验保留。
| 生效输入/条件 | 结果 |
| --- | --- |
| effort=None | 不注入,applied_effort=Noneraw 仍按旧优先级发送,不推断其档位 |
| 所需方向形态未知 | ThinkingUnsupportedError,指路注册 profile;不因模型未登记而假造 wire |
| 已登记 AUTO 在清单 | 仅发 on_base,不附强度,applied_effort=AUTO |
| 已登记 AUTO 不在清单 | 明确拒绝并列可用档;fallback=nearest 同样拒绝,不能建议 nearest 修复 |
| 已登记显式强度不支持+nearest | 保留最近开启档、等距弱侧;只有纯开关候选时可强度→已登记 AUTO,不能 AUTO→强度 |
| NONE 不支持/没有 off 形态 | 保留专用不可关闭错误;替代配置由用户选择,不自动执行 |
| 未登记 AUTO/强度/NONE | 按已知 wire 尽力注入+warning;wire 表达不了仍拒绝,不编造支持清单,不保证开启/关闭/强度有效 |
未知 AUTO 的空 on_base 可能发送零推理字节,返回 AUTO 仅表示尽力编码的选择,**不构成能力验证 PASS**。既有 warning 需明确能力未知、可能不生效,保留实例节流。
NONE 方向仍按 `_wire_unknown_for` 既有分工:off、on_base 皆 None 才是整体未知;off None 而 on_base 已知是缺关闭形态。
### 4.2 MiniMax 与能力证据
移除默认 minimax.on_base 的 medium,改为空映射;M3 不加 AUTO。显式 medium 仅作为恢复旧字节的迁移示例,不是库推荐的最优成本档。
按本轮源码清点,默认表 24 个型号、10 个含 AUTO;live 候选 26 型号不等于能力表 26 条。**本候选不授权新增任何 AUTO 条目**;追加须另附逐型号证据并复审,不因未知、不可达或同厂近代型号有能力而补登。
| 模型组 | 已有证据与本批处置 |
| --- | --- |
| MiniMax-M3 | 2026-08-25 裸 HTTP 无参数不推理、medium 有推理;2026-09-05 evidence 同向。不加 AUTO,显式档保留 |
| MiniMax-M2.5M2.7 | 已有 AUTO;历史默认/mandatory 旁证及 T10 开启观测。T10 未留最终 wire,移除 medium 后需补空 on_base 实测,不冒充已复验 |
| qwen3.7-plusmax、qwen3.6-plus、qwen3.5-flash、qwen-plus-latest、glm-4.6v | 保留已有 AUTO 与逐型号证据,不推及其他型号 |
| glm-55.1 | 保留既有文档推定 AUTOT10 回报 glm-5.3,身份不足,不算本型号实测 |
| deepseek-v4-proflashflash-vision-exp、glm-5.25.35.3-flash、kimi-k3kimi-for-coding | 现无 AUTO;显式档成立不等于 AUTO wire 成立,本批仍拒绝其 AUTO |
| gpt-5.5 | 现无 AUTO;历史报告 `tier_probe_20260905_184307.md:97` 无参数基线 5/5 rt=18、身份一致,是候选线索,但最终 payload/raw 覆盖未留证,不直接追加 |
| gpt-5.4、claude-opus-5sonnet-5、gemini-3.1-pro | 现无 AUTO;限额/上游错误导致未覆盖,不能以“默认 medium”或同代替代证据 |
| claude-haiku-5、gemini-3-flash | 未登记 live 候选,按 D1 尽力+告警;不自动登记 |
历史报告仅局部抽查;本轮未全表复验、未新跑付费实验。默认 wire 改变的 M2 两型是发布前显式缺测项,不能靠缓存回放旧 medium 的成功结果过门。
### 4.3 D2 冲突规则(窄边界,不做通用 overlay)
“受管意图”指按 §4.1 得到的 effort 非 None**包括 NONE、AUTO、糖及未知模型**。单凭 raw 不构成受管意图。
当前浅层次序保持为 `基础 payload → resolution.payload → source.extra_body → request.overlay`;禁止改成深合并。守卫只校验所有权,不重写/删除 raw,不反推档位。
| 检查对象 | 精确规则 |
| --- | --- |
| 已知 raw 推理控制 | 顶层 `reasoning_effort``enable_thinking``thinking``thinking_budget``reasoning``thinkingConfig` 为控制根;`output_config` 为对象且含 `effort` 也算控制。这是显式有限词表,不按任意键的子串/模型名猜测 |
| 当前 profile 的控制根 | 加入 on_base、off 的全部顶层键及非 None 的 effort_key;即使本次为 AUTO 且 on_base 为空,也保护 effort_key/off 根。保护范围取开关两向并集,不只取本次实际注入键 |
| 同值与遮蔽 | 有受管意图时,extra_body 或 overlay **任一层**出现控制根即拒绝,值相同也拒绝;被后一层遮蔽也不豁免,避免双来源随配置变化重新失配 |
| 嵌套/浅覆盖 | `thinking={}``thinking={"budget_tokens":100}` 也拒绝:替换整个根会删除受管 type。当前 wire 持有某根时,raw 仅改其看似无关子键仍拒绝。根未被 wire 持有时,`output_config={"format":"json"}` 不因兄弟键 effort 被保护而误拒 |
| 新增而非覆盖 | AUTO 的空片段遇 raw reasoning_effort=high 仍拒绝;qwen 受管开关遇 raw reasoning_effortthinking_budget 也拒绝,不能只检查字典交集 |
| 无受管意图 | 即请求、源级档与糖都不表态,保留 extra_body→overlay 原有浅覆盖(含 raw 推理),applied_effort=None;原 model/messages/stream/stream_options 禁写规则照旧 |
自定义 wire 不新增路径 DSLeffort_key 仍是一个**顶层字面键**,不把点号解释成嵌套路径;on_base/off 可含嵌套对象,raw 守卫保护其整个顶层根。
自定义 on_base 不得包含自己的 effort_key,也不得借标准 reasoning_effort 偷带档位;无论其值是 medium、auto 或 None 都拒绝为配置错误,不能默默删键。标准嵌套 `output_config.effort` 同属禁带强度的已知路径;不解析任意私有嵌套方言。需要强度请走显式档,不能将其固化在开启片段。
其余自定义不透明方言的语义真实性由注册者提供证据;本批保证声明键不被 raw 冲掉,**不宣称可以识别所有未声明的私有别名/预算语义**。扩展别名应登记 wire 后受控,不新增通用参数解释器。
### 4.4 守卫时机、错误与保证范围
| 入口 | 时机/职责 |
| --- | --- |
| 工厂 `_guard_thinking` | 已有 profilecapabilitysource 材料齐全;装配期(网络及准入前)校验 wire、源级可满足性与源 extra_body 冲突,抛 ThinkingUnsupportedError(配置 ValueError)。工厂拒绝的源不能靠未来请求覆盖“救活” |
| `chat` 前置参数校验 | 保留 validate_request_overlay;请求显式档+调用 overlay 的已知控制词表冲突可在进入洋葱/准入前报配置 ValueError。不新增全源能力预解析,不声称这里可见自定义 transport 的注册表 |
| 默认 transport `_build_payload` | 以本次选中源、实际 profile、请求覆盖后的意图,对两层 raw 再做完整守卫;全量注入与自定义注册表同样覆盖。ThinkingUnsupportedError 翻译 RequestRejectedErrorHTTP 发送前拒绝,无重试/换源/故障熔断计数 |
| 自定义 Transport | 不通过默认 transport 的调用仍由端口实现方履约;本批不添加 preflight 端口,不反射读取私有注册表,不承诺能验证任意注入实现 |
transport 守卫**可能已经经过选源、限流预扣与准入**,拒绝后的结算沿既有 finally 路径;“零 HTTP”不等于“零准入操作”。不移动洋葱层次以制造所有请求均前置拒绝的过宽保证。
真实成功尝试将同一 resolution.applied_effort 传给响应与 emitter,不重算;AUTO 是未指定强度的选择,不是服务端内部强度。服务端是否接受/执行推理由 ThinkingObservation 回答,失败与缓存行另见 §8。
缓存命中不经过上述 transport 检查,所有“缓存不得绕过新拒绝”验收都以 §5 完成迁移为前置。
## 5. D3 缓存迁移与下游配置
### 5.1 显式迁移边界
缓存身份继续沿 ARCH §7.5;**不加入自动 revision、包版本、fallback、能力表或 wire 版本字段**,不删除旧键、修改共享 Redis 或重写历史遥测。
语义变更包括本次 AUTO 成员检查/raw 冲突规则、MiniMax wire 变化,以及同版本下 fallback=nearest→error、能力增删/映射变化、自定义 profile wire 变化;这些都由下游显式换 namespace/salt 隔离。现有源指纹包含部分配置不代表包含全部语义。
| 操作 | 下游必须做/边界 |
| --- | --- |
| 迁移前 | 盘点工厂与全量注入、scope/租户、源级糖/档/raw、请求级覆盖、fallback、自定义 wire,以及实际 per-call namespacesalt 覆盖 |
| 切换 | 为受影响调用集合选择从未承载旧语义的 namespace 或 salt;在首次新语义读写前部署到该集合全部调用者。租户前缀与原 epoch salt 保留后再追加人工迁移标记,不共享租户身份 |
| 多源 | 一个缓存 scope 内只要有源受影响,必须隔离该共享身份的调用集合;要求更细范围由下游拆独立 scope/namespace,本批不替下游重分组 |
| 并行与回滚 | 新旧客户端不得共享迁移后身份;回滚到旧 namespace 会重见旧语义,不能说旧键已被清理。未来策略变更须再次显式迁移,不能复用一次标记包办所有变化 |
| 未完成迁移 | 旧缓存或同版本 nearest 写入可被 error 客户端命中,库不会自动验证其新可满足性。文档警示是操作前置,不是新增的自动安全机制 |
**不要求所有 chat 冷启动**;未受影响调用可保留身份。若受影响与不受影响调用原本共享一套身份,下游须明确选择整体换标记的成本或先拆分,库不代选。
### 5.2 旧→新配置与验证矩阵
下表是**配置迁移示例及拟新增离线节点**,不是已执行测试。显式档均为当前清单成员示例,不代表所有渠道已实测、不作成本代选。所有受影响且启用缓存的行还须执行 §5.1。
| ID/旧配置 | 受影响模型 | 新行为(源级工厂/请求级默认 transport) | 用户明确选择的新配置 | 离线验证锚点(拟) |
| --- | --- | --- | --- | --- |
| M1 `ENABLE_THINKING=true``REASONING_EFFORT=auto` | MiniMax-M3 | 装配 ThinkingUnsupportedError/请求 RequestRejected,零 HTTP | 删除糖,`REASONING_EFFORT=medium` 可恢复旧 medium 字节;也可选择表内其他档 | `test_thinking.py`M3 AUTO 拒绝/medium payload |
| M2 同上 | deepseek-v4-proflashflash-vision-exp、glm-5.2 | 同上;开关 on_base 非空也拒绝 | 删除糖,显式 `REASONING_EFFORT=high` 或经用户选择 max | `test_thinking.py`:非空 wire AUTO 成员约束 |
| M3 同上 | glm-5.35.3-flash、kimi-k3kimi-for-coding | 同上,nearest 不能解 AUTO | 删除糖,显式 `REASONING_EFFORT=low`(也可选 highmax | `test_thinking.py`AUTOnearest 拒绝 |
| M4 同上 | gpt-5.45.5、claude-opus-5sonnet-5、gemini-3.1-pro | 同上;不可达不补 AUTO | 删除糖,用户选表内 `REASONING_EFFORT=medium`;这不是新增 live 证明 | `test_thinking.py`:空 wire 非成员拒绝 |
| M5 同上 | MiniMax-M2.5M2.7 | 仍 AUTOon_base 从 medium 改空,真实语义待补测 | 保留 True/AUTO 并迁移缓存;需要旧 raw 字节者须完全退出受管意图,不可谎称该模型支持 medium | `test_thinking.py`:空 wire AUTO 字节;live 单列缺测 |
| M6 同上 | qwen 五型、glm-55.14.6v | AUTO 仍是成员,原开关形态保留 | 保留配置;glm-5/5.1 仍身份未覆盖 | `test_thinking.py`:已登记 AUTO 放行 |
| M7 同上 | 未登记模型(含两个 live 候选) | 已知形态尽力+warning,不保证开启 | 可保留 AUTO;要求确定保证者先取得能力证据再登记,不自动加表 | `test_thinking.py`:未知空/非空 wire 警告 |
| M8 源 HIGH`EXTRA_BODY={"reasoning_effort":"high"}`;或请求 AUTO+raw HIGH | 所有受管模型,含未知 | 同值也拒绝;工厂或前置/transport 对应守卫报错 | 保留受管档并删除两层 raw 控制键;或清空源糖/档、请求不表态,仅 raw | transport 单测:同值、嵌套、被遮蔽、raw-only |
| M9 请求 medium,源 `EFFORT_FALLBACK=nearest` 改 error | 如 glm-5.3(映射 low→拒绝) | 源未表态时两工厂均可装配;旧身份可命中,新隔离身份在 transport 拒绝 | 改 error 的同时显式迁移 namespacesalt | cache 单测:nearest 写入→error 读,新身份必须 miss |
配置实例(仅列需替换项,其他已校验的源配置保留):
| 场景 | 旧 | 新 |
| --- | --- | --- |
| M3 源级 | `LLM__MINIMAX__1__ENABLE_THINKING=true` | 删除该键;`LLM__MINIMAX__1__REASONING_EFFORT=medium` |
| 请求级 AUTO | 源不表态;`chat(..., reasoning_effort="auto")` | 源仍不表态;用户选择 `chat(..., reasoning_effort="medium", cache_salt="epoch-7:thinking-134-a")`M3 示例) |
| 工厂缓存 | `PGW_CACHE_NAMESPACE=lab:tenant-a` | `PGW_CACHE_NAMESPACE=lab:tenant-a:thinking-134-a`(人工标记,不是新增配置键) |
| per-call 租户覆盖 | `cache_namespace="tenant-a", cache_salt="epoch-7"` | `cache_namespace="tenant-a:thinking-134-a", cache_salt="epoch-7"`;只改工厂默认值对此路径无效 |
| 全量注入 | 构造参数 `cache_namespace="lab:tenant-a"` | 改成上述新 namespace;既有 cachettl 参数照常注入 |
| 自定义 wire 偷带强度 | `on_base={"reasoning_effort":"high"}` | `on_base={}`、保留 effort_key;用户显式选 HIGH(须模型支持),并迁移缓存 |
三项目迁移验收须由各自负责人提供脱敏的实际配置/装配与调用位置,映射 M1–M9、提交所选替代与缓存身份切换证据。**当前三项目均为未核验**;Protocol 合成兼容测试通过也不能代替现行配置迁移验收。
### 5.3 旧行为处置
| 旧行为 | 处置 |
| --- | --- |
| True→AUTO、请求>源>糖、NoneNONE、未知尽力+warning | 保留;未知仍受 wire 可表达性约束 |
| 已登记 AUTO 无条件放行、MiniMax 偷带 medium、受管与 raw 双来源 | 替换,明确放弃这些隐式兼容;迁移见上 |
| 原 raw-only、普通采样优先级、显式强度 nearest | 保留,不做通用 overlay 重构 |
| 缓存旧键/历史遥测/响应字段/端口签名/成本口径 | 保留数据及签名,语义变化靠显式缓存身份迁移,历史不伪造新观测 |
| 任务恢复/断点续跑 | 不适用,无任务状态;并行版本与持久化纪律见 §5.1 |
## 6. #25:可执行的测试侧归因与证据
### 6.1 输入来自哪里
仅在测试侧增加一个纯分类函数及窄取证 fixture,复用 Markdown 报告;不新增生产事件、字段、端口或通用诊断框架。**不能从压平的最终异常字符串重建缺失尝试**。
| 输入 | 可实现来源与限制 |
| --- | --- |
| 预期请求 | 测试矩阵显式提供目标 model、POST 端点路径、stream、允许的源 origin、预期推理/结构化片段与提示词摘要;不能调用待测 `_build_payload` 生成“预期” |
| 实际请求与 HTTP 响应 | 利用既有 `OpenAICompatTransport(client_factory=...)` 注入带 httpx requestresponse hooks 的真实 AsyncClientrequest hook 检查 method、规范 URL、JSON modelstream/控制片段,Authorization 与该源凭据仅在内存精确比较,输出布尔值 |
| HTTP 错误体 | response hook 保留本次响应引用;该次 complete 结束后读取**已缓冲** content(最多接受 64 KiB 完整内容作为分类输入)。未缓冲/超限/解析失败均标证据不足;不在 hook 预读成功 SSE,不另发请求,不以摘要假装完整 JSON |
| attempt 关联 | 测试专用窄 Transport 委托器原样转发 completeembed 参数与异常,将入参 call_idattempt UUID)放入任务局部 ContextVar 供 hooks 使用;finally 复位。只改测试装配,实际 payload/解析仍由真实 transport 执行 |
| 逻辑调用与错误 | 每轮 chat 显式传 session_id=运行 ID、parent_call_id=本轮 UUID,并由测试在 chat 外围将同一二元组绑定任务局部上下文、finally 复位;委托器从该上下文建立二元组→attempt UUID 关联,保存原始异常类/cause 与 HTTP 记录。归因用逐次记录,不靠最后一个错误推断所有前序 |
带取证的 live 用例走既有全量注入路径,工厂与默认 client_factory 的装配/鉴权构造另有离线回归,不能用测试工厂替换后宣称原工厂已验证。需验证工厂本身的 live 用例若没有该证据通道,失败就保持 FAIL,不补生产接口凑证据。
请求 hook 校验不通过须记证据并使测试 FAIL;不得改写请求后再称原请求正确。凭据不写哈希、不落盘;URL 去 userinfoquery,只存安全 origin 标识与路径。
### 6.2 精确分类:默认 FAIL
分类输出为 FAIL 或外部未覆盖;外部未覆盖在 pytest 中可呈 SKIP,但报告与覆盖汇总必须记“未覆盖”。成功响应的行为断言仍单独执行,不经此分类器放宽。
| 情形 | 精确规则 |
| --- | --- |
| 环境前置缺失 | 测试矩阵列出的必需凭据/可选外部 Protocol 包未提供,网络前记未覆盖;键存在但配置格式错误、值校验失败是 FAIL |
| 404 model_not_found | 仅当本轮恰有一次实际 HTTP 尝试、无其他错误,请求检查全通过,响应为 404,完整 JSON 对象的 `error` 是对象且 `error.type == "model_not_found"`,默认 transport 对外为 RequestRejectedError 且 status 一致,才记“端点回报该请求型号不可用,未覆盖”。JSON 重复键也拒绝作为证据 |
| 普通 404/伪机器字段 | 只在 message 出现子串、字段类型错误、截断体、未缓冲体、端点/model/鉴权不符、响应与 attempt 无法配对,一律 FAIL |
| 4295xx401403、网络错误 | **本批不建立自动外因豁免**:当前资料未给可核验的网关机器码白名单及独立归因来源,全部 FAIL 并保存实收证据;不能仅凭 HTTP 状态、TransientSourceDead 类或“请求离线测过”跳过 |
| no_sourcesstalledretry_exhaustedAllSourcesExhausted | FAIL;本批不从生产遥测补失踪逐次原因,不将“曾见过一条 429”推断为整个终态均外因 |
| SSEJSON 解析、空补全、ValueError、一般 RequestRejectedResultInvalid、断言失败 | FAIL;请求正确不证明解析器或治理正确 |
| CancelledError | 原样穿透,finally 清理,不变成 SKIP |
| 成功但模型身份缺失/不符 | 实发 model 检查不通过是 FAIL;最终 model_reported 缺失/不符且没有独立原始响应身份取证时,保持 FAIL 并标记“身份来源无法区分”,不能推断上游没报。只有独立原始响应证据证明上游身份缺失/不在显式别名集合,才可记能力未覆盖;原始身份正确而解析/搬运丢失或改错必须 FAIL。本批不新增成功 SSE 捕获器,无该证据通道时按 FAIL 处理;别名不按前缀猜测 |
没有证据通道时宁可 FAIL,不用抽象的“已证明外因”做逃生条件。运维人工确认可附外部证据供发布负责人决定豁免,**不自动把 FAIL 改 PASS 或扩大分类白名单**;未来扩大自动归因须独立给真实样本及反例契约。
不追加裸 HTTP 对照诊断、自动重跑或悄悄缩小超时/stall;新增调用须先有人类模型/轮次/并发预算。
### 6.3 覆盖判据与报告
**先声明测试命题,再解释观测**:下述关闭成功判据只适用于“支持 NONE 的型号应成功关闭”,不是要求所有型号均可关闭。
| 测试命题 | 观测与结论 |
| --- | --- |
| 已声明可关闭,验证受支持 NONE | 任一 OBSERVED 证伪关闭保证,FAIL;完整必需轮次且请求/身份合格、每轮 ABSENT 才可关闭覆盖 PASS;混入 UNKNOWN 记未覆盖 |
| 已声明不可关闭,T10 绕过库能力守卫验证上游 | 合格 OBSERVED 是本轮仍推理的证据,可支持该测试条件下的不可关闭声明,不是关闭成功,也不得仅因 OBSERVED 而 FAIL;必须按预先固定命题及轮次集合比较观测与声明,UNKNOWN 不补足证据,不据有限探测声称证明所有上游参数均无法关闭 |
| 有意请求不支持档位,验证预期拒绝 | 独立于外因分类器,直接断言预先声明的拒绝类型/状态与机器字段。符合预期的 400/RequestRejected 是负向契约通过,不是外因 SKIP;非预期错误仍 FAIL。上游探测照过请求资格、独立身份可得性与逐轮证据要求 |
增加离线反例:可关闭声明+OBSERVED 必须 FAIL;不可关闭声明+合格 OBSERVED 不得仅因出现推理而 FAIL;原始 JSON/SSE 含正确 model 但公共响应丢失/改错,必须 FAIL 而非 SKIP。全 UNKNOWN 绝不能算关闭覆盖通过。
本候选不采用 completion_tokens 长短作为通用关闭证明;旧 prompt_tokens 差异只保留为指定历史样本的 wire/观测回归锚点,不使 UNKNOWN 升格关闭能力 PASS。没有经单独审定的独立关闭证据就记未覆盖,运行时 UNKNOWN 不告警的既有语义不变。
开启测试保留显式轮数和多数 OBSERVED 规则(`observed_count > planned_rounds / 2`),但必须先保证全部计划轮次完成且请求/身份合格;失败/缺轮不得从分母删除。未知模型单次成功不自动变成能力登记。
逐轮先保存结果再判断后续处置;第 2 轮失败不能丢第 1 轮。报告字段包含矩阵 ID、provider、请求/回报模型、stream、请求档/响应 applied_effort、轮次、parent/session、attempt UUID、请求校验结果、异常类/status/安全摘要、已完成轮数及覆盖状态。
完整错误体仅内存分类,落盘只存机器字段与沿用 2048 字符上限的脱敏摘要;清除已知凭据及私有提示词回显,无法安全保留则摘要省略并标明。不得写 Authorization、完整 .env 或私有提示词。
每轮用唯一运行目录与轮次文件安全写入,汇总不能覆盖前轮失败;报告写失败使测试 FAIL,不允许无证据 skip。取消 finally 关闭自建资源,不引入无界网络等待。
| 既有测试接缝 | 窄修正 |
| --- | --- |
| L9 `_MYSTERY_PROFILE` | 保留显式全 None profile,将“未知形态报错”纳入离线断言;默认 openai 已非未知 |
| L8 `can_disable=False` | 装配拒绝只记本地契约通过,不写“与实测一致”;L8/T10 的 UNKNOWN 按上述覆盖门修正 |
| `_rounds_or_skip`、默认基线、T10 | 统一精确分类与逐轮留证;部分档未覆盖不能汇总为模型全覆盖 |
| compat Protocol/平铺键 | 完整合成 env/注入组件离线测装配;外部 Protocol 缺包只标该兼容项未覆盖,不声称读过缺失项目 |
| embedding 探测 | 去掉捕全错误为“不支持”,使用同一窄判据及 finally 关闭;不扩展端点能力 |
发布门分开统计离线契约与预先声明的 live 单元。必需单元 SKIP/UNKNOWN/缺行/未完成不能凭 pytest exit 0 放行,须重测或人类明示豁免;不自动缩小覆盖集合。
## 7. #26:真实调用链与变异证据
复用 `test_embedding.py``test_ocr_client.py` 的真实 client、脚本 transport、recorder 工装;仅替换外部传输,不 mock emitter,不手工 emit_attempt 伪造 False。
| 验证 | 必须断言 |
| --- | --- |
| embed、recognize_text、parse_layout | 源分别填 True 糖/显式 HIGH;一次 Transient 后成功,每路径确有两条尝试行(一失败一成功),reasoning_effort 全 None |
| 拒绝与耗尽 | 至少一条已发生的失败尝试落库且档位 NULL,主异常仍上抛;不凭本 issue 新增不存在的终态行 |
| chat 阳性 | 已登记 AUTO 源的 True 糖失败行 auto;显式档失败行请求意图;nearest 成功行映射档;emitter 恒 NULL 必须被抓住 |
| SQLite 锚点 | 每个无推理入口至少一组真实 client+临时 SQLite,断言总行数、失败行数和 NULL 数,不用 all([]),不接共享 llm_calls |
| wire 边界 | MockTransport/录制响应验证 embedding、OCR 不发推理参数;layout POST+ZIP GET 属同一次治理尝试,不误算两条遥测 |
| 并发 | 共享 recorder 并发 chat/无推理调用,**按测试指定的 (session_id, parent_call_id) 分组逻辑调用**;组内 call_id 是不同 attempt UUID,集合无交集、档位不串,不把 call_id 当所有重试共用的 ID |
隔离副本/worktree 逐一变异并还原:embedding False→TrueOCR False→True(两个入口分别红);chat True→False;移除 emitter applies 短路。记录节点、目标语义失败断言、退出码,原实现及还原后通过。
Issue #26 当前实现正确,先红来自上述变异,不为 TDD 改坏主工作区;仅因无关签名异常变红不算杀死目标变异。注入资源由测试自己关闭。
## 8. 非功能与四种遥测行口径
继续通过 `TelemetryEmitter` 唯一出口与现有 `llm_calls.reasoning_effort`;**不新增生产遥测字段、表、事件或旁路日志流水**。
| 行类型 | reasoning_effort 来源 | 分析限制 |
| --- | --- | --- |
| 真实成功尝试 | response.applied_effortnearest 后);无推理路径由 applies=False 短路为 NULL | 不重算;未知 AUTO 仅尽力编码,不证明上游能力;raw-only 为 NULL |
| 失败尝试 | effective_effort(请求>源>糖);无推理路径 NULL | 是请求意图,不是已发出的/映射后的档,也可能零 HTTP |
| 缓存命中 | **本次请求级** reasoning_effort | 不取历史 response.applied_effort,不推源级档;thinking_observation 回放历史,不是本次实测 |
| scope 终态失败 | **本次请求级** reasoning_effort | 可能未选源;不推源级档或实际档,不凭本批补充逐次根因 |
实际档分析仅使用真实成功尝试(排除 cache_hit、失败/终态行),还须保留未知/raw-only 的语义限制。#26 的 NULL 与 chat 阳性不能误套到缓存/终态来源上。
| 维度 | 约束 |
| --- | --- |
| 并发/幂等 | 能力表不可变、注册返回新表;解析局部结果,不用全局最后档;同声明同意图同结果;报告按运行/逻辑调用/attempt 隔离 |
| 取消 | 同步守卫不捕 BaseExceptionCancelledError 穿透,既有 in-flight finally 释放;不重构真实调用/遥测取消时序 |
| 降级 | 缓存/遥测故障 warning 降级,限流/熔断后端不可用仍报错;配置守卫与运行期四分类见 §4.4 |
| 持久化/原子性 | 无 schema/DDL;SQLite 临时文件,旧缓存不改写,报告安全写且失败显式失败;无任务恢复子系统 |
| 告警/评估 | 未登记与对账 warning 保持实例节流;错误含 model、请求档、支持集合与可执行配置例,不泄露凭据;离线矩阵和变异须全过,live 覆盖基线待实际运行 |
## 9. 实施接缝与文档同步
| 接缝 | 预期改动 |
| --- | --- |
| thinkingproviders | AUTO 成员约束、nearest 边界、文案、空 wire 语义、D2 窄纯校验;已有能力证据保留出处,不无证据追加 |
| client/默认 transport | 工厂及请求前置可执行的守卫、最终双 raw 守卫与错误翻译;不改端口、不移动层序、不引入全源请求准入解析 |
| cache | 本批不加语义 revision 或自动校验;只交付显式迁移回归与边界说明 |
| 单元/轻集成/e2e | 真实调用链+SQLite、分类输入与反例、逐轮完整性、隔离变异;M3 True 改为“本地拒绝”和“medium 真实开启”两个命题 |
| 文档 | 实施时同步 ARCH D11/§5.17.57.8、README、CHANGELOG、.env.example、源码 docstring;旧设计标注被替代段,不追改历史实验事实 |
Wiki 已下线,按 docs-convention 落到上述文件,不虚报 wiki 多页;schema/端口未变不 bump 字段数。本次仅写本设计,其他同步留正式批准后的计划。
## 10. 验收矩阵与审批门
下列均为**待实施验收**,不是本轮通过记录。凡缓存不得绕过/救活新拒绝的断言,统一以**已完成 §5.1 显式迁移前置**为条件。
| 层 | 必需验收 |
| --- | --- |
| 解析离线 | None/NONE/AUTO/强度、已登记含/不含 AUTO、未知空/非空/未知 wire、nearest 两方向、默认与自定义注册表;AUTO 拒绝不提示 nearest |
| raw 冲突 | 源/请求双入口,同值、被遮蔽、嵌套根替换、新增控制键、自定义 effort_keyoff 根、非法 on_base;无受管意图保留 raw,普通采样不误拒 |
| 守卫时机 | 工厂失败零准入/零 HTTP;前置可判请求冲突零准入;默认 transport 拒绝允许既有准入但零 HTTP、正确结算、无重试换源;全量注入同测 |
| 缓存迁移 | 旧客户端写旧身份→新客户端使用全新身份 miss 并执行新拒绝;nearest 写→error 读、能力表/wire 变化均换身份;工厂默认、per-call 覆盖、全量注入、多源集合、并行旧新客户端覆盖 |
| 缓存已知边界 | 另测未迁移的共享身份可能命中并绕过 transport;将其明确记录为操作风险,不把迁移前未拒绝伪装为迁移后安全已验证;无强制全部 chat 冷启动 |
| 归因/防假绿 | 404 精确 type/仅 message/截断/重复键;400429503SSEno_sources 默认 FAIL;故意改错 model、Authorization、端点、SSE 解析必须红;第二轮失败保留第一轮证据;取消穿透 |
| 遥测 | 四种行来源逐项断言;三个无推理入口真实调用链/SQLite NULL 与 chat 阳性;按 parent/session 归组且 attempt UUID 唯一;四类变异被目标断言杀死 |
| 真实核心 | M3 AUTOTrue 拒绝零网络、M3 medium 流/非流、M2.5M2.7 空 wire AUTO、qwen AUTO;缺观测保留未覆盖,不靠旧缓存 |
| 真实扩展 | 逐型号/模式列出默认基线、开启/关闭;身份缺失、UNKNOWN、不可达不算关闭 PASS,不用同系替代;新增 AUTO 如另获批准,逐型单列最终 wire/身份/信号 |
| 迁移/全局门 | 三项目实际配置另取证;conda 静态检查、日常套件、独立 verifier;发布按 CLAUDE 显式跑 slow 与下游视角包验证,不能以本设计代替运行结果 |
付费 live 必须先通过离线构造契约,再经人类批准模型/轮次/并发预算;生产超时不为赶结果压小。本文件不授权任何新真实付费调用。
## 11. 审查历史、修订对应与当前状态
前稿记录 Codex 4 项 Important;本轮按下表修订,**修订不等于独立复审通过**。上一轮 Claude 输出不对应本仓库,整份无效且不作为任何技术结论/通过证据引用。
| Codex 项目 | 本轮修订与复审锚点 |
| --- | --- |
| 缓存迁移边界不足 | §5.1/5.2 M9、§10D3 显式迁移,不加 revision;同版本 fallback/能力/wire 变化、全量注入和 per-call 覆盖均纳入;未迁移仍可能绕过 |
| 遥测“成功=实际档”过宽 | §8 四种行逐项来源,真实成功尝试才读 applied_effort;缓存与终态仍读请求级,不扩大生产遥测 |
| UNKNOWN 被计关闭覆盖 PASS | §6.3/§10:UNKNOWN 不证实关闭,未覆盖不占 PASS;长度差不作通用关闭证明,运行时 UNKNOWN 语义不变 |
| 缺下游迁移矩阵 | §5.2 M1–M9 与具体键/调用实例;受影响型号、错误时机、人工替代、离线锚点齐全;三项目现行配置明确未核验 |
已决 D1–D3 的旧“未决推荐”段已移除。2026-09-09 第二轮 Codex 审查对修订版无 CriticalImportantMinornative reviewer 另发现两个测试归因 Important:公共响应身份不足以归因上游,以及 NONE 关闭成功与不可关闭负向研究混同。父会话已在 §6.2/6.3 作最小修订(证据不足 FAIL、不新增成功 SSE 捕获器、按测试命题判定),native reviewer 定向复审通过(run `2f92c90a-298d-459d-8fcb-087dea475770`),无新增 CriticalImportantMinor。
剩余证据门为 M2 空 wire 实测、其他 live 缺测及三项目实际迁移,不重新悬置已决 D1–D3。2026-09-09 用户已明确选择“批准并继续”,正式批准本文件;独立审查及 native reviewer 定向复审均已通过。
**当前结论:设计已批准,进入实施计划;计划独立审查通过后直接执行。** 设计批准不等于变异、真实矩阵或下游迁移已通过;尚未取得的证据仍按 §10 门控。