Files
PolyGateway/research-wiki/findings/2026-09-10-136-call-deadline-validation.md
T

11 KiB
Raw Permalink Blame History

type, node_id, title, date
type node_id title date
finding finding:2026-09-10-136-call-deadline-validation 1.3.6 可选调用期限与取消结算验证记录 2026-09-10

1.3.6 可选调用期限与取消结算验证记录

范围:分支 feature/1.3.6-call-budgets 上的 T1T3 三个行为提交(1ff83bb / 9474c76 / da77b12)与 T4 文档提交。设计 research-wiki/designs/2026-09-09-136-call-budgets-design.md(人类已批准),计划 research-wiki/plans/2026-09-10-136-call-deadline.md。 本文件是证据索引:原始输出在 tests/outputs/136/(按纪律不提交),此处只记路径、命令、退出码与结论。 环境:conda 环境 PolyGatewayPython 3.12.13conda run -n PolyGateway python -V 实测)。所有 pytest/lint 命令均不接管道,退出码直取。 版本号未 bump、未 tag、未发布——发布清单(CLAUDE.md §4.4.1)不在本轮范围。

1. 红绿证据索引

任务 阶段 证据文件 结果
T1 取消结算 红/绿 未落盘(见 §1.1 诚实说明) 见 §1.1
T1 真实 Redis 绿(本轮在 HEAD da77b12 上复跑) tests/outputs/136/t4/redis_cross_connection.txt 8 passedexit=0
T2 期限 红(值域+形态) tests/outputs/136/t2/red_deadline.txt 收集期 1 errordeadline.py 缺席),exit=2
T2 期限 绿(test_deadline.py tests/outputs/136/t2/green_deadline.txt 22 passedexit=0
T2 接线中途 绿 tests/outputs/136/t2/unit_contracts_midway.txtunit_contracts_after_wiring.txt 1560 passed, 17 skipped
T2 入口冒烟 绿 tests/outputs/136/t2/entry_smoke.txt 四个方法签名含 call_deadline_s;到期异常 has retry_after_s: Falseexit=0
T2 回归门 绿 tests/outputs/136/t2/final_unit_contracts.txt 1572 passed, 17 skippedpytest_exit=0
T2 lint 红→绿 tests/outputs/136/t2/lint.txtFound 3 errorslint_exit=2)→ lint_final.txtAll checks passed! + Contracts: 1 kept, 0 broken.lint_exit=0 修后绿
T2c 补测 红 pass1 tests/outputs/136/t2c/red_pass1_import_absent.txt 3 errors in 0.29s(三个模块收集期 ImportError
T2c 补测 红 pass2 tests/outputs/136/t2c/red_pass2_real_reasons.txt + red_method_and_reasons.txt 32 failed;分布见 §1.2
T2c 补测 绿 green_unit_after_hardening.txt1530 passed)、green_client_recheck.txt110 passedEXIT=0)、green_recheck_client_embedding.txt165 passed)、green_unit_contracts.txt1592 passed, 17 skipped)、final_unit_contracts.txt1606 passed, 17 skipped 全绿
T2c lint 绿 tests/outputs/136/t2c/lint.txt All checks passed! + Contracts: 1 kept, 0 broken.
T3 Retry-After tests/outputs/136/t3/red.txt 6 failed, 8 passed, 129 deselected
T3 Retry-After 绿 green_file.txt143 passed)、green_unit_contracts.txt1606 passed, 17 skipped 全绿
T3 lint 绿 tests/outputs/136/t3/lint.txt All checks passed! + Contracts: 1 kept, 0 broken.

tests/outputs/136/t2/lsp_noise_refutation.txtt2c/lsp_noise_refutation.txt 记录编辑器 LSP 报的 import/属性告警属环境噪声(pydantic 在 conda 环境可解析),不是代码缺陷。

1.1 T1 的证据形态(诚实说明)

T11ff83bb)的先红后通过证据产生于当时的会话工具输出,未落盘为 tests/outputs/136/t1/ 文件。本文件不追认那次输出,只登记两项当下可复核的替代证据:

替代证据 内容
提交 1ff83bb 的 diff 三个源文件 + 四个测试文件共 213 插入;测试侧含 S3/S7(取消结算按 est)、S5-dead(仍 0)、S8RuntimeError 逃逸仍 0)、真实 usage 恰为 0 的成功仍 0 四组断言
本轮在 HEAD da77b12 上重跑真实 Redis pytest tests/integration/test_redis_cross_connection.py -q8 passedexit=0tests/outputs/136/t4/redis_cross_connection.txt

结论口径:T1 的“红”只有会话内证据、无归档文件;T1 的“绿”在当前 HEAD 上已被真实 Redis 复现证实。

1.2 T2c 两趟红证据的方法说明(诚实说明)

T2c 的红证据是在**基线 1ff83bbT2 之前)**的 git worktree --detach 检出上取的,用 PYTHONPATH=<worktree>/src 覆盖 editable .pth(已实测 polygateway 加载自 worktree 且 deadline.py 缺席):

趟次 做法 结果
pass1 用例原样跑 三个测试模块收集期 ImportErrorCallDeadlineExceeded 不存在)→ 3 errors。只证明符号缺席,没有执行到函数体
pass2 仅把缺失的导入符号替换成本地占位异常类shim),让函数体真正跑起来 32 failed27 条“参数/属性不存在”chat() 9、embed() 4、GatewaySettings.__init__() 3、GatewaySettings.call_deadline_s 属性 3、recognize_text() 2、GatewayClient.__init__() 2、parse_layout()OcrClient.__init__()EmbeddingClient.__init__()GatewayClient._call_deadline_s 各 1)+ 5 条 Failed: DID NOT RAISE ValueError

该 shim 是一次性本地脚手架,未提交、不在任何分支上;它只替换导入符号,不改被测源码。故 pass2 的红是针对预 T2 源码的真实失败原因分布,而非构造错误——但读者需知这份红无法从仓库检出复现,只能从上表与 red_method_and_reasons.txt 复核。

2. 命令与退出码

命令(前缀均为 conda run -n PolyGateway python -mlint 为 make lint 何时 结果
pytest tests/unit/test_deadline.py -q T2 红 exit=2collection error,符合预期)
pytest tests/unit/test_deadline.py -q T2 绿 22 passedexit=0
pytest tests/unit tests/contracts -q T2 门 1572 passed, 17 skippedexit=0
pytest tests/unit/test_client.py tests/unit/test_embedding.py tests/unit/test_ocr_client.py tests/unit/test_config.py -q -rf -k "…deadline…" T2c 红 32 failed(基线 worktree,见 §1.2
pytest tests/unit tests/contracts -q T2c 门 1606 passed, 17 skippedexit=0
pytest tests/unit/test_openai_compat.py -q -rf -k RetryAfterNonFinite T3 红 6 failed, 8 passed, 129 deselected
pytest tests/unit/test_openai_compat.py -q T3 绿 143 passed
pytest tests/unit tests/contracts -q T3 门 1606 passed, 17 skippedexit=0
pytest tests/integration/test_redis_cross_connection.py -q T1T4 复跑 8 passedexit=0
make lintruff + import-linter T2T2cT3 收尾 All checks passed!Contracts: 1 kept, 0 broken.(新 polygateway.deadline 层在内)

T4(本次文档提交)不含行为变更,故未跑测试;仅新增上表最后一行的真实 Redis 复跑作为 §1.1 的替代证据。

3. 豁免索引(哪些门没跑,为什么,谁来兜)

未执行项 原因 兜底责任
pytest -m slow(真实网关 e2e、Redis 时间语义变体) 成败取决于外部服务当下状态,默认被 addopts = "-m 'not slow'" 排除;计划 §5 明确本轮不跑 发布清单(CLAUDE.md §4.4.1)第 4 步,合并 main 后统一执行
tests/e2e/ 四个文件 同上,本版零付费调用 同上
真实网关的期限行为实测 期限用例用真实事件循环时钟+假 transport 构造,余量 4–10 倍,不依赖网关 发布清单第 4 步的 e2e 顺带覆盖;本轮无真实网关证据
模型能力矩阵复验 本版未触碰推理/能力表 不适用
跨 Python 版本验证 见 §4 残余三 未兜底,登记为残余

真实 Redis 不在豁免之列:T1 已证、本轮在 HEAD 上复跑(8 passed),未以 memory 后端冒充。

4. 残余风险(三条,逐条复述设计 §12)

# 残余 诚实口径
1 取消结算按 est 保留预扣 这是保守选择,不是“上游已计费”的证明。库无法知道端口已开始的那次调用是否真的产生了计费用量;方向定为宁多扣不空退(多扣只损失本窗口一点额度,空退会让已计费用量绕过闸门)。真实 usage 已知(含恰为 0)与已判 SourceDead0 不被覆写;未分类异常逃逸仍按 0,属已知残留,本版不动
2 清理期自抛 TimeoutError无终态遥测行 与 1.3.5 的裸 TimeoutError 穿透同一口径(这条路径一直存在、一直没有终态行),本版没有让它变坏;但期限把这条路径常态化了——启用期限后触发清理的频率上升,其可达性随之上升。with_call_deadline 的局部变量身份比较保证这种 TimeoutError 不会被误标成 CallDeadlineExceededtest_deadline.py 有断言钉住)
3 跨 Python 版本仅 3.12.13 有实证 asyncio.timeoutcm.expired()uncancel() 行为与清理期异常传播是探针在 3.12.13 单一版本上实测的;3.13+的行为未验证。库声明 3.12+,故这是真实的验证缺口,不是理论担忧

5. 发布清单第 4 步结果(2026-09-10main b0ab39e

结果 证据
make lint(合并后 main 通过(ruff + import-linter 1 kept 0 broken 会话内输出
全套件(unit+contracts+integration 1683 passed, 23 skipped, exit=0 tests/outputs/136/release/full-gate.log + .exit
slow 交集子集(Redis 时间语义变体 + 真实网关冒烟) 22 passed, exit=021 分钟) tests/outputs/136/release/slow-scoped.log + .exit
test_thinking_live.py 全模型能力矩阵 未跑——按 2026-09-10 人类批准的新规则豁免:本版 diff 零触碰 thinking.py/能力注册表/相关 e2e 设施,复用 1.3.4/1.3.5 已登记矩阵证据;当晚多渠道额度耗尽,全矩阵只会产出超时链噪声。规则变更已写入 CLAUDE.md §4.4.1 第 4 步与 §4.6(提交 b0ab39e);被中途终止的全量尝试日志留存于 slow-gate.log 备查 本表

6. 未在本轮做的事

  • 不实现 issue #24(长尾对冲):未获批准,代码与文档均无 hedge 机制,本版不缓解 #24
  • 不修 ResultInvalidRequestRejected 已计费坏结果仍退全款:属另一族记账语义,未批准,登记待立 issue。
  • 不 bump 版本号、不打 tag、不构建、不上传 registry、不同步 wiki——全部留给发布清单。