11 KiB
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上的 T1–T3 三个行为提交(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 环境PolyGateway,Python 3.12.13(conda 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 passed,exit=0 |
| T2 期限 | 红(值域+形态) | tests/outputs/136/t2/red_deadline.txt |
收集期 1 error(deadline.py 缺席),exit=2 |
| T2 期限 | 绿(test_deadline.py) |
tests/outputs/136/t2/green_deadline.txt |
22 passed,exit=0 |
| T2 接线中途 | 绿 | tests/outputs/136/t2/unit_contracts_midway.txt、unit_contracts_after_wiring.txt |
各 1560 passed, 17 skipped |
| T2 入口冒烟 | 绿 | tests/outputs/136/t2/entry_smoke.txt |
四个方法签名含 call_deadline_s;到期异常 has retry_after_s: False,exit=0 |
| T2 回归门 | 绿 | tests/outputs/136/t2/final_unit_contracts.txt |
1572 passed, 17 skipped,pytest_exit=0 |
| T2 lint | 红→绿 | tests/outputs/136/t2/lint.txt(Found 3 errors,lint_exit=2)→ lint_final.txt(All 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.txt(1530 passed)、green_client_recheck.txt(110 passed,EXIT=0)、green_recheck_client_embedding.txt(165 passed)、green_unit_contracts.txt(1592 passed, 17 skipped)、final_unit_contracts.txt(1606 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.txt(143 passed)、green_unit_contracts.txt(1606 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.txt 与 t2c/lsp_noise_refutation.txt 记录编辑器 LSP 报的 import/属性告警属环境噪声(pydantic 在 conda 环境可解析),不是代码缺陷。
1.1 T1 的证据形态(诚实说明)
T1(1ff83bb)的先红后通过证据产生于当时的会话工具输出,未落盘为 tests/outputs/136/t1/ 文件。本文件不追认那次输出,只登记两项当下可复核的替代证据:
| 替代证据 | 内容 |
|---|---|
提交 1ff83bb 的 diff |
三个源文件 + 四个测试文件共 213 插入;测试侧含 S3/S7(取消结算按 est)、S5-dead(仍 0)、S8(RuntimeError 逃逸仍 0)、真实 usage 恰为 0 的成功仍 0 四组断言 |
本轮在 HEAD da77b12 上重跑真实 Redis |
pytest tests/integration/test_redis_cross_connection.py -q → 8 passed,exit=0(tests/outputs/136/t4/redis_cross_connection.txt) |
结论口径:T1 的“红”只有会话内证据、无归档文件;T1 的“绿”在当前 HEAD 上已被真实 Redis 复现证实。
1.2 T2c 两趟红证据的方法说明(诚实说明)
T2c 的红证据是在**基线 1ff83bb(T2 之前)**的 git worktree --detach 检出上取的,用 PYTHONPATH=<worktree>/src 覆盖 editable .pth(已实测 polygateway 加载自 worktree 且 deadline.py 缺席):
| 趟次 | 做法 | 结果 |
|---|---|---|
| pass1 | 用例原样跑 | 三个测试模块收集期 ImportError(CallDeadlineExceeded 不存在)→ 3 errors。只证明符号缺席,没有执行到函数体 |
| pass2 | 仅把缺失的导入符号替换成本地占位异常类(shim),让函数体真正跑起来 | 32 failed:27 条“参数/属性不存在”(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 -m,lint 为 make lint) |
何时 | 结果 |
|---|---|---|
pytest tests/unit/test_deadline.py -q |
T2 红 | exit=2(collection error,符合预期) |
pytest tests/unit/test_deadline.py -q |
T2 绿 | 22 passed,exit=0 |
pytest tests/unit tests/contracts -q |
T2 门 | 1572 passed, 17 skipped,exit=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 skipped,exit=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 skipped,exit=0 |
pytest tests/integration/test_redis_cross_connection.py -q |
T1/T4 复跑 | 8 passed,exit=0 |
make lint(ruff + import-linter) |
T2/T2c/T3 收尾 | 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)与已判 SourceDead 的 0 不被覆写;未分类异常逃逸仍按 0,属已知残留,本版不动 |
| 2 | 清理期自抛 TimeoutError 时无终态遥测行 |
与 1.3.5 的裸 TimeoutError 穿透同一口径(这条路径一直存在、一直没有终态行),本版没有让它变坏;但期限把这条路径常态化了——启用期限后触发清理的频率上升,其可达性随之上升。with_call_deadline 的局部变量身份比较保证这种 TimeoutError 不会被误标成 CallDeadlineExceeded(test_deadline.py 有断言钉住) |
| 3 | 跨 Python 版本仅 3.12.13 有实证 | asyncio.timeout 的 cm.expired()/uncancel() 行为与清理期异常传播是探针在 3.12.13 单一版本上实测的;3.13+的行为未验证。库声明 3.12+,故这是真实的验证缺口,不是理论担忧 |
5. 发布清单第 4 步结果(2026-09-10,main 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=0(21 分钟) | 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。
- 不修
ResultInvalid/RequestRejected已计费坏结果仍退全款:属另一族记账语义,未批准,登记待立 issue。 - 不 bump 版本号、不打 tag、不构建、不上传 registry、不同步 wiki——全部留给发布清单。