Files
PolyGateway/research-wiki/designs/settings-invariant-guards.md
iomgaa f92065bc0b docs: design settings invariant guards on every construction path
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'.
2026-07-29 23:55:47 -04:00

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)。