fix(memory): dedupe_group 补取值门 + 临时层双击复核;闸补 3 类长期事实安全阀(M34 r1 复审)
r1 复审 p1-1(会毁数据):tag_set 簇的安全门只有数值与否定,**专有名词/引号取值不比**, 生产语料实测把 `用户的昵称是「叶酱」。` 并进 `主人的昵称是「喵喵七号」…` 的存活行, 而同库另有 4 行写着「喵喵七号已作废、应称叶酱」⇒ 一次 apply 丢掉当前事实。 修法(结构性,不是补丁): - 新增取值门 tidy_dedupe_values.go:被吸收行的**引号取值**(「」『』“”‘’")与 **拉丁 token**(≥2 字母,含 h3R3 这类混排)必须在存活行正文中一个不少; 方向单向(被吸收 ⊆ 存活),与软合并语义同向(存活行多出的取值不会丢)。 补齐 design/23 §5.1-3 要求的「专有名词 / 数值 / 否定词」三件套里缺的第一件。 - tag_set 簇也过相似度下限(dedupeGroupMinSimilarity 两档统一)。取值 0.10 而非更高: 语料实测 tag_set 合法改写(`用户可能需要提醒吃早饭…` 0.047、`叶酱有工作…` 0.109) 的余弦本来就在 0.05~0.4 —— 抬高它只会删掉合法去重,不会多挡住危险对。 实测(503 对):0.10→保留 98%、0.20→86%、0.30→63%、0.40→40%、0.50→20%。 - 回归用例:TestDedupeGroupRejectsSameTagDifferentValue(脱敏真实语料 + 同值对照组)、 TestDedupeGroupLatinTokenIsAlsoAValue、TestDedupeGroupNeverLosesAValue(簇级不变式)、 TestDedupeGroupTagSetAlsoNeedsSimilarityFloor。去掉取值门 ⇒ 前三条红;把 tag 相似度 归零 ⇒ 第四条红。 r1 复审 p2-1(数据安全):元信息查询失败时**该分区本轮不分簇**(旧实现降级成 nil map, `temporary` 恒 false ⇒ 绕开「临时行绝不进簇」红线);apply 侧补 `expires_at` 复核 (候选久置/生成后变临时 ⇒ 整簇 skip,tidySkipTemporaryRow)。用例: TestDedupeInPartitionSkipsWhenMetaUnavailable、TestTidyApplyDedupeGroupSkipsTemporaryRows。 r1 复审 p2-2(闸误杀被低报):补三类安全阀 ——「以后/从此/往后/今后」= 自此刻起永久成立、 「换成/变成/改成/搬进」= 状态已改变且新状态持续、N7 的进行中标记同样必须句首 12 字内; 「宿舍」进长期事实词表;顺手修 `<=` 的 off-by-one(改为 `<`)。 实测(生产库只读 mode=ro,active 3667): - dedupe:281 簇/594 行 → **255 簇/505 行(13.8%)**;tag_set 240 / prefix12 15; 取值门挡 76 对、相似度门挡 12 对;簇大小 2:155 3:48 4:26 5:8 6:7 7:4 8:2 9:2 18:1 20:2 (两个 20 行簇与 18 行簇的取值集合均为单一「喵喵七号」⇒ 大簇=同一事实的重写)。 - 闸:改前 2 (0.05%) → 改后 **75 (2.05%)**(n6 53 / n7 20 / n1 2);含锚点词的 27 条 逐条人读均为「约定明天中午见 / 下周留个午后」类到期即失效的安排。 - 达标代价:+目前/现在 当锚点 → 2.13%;再叠一次性事件动词表(曾/发来/分享…)→ 6.74% 且新增 172 条里 56 条是长期身份/里程碑(「i love you 文件」17 条、「海报就是助手本人」 10 条…)⇒ 仍到不了 8%,不许为凑区间放宽。 验收:go build ./... + go vet ./... + go test ./... 全绿;-race ./internal/runtime/memory ok。
This commit is contained in:
1 parent
6a3f3a329a
commit
54e32304f2
12 files changed
+704
-147
No files matched your search
@@ -258,7 +258,9 @@ func judgeRetentionOneOff(text string) retentionRule {
|
||||
// 不是那个时点,因此不许开火;而 `昨天用户发来…`、`用户近期在看…` 的锚点就是它。
|
||||
//
|
||||
// **N7(进行中状态)** 是同一时效轴的第二形态:「正在/暂时/眼下」说的事情有始有终
|
||||
// (正在测通道、暂时搁置、正在慢慢想),安全阀 1~4 与 N6 完全一致。
|
||||
// (正在测通道、暂时搁置、正在慢慢想),安全阀与 N6 完全一致,并且**位置判据也一致**
|
||||
// (r1 复审 P2-2:`用户会主动维护…并告诉助手她正在维护…` 里的「正在」只出现在从句,
|
||||
// 是「会…」的习惯陈述,不许开火)。
|
||||
//
|
||||
// 🔴 **刻意不做的事**(M27 §4 的实测反例,别再重新发明):
|
||||
// - 不做**长度阈值**筛(R3:≤15 字的条目抽 8 条 8/8 是可复用事实);
|
||||
@@ -278,7 +280,18 @@ func judgeRetentionTransient(text string) retentionRule {
|
||||
if retentionHasConcreteConstraint(text) {
|
||||
return retentionNone
|
||||
}
|
||||
// 安全阀 5:N6 **不接管 N1/N2 的语料**。含寒暄/致谢词或情绪词的句子归那两条
|
||||
// 安全阀 5/6(2026-10-25 r1 复审 P2-2 补):
|
||||
// - **「以后/从此/往后…」= 自此刻起永久成立** ⇒ 不是一次性事件。
|
||||
// 反例(复审构造):`昨天主人说以后都叫我小叶` —— 句首时点只是说话的时刻。
|
||||
// - **「换成/变成/改成/搬进…」= 状态已改变且新状态持续** ⇒ 不是一次性事件。
|
||||
// 反例(语料实测):`茂密最近换了装:蝴蝶结从棕色变成深色…`(宠物稳定外观)。
|
||||
if containsAny(text, retentionPermanenceMarkers()) {
|
||||
return retentionNone
|
||||
}
|
||||
if containsAny(text, retentionStateChangeMarkers()) {
|
||||
return retentionNone
|
||||
}
|
||||
// 安全阀 7:N6 **不接管 N1/N2 的语料**。含寒暄/致谢词或情绪词的句子归那两条
|
||||
// 规则判(N1 有它的长度安全阀,N2 有形态匹配),N6 不越过它们改判:
|
||||
// - 「谢谢你今天特地提醒我这件事,真是帮了大忙了」是社交致谢,不是事件记录;
|
||||
// - 「主人最近很容易焦虑」是**情绪倾向**(长期事实),不是「最近发生了一件事」。
|
||||
@@ -289,7 +302,7 @@ func judgeRetentionTransient(text string) retentionRule {
|
||||
if judgeRetentionTimepoint(text) != retentionNone {
|
||||
return retentionTimepoint
|
||||
}
|
||||
if containsAny(text, retentionInProgressMarkers()) {
|
||||
if judgeRetentionInProgress(text) != retentionNone {
|
||||
return retentionInProgress
|
||||
}
|
||||
return retentionNone
|
||||
@@ -297,22 +310,32 @@ func judgeRetentionTransient(text string) retentionRule {
|
||||
|
||||
// judgeRetentionTimepoint 是 N6 的形态判定:正文里**句首前 N 个字内**出现一个
|
||||
// 指示性时点词。位置判据是这条规则精度的来源(见 judgeRetentionTransient 安全阀 5)。
|
||||
//
|
||||
// 位置口径是「下标 < retentionTimepointLeadRunes」(0..11 共 12 个字);r1 复审
|
||||
// 指出旧写法 `<= 12` 让「起于第 13 个字」也命中,与注释差 1 字,这里改严。
|
||||
func judgeRetentionTimepoint(text string) retentionRule {
|
||||
if idx := retentionFrontedTimepoint(text); idx >= 0 && idx <= retentionTimepointLeadRunes {
|
||||
if idx := retentionFrontedTimepoint(text); idx >= 0 && idx < retentionTimepointLeadRunes {
|
||||
return retentionTimepoint
|
||||
}
|
||||
return retentionNone
|
||||
}
|
||||
|
||||
// retentionFrontedTimepoint 返回第一个指示性时点词在 text 里的**rune 下标**;
|
||||
// 没有时点词时返回 -1。调用方再用 retentionTimepointLeadRunes 判它是否「句首」。
|
||||
//
|
||||
// 用 rune 下标而不是字节下标是必须的:中文一个字 3 字节,按字节算会把「前 12 字」
|
||||
// 变成「前 4 字」,位置判据当场失效。
|
||||
func retentionFrontedTimepoint(text string) int {
|
||||
runes := []rune(text)
|
||||
// judgeRetentionInProgress 是 N7 的形态判定:**句首前 N 个字内**出现进行中标记
|
||||
// (正在 / 暂时 / 眼下 / 这会儿)。位置判据与 N6 同口径 —— 它挡的是「正在」只出现在
|
||||
// 从句里、而主句其实是习惯陈述的那些句子(r1 复审 P2-2 的实测反例)。
|
||||
func judgeRetentionInProgress(text string) retentionRule {
|
||||
idx := retentionFirstMarkerIndex(text, retentionInProgressMarkers())
|
||||
if idx >= 0 && idx < retentionTimepointLeadRunes {
|
||||
return retentionInProgress
|
||||
}
|
||||
return retentionNone
|
||||
}
|
||||
|
||||
// retentionFirstMarkerIndex 返回 markers 里最早出现的那个在 text 中的 rune 下标
|
||||
// (-1 = 一个都没有)。
|
||||
func retentionFirstMarkerIndex(text string, markers []string) int {
|
||||
best := -1
|
||||
for _, marker := range retentionTimepointMarkers() {
|
||||
for _, marker := range markers {
|
||||
idx := runeIndex(text, marker)
|
||||
if idx < 0 {
|
||||
continue
|
||||
@@ -321,7 +344,17 @@ func retentionFrontedTimepoint(text string) int {
|
||||
best = idx
|
||||
}
|
||||
}
|
||||
if best < 0 || best >= len(runes) {
|
||||
return best
|
||||
}
|
||||
|
||||
// retentionFrontedTimepoint 返回第一个指示性时点词在 text 里的**rune 下标**;
|
||||
// 没有时点词时返回 -1。调用方再用 retentionTimepointLeadRunes 判它是否「句首」。
|
||||
//
|
||||
// 用 rune 下标而不是字节下标是必须的:中文一个字 3 字节,按字节算会把「前 12 字」
|
||||
// 变成「前 4 字」,位置判据当场失效。
|
||||
func retentionFrontedTimepoint(text string) int {
|
||||
best := retentionFirstMarkerIndex(text, retentionTimepointMarkers())
|
||||
if best < 0 || best >= len([]rune(text)) {
|
||||
return -1
|
||||
}
|
||||
return best
|
||||
|
||||
Reference in new issue
Block a user