-- 0028_memory_sensitive:**主人的私事不许进受限车道**(切片 M63,M60 审计 P0-1)。 -- -- 依据:DevDocs/audit/群聊主人信息泄漏调查-2026-10-30.md §6 R1 + §7 P0-1; -- 塔裁决 2026-10-30:私事用 sensitive 标记,**不迁 scope**(迁 persona 域会让其他角色 -- 可读,否决);「用户习惯可查」的既有裁决(design/03 §5)不动。 -- -- 为什么是**新列**而不是换作用域:作用域与敏感度是两根正交的轴。`scope` 回答 -- 「这条归谁、谁可写谁可读」,本列回答「这条能不能说给第三方听」。实证(只读实测, -- 审计 §2):`scope='user'` 里装着住址(`花溪` 64 条 active)、私称(`叶酱` 63 条)、 -- 作息(`早睡` 38 条)—— 作用域是共享域,内容却是私事。把它们迁进 `persona` 域 -- 等于让其他角色可读(memory/rules.go 的跨角色只读),语义更差。 -- -- 🔴 **sensitive 是数据层标记,不是权限系统**:主人车道(非受限环境)照常读到全部 -- 记忆(含 sensitive=1);它只让**受限车道**(群聊 / 陌生人)的召回过滤器把这一条 -- 排除在候选池外(internal/runtime/memory/sensitive.go 的 SharedVisible → -- internal/runtime/loop/memory_filter.go 的 FilterSharedVisible)。谁是主人仍只由 -- actor 元数据 + 白名单 + 策略引擎判定(AGENTS §9),本列不参与任何身份判定, -- 也不改变任何一行的读/写权限。 -- -- 列值:0 = 非敏感(默认,含全部历史行)、1 = 敏感。闭集是 BOOL 语义,不是枚举。 -- -- 幂等性与形状:`ALTER TABLE ADD COLUMN` 在 SQLite 里没有 IF NOT EXISTS 形式,因此 -- 与 0019 / 0026 / 0027 同一手法 —— 依赖启动顺序「先 MigrateUp、后 NewStore」 -- (cmd/cyrene/wire.go):迁移先落地,随后自举前滚 -- (internal/runtime/memory/sensitive_schema.go 的 ensureSensitiveSchema)走到 -- "duplicate column name" 是预期路径。两份 DDL 必须**逐字同形** -- (INTEGER NOT NULL DEFAULT 0),由 internal/runtime/memory/sensitive_migration_test.go -- 钉住;列一律追加在末尾,尾序因此变成 `… retention_tier, temporal, sensitive`。 -- -- 本列**不建索引**:受限车道的过滤在内存里做(候选集本来就已过 status / 时效 / -- scope,规模在千级),而「按 sensitive 取样巡检」是人工低频查询。将来真出现按它 -- 筛的读路径,迁移与自举 DDL 两条真源一起加。 ALTER TABLE memory_items ADD COLUMN sensitive INTEGER NOT NULL DEFAULT 0; -- 回填历史数据(本迁移唯一的一次性数据动作)。 -- -- 与 0026 / 0027「刻意不回填」的差别(这里必须说清,否则下一次会有人照着抄错): -- 那两条不回填,是因为它们的「正确值」只能靠**再判一次**得到(0026 是留存规则、 -- 0027 是拿正文反推时间性)—— 把一次全表判定藏在迁移里,等于在启动路径上静默跑 -- 第二现场。本列的回填判据是**人工确认的精确短语白名单**(塔裁决 2026-10-30 批准): -- 结论已定、不需要模型、结果可用一条只读查询复核,因此回填就该与建列一起落地 —— -- 否则「列建好了、历史行还是敏感=0」这件事本身就是一个持续存在的泄漏面。 -- -- 白名单口径(判据与理由见 internal/runtime/memory/sensitive.go 的 privateFacts): -- `花溪` 住址(审计 §2.1:全库 user 消息只在主人私聊出现过 1 次) -- `叶酱` 主人私称(审计 §2.2) -- `早睡` 作息(审计 §7 P0-1 的回填面数据) -- 刻意**不**用泛类目关键词(「住」「称呼」「睡」「家人」「健康」):会把「住手」 -- 「喜欢睡懒觉的猫」这类习惯一起标掉,而受限车道少一条合法习惯是主人看不见、 -- 也不会去改回来的缺口 —— 缺口的可见性远低于泄漏,故宁窄勿宽。 -- -- 幂等:`sensitive = 0` 前置条件使重跑只补标、不撤标、不改已标行(连跑两次的第二次 -- 影响 0 行)。同口径的可重跑入口是 internal/runtime/memory 的 BackfillSensitive -- (dry-run + 报告,白名单扩充后用它补跑;本迁移只在 0028 首次应用时跑一次)。 -- -- 覆盖**全部** scope 与 status,不限于 `user`+`active`:merged / archived 的行会被 -- 「撤销软合并」拉回可见集(store_list.go 的 Get 刻意不过 status 闸),漏标它们等于 -- 留一条可被复活的泄漏面;persona 域虽然已经被 Loop 的受限过滤排除,标上也不改变 -- 行为(只让巡检口径一致)。不依赖「先去重」(审计 P2-2):标重复行没有额外代价, -- 漏标一条才有。 -- -- 不改 `updated_at`:本列是数据层标记,不是内容/活跃度事件;改它会扰动「最新在前」 -- 的列表顺序与衰减基准(decay 由 updated_at 参与排序)。 UPDATE memory_items SET sensitive = 1 WHERE sensitive = 0 AND (instr(content, '花溪') > 0 OR instr(content, '叶酱') > 0 OR instr(content, '早睡') > 0);