Files
PolyGateway/research-wiki/plans/2026-09-09-134-thinking-contracts.md
T

45 KiB
Raw Blame History

type, node_id, title, date
type node_id title date
plan plan:2026-09-09-134-thinking-contracts 1.3.4 推理契约实施计划 2026-09-09

1.3.4 推理契约与测试证据实施计划

日期:2026-09-09。状态:自审及 Codex 独立计划审查通过(复审 run ea38c3a7-12ef-4bf0-bb04-257ce37eb96f),T0T7 已实现并通过确定性验证;T8 文档已同步,独立验证/集成/live 与 T9 待执行。 设计:research-wiki/designs/2026-09-09-134-thinking-contracts-design.md,用户已正式批准。 目标:解决 #21 的受管推理语义漏洞、#25 的测试归因漏洞、#26 的客户端遥测守卫缺口,不扩展生产端口或遥测 schema。 方案:在既有推理决策层添加窄校验并接入工厂/默认 transport;测试侧独立保留请求与响应证据,按明确命题判定覆盖。缓存仍由下游显式迁移,生产治理循环不重写。 技术:Python 3.12+、asyncio、httpx hooksMockTransport、frozen dataclass、pytest、临时 SQLite、ruff、import-linter;不新增依赖。

本计划不涉及参考实现迁移,保真校验不适用;不得变更 Redis Lua、429/stall、取消结算、结构化重试或 #19/#23/#24 的生产机制。

1. 基线、授权与执行纪律

项目 固定边界
分支/历史 feature/1.3.4-thinking-contracts;保留已有 758a1276a09054,不重写 main 历史;开始时记录实际 HEAD 与 origin/main
D1 已登记 AUTO 必须为清单成员;未知 AUTO 保留尽力+warning,空 wire 可能不发送推理字节,不保证开启
D2 受管意图非 None 时,两层 raw 推理控制同值/被遮蔽也拒绝;raw-only 保留,不反向推断实际档
D3 不添加 fallback 指纹、语义 revision、能力表版本或新缓存前置解析;显式更换 namespace/salt 是操作前置,未迁移可能回放旧语义
实施权限 一工作区仅一 writer;父会话负责前台委派与审核。用户已授权门通过后自行合并 main、测试并发布 1.3.X,无需逐步请示;1.4、新公共决策、验证豁免须停下确认
证据与秘密 不打印 .env、token、Authorization、私有提示词;不提交 .pi/、运行报告或 reference;命令输出只记录安全路径、状态、退出码

T0 开始调用 writing-plansT1T7 行为测试执行 test-driven-development 并阅读其 testing-anti-patternsT1T5 落日志前执行 structured-logging。每次提交执行 commit skill(英文祈使标题、无 AI 签名、显式路径暂存),T8 前执行 requesting-code-reviewverification-before-completion,收到意见执行 receiving-code-review;异常先用 systematic-debugging 定根因。

用户自主发布授权涵盖既有发布清单的真实 slow 套件,沿既有配置/轮次/并发执行,不重复索取这一授权。超出既有测试矩阵的新研究实验先提交型号、轮次、并发和费用预算;禁止借研究名义追加裸 HTTP 对照。设计要求的新增覆盖先用现有矩阵表达,无法表达且增加调用量时升级该预算决策。

2. 文件职责与不变接缝

创建/修改 精确路径 职责
修改 src/polygateway/thinking.py AUTO 成员检查、nearest 边界、wire 与 raw 冲突纯校验、错误及未知告警文案
修改 src/polygateway/providers.py MiniMax on_base 改空;修正空 wire docstring,不在声明层引入决策依赖
修改 src/polygateway/client.py _guard_thinking 校验源 rawchat 请求显式档+已知 raw 冲突前置校验;指纹不改
修改 src/polygateway/transports/openai_compat.py _build_payload 完整守卫,两层浅覆盖次序不改,沿 complete 的异常翻译
修改 tests/unit/test_thinking.pytests/unit/test_providers.py 纯解析、声明、已知/未知/自定义 wire、告警与边界
修改 tests/unit/test_client.pytests/unit/test_config.pytests/unit/test_openai_compat.pytests/unit/test_retry.py 工厂、请求前置、真实 transport、无 HTTP 拒绝与治理收尾;离线兼容
修改 tests/unit/test_cache.py 显式迁移和未迁移风险回归;保留旧键黄金值
修改 tests/unit/test_embedding.pytests/unit/test_ocr_client.pytests/unit/test_telemetry.pytests/unit/test_monkey_ocr.py 三入口 NULL、阳性、SQLite 与 wire;不改变生产 emitterclient 循环
新建 tests/live_evidence.py 测试专用 frozen 证据、有限归因、身份与覆盖判据、逐轮安全报告;无环境自读取
新建 tests/e2e/conftest.py 测试侧 hooks、任务局部关联、薄 transport 委托与配置装配;不复制生产 payload/重试算法
新建 tests/unit/test_live_evidence.py 分类、hooks/委托器和报告离线反例;导入新 conftest 中无副作用定义,不导入读 .env 的 live 模块
修改 tests/e2e/test_smoke_gateway.pytests/e2e/test_compat_projects.pytests/e2e/test_embed_probe.pytests/e2e/test_thinking_live.py 迁入窄证据通道、逐轮完整性与命题分流,保留必须真实执行的行为断言
修改 README.mdCHANGELOG.md.env.exampleresearch-wiki/ARCHITECTURE.mdresearch-wiki/designs/2026-09-04-reasoning-effort-design.md 用户可达迁移说明、架构同步、旧设计被替代指针;不追改历史实验事实
修改/登记 research-wiki/schemas/llm-calls.mdresearch-wiki/metrics/call-telemetry-coverage.mdresearch-wiki/graph/edges.jsonresearch-wiki/index.mdresearch-wiki/log.md 复用既有实体,登记本计划与四种遥测口径;只接受工具对相关实体的必要索引更新
新建(验收时) research-wiki/findings/2026-09-09-134-thinking-contracts-validation.md 红绿、变异、失败与豁免索引,≤300 行;原始输出留 tests/outputs/134/
修改(发布时) pyproject.tomlsrc/polygateway/__init__.py 两处版本一致到 1.3.4,不改变依赖或导出面

