feat(memory): retention_tier 档位列 + defer 与档位合并为同一判定(M37)
主人 2026-10-26 对「记忆分型」裁定「两列」:kind(关于什么)保留,新增 retention_tier(留多久 / 干什么用)。本片把这条轴从列、判定、写入、读侧过滤 一路打通,并把「判第五态 defer」与「判 retention_tier=temp」合并成一个判定。 结构(迁移 0026 + 自举前滚): - memory_items 新增 retention_tier TEXT NOT NULL DEFAULT ''(空串 = 尚未分型, 历史行与受限来源行),索引 idx_memory_items_retention_tier;up/down 均实跑过 (在隔离的生产库副本上 migrate → 建 Store → down → 再 up,5073 行零丢失)。 - NewStore 幂等前滚该列 + 建索引(ensureRetentionSchema),与迁移同形由测试钉住。 判定(retention.go,全仓唯一入口): - RetentionTier 闭集 temp/daily/work/long/knowledge + 空串未分型;与 kind 正交。 - resolveRetention 的优先级:调用方显式档位 > 显式有效期(⇒ temp)> 受限来源 不分型 > 留存规则 N1~N7 ⇒ temp > 默认 long。判据靠元数据,规则不许推翻显式档位。 - tempDeadline 是「档位 → expires_at」的唯一换算:temp ⇔ 有效期非空,两列同一笔 INSERT 落下(M2 登记过的「INSERT 与补写之间的短窗口」就此关闭)。 合并(defer ⇔ temp): - judgeIngest 不再跑留存规则,只读 Add 落定的 item.RetentionTier;规则调用点因此 全仓唯一(源码级断言钉住)。 - 判定不再依赖同步闸是否开启:闸未接/关着时档位与有效期照样落库。 - 异步层对已是 temp 的行不重复改写(避免 TTL 随审查延迟漂移)。 - IngestRuleVersion d3.3 → d3.4(判据作用范围变了,按既有硬规矩升版)。 读侧: - 四条列表读路径的 SELECT 与 scanItem 读出 retention_tier(14 列);Get 同样读回。 - 新增 ListByPersonaTiers / ListScopeTiers / ListTiers / ListProjectTiers(可选档位 过滤,缺省不过滤 ⇒ 历史行照常返回;显式传空串只取未分型;非法档位报错)。 原方法签名逐字不变 —— 网关 MemoryStore 接口钉着它们,可变参数会编译不过。 - store.go 的读路径拆到 store_list.go,守 500 行上限。 回归:go build ./... + go vet ./... + go test ./... 全绿(内存包另有 -race)。
This commit is contained in:
1 parent
240b59708d
commit
cc6904b1b4
23 files changed
+1592
-181
No files matched your search
@@ -195,13 +195,27 @@ func TestExtractionRetention_UpperCaseASCII(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestJudgeIngestDefersLowValueItem 是**接线**断言:留存规则闸真的接在判定链上,
|
||||
// 命中的新条目拿到第五态 `defer` + 留痕原因码 `t0_low_value`,且**不进灰区**
|
||||
// TestJudgeIngestDefersLowValueItem 是**接线**断言:档位判定真的接在判定链上,
|
||||
// 被判成 temp 的新条目拿到第五态 `defer` + 留痕原因码 `t0_low_value`,且**不进灰区**
|
||||
// (design/03 §10.6:规则闸拦下的不进 T1)。
|
||||
//
|
||||
// 🔴 切片 M37 起,`judgeIngest` **不再自己跑留存规则** —— 档位由 `resolveRetention`
|
||||
// 一次判完(commit 前的写法是这里再调一次 `judgeExtractionRetention`,那正是
|
||||
// 「两套规则各判一遍」)。因此本用例先把档位判出来放进条目(与 `Store.Add` 的
|
||||
// 做法逐字一致),再断言判定链**只读**这一位。变异(把规则调用搬回 judgeIngest)
|
||||
// 会让 retention_test.go 的唯一性断言先红。
|
||||
func TestJudgeIngestDefersLowValueItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
item := habitItem("谢谢啦", tidyTestTime(t), 0.9)
|
||||
// 故意给一条「相似候选」:它本该进灰区,但留存规则先命中 ⇒ 灰区必须为空,
|
||||
verdict, err := resolveRetention(item)
|
||||
if err != nil {
|
||||
t.Fatalf("resolveRetention: %v", err)
|
||||
}
|
||||
if verdict.Tier != RetentionTemp {
|
||||
t.Fatalf("低价值条目档位 = %q, want %q", verdict.Tier, RetentionTemp)
|
||||
}
|
||||
item.RetentionTier = verdict.Tier
|
||||
// 故意给一条「相似候选」:它本该进灰区,但 temp 档先命中 ⇒ 灰区必须为空,
|
||||
// 于是 T1 一个 token 都花不出去。
|
||||
cands := []ingestCandidate{{item: habitItem("谢谢", tidyTestTime(t), 0.9), score: 0.9, mode: tidyRecallLexical}}
|
||||
v := judgeIngest(item, cands)
|
||||
|
||||
Reference in new issue
Block a user