Files
Cyrene_v2/internal/config/memory_tidy_test.go
T
AskaEth 6e28b38b08
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(memory): 护栏抬高+时间预算闸+试算加性异步 与 D3 入库前审查(耦合两片一起落)
【MT-A:护栏 + 诚实截断 + 试算异步】
- maxTidyCompareItems 400(写死)→2000 并开放为 MEMORY_TIDY_COMPARE_LIMIT;
  MEMORY_TIDY_CANDIDATE_LIMIT 默认 20→200(语义不变,仍每分区)
- 依据是首跑实测:459 条的分区被 400 截断 ⇒ compared(705) < scanned(784),
  候选数(77)由上限而不是规则决定;三处上限的 WARN 都补齐了总数/上限/分区名
- 新增「上限 + 时间预算」双闸(实测 2000 条/分区 ≈234s > 3 分钟墙钟):
  整分区跳过 / 两两比对中途收手,一律如实记 (budget) + WARN
- ?async=1 ⇒ 202 {runId,status};GET …/{runId} 轮询(writeAuth 同档);
  done 的 result 与同步 data 逐字节同形;并发=复用既有 runId、TTL 30min 自完成起算、
  重启即如实 404 tidy_run_not_found;不带 async 时同步路径逐字不变

【MT-B:D3 入库前审查(design/23 §15.1)】
- 新记忆经 Store.Add(仓库里唯一 INSERT INTO memory_items)落库后,由独立记忆管理
  会话(sess-memorytidy)在有界后台队列异步判定 skip/merge/modify/create
- 软合并回写(status + merged_into/merged_from/merge_count,可 revert、绝不改 content)
- 隔离来源(群聊/陌生人)**概不审查**、召回/超时失败**零改写**(fail-safe 结构性成立)
- 写入侧仅 +62.7µs/条(非阻塞入队);默认 ENABLED=true(主人裁定:入库前先审查)

【耦合说明(重要)】两片同包且互相依赖:MT-B 的 preinsert_recall.go 用 MT-A 的
collectTidyPairs 新签名 + tidyBudget ⇒ **各自单独都 build 不过**,故一并落地。
MT-A 为让整包编译,最小幅度适配了 MT-B 的 preinsert_recall.go / preinsert_test.go
(零值预算=行为不变;符号重名改名)。

【我的验收】隔离 worktree(37 文件)build/vet/linux/darwin 全过,切片包全绿;
我亲手打红两个变异:① 去掉 (compare) 截断标记 ⇒ TestTidyCompareLimit_HitIsLoud 红;
② 中和隔离来源守卫 ⇒ TestReviewRestrictedSourceIsNeverTouched 红。
唯一红是 internal/runtime/tool 的两条超时用例(该包无人改)⇒ 主仓库 -count=8 全绿,
判定为负载敏感 flake(当时两个整树验收 + 子代理并发),已记入开发习惯与约定 §7。

.env.example 补 MEMORY_REVIEW_* 四键(MT-B 无该文件权限,由我按其代码语义补写)。
2026-10-06 08:04:12 +08:00

60 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"strings"
"testing"
)
// TestLoad_MemoryTidyDefaults 锁定记忆整理(design/23 §4/§9/§13)的 env 契约默认值。
// 它们必须与 design/23 的默认/建议值一致,也必须与 `memory` 包的兜底默认值一致
// (两处不一致会让「没配这个键」与「配了但解析失败」走向不同的行为)。
func TestLoad_MemoryTidyDefaults(t *testing.T) {
t.Setenv("JWT_SECRET", strings.Repeat("s", 40))
cfg, err := Load("")
if err != nil {
t.Fatalf("Load() error: %v", err)
}
got := cfg.MemoryTidy
if !got.Enabled {
t.Error("MEMORY_TIDY_ENABLED 默认应为 true(design §4:可用 false 关闭)")
}
if got.IntervalSec != 86400 {
t.Errorf("MEMORY_TIDY_INTERVAL_SEC = %d, want 86400(每天 1 次)", got.IntervalSec)
}
if got.QuietStartHour != 23 || got.QuietEndHour != 7 {
t.Errorf("低峰窗口 = %d-%d, want 23-7(复用 backthink 语义)", got.QuietStartHour, got.QuietEndHour)
}
if got.RecallThreshold != 0.80 {
t.Errorf("MEMORY_TIDY_RECALL_THRESHOLD = %v, want 0.80(design §13 开放点 2 的建议值)", got.RecallThreshold)
}
if got.ScanLimit != 5000 || got.CandidateLimit != 200 || got.CompareLimit != 2000 {
t.Errorf("配额 = %d/%d/%d, want 5000/200/2000(scan/candidate:design §9;compare:切片 MT-A 抬高后)",
got.ScanLimit, got.CandidateLimit, got.CompareLimit)
}
}
// TestLoad_MemoryTidyEnvOverrides 锁定键名与类型:写进 .env 的旋钮真的生效
// (键名写错会静默沿用默认值,这类错误只能靠这条用例抓住)。
func TestLoad_MemoryTidyEnvOverrides(t *testing.T) {
t.Setenv("JWT_SECRET", strings.Repeat("s", 40))
t.Setenv("MEMORY_TIDY_ENABLED", "false")
t.Setenv("MEMORY_TIDY_INTERVAL_SEC", "3600")
t.Setenv("MEMORY_TIDY_QUIET_START_HOUR", "1")
t.Setenv("MEMORY_TIDY_QUIET_END_HOUR", "5")
t.Setenv("MEMORY_TIDY_RECALL_THRESHOLD", "0.7")
t.Setenv("MEMORY_TIDY_SCAN_LIMIT", "10")
t.Setenv("MEMORY_TIDY_CANDIDATE_LIMIT", "3")
t.Setenv("MEMORY_TIDY_COMPARE_LIMIT", "42")
cfg, err := Load("")
if err != nil {
t.Fatalf("Load() error: %v", err)
}
got := cfg.MemoryTidy
if got.Enabled || got.IntervalSec != 3600 || got.QuietStartHour != 1 || got.QuietEndHour != 5 ||
got.RecallThreshold != 0.7 || got.ScanLimit != 10 || got.CandidateLimit != 3 || got.CompareLimit != 42 {
t.Fatalf("MEMORY_TIDY_* 未生效:%+v", got)
}
}