生产启动行仍是 timeout=20s —— M15 把 memory 包的 defaultReviewTimeout 抬到 90s 的修复**从未生效**:config 层的 env tag 自己带着 default:"20", env 层永远产出非零值,于是 memory 侧「≤0 才回落默认」的归一化成了死代码。 修法(二选一中的「① 让 memory 侧成为唯一真源」):去掉 config 侧 MEMORY_REVIEW_TIMEOUT_SEC 的字面默认值,未配置时读出 0 = 「没配」, 由 normalizeReviewConfig 单点落地 90s。 生效值: .env / env 没写这个键 ⇒ 90s(唯一真源) 写了正数 ⇒ 原样生效(不做任何「保底抬到 90」的加码) 写了 0 / 负数 ⇒ 90s(与项目其它旋钮同一口径:≤0 = 用默认) 新增测试(走真实组装路径,不绕过任何一层): config.LoadMemoryReviewConfig → ReviewConfig.Timeout → NewPreInsertReviewer → normalizeReviewConfig → 生效值(= 启动行打印的 timeout=) 断言生效值严格大于「实测最坏召回链 57s + T1 子超时 8s」,并断言启动行打印它。 变异自证(修前红):把 tag 的 default:"20" 加回去 ⇒ 报出 「组装后 timeout = 20s」+「生效值 20s != 唯一真源 1m30s」 原有 TestEmbeddingTimeoutFitsReviewBudget 拦不住这条,因为它比的是**本包常量**, 看不见 env 层;注释里已写明「比常量」与「比组装后生效值」的区别(防后人重蹈)。
103 lines
4.4 KiB
Go
103 lines
4.4 KiB
Go
package config
|
||
|
||
import (
|
||
"os"
|
||
"testing"
|
||
)
|
||
|
||
// reviewTimeoutEnvUnset 让 MEMORY_REVIEW_TIMEOUT_SEC 在本次用例中**真的未设置**。
|
||
//
|
||
// 为什么不用 t.Setenv(key, ""):空串在 decode.go 的 lookupRaw 里**照样命中 env 分支**
|
||
// (`os.LookupEnv` 对空值返回 ok=true)⇒ 配置层读出 0,于是**看不见 tag 上的字面
|
||
// 默认值** —— 而那正是本切片的回归面(tag 上再加回 `default:"20"` 必须变红)。
|
||
// 未设置才会走到 tag 默认那一档。
|
||
func reviewTimeoutEnvUnset(t *testing.T) {
|
||
t.Helper()
|
||
old, had := os.LookupEnv("MEMORY_REVIEW_TIMEOUT_SEC")
|
||
t.Cleanup(func() {
|
||
if had {
|
||
_ = os.Setenv("MEMORY_REVIEW_TIMEOUT_SEC", old)
|
||
return
|
||
}
|
||
_ = os.Unsetenv("MEMORY_REVIEW_TIMEOUT_SEC")
|
||
})
|
||
if err := os.Unsetenv("MEMORY_REVIEW_TIMEOUT_SEC"); err != nil {
|
||
t.Fatalf("unset MEMORY_REVIEW_TIMEOUT_SEC: %v", err)
|
||
}
|
||
}
|
||
|
||
// TestLoadMemoryReviewConfigDefaults 锁定 D3「入库前审查」(design/23 §15.1 D3)
|
||
// 的 env 契约默认值。它们必须与 memory 包的兜底默认值一致 —— 两处不一致会让
|
||
// 「没配这个键」与「配了但解析失败」走向不同的行为。
|
||
//
|
||
// 🔴 TimeoutSec 是**例外的那一个,而且是刻意的**(切片 M16,2026-10-13):
|
||
// 它**没有**字面默认值,未配置时就是 0,含义是「没配」而不是「0 秒」。
|
||
// 单条审查预算的唯一真源在 memory 包的 `defaultReviewTimeout`(90s),
|
||
// 全链的生效值 = `ReviewConfig.Timeout` 经 `normalizeReviewConfig` 归一化之后的值。
|
||
//
|
||
// 为什么不在这里断言 90:那会让本仓重新长出**两个**真源(env tag 一处、
|
||
// memory 常量一处),而 `.env` 冻结口径(AGENTS §10)本来就把这类「上限/调优」
|
||
// 参数指向**代码默认值**。断言生效值的那条测试在 memory 包
|
||
// (TestReviewTimeoutEffectiveValueIsTheOnlyDefault,走完整组装路径)——
|
||
// 本包不能引 memory 包(memory → config 已成立,反向会成环)。
|
||
func TestLoadMemoryReviewConfigDefaults(t *testing.T) {
|
||
reviewTimeoutEnvUnset(t)
|
||
cfg, err := LoadMemoryReviewConfig("")
|
||
if err != nil {
|
||
t.Fatalf("LoadMemoryReviewConfig() error: %v", err)
|
||
}
|
||
if !cfg.Enabled {
|
||
t.Error("MEMORY_REVIEW_ENABLED 默认应为 true(主人 2026-10-05 裁定「入库前审查」;T0 零模型调用)")
|
||
}
|
||
if cfg.QueueSize != 256 {
|
||
t.Errorf("MEMORY_REVIEW_QUEUE_SIZE = %d, want 256", cfg.QueueSize)
|
||
}
|
||
if cfg.ScanLimit != 200 {
|
||
t.Errorf("MEMORY_REVIEW_SCAN_LIMIT = %d, want 200(§15 D3-4 每条审查有界)", cfg.ScanLimit)
|
||
}
|
||
if cfg.TimeoutSec != 0 {
|
||
t.Errorf("MEMORY_REVIEW_TIMEOUT_SEC = %d, want 0(未配置)—— "+
|
||
"这里出现非零值说明 tag 上又被加回了字面默认值,memory 侧的 "+
|
||
"defaultReviewTimeout 会再次变成死代码(M16 修的正是这个形态)", cfg.TimeoutSec)
|
||
}
|
||
}
|
||
|
||
// TestLoadMemoryReviewConfigEnvOverrides 锁定键名与类型:写进 .env 的旋钮真的
|
||
// 生效(键名写错会静默沿用默认值,这类错误只能靠这条用例抓住)。
|
||
//
|
||
// TimeoutSec 另钉一条口径(M16):**正数原样透传、≤0 原样透传**。配置层不做
|
||
// 「坏值回落」——「0 或负数 ⇒ 用默认 90s」是 memory 包 normalizeReviewConfig
|
||
// 的语义,全链只有那一处决定它(组合根不许在这里补默认值)。
|
||
func TestLoadMemoryReviewConfigEnvOverrides(t *testing.T) {
|
||
t.Setenv("MEMORY_REVIEW_ENABLED", "false")
|
||
t.Setenv("MEMORY_REVIEW_QUEUE_SIZE", "8")
|
||
t.Setenv("MEMORY_REVIEW_SCAN_LIMIT", "32")
|
||
t.Setenv("MEMORY_REVIEW_TIMEOUT_SEC", "3")
|
||
|
||
cfg, err := LoadMemoryReviewConfig("")
|
||
if err != nil {
|
||
t.Fatalf("LoadMemoryReviewConfig() error: %v", err)
|
||
}
|
||
if cfg.Enabled || cfg.QueueSize != 8 || cfg.ScanLimit != 32 || cfg.TimeoutSec != 3 {
|
||
t.Fatalf("MEMORY_REVIEW_* 未生效:%+v", cfg)
|
||
}
|
||
|
||
for _, tc := range []struct {
|
||
raw string
|
||
want int
|
||
}{
|
||
{"0", 0}, // 显式 0 = 用默认(下游归一化),不许在这里被改成 90
|
||
{"-5", -5}, // 负数同样是「用默认」,也不在这里报错(AGENTS:不让笔误把进程弄起不来)
|
||
} {
|
||
t.Setenv("MEMORY_REVIEW_TIMEOUT_SEC", tc.raw)
|
||
got, err := LoadMemoryReviewConfig("")
|
||
if err != nil {
|
||
t.Fatalf("LoadMemoryReviewConfig(%q) error: %v", tc.raw, err)
|
||
}
|
||
if got.TimeoutSec != tc.want {
|
||
t.Fatalf("MEMORY_REVIEW_TIMEOUT_SEC=%q 读出 %d, want %d(配置层不回落,回落只在 memory 包)",
|
||
tc.raw, got.TimeoutSec, tc.want)
|
||
}
|
||
}
|
||
}
|