生产不修改 ports.pytypes.pyerrors.py、cachetelemetry 实现及 embedding/OCR 循环;若实际实现需要突破该清单,先说明设计要求与最小原因,由父会话核定,不顺手改动。

3. 跨任务接口(内部实现约定,不新增公共导出)

3.1 推理守卫

新函数置于 thinking.py,其余模块显式 import;保持决策方向 client/transport → thinking → providers/typesMappingAnyEffortThinkingWire 均为既有类型。函数体由 T2 实现,以下固定消费者签名:

def validate_thinking_wire(wire: ThinkingWire, *, model: str) -> None:
    """拒绝 on_base 偷带已知强度,抛 ThinkingUnsupportedError。"""
def validate_thinking_raw(
    raw: Mapping[str, Any], *, effort: Effort | None,
    wire: ThinkingWire | None, origin: str,
) -> None:
    """effort 表态时拒绝 raw 控制;wire=None 只检查标准词表。"""

签名中的 wire 必填但可 None,None 是 chat 前置看不到实际 profile 的事实,不是容错默认;origin 只取固定位置名/源名,不含 raw 值。两函数无 I/O,不改变输入,抛现有 ThinkingUnsupportedErrorValueError 子类),不新建错误类。validate_thinking_wireresolve_thinking 的 None 早退之前验证声明结构;不会要求无意图时 wire 必须已知,只拒绝结构上偷带强度。

标准 raw 根:reasoning_effortenable_thinkingthinkingthinking_budgetreasoningthinkingConfigoutput_config 为 Mapping 且有 effort 时冲突。wire 可见时并入 on_baseoff 全部顶层根与 effort_key,点号只是字面键。

wire 校验只拒绝 on_base 中自己的非 None effort_key、标准 reasoning_effort、标准嵌套 output_config.effort(含值为 auto/None);不解析任意私有方言。未知/非当前方向的形态检查继续由现有 _wire_unknown_for 负责,不改变 off-only 可用性。

3.2 测试证据与归因

tests/live_evidence.py 不 import e2e conftest、不读环境、不发网络。上下文中的密钥只做内存比较,不进下列值类型。一个 attempt 可以记录零/一/多 HTTP 事件;不能用 len(attempts) 冒充 HTTP 数。

@dataclass(frozen=True)
class HttpEvidence:
    call_id: str
    request_checks: tuple[tuple[str, bool], ...]
    status_code: int
    error_body: bytes | None
    raw_identity: tuple[bool, str | None]

raw_identity 是 T5 生产、T6 消费的成功非流式原始身份快照(False, None) 表示未取证,(True, None) 表示已独立解析完整 JSON 对象且 model 缺失/为 null(True, str) 表示原始字符串。由 response hook 保存的响应引用在该次 complete 结束后读取已缓冲 content,独立 JSON 解码(拒绝重复键)取得;不得从 TransportResultLLMResponse.model_reported 回填。未缓冲、无可配对响应、非成功非流式、JSON 非对象/非法或 model 非字符串非 null 均不生成肯定证据,并保存原因供 FAIL;不把解析异常解释成身份缺失。成功 SSE 始终 (False, None),不新增捕获器、不预读流。

request_checks 必须完整包含 methodoriginpathmodelstreamembedding 为 input_shape)/authorizationcontrolmessages_digest,缺项不算全过;error_body 只允许 ≤65536 字节已缓冲完整错误体,超限或未缓冲为 None,并在安全报告写证据不足,不把截断文本拿来解析。记录只存在测试内存,不能直接 asdict 后落盘。

@dataclass(frozen=True)
class AttemptEvidence:
    call_id: str
    http: tuple[HttpEvidence, ...]
    error: Exception | None
@dataclass(frozen=True)
class LiveVerdict:
    status: Literal["PASS", "FAIL", "UNCOVERED"]
    reason: str

消费者固定为 T5→T6,纯函数与报告出口如下;参数所用 Path、Mapping、Sequence、ThinkingObservation、Effort 为标准库/现有领域类型:

def classify_live_failure(
    error: Exception, attempts: Sequence[AttemptEvidence],
) -> LiveVerdict:
    """异常分类仅 FAIL/UNCOVERED;取消不交给此函数。"""
def write_live_round(
    output_dir: Path, *, run_id: str, matrix_id: str, round_index: int,
    safe_fields: Mapping[str, Any],
) -> Path:
    """只接受已脱敏报告字段,唯一文件写失败必须冒泡。"""

身份与命题判定继续放 tests/live_evidence.py,避免 e2e 中四份条件分支:

def assess_model_identity(
    *, requested: str, aliases: frozenset[str], reported: str | None,
    raw_identity: tuple[bool, str | None], request_valid: bool,
) -> LiveVerdict:
    """raw_identity[0] 表示有独立原始身份取证;无证据不得归因上游。"""
def assess_thinking_coverage(
    observations: Sequence[ThinkingObservation], *, planned_rounds: int,
    proposition: Literal["enabled", "disabled", "cannot_disable"],
) -> LiveVerdict:
    """输入须先过请求/身份资格;缺轮与 UNKNOWN 不补足证明。"""

拒绝能力测试不送入以上观测函数,按预声明异常类型/状态/机器字段独立断言。cannot_disable 保留 T10“实际未关闭与声明比较”的反证形态:完整合格轮次有 OBSERVED 可支持本条件下不可关闭;全 ABSENT 证伪声明;无 OBSERVED 但有 UNKNOWN 只能未覆盖。不得扩大成“证明所有私有上游参数都无法关闭”。

3.3 测试侧取证装配

tests/e2e/conftest.pyObservedTransport 包裹同一个真实 OpenAICompatTransportcompleteembed 签名逐字保持 ports.py,参数原样传递。每次调用将 call_id 绑定实例持有的 ContextVarfinally reset;异常原样上抛,CancelledError 不转普通错误。不得在委托器做治理重试或 payload 修正。

class LiveCapture:
    def __init__(self, *, expectations: Mapping[str, Mapping[str, Any]]) -> None:
        """按源名持有测试矩阵显式预期,不含凭据或真实响应。"""
    def round_context(self, *, session_id: str, parent_call_id: str) -> AbstractContextManager[None]:
        """外围绑定逻辑轮次,finally 复位,嵌套任务不串线。"""
    def client_factory(self, source: SourceConfig) -> httpx.AsyncClient:
        """按实际源构造带 hooks 客户端,沿生产 timeout/trust_env。"""
    def attempts(self, *, session_id: str, parent_call_id: str) -> tuple[AttemptEvidence, ...]:
        """返回本轮快照,含零 HTTP 的尝试,不从最终异常猜前序。"""
    def raw_identity(self, *, session_id: str, parent_call_id: str, call_id: str) -> tuple[bool, str | None]:
        """精确读取本逻辑轮次和成功 attempt 唯一 HTTP 事件的原始身份快照。"""

LiveCapture.expectations 由调用者按源名提供,内层必需键为 model、origin、path、streamembedding 用 input_shape)、control、messages_digest;缺键直接测试配置错误,不自动从待测 payload 补齐。结构化预期片段放 control 的显式预期对象,输出路径由 write_live_round 单独接收;并发异构轮次使用各自 capture 实例,不共享可变“当前预期”。预期不调用生产 _build_payload 生成。实际请求 hooks 逐项比较,凭据不进 repr/序列化;可保留失败事实后由轮次出口 FAIL,不能改写实发请求“修正”它。

取证 live 使用 GatewayClient(...) 全量注入。conftest 可复用 client.py 现有 _build_limiter_build_breaker_build_selector_build_structured 等装配函数,但不新增生产注入口、不复制它们实现;自己创建的组件用 ExitStack/显式 finally 关闭,注入 GatewayClient 不会代关。工厂行为单独离线验证。未获得取证通道的工厂 live,异常一律保存后 FAIL。

T5 按 (session_id, parent_call_id) → AttemptEvidence.call_id → HttpEvidence 保存快照;T6 使用最终 LLMResponse.call_id 调用 capture.raw_identity(...),将返回值交给 assess_model_identity(raw_identity=...),不可取本轮最后一条响应猜关联。查找必须精确匹配本轮且只有一条成功 HTTP 事件;重复/跨轮 call_id、多个候选响应是取证契约错误,报告后 FAIL,不降成外因未覆盖。未发 HTTP 或没有独立身份快照时返回 (False, None)。 成功非流式原始 model 通过上述快照接口交付;成功 SSE 不加捕获器、不预读流。原始身份无从确认而公共结果缺失/不符时 FAIL;原始正确而结果丢失/改错也 FAIL。只要公共身份合格且请求合格,正常能力测试不要求新增成功 SSE 的原始副本。

4. 任务与提交点

T0:基线、计划审查与文档回滚点

  • 修改设计批准状态,新增本计划;父会话自审后前台 Codex 独立审,具体问题修正后方可执行 T1。计划无需再走人类门,不能把“已生成”当“已审”。
  • 记录 git status --short --branchgit log --oneline origin/main..HEAD、实际 HEAD;确认源代码零差异,保存未跟踪文件清单,禁止暂存 .pi/
  • 执行基线:make checkconda run -n PolyGateway pytest tests/unit/test_thinking.py tests/unit/test_providers.py tests/unit/test_client.py tests/unit/test_config.py tests/unit/test_openai_compat.py tests/unit/test_cache.py tests/unit/test_embedding.py tests/unit/test_ocr_client.py tests/unit/test_telemetry.py -q。记录实际失败,不能先改期待绕过;本步骤预期现有非 slow 测试通过。
  • research-wiki 工具登记 designplan 节点及 implements 边,现有同路径文档不可被 add_entity 模板覆盖;先读工具已有文件处理行为,再登记、重建索引、检查生成 diff。只在本计划 writer 移交后由父会话执行这些额外文件写入。
  • 调用 commit skill,提交点 docs: record approved thinking contracts and implementation plan,形成生产修改前回滚点。

T1AUTO 成员语义与默认 MiniMax wire

文件thinking.pyproviders.pytests/unit/test_thinking.pytests/unit/test_providers.py,路径均按 §2。

  1. 先添加/替换 test_auto_never_trips_phase5,以已登记不含 AUTO 的空/非空 on_base 为反例;调用 resolve_thinking 的 errornearest 都明确拒绝且不提示 nearest。先跑新测试,旧实现因未拒绝而红;再修改 _settle_tier_tier_unsupported,避免 AUTO 进入 EFFORT_ORDER.index
  2. 保留强度→纯开关 AUTO、等距弱侧、NONE 不自动映射、None 不表态、未知空/非空 wire 尽力警告。删除 default MiniMax 的 medium 并修正注释;先测试 M3 AUTO 拒绝、M3 medium payload、M2.5M2.7 AUTO 空 payload 与 applied=AUTO。默认能力表成员不增删。
  3. 用 loguru sink 检查未知告警确实说明不保证生效,default transport 实例节流不改;不引入新日志通道,不输出 raw 密钥。按 structured-logging 明确这是既有 warning 与既有列的修正。

验证conda run -n PolyGateway pytest tests/unit/test_thinking.py tests/unit/test_providers.py -q;新拒绝和 wire 回归先红后绿,其余保留行为通过。若旧下游形态测试依赖 M3 True,需要在 T3 明确改为已批准迁移样本,不能暗改能力表让它绿。

  • 提交点:fix: enforce registered auto reasoning capabilities

T2:纯 wireraw 所有权校验

文件src/polygateway/thinking.pytests/unit/test_thinking.py。实现 §3.1 两个签名;resolve_thinking 集中校验 wireproviders 不反向 import thinking。

红绿组 最小反例/保留不变量
标准控制 六个顶层根+output_config.effortAUTO 空片段仍拒绝 raw highNONE/糖/未知同测
双来源 同值仍拒绝;分别传源与请求 raw 验证,被后层遮蔽也拒绝;不修改两个 Mapping
自定义 on_baseoff 根并集、effort_key 自定义字面键、点号不解释路径;on_base 偷带自己的键或标准强度值(含 None)拒绝
嵌套 替换 thinking 整个根即拒绝;profile 不拥有 output_config 时仅 format 可过、effort 不可过;拥有根时 format 也不可覆写
不误伤 effort=None 时 raw 原样允许;temperatureseedresponse_format 不属词表,合法普通采样保持;off-only 形态及当前方向未知语义保持

新校验首次未实现导致的 import 错误不算行为红;可先在隔离基线把同输入经现有 payload 路径表现记录为“覆盖成功但本应拒绝”,或待 T3 在旧实现回放其失败断言,补齐语义红证据。纯函数自身还需逐例断言异常及未修改输入。

验证conda run -n PolyGateway pytest tests/unit/test_thinking.py -q,每类目标反例有有效红绿,保留行为绿。

  • 提交点:fix: validate ownership of managed reasoning parameters

T3:接入工厂、请求入口和默认 transport

文件src/polygateway/client.pysrc/polygateway/transports/openai_compat.pytests/unit/test_client.pytest_config.pytest_openai_compat.pytest_retry.py

先在旧路径跑源 HIGH+相同 raw HIGH、本次 AUTOraw HIGH 的行为反例,确认旧实现实际发出 raw 参数而未拒绝。再接入两守卫:工厂先求 effective_effort 并校验 source.extra_bodychat coerce 后调用 wire=None 的已知词表检查;transport 对当前 profile 和 effective_effort 分别检查 extra_bodyoverlay,再保持原浅 update 顺序。

接缝 验收
工厂 SourceConfig 仍能表达 raw-onlyfrom_envfrom_settings 对受管源拒绝发生在 limiterHTTP client 创建之前;记录构建计数零,不以网络偶然没发代替
请求前置 显式请求档与 overlay 已知键冲突,ValueError 且 handler/准入未触发;源级意图或自定义根留 transport 再查
全量注入 真实 OpenAICompatTransport 翻译为 RequestRejectedErrorMockTransport 记录零 HTTPRetryMW 不换源不重试、limiter inflight=0、已有探针收尾路径正常
参数保真 raw-only 允许,applied=None;普通采样源<请求<结构化 overlay 的现状保留;显式档/nearest 成功 payload、TransportResultLLMResponse applied 与真实成功遥测相符
多源/并发 每次以选中源 profile 校验,不因另一个源清单不同提前判整个池死;共享 client 无“最后档”串线;错误不包含 raw 值

工厂将来被请求覆盖不能救活一个已拒绝源,这是已批行为。不要为全量注入自定义 transport 添加 preflight 端口。已有 fixture 需要调整时,只将不再合法的受管+raw 双来源改为显式单来源,新增拒绝反例保留迁移证明。

新增生产默认 HTTP factory 离线守卫:现有 tests/unit/test_openai_compat.py 没有 authtimeouttrust_env 构造断言,不能写作“保留”。新增 TestDefaultClientFactory,直接调用生产 _default_client_factory(source) 返回真实 AsyncClient,不使用 T5 的测试 factory,也不 mock 整个 AsyncClient。用两组不同假 api_key、非默认 timeout_s、trust_env=TrueFalse 参数化;不发送网络,finally aclose。节点为 test_authorization_uses_source_api_key(检查 client.headers 及 build_request 生成的 Authorization)、test_timeout_uses_source_timeout_for_all_phasesconnect/read/write/pool 全部等于输入 timeout_s)、test_trust_env_uses_source_setting(检查 client.trust_env)。在隔离副本逐个删除 Authorization 传入、遗漏 timeout 参数、遗漏 trust_env 参数/写死 True,指定节点必须因值不符红,再恢复通过;当前实现本来正确,以这些语义变异作为红证据,不改生产 factory 凑红。 独立命令:conda run -n PolyGateway pytest tests/unit/test_openai_compat.py::TestDefaultClientFactory -q,原始实现绿、每个遗漏变异被对应断言杀死、恢复绿;这套测试与 T5 hooks 校验分别验证生产装配和测试取证两条路径。

验证conda run -n PolyGateway pytest tests/unit/test_client.py tests/unit/test_config.py tests/unit/test_openai_compat.py tests/unit/test_retry.py -q,加 T1/T2 的测试一起跑;工厂/请求/全量注入拒绝均有旧实现红、新实现绿。

  • 提交点:fix: reject conflicting raw reasoning overrides before sending

T4:显式缓存迁移和四种遥测口径回归

文件tests/unit/test_cache.pytests/unit/test_client.pytests/unit/test_telemetry.py;不改生产 cache、指纹或 emitter。

使用真实 InMemoryCache、GatewayClient、默认 transportMockTransport 构造两个客户端。旧语义 payload 可按 1.3.3 真实序列化形态预写(历史数据夹具,不需要在当前生产放回漏洞);同版本 nearest→error 则运行真实客户端写入。

场景 断言
旧 AUTOraw 记录 旧身份可回放是已知风险;换全新 namespace 或 salt 后 miss,实际进入新拒绝,异常不缓存
nearest→error 源不表态、请求 medium、模型 glm-5.3nearest 写入后 error 同身份可命中;error 换身份后零 HTTP 拒绝,不添加 fallback 指纹
能力表变化 新增 TestExplicitCacheMigration::test_capability_change_requires_explicit_identity:相同源配置、请求 AUTO 和 wire,两客户端注入同一测试模型的不同能力表(旧含 AUTO+HIGH,新仅 HIGH),源级不表态以允许装配。旧客户端真实写入后新客户端同身份回放且无新 HTTP;换全新 namespace 或 salt(参数化)后 miss,进入新能力表并 RequestRejected、无新 HTTP、不写失败值。只用局部测试能力表,不修改 DEFAULT
自定义 wire 变化 新增 TestExplicitCacheMigration::test_custom_wire_change_requires_explicit_identity:相同源/模型/能力表及请求 HIGH,分别注入同名自定义 profile 的旧/新 effort_key(例如 depth_adepth_b),on_base 均为空。旧客户端写缓存,新客户端同身份回放旧值且无新 HTTP;新 namespace 或 salt 后 missMockTransport 必须收到 depth_b=high 且无 depth_a,返回可区分的新结果,旧身份仍能回放旧值。profile 仅局部注入,不改源配置让现有指纹意外变化
入口与范围 工厂默认 namespace、per-call 覆盖默认、构造全量注入、共享多源 scope、两个租户原前缀保留;只改默认无法覆盖 per-call,需专门反例
并行/回滚 旧新身份可并行且不覆盖对方;回到旧身份确实重见旧值;未受影响调用 key 黄金值逐字不变
四行口径 真实成功=applied、失败尝试=effective 意图、cache_hitscope 终态=本次请求级;缓存不读取历史 applied 作本次遥测档

该任务多数是已有正确行为的守卫,不人为改生产获得红:隔离变异遗漏 namespacesalt、将 cache_hit 遥测改读历史 applied,要求相应行为断言红,恢复后绿。T3 新拒绝路径旧实现红绿可复用,但不能只报它替代迁移维度证据。

验证conda run -n PolyGateway pytest tests/unit/test_cache.py tests/unit/test_client.py tests/unit/test_telemetry.py -q。两项新增能力/wire 迁移节点均置于 tests/unit/test_cache.py::TestExplicitCacheMigration,单跑 conda run -n PolyGateway pytest tests/unit/test_cache.py::TestExplicitCacheMigration -q;分别在隔离副本去掉其 namespace/salt 隔离输入,必须因没有新拒绝/新 wire 而红,恢复后绿,不能只以 nearest→error 的测试代替这两类。两客户端的源指纹必须断言相等,生产指纹算法一字不改。

  • 提交点:test: pin explicit cache migration and reasoning row semantics

T5:有限测试归因和独立取证

文件:新增 tests/live_evidence.pytests/e2e/conftest.pytests/unit/test_live_evidence.py。按 §3.2/3.3 实现;不读 .env 的模块可被日常单测安全 import。执行 structured-logging:记录内容按设计 §6,不另建库表。

  1. 纯分类默认 FAIL。仅一条完整 HTTP 错误、请求检查齐全、无别的 attempt 异常、404、完整无重复键 JSON 的 error.type 精确匹配、外抛 RequestRejectedError 且 status 一致,才 UNCOVERED;多次尝试/多 HTTP、不同 call_id、空检查元组、重复 JSON 键均 FAIL。
  2. hooks 不预读成功 SSE,不将 summary 当 JSONresponse 引用等该次 transport 完成后检查 content 是否已缓冲。64 KiB 上限、0 字节、非对象 error、重复键、坏编码各有反例。薄委托器 finally 恢复上下文,零 HTTP 尝试也保存。
  3. 身份函数区分 raw 取证缺失和原始响应明确缺 model;增加 test_raw_identity_snapshot_reaches_round_consumer,用真实默认 transportMockTransport 非流式响应依次覆盖正确 model、缺失/null,以及 JSON 非对象/非法/重复键,断言 §3.3 accessor 的来源和区别;故意让公共响应丢 model 时原始快照仍保留正确串并判 FAIL。并发两逻辑轮次+一次重试验证按 sessionparent/成功 call_id 精确选择,不回放前次失败的身份;成功 SSE 快照未取证且公共身份异常时必须 FAIL。覆盖函数按 enableddisabledcannot_disable 命题判断,UNKNOWN 不假绿;预期 400 负向契约单独测。
  4. 用 MockTransport 驱动 requestresponse hooks:改错 model、Authorization、端点、SSEJSON 解析→FAIL;成功 SSE 不被提前消费;原始 model 正确但公共字段错误→FAIL。交错并发及取消证明 context reset、凭据不泄露、资源释放;薄委托器不额外调用一次 HTTP。
  5. 安全报告每轮独立文件,采用 run UUID+轮次与矩阵安全标识;只接受白名单 safe_fields,拒绝原始异常/HttpEvidence 对象直接序列化。第二轮失败仍可读第一轮;写入失败是 FAIL;最终汇总统计 PASSFAILUNCOVERED 和缺轮,不能只数 pytest 退出码。

对旧策略红证据:用合成记录隔离执行现有“整类 skip/正文子串/UNKNOWN 安静”判据,目标测试要求 FAIL/UNCOVERED,确认语义不符;恢复新纯函数后通过。新文件缺失造成 import error 不计红。

验证conda run -n PolyGateway pytest tests/unit/test_live_evidence.py -q。安全测试使用假的唯一 sentinel 凭据/私有提示词,逐文件检查不出现 sentinel,不能拿真实密钥做输出搜索。

  • 实现及离线证据完成:窄分类/hooks/独立身份/逐轮安全报告;与 T6 合并提交。

T6:迁移四个 live 文件并离线化装配断言

文件:四个 tests/e2e/test_*.py 路径见 §2tests/e2e/conftest.pytests/unit/test_live_evidence.pytests/unit/test_client.pytests/unit/test_config.py

原接缝 改动与离线验收
smokecompat chat 每轮 session_idparent_call_id,委托原参数,先报告再 skip/raise;仍验证流/非流、结构化 JSON/模型。断言异常也必须留报告,不只包 await 的异常
compat 平铺键 移到 test_config.py 的完整合成 env,删除逐源 TIMEOUT_S 才能验证 LLM_TIMEOUT 回落;不靠真实配置“恰好已有覆盖”过测。无 HTTP、无可达 Redis/PG,明确其仅是本库兼容契约
compat Protocol 既有真实外部 Protocol 缺包时记录未覆盖;合成 runtime Protocol 和本库调用签名在 test_client.py 无 slow 执行,不能宣称缺失仓库原测试通过
embedding probe 走同一薄 embed 委托和窄分类,所有路径 finally 关闭;model_not_found 不写成“网关不支持 embeddings”;timeouttrust_env 取已校验源配置,不用 30s 硬编码压紧生产预算
L1L9 M3 True 拒绝单独离线/本地断言,真实开启用显式 medium,保留未登记/未知 wire 场景;L8 装配拒绝只计本地契约,不算 live 能力
T10 预声明 NONE 可关闭/不可关闭/档位预期拒绝;每轮结束即留证。去掉“仅保留可用轮降低分母”、completion 长短提升 UNKNOWN 的成功逻辑;模型部分档未覆盖不可汇总全 PASS

保持原 _MODEL_PROVIDER/显式别名表,不新增 AUTO 成员。_run_rounds_probe_effort 返回路径不许遗漏失败轮;默认基线也走同一证据出口。T10 临时能力表仅为探测绕过清单,不写回 DEFAULT;其控制字段预期由矩阵声明,不能调用被测 resolver 产生预期。

既有 _tier_settings 将 stall 强制压到 60s,迁移时去掉该临时缩小值,沿已校验生产配置;不得因持续 429 慢而修改 #22 算法。保留既有轮次/并发设置;先收集矩阵和预计调用数,额外研究不自动展开。M2.5/M2.7 AUTO 复用 T10 现有登记档,M3 medium 流/非流复用对应原开启用例,不以新增多轮研究暗增预算。

测试工厂替代仅改变取证装配,不能靠调用生产私有 _client_factory 的同一实现来证明鉴权构造正确;生产默认 factory 的头/timeouttrust_env 离线守卫由 T3 新增 TestDefaultClientFactory,T6 验证时一并运行,不宣称旧源码已有覆盖。各能力轮次用 §3.3 的 raw_identity(session_id=..., parent_call_id=..., call_id=resp.call_id) 给身份函数提供独立快照,缺失来源不得猜测。live 无取证通道的失败按 FAIL,不为凑分类额外开生产接口。

验证conda run -n PolyGateway pytest tests/unit/test_live_evidence.py tests/unit/test_client.py tests/unit/test_config.py tests/unit/test_openai_compat.py::TestDefaultClientFactory -qconda run -n PolyGateway pytest tests/e2e/ -m slow --collect-only -q(只采集,不视作真实通过)。在离线注入旧整类 skip、丢第一轮、UNKNOWN→PASS、identity 丢失→skip 变异,分别红;新实现恢复绿。

  • 实现及日常离线验证/90节点collect-only完成;未执行live,不代表能力覆盖通过。

T7:无推理路径真链路与四类变异

文件tests/unit/test_embedding.pytests/unit/test_ocr_client.pytests/unit/test_telemetry.pytests/unit/test_monkey_ocr.py;生产不改。

复用 _embed_client_client/脚本 transport/内存 recorder,参数化 embed、recognize_text、parse_layout 与源 TrueHIGH,两次尝试(Transient→成功)必须恰有 2 行、一错一成、所有 reasoning_effort None。再覆盖 RequestRejected 一行和耗尽非零失败行;不要求新增不存在的逻辑终态。SQL 锚点用真实 SQLiteRecorder(tmp_path / "reasonless.sqlite", auto_migrate=True),三入口分别走 client→emitter→SQLite,查询总数/失败数/NULL 数,finally 同步 close 注入 recorder。

chat 阳性走真实 RetryMWemitterTrue 糖失败 auto、显式请求失败保留意图、nearest 成功为实际映射档。四行遥测继续沿 T4 口径。共享 recorder 并发用测试 sessionparent 配对,attempt call_id 唯一且集合不相交。embedding 默认 transport 和 MonkeyOCR textlayout 真实 MockTransport 回包验证 wire 无推理键,不仅断言 emitter 的 False 实参。

隔离变异 必须被哪些断言杀死
embedding.py::_emit False→True 误配 TrueHIGH 的失败尝试 NULL 断言
ocr.py::_emit False→True text 和 layout 各一个独立节点均因错误行非 NULL 红
middleware/retry.py::_emit True→False chat 阳性实际档/请求档断言,不是签名 TypeError
middleware/telemetry.py::_attempt_effort 去掉 applies 短路 无推理真实失败行断言;确认不是全空数据或假 recorder

工作方法:以 T7 当前提交建仓库外临时副本(仅 src/tests/必要工程文件,不复制 .envreference.pi),用 PYTHONPATH=<副本>/src 和副本 cwd 执行 conda pytest;先检查 polygateway.__file__ 指向副本。逐个变异、跑指定节点记录 exit 1 和目标断言、恢复文件校验散列,再跑 exit 0。绝不在主工作区改 False 假装先红。

验证conda run -n PolyGateway pytest tests/unit/test_embedding.py tests/unit/test_ocr_client.py tests/unit/test_telemetry.py tests/unit/test_monkey_ocr.py tests/unit/test_retry.py -q,再执行上表隔离变异;原实现绿、四类有效红、还原绿。

  • 提交点:test: guard reasoning-free telemetry through real client paths

T8:文档、日志登记与独立验证

文件:§2 列出的用户文档/架构/旧设计/schema/metric/知识索引,以及验收 finding;不新增运行时字段。

同步设计 M1–M9 到 README 可执行迁移节与 .env.example 注释,保留型号证据来源;CHANGELOG 未发布段点名 AUTO 新拒绝、未知尽力、raw 同值拒绝、显式缓存身份迁移、UNKNOWN/SKIP 限制。schema 既有 reasoning_effort “实际发出”总括修成四种行来源,不修改 DDL;metric 复用既有 call-telemetry-coverage,记录三个无推理入口错误行 NULL/chat 阳性为 100% 契约,真实覆盖基线留待首次实际运行,不能填伪百分比。

由父会话前台派全新 verifier:只给批准设计、计划、分支 diff、验证命令,不给实现自评。至少覆盖正确性/回归和测试归因/范围两个角度;Critical/Important 清零。审查先核对实际路径与仓库语言,不接受不存在文件的结果。补丁回到单 writer,重跑受影响红绿及静态门。

检查 命令/证据要求
静态与边界 make checkgit diff --checkconda run -n PolyGateway python -m compileall -q src/polygateway tests/live_evidence.py tests/e2e/conftest.py
日常全量 make test,保存真实退出码/coverage ≥80%,不能只运行改动文件;连接依赖 skip 单列
LSP 若会话已有 LSP diagnostics 工具,对四个生产变更文件和新增测试支持文件取诊断;本轮检查 conda 内 pyrightbasedpyright 均未安装且工程无其配置,不安装新依赖或虚报 LSP 通过。可用时命令 conda run -n PolyGateway pyright src/polygateway/thinking.py src/polygateway/providers.py src/polygateway/client.py src/polygateway/transports/openai_compat.py tests/live_evidence.py tests/e2e/conftest.py,不可用明确记未执行,ruffimport-lintercompileall 是实际既有静态门,不冒称等价 LSP
真实采集清单 conda run -n PolyGateway pytest tests/ -m slow --collect-only -q,先列必需节点、型号/模式/轮次/并发/所用配置身份(不含秘密)
真实执行 conda run --no-capture-output -n PolyGateway pytest tests/ -m slow -ra;保持生产超时,检查每项报告而非仅 exit 0
反回归 四类 #26 变异+T4 缓存迁移+T5/T6 假绿反例全部有独立失败断言与还原通过,finding 引用原始报告路径

长跑用 tmuxPYTHONUNBUFFERED=1,命令 stdoutstderr 重定向到 tests/outputs/134/,原命令后立刻独立保存 $?;不得接 tail 管道改写退出码。父会话等待准确 tmux 完成信号/PID,不能 pgrep 完整命令自匹配。不把初次失败覆盖成重跑后的单一绿日志。

  • 提交点:docs: document reasoning ownership and explicit cache migration;必要修复各自按 commit skill 提交,不把 verifier 自动反馈当授权扩范围。

T9:发布准备、合并后复验与 1.3.4 发布

仅在 T8 无未处理阻塞后执行。用户已授权所有本节动作,无需为 merge/push/上传再请示;发现 1.3.4 已存在不可覆盖,停下协调版本,不能私自跳到 1.4。

顺序 精确动作与完成证据
文档先行 更新 README 安装约束与能力说明;CHANGELOG 定版为 1.3.4(实际日期),pyproject 和包 __version__ 同步;本计划复选框只能按已得证据勾选
发版提交 执行 commit skill,标题 chore: prepare release 1.3.4,先核对测试报告和 staged 无秘密;运行 conda run -n PolyGateway pytest tests/unit/test_package.py -q,不改变公共字段计数
合并 git fetch origin,确认远端未出现未审变更;git switch maingit merge --no-ff feature/1.3.4-thinking-contracts。保留已有两个本地提交,禁止 reset/force push
合并后门 main 上重新 make lintmake testconda run --no-capture-output -n PolyGateway pytest tests/ -m slow -ra;若 lint --fix 改代码,重新审 diff、提交并重跑,不把脏代码与 tag 分离
推送与 tag git push origin maingit tag -a v1.3.4 -m "Release 1.3.4"git push origin v1.3.4,核对远端 tag 指向最终已验证提交
构建 核实 cwd 后按 CLAUDE 清除旧 dist 产物;conda run -n PolyGateway python -m buildconda run -n PolyGateway python -m twine check dist/*;缺构建工具先报告环境缺项,不更改核心依赖
上传 从既有 tea 配置安全取 token,仅放 TWINE_PASSWORD 环境变量;conda run -n PolyGateway python -m twine upload --repository-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi dist/*TWINE_USERNAME 沿已有账号;不在 argv/日志输出 token,不把命令成功当最终发布完成
下载检查 conda run -n PolyGateway pip download --no-deps --index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ polygateway==1.3.4 -d <临时目录>;解包核对新守卫与 MiniMax wire、版本、README 元数据;从仓库外使用该环境 Python 将 wheel 安装到独立 target 并验证 import 来源及拒绝行为
外部可见 建 Gitea v1.3.4 Release(正文来自定版 CHANGELOG);调用 POST /api/v1/packages/iomgaa/pypi/polygateway/-/link/PolyGateway;查看 Releaseregistry 包页面正文、仓库链接、下载产物,逐项记录 URL 与实际结果

测试不在 wheel 内,下载后以无网络小调用核对已安装 resolve_thinking 和冲突守卫;不要从工作树 src import 后宣称发布包通过。只在外部结果确认后评论/关闭 #21/#25/#26,正文引用各自验证与迁移边界,不能称所有渠道故障已自动识别。若上传成功但页面/下载校验失败,记录部分发布状态,不重发同版本不同字节。

  • 提交/发布点:main 的发布提交与 v1.3.4 对齐;Release 与 registry 外部验证全部成立。

5. 阻塞矩阵:哪些可以执行,哪些不能冒充通过

缺口 本库可完成 不可自行宣称/处置
下游工作区缺失 本库完整合成 env、runtime Protocol、M1M9 与显式缓存迁移回归 GovDocCHS 实际配置未取证、Video-Tree 已退出迁移但历史兼容面仍可测;三者均不能虚构实测。提前向父会话登记缺口,发布前须拿到相关负责人脱敏配置与验证证据,或人类明确豁免缺失项;不阻止独立离线实现继续
M2 空 wire AUTO 默认 wire 单测、实际既有 T10 型号档位复验 真实缺身份/无信号/不可达不能当已验证;需有效重测或人类具名豁免,不补回 medium 或无证据改表
M3 非流式 UNKNOWN 可验证 payload、响应形态、UNKNOWN 不假绿 不把长度差当开启/关闭证明;必需能力单元无法满足时保留未覆盖并走人类决策,不新增临时“通过”阈值
4295xx/网络失败 完整证据保存,库回归用离线契约定位 本批归因默认 FAIL 是设计批准范围,不能为了 #25 关闭率改宽 skip;外部证据由人类决定发布豁免
研究新增预算 原有 slow 套件按已有授权跑,已有配置保持 额外模型/轮次/对照实验需预算批准;不得将批准设计偷换成无限研究调用授权
设计外漏洞 独立记录实际文件与反例,父会话核定是否阻塞 不顺手实施 #19#22#23#24、新 schema 或新 deadline;无强制单源分支

6. 自审与验收映射

设计节/需求 任务
§4.14.2 AUTO 与 MiniMax、未知尽力 T1,T3 双入口,T6/T8 真实证据
§4.3/4.4 raw 同值/嵌套/自定义/时机 T2、T3;无公共端口新增、无深合并
§5 D3 与 M1M9 T4、T8;未迁移风险显式保留,绝不补指纹
§6 归因/身份/UNKNOWN/负向命题 T5、T6;完整请求证据、默认 FAIL、逐轮持久化
§7 无推理路径与变异 T7;三入口真 clientSQLite,四类隔离变异
§8 四种行口径与日志 T1T4T7T8;既有 schemaemitter,不新增数据面
§910 文档、下游与发布门 T8/T9及阻塞矩阵;外部结果与测试缺口不冒充通过

自审已核对:生产守卫所有消费者在 §3 定义;新增测试文件有确定路径;conftest 当前不存在故明确新建;默认工厂不支持 transport 注入故使用已批准全量注入而非偷扩 API;缓存不改指纹;无从公共 model_reported 倒推上游身份;所有命令均在 conda 环境;未执行的测试不写为已通过。 计划审查由父会话组织,完成后直接实施,不新增人类计划审批门。执行中本文件任务勾选与 finding 保持实际状态一致;本次计划编写未运行 pytest、变异或真实模型调用。

本轮实施证据

T0–T4/T7 的命令、实际失败与修复、11 个隔离变异及 1241 项单测通过,见 findings/2026-09-09-134-thinking-contracts-validation.md。T5/T6 已续作:1357单元通过、8个隔离假绿变异exit1/还原0、e2e 90节点仅采集;T8文档同步完成。未执行live、集成、独立verifier和发布。具体节点及残余见同一finding续作节。

T5/T6续作决策记录

父会话确认无已批准型号→400机器type白名单:不编造,缺机器证据400默认FAIL;精确预期拒绝契约离线守卫,具体live负向缺基线记录未验证。不可关闭命题完整合格轮次有OBSERVED支持本条件下未关闭,全ABSENT证伪,无OBSERVED但UNKNOWN未覆盖。T8复选框保持未勾选,因为独立verifier与全量/live证据门未执行;本轮仅其文档同步部分完成,禁止发布。

T5/T6实现提交:73008ad。最终日常单元1357、受影响含factory331、make check、compileall、e2e collect-only90通过;完整T8/T9仍未执行。日志路径及8项红→还原绿详见同一finding。

独立审查修复续作(d332287 后)

已按 receiving-code-review 核验四项并仅修改测试及本计划/finding:结构化重问采用先验前缀/反馈角色与预算+委托摘要的 wire 保真;未登记候选先资格再观测未覆盖;同一用例 run/model 关联所有子运行并保留计划/完成分母;落盘机器字段只准 model_not_foundomitted。没有生产/版本修改、slow执行或额外调用预算。

新增18个离线节点,四项及首轮精确消息守卫均有目标断言先红→绿。最终129项取证单测、1375全单元、make check及e2e collect-only90通过;命令日志/退出码详见既有finding“独立审查四项修复”。修复后独立复审、集成与live尚未完成,T8/T9仍不勾选,不放行发布。原结构化重问“保守FAIL”说明已标为被本次修复替代,不能再当成可接受限制。