refactor(memory): M45 r1 复审返工 —— temporal 位只放开 N6 位置判据

按 r1 复审裁定(p1-3items / fix-then-merge)重做三处:

P1-1/P1-2:删掉自造的六条语义安全阀(temporalSanityVeto)与约定词表
(retentionStatesAgreement)。理由被复审用两条生产行否证:它们既把该救的行
杀了(「…睡得比平时早」被习惯词、「…以后不用再惦记」被永久化标记),又挡不住
同义异形的约定(说定 / 约了)。现在 `one_off` 的全部作用是**放开 N6 的位置判据**:
judgeExtractionRetention 新增 temporal 入参 → judgeRetentionTransient →
judgeRetentionTimepointWide(位置内命中仍返 n6_timepoint,只有靠放开位置才命中
才返新增的 n6_timepoint_wide,供观测区分)。N1~N5、N7、护栏①②、N6 的 1~7 条
语义安全阀一字不动;durable / recurring 仍直接判 long(fail-safe 方向)。

P1-3:上界口径改为「时点词存在 ∧ 语义安全阀全过 ∧ 当前 long」——不依赖任何
标注,可逐条列出。实测(生产库只读副本,快照 2026-10-26):最近 500 条 active
能翻 13 条、**逐条都是「约定 + 今天没走成」**;全量 3694 条能翻 26 条(13 条
约定 + 13 条状态行)。旧的「句首时点词」口径是同义反复,已删。

已知代价(写进 temporal.go 头注与 TestTemporalOneOffWidenedPositionTouchesAgreements,
不藏在词表后面):位置窗口今天正是那批约定行的唯一保护,放开后只要模型标
one_off 它们就会判 temp;而复审点名的两条目标行本片救不了(被语义阀挡,与位置
无关)。因此这一位在当前语料上**没有可测收益**,是否保留是产品裁决。

P2-1:store.go 503 → 497 行,拆出 store_temporal.go(哨兵错误 + 字段校验 +
列值编码)。
P2-2:temporal.go 头注改准 —— `''` 是「0027 之前的历史行 + 没做时间性判断的
写入路径(ManualExtractor / 网关手写 / 整理回填,它们由调用方显式给档位)」。
P2-3:报告补语境(retention_tier 非空仅 26 行、temp 1 行、temporal 列当时还不
存在 ⇒ 重放结论对新写入没有预测力)。

测试:验收点 ①~⑤ 按新语义重写;新增「位置放开可打红」的变异靶子与「N7 位置
不放宽」「无时点词时这一位不做事」「两条目标行救不了」「约定行会翻(已知风险)」
五条边界断言。变异实测:unlocksTimepointPosition 改恒 false ⇒ 三条用例红。
回归:go build ./... + go vet ./... 全绿;memory/db 包测试与 memory -race 全绿。
This commit is contained in:
AskaEth committed 2026-10-26 19:15:08 +08:00
1 parent 91101dedfb
commit 3b9ae3f918
9 files changed
+412 -370

No files matched your search

@@ -73,7 +73,7 @@ func retentionLongTermCases() []struct {
func TestExtractionRetention_TimepointIsDeferred(t *testing.T) {
t.Parallel()
for _, content := range retentionTimepointCases() {
if got := judgeExtractionRetention(content); got != retentionTimepoint && got != retentionInProgress {
if got := judgeRetentionNoTemporal(content); got != retentionTimepoint && got != retentionInProgress {
t.Fatalf("%q 判成 %q, want %q(锚点是一个时刻 ⇒ 临时层)",
content, got, retentionTimepoint)
}
@@ -84,10 +84,10 @@ func TestExtractionRetention_TimepointIsDeferred(t *testing.T) {
// 它让两条规则的词表**各自**可被单测,不会一条盖住另一条。
func TestRetentionTimepointHitsBySpecificRule(t *testing.T) {
t.Parallel()
if got := judgeExtractionRetention("用户昨天发送了一份长达一万两千行的验收纸条"); got != retentionTimepoint {
if got := judgeRetentionNoTemporal("用户昨天发送了一份长达一万两千行的验收纸条"); got != retentionTimepoint {
t.Fatalf("时点句 = %q, want %q", got, retentionTimepoint)
}
if got := judgeExtractionRetention("用户正在验证分享确认面板的展开功能"); got != retentionInProgress {
if got := judgeRetentionNoTemporal("用户正在验证分享确认面板的展开功能"); got != retentionInProgress {
t.Fatalf("进行中句 = %q, want %q", got, retentionInProgress)
}
}
@@ -97,7 +97,7 @@ func TestRetentionTimepointHitsBySpecificRule(t *testing.T) {
func TestExtractionRetention_RealLongTermFactsSurviveTimepoints(t *testing.T) {
t.Parallel()
for _, tc := range retentionLongTermCases() {
if got := judgeExtractionRetention(tc.content); got != retentionNone {
if got := judgeRetentionNoTemporal(tc.content); got != retentionNone {
t.Fatalf("长期事实 %q(%s)被判成 %q —— 它会在 7 天后被物理删除(不可逆)",
tc.content, tc.name, got)
}
@@ -110,10 +110,10 @@ func TestExtractionRetention_TimepointMustBeFronted(t *testing.T) {
t.Parallel()
fronted := "用户昨天发来一卷验收纸条"
buried := "用户与助手讨论过验收纸条的格式(他自己说昨天发来一卷)"
if got := judgeExtractionRetention(fronted); got != retentionTimepoint {
if got := judgeRetentionNoTemporal(fronted); got != retentionTimepoint {
t.Fatalf("时点在句首 = %q, want %q", got, retentionTimepoint)
}
if got := judgeExtractionRetention(buried); got != retentionNone {
if got := judgeRetentionNoTemporal(buried); got != retentionNone {
t.Fatalf("时点埋在从句里 = %q, want %q(位置安全阀失效会误杀长内容)", got, retentionNone)
}
}
@@ -124,17 +124,17 @@ func TestExtractionRetention_TimepointMustBeFronted(t *testing.T) {
// 前一半会变红;把组合条件删成「任一命中即放行」时,整条用例会红。
func TestExtractionRetention_ConjunctionIsNotANoOp(t *testing.T) {
t.Parallel()
if got := judgeExtractionRetention("约定下周给用户留一个安静的午后"); got != retentionTimepoint {
if got := judgeRetentionNoTemporal("约定下周给用户留一个安静的午后"); got != retentionTimepoint {
t.Fatalf("「约定 + 时点、无约束」= %q, want %q(锚点单独出现不再一票放行)",
got, retentionTimepoint)
}
if got := judgeExtractionRetention("约定每天给主人留一个安静的午后"); got != retentionNone {
if got := judgeRetentionNoTemporal("约定每天给主人留一个安静的午后"); got != retentionNone {
t.Fatalf("「约定 + 每天(具体约束)」= %q, want 放行(组合条件命中即长期)", got)
}
if got := judgeExtractionRetention("主人每天要喝 2 杯咖啡"); got != retentionNone {
if got := judgeRetentionNoTemporal("主人每天要喝 2 杯咖啡"); got != retentionNone {
t.Fatalf("「偏好 + 数值」= %q, want 放行", got)
}
if got := judgeExtractionRetention("主人说过不要在群里提她的工作"); got != retentionNone {
if got := judgeRetentionNoTemporal("主人说过不要在群里提她的工作"); got != retentionNone {
t.Fatalf("「禁忌」= %q, want 放行", got)
}
}
@@ -159,7 +159,7 @@ func TestExtractionRetention_TimepointSafetyValves(t *testing.T) {
{"用户昨夜睡得还行,今天心情不错", "情绪词 ⇒ 归 N2 判"},
}
for _, tc := range cases {
got := judgeExtractionRetention(tc.content)
got := judgeRetentionNoTemporal(tc.content)
if got == retentionTimepoint || got == retentionInProgress {
t.Fatalf("%q 判成 %q, want 不被 N6/N7 判临时(%s)", tc.content, got, tc.why)
}