feat(memory): sensitive 标记 + 提取器敏感判据,私事退出受限车道召回

M63(M60 审计 P0-1/P0-2)。作用域 ≠ 敏感度:`scope='user'` 是共享域,里
面却装着住址(花溪 64 条 active)/私称(叶酱 63)/作息(早睡 38),于是受限车
道(群聊/陌生人)把共享域整锅注入上下文 —— 2026-10-29 群 531082168 的住址泄漏
即此路径。本片给 memory_items 加 `sensitive` 列,把「这条归谁」(scope)与
「这条能不能外说」(sensitive)拆成两根正交的轴。

- 迁移 0028:加列(INTEGER NOT NULL DEFAULT 0)+ **同迁移幂等回填**(精确短语
  白名单 花溪/叶酱/早睡,`AND sensitive=0`,不改 updated_at),down 只撤结构;
  memory 包内 `ensureSensitiveSchema` 同形前滚(任意 *sql.DB 自举)。
- 受限判据:新增 `MemoryItem.SharedVisible()` = 共享域 且 !sensitive,成为
  restricted 侧唯一起点;`loop.FilterSharedVisible` 与复用它的 knowledge 工具
  自动跟随。主人车道不过这道判据,照常读到全部记忆(sensitive 不是权限系统)。
- 提取器:prompt 追加 sensitive 位(住址/私人称呼/家人/健康/作息与行程 → true,
  口味/兴趣/项目约定 → false,含正反例);产出侧与落库侧共用
  `resolveSensitive`(模型的类目判断 ∨ 已实证短语白名单),只加标不撤标。
- 回填入口 `BackfillSensitive(ctx, db, dryRun)`:幂等、可 dry-run、返回候选池
  前后规模与逐短语计数(生产快照实测 3116 → 2948,标 725 行)。
- 读路径并到 15 列(scanItem + 7 处 SELECT);store.go 拆分 store_insert.go
  守住 500 行上限。

验收:go build/vet/test 全绿(memory + loop 单包复跑绿);删实现三处变异分别打红
(sensitiveColumn 恒 0 / SharedVisible 忽略 Sensitive / Extractor 跳过白名单);
生产库快照上跑 0028 后 Go 侧回填 marked=0(迁移 SQL 与 privateFacts 等价)。
This commit is contained in:
AskaEth committed 2026-10-30 09:26:15 +08:00
1 parent a8bc2b4470
commit 44fbd29836
22 files changed
+1360 -102

No files matched your search

+6 -4
View File
@@ -28,7 +28,8 @@ CREATE TABLE IF NOT EXISTS memory_items (
valid_to TEXT NOT NULL DEFAULT '',
expires_at TEXT NOT NULL DEFAULT '',
retention_tier TEXT NOT NULL DEFAULT '',
temporal TEXT NOT NULL DEFAULT ''
temporal TEXT NOT NULL DEFAULT '',
sensitive INTEGER NOT NULL DEFAULT 0
)`
const createMemoryItemsPersonaIndex = `
@@ -46,9 +47,10 @@ CREATE INDEX IF NOT EXISTS idx_memory_items_persona_id_created_at
// (retention_migration_test.go 钉住这条同形,少一列就是「新库有、老库无」的漂移)。
//
// ⚠️ 0027 之后 `retention_tier` **不再是最后一列**:`temporal`(迁移 0027)追加在
// 它之后,两条路径的尾序都是 `… expires_at, retention_tier, temporal`。新列一律
// 追加在末尾这条口径不变,只是「末尾」会随迁移推进(那条同形断言比对**列集合**,
// 另外对尾序做一条显式断言)。
// 它之后,0028 又把 `sensitive` 追加在 `temporal` 之后。两条路径的尾序都是
// `… expires_at, retention_tier, temporal, sensitive`。新列一律追加在末尾这条口径
// 不变,只是「末尾」会随迁移推进(那条同形断言比对**列集合**,另外对尾序做一条
// 显式断言)。
const createMemoryItemsRetentionTierIndex = `
CREATE INDEX IF NOT EXISTS idx_memory_items_retention_tier
ON memory_items (retention_tier)`