docs: record approval, verification evidence and a follow-up gap

Corrects the changelog claim that the old guard messages only named env keys:
they named fields too, it was the remediation advice that pointed at env keys.

Records the human approval of the design, the mutation-testing evidence from
the independent verifier, and the same-class gap it found: 14 checks (redis_url
presence, telemetry paths, enum validity) still live only in from_env, while
client.py asserts they were already validated. Deliberately out of scope here.
This commit is contained in:
2026-07-30 00:36:59 -04:00
parent 64d0fac879
commit b693d442f5
2 changed files with 27 additions and 7 deletions
@@ -1,6 +1,6 @@
# GatewaySettings 跨字段不变量守卫的生效范围
- **日期**: 2026-07-29;**状态**: 待人类审批(改变库对下游的构造承诺,强制人类门)
- **日期**: 2026-07-29;**状态**: **已批准并实施**(2026-07-29 人类门通过;§8 结论见文末)
- **范围拍板**(用户 2026-07-29): 功能对齐社区 PR#1,但按本库规范重写;顺带销掉 PR#1 遗留的两个缺陷
- **上游依据**: ARCHITECTURE §7.3 契约补强 G6(装配期守卫,"违反直接报错拒绝装配")、§9 配置聚合、CLAUDE.md §4.5(装配只有两条路)、`types.py` 同族 frozen dataclass 的既有校验笔迹
@@ -123,9 +123,29 @@ TDD 顺序:先写测试跑出预期失败(预计 3 条守卫 + 空源 + from_set
合并后应关闭 PR#1 并在其中说明:诊断被采纳,实现按库内规范重写并扩展了覆盖范围。
## 8. 人类拍板
## 8. 人类拍板结论(2026-07-29)
1. **主决策**:接受方案 A(构造期强制,承诺收紧)?
2. **范围**:`probe_ttl_s` 与空 sources 一并纳入(推荐),还是严格只对齐 PR#1 的两条?
3. **消息文案**:去掉 env 键名(推荐,§2.3),还是保留 PR#1 的附注形式?
4. **PR#1 处置**:重写合并后关闭并说明,还是先请提交者按 review 修改?
| 问题 | 结论 |
|---|---|
| 主决策 | **接受方案 A**,构造期强制,承诺收紧 |
| 范围 | **全量**:`probe_ttl_s` 与空 sources 一并纳入 |
| 消息文案 | **去掉 env 键名**,只点字段名(§2.3) |
| 版本 | **1.0.1**(patch);初稿建议的 minor 被否,理由与代偿见 §4 |
| PR#1 处置 | 重写合并后关闭并说明,诊断归功于提交者 |
## 9. 实施与验证留痕
实施于 `fix/settings-invariant-guards`(5 commits)。TDD 证据:新测试类先 **6 failed / 3 passed**(3 条为边界护栏,本就应过),实现后全绿。
独立 verifier(全新上下文)核验结论 **可以合并,无阻塞**,其中两项证据值得留档:
- **变异测试 12/12 全杀**:逐个破坏实现(删各 `_validate_*` 调用、`>``>=``<``<=`、删空源检查、`_PROBE_GRACE_S` 归零)均有测试失败,无一存活。边界侧用例(恰好相等必过)对每条守卫都真实有效,差一错误可捕获。
- **无热路径回归**:库内**没有任何地方**构造或 `replace` `GatewaySettings`(`src/` 中 4 处 `dataclasses.replace` 全在 `middleware/structured.py`,作用于 `ChatRequest`/`LLMResponse`)。单次构造实测 1.45 µs,装配期一次性成本。`pickle`/`deepcopy` 不触发 `__post_init__`,只有 `replace` 触发——序列化往返既无额外开销也不构成二次守卫点。
### 9.1 verifier 发现的同族遗漏(范围外,另起任务)
`GatewaySettings` 仍有 **14 条校验只挂在 `from_env`**,直接构造/`replace` 全部放行,与本设计所修的是同一个 bug 类:`limiter/breaker/cache_backend=redis``redis_url=None``telemetry_backend=sqlite/postgres` 但 path/dsn 为 None、`selector`/`quota_full`/各 backend 的枚举合法性、`structured_max_retries` 负值、`scope` 空串等。
严重性高于本次所修的三条,因为 `client.py:262/282/302/312/316` 有 5 处 `assert ... # 内部不变量: config 已校验` **明文依赖这个前提**,而该前提在 `from_settings` 路上为假:断言开启时抛裸 `AssertionError`(不点字段不说原因),`python -O` 下断言消失、错误退化为 redis 库抛出的天书。后者同时违反 CLAUDE.md §4.3"禁止 assert 承担生产校验"。
**有意不纳入本次交付**(避免任务外扩张),另起任务处理。