feat(memory): T1 加保守全局速率限流(防每天数百到上千次调用失控)
主人 2026-10-07 裁定:按最优解加全局限流。 - 新增 t1RateGate(滑动窗口、可注入时钟,~70 行);默认 120 次 / 60 分钟(现网 700-1000 条新记忆/天 ≈ 25-42/小时, 取 120 留约 3-5 倍余量) - 过闸摆在「t1Enabled + 有灰区候选」两个守卫之后、发请求之前 ⇒ 不调模型就不占额度;放行才记账 - 超限语义与既有失败路径同源:跳过 T1、决定仍是 create、零改写, 只多一个 reason=t1_throttled 进留痕 + ReviewStats.T1Throttled 计数 - 配置键 t1PerWindow / t1WindowSec 放 $DATA_DIR/config/memory-tidy.json (零新 env 键,启动期读一次);坏配置回落默认 + WARN,不启动失败 - 每窗口只记一条 WARN(超限是预期行为,一天几百条会淹掉真故障) - IngestRuleVersion 仍为 d3.2:判定规则集与解析契约没变,变的只是 额度状态,reason 列已可分辨(升版会有 §17.6 记过的幂等键重复行副作用) 自研而非复用 session.NewRateLimiter 的理由(design/12 §N C.5 自己登记过 这条取舍):既有件 Allow() 内部写死 time.Now(),没有时钟注入缝, 而本切片必须能注入可控时钟;且复用会把 channel 层概念拖进 memory 包。
This commit is contained in:
1 parent
987cefdf28
commit
a684328b76
10 files changed
+782
-10
No files matched your search
@@ -134,6 +134,54 @@ func TestLoadMemoryTidyTuning_IngestGate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadMemoryTidyTuning_T1RateLimit 钉住 **T1 全局限速**的两个数值键
|
||||
// (design/23 §15.4,主人 2026-10-07 裁定):它们与 `t1Judge` 同一个文件、
|
||||
// **零新 env 键**,且**零值只能是「没配」**——绝不能是「无上限」。
|
||||
func TestLoadMemoryTidyTuning_T1RateLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// ① 没写 ⇒ 保持零值(调用方据此回落代码默认额,不是「不限流」)。
|
||||
absent := writeMemoryTidyFile(t, `{"t1Judge": true}`)
|
||||
got, err := LoadMemoryTidyTuning(absent)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadMemoryTidyTuning: %v", err)
|
||||
}
|
||||
if got.T1PerWindow != 0 || got.T1WindowSec != 0 {
|
||||
t.Fatalf("没写限速键应当给零值:%+v", got)
|
||||
}
|
||||
|
||||
// ② 写了 ⇒ 如实回读,且不连带改动别的键(一个键不能顺手关掉另一件事)。
|
||||
set := writeMemoryTidyFile(t, `{"t1PerWindow": 240, "t1WindowSec": 1800}`)
|
||||
got, err = LoadMemoryTidyTuning(set)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadMemoryTidyTuning(set): %v", err)
|
||||
}
|
||||
if got.T1PerWindow != 240 || got.T1WindowSec != 1800 {
|
||||
t.Fatalf("限速键回读 = %d/%d, want 240/1800", got.T1PerWindow, got.T1WindowSec)
|
||||
}
|
||||
if got.T1JudgeOff() || got.IngestGateOff() || got.AutoApplyOff() {
|
||||
t.Fatalf("限速键顺手改了别的开关:%+v", got)
|
||||
}
|
||||
|
||||
// ③ 显式 0 ⇒ 仍是零值(「没法用 0 关掉限流」是刻意的:那会是一处静默的失效面)。
|
||||
zero := writeMemoryTidyFile(t, `{"t1PerWindow": 0, "t1WindowSec": 0}`)
|
||||
got, err = LoadMemoryTidyTuning(zero)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadMemoryTidyTuning(zero): %v", err)
|
||||
}
|
||||
if got.T1PerWindow != 0 || got.T1WindowSec != 0 {
|
||||
t.Fatalf("显式 0 的回读 = %+v", got)
|
||||
}
|
||||
|
||||
// ④ 类型错 ⇒ 分类错误(调用方据此记 WARN 并回落代码默认额,绝不静默不限流)。
|
||||
for _, body := range []string{`{"t1PerWindow": "many"}`, `{"t1WindowSec": "1h"}`} {
|
||||
broken := writeMemoryTidyFile(t, body)
|
||||
if _, err := LoadMemoryTidyTuning(broken); !errors.Is(err, ErrInvalidMemoryTidy) {
|
||||
t.Fatalf("坏配置 %s 的 err = %v, want ErrInvalidMemoryTidy", body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultMemoryTidyPathIsDataDirConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := DefaultMemoryTidyPath(filepath.Join("root", "data"))
|
||||
|
||||
Reference in new issue
Block a user