f92065bc0b
Guards for the three cross-field invariants (source timeout vs lease TTL, stall window vs max TTFT, probe TTL vs slowest timeout) only ran inside from_env, so the from_settings path could build a GatewaySettings that violates the class's own documented invariants. Design moves all of them plus a non-empty sources check into __post_init__ as _validate_* methods, matching the existing frozen dataclasses in types.py. Covers two defects left by PR#1: probe_ttl_s was never moved, and an empty sources tuple leaked a bare 'max() arg is an empty sequence'.
1.9 KiB
1.9 KiB
type, node_id, title, date
| type | node_id | title | date |
|---|---|---|---|
| design | design:settings-invariant-guards | GatewaySettings 跨字段不变量守卫的生效范围 | 2026-07-29 |
GatewaySettings 跨字段不变量守卫的生效范围
全文见 2026-07-29-settings-invariant-guards-design.md。
- 缘起: 社区 PR#1 指出装配守卫只挂在
from_env,走 CLAUDE.md §4.5 的另一条官方路from_settings()能装出违反类不变量的配置且不报错。诊断采纳,实现按库内规范重写并扩大覆盖。 - 选定方案: A——四条跨字段不变量(lease/stall/probe_ttl/sources 非空)全部收进
GatewaySettings.__post_init__,拆_validate_*私有方法,与types.py同族五个 frozen dataclass 的既有笔迹一致;模块级_guard_lease/_guard_stall删除。 - 关键理由: 这三条约束是类的定义的一部分,不是
from_env的输入检查;放在函数里类就失去自我描述能力。构造期一处覆盖六个工厂 + 直接构造 +dataclasses.replace。 - 被否决备选: B 六个工厂各调
validate()(六处永久同步,新增 client 必漏,replace仍绕过);C 公共validate()自愿调用(把不变量降级为建议,违反 P5 与 ARCH §7.3"拒绝装配")。 - 补 PR#1 的两个缺口(实测):
probe_ttl_s ≥ 最慢 timeout + 5仍只在from_env(直接构造未拦截);守卫上移后sources=()泄漏内置异常max() arg is an empty sequence。 - 承诺变化: 经
from_env装配的调用方零影响;手工构造/replace出非法组合者由静默故障改为构造期ValueError。发版走 1.0.1(patch,用户拍板;CHANGELOG 单列"行为收紧"小节代替版本号预警),wiki参考-配置键表述与新行为一致无需改。 - 子决策: 错误消息只点字段名不列 env 键(
types.py既有笔迹 + 键名单一事实源在.env.example/wiki)。