由 #4790 的 dev 在实施中发现并记录,PM 代为立单(dev 判断"属默认值不会触及的边角"而只写进报告,我认为它够得上一条 issue —— 理由见下)。未认领。改动前后行为一致,非 #4806 引入。
现象
OtpSendGuard 的发送历史 TTL 硬编码为 1 小时。若把 phoneOtp.cooldownSeconds 配成大于 3600,冷却判定所依赖的历史记录会先被滚动窗剪枝掉,于是:
- 声明:「两次发送之间至少间隔 N 秒」(N > 3600);
- 实际:超过 1 小时之后历史就没了,冷却最多只有 1 小时;
- 信号:无。配置被接受,没有校验错误,没有 warn。
为什么值得立单,而不只是记在报告里
dev 的判断(边角、默认值不会触及)是对的,但这恰好是本仓正在系统性关掉的那一类,而且是其中最隐蔽的一种:
配置项不是被忽略(那还容易发现),而是被接受、被部分执行。管理员设了 2 小时冷却,系统给了 1 小时,而且看起来一切正常 —— 反滥用强度是声明值的一半,没有任何东西说出来。这与 #4790 本身(预算按节点倍增、无信号)是同一个形状,只是维度从"节点数"换成了"时间"。
按 ADR-0049(declared = enforced):要么真的支持 > 1 小时的冷却,要么在配置校验期拒绝这个值并说明上限。静默截断是唯一不该保留的选项。
修法方向(择一,倾向 1)
- 让 TTL 跟随配置:历史保留时长取
max(1h, cooldownSeconds),冷却按声明值真正生效。最符合"声明即强制",代价是超长冷却会让历史条目留得更久(可加一个合理上限,但那个上限要显式拒绝超出的配置,而不是截断)。
- 在配置校验期拒绝
cooldownSeconds > 3600,错误信息说明当前实现上限及原因。诚实、成本最低,但把一个能力限制写死进契约,以后想放开要走变更。
不建议第 3 条(维持现状 + 文档说明)—— 文档拦不住配置文件里那个数字。
验收
- 配
cooldownSeconds 大于 1 小时时,要么真的生效,要么被明确拒绝;两种都可接受,静默截断不可接受;
- 默认配置行为不变(有测试证明,别为边角改动误伤主路径);
- 若取方案 1,加一条测试:冷却设为 > 1 小时时,超过 1 小时后的发送仍被拒。
关联
由 #4790 的 dev 在实施中发现并记录,PM 代为立单(dev 判断"属默认值不会触及的边角"而只写进报告,我认为它够得上一条 issue —— 理由见下)。未认领。改动前后行为一致,非 #4806 引入。
现象
OtpSendGuard的发送历史 TTL 硬编码为 1 小时。若把phoneOtp.cooldownSeconds配成大于 3600,冷却判定所依赖的历史记录会先被滚动窗剪枝掉,于是:为什么值得立单,而不只是记在报告里
dev 的判断(边角、默认值不会触及)是对的,但这恰好是本仓正在系统性关掉的那一类,而且是其中最隐蔽的一种:
配置项不是被忽略(那还容易发现),而是被接受、被部分执行。管理员设了 2 小时冷却,系统给了 1 小时,而且看起来一切正常 —— 反滥用强度是声明值的一半,没有任何东西说出来。这与 #4790 本身(预算按节点倍增、无信号)是同一个形状,只是维度从"节点数"换成了"时间"。
按 ADR-0049(declared = enforced):要么真的支持 > 1 小时的冷却,要么在配置校验期拒绝这个值并说明上限。静默截断是唯一不该保留的选项。
修法方向(择一,倾向 1)
max(1h, cooldownSeconds),冷却按声明值真正生效。最符合"声明即强制",代价是超长冷却会让历史条目留得更久(可加一个合理上限,但那个上限要显式拒绝超出的配置,而不是截断)。cooldownSeconds > 3600,错误信息说明当前实现上限及原因。诚实、成本最低,但把一个能力限制写死进契约,以后想放开要走变更。不建议第 3 条(维持现状 + 文档说明)—— 文档拦不住配置文件里那个数字。
验收
cooldownSeconds大于 1 小时时,要么真的生效,要么被明确拒绝;两种都可接受,静默截断不可接受;关联
formatrule with an invalid regex, and ajson_schemarule ajv cannot compile, still fail OPEN — the same trap #4649 closed, one rule type over #4762 —— 「声明的校验实际不生效」同族