package memory import ( "context" "testing" ) // 本文件钉住「敏感标记」这根轴(迁移 0028,切片 M63)在**数据层**的四条: // // 1. 白名单判据的方向性:已实证私事命中、习惯类不命中(审计 §2 的实测短语); // 2. SharedVisible 是作用域与敏感度的合取(受限车道唯一判据); // 3. 写入侧兜底:白名单命中的内容经 Store.Add 落库后 sensitive=1,且**只加不撤**; // 4. 15 列读回路:sensitive 在四条列表读路径(Get / ListByPersona / ListScope / // List / ListProject)上都读得回来 —— 少同步一处 SELECT,这里就红。 // // 删掉实现(sensitive 列、resolveSensitive、scanItem 的第 15 列、SharedVisible 的 // 判据)会分别让 1/2/3/4 变红;这正是本文件存在的意义。 // TestIsPrivateContent_判据方向 钉住白名单的两个方向:主人的私事命中,习惯与 // 普通词不命中。反向用例(「住手」「喜欢睡懒觉的猫」)是**刻意**的:泛关键词 // (住 / 睡)会把它们一起标掉,那正是本片拒绝的误标方向(sensitive.go 理由 2)。 func TestIsPrivateContent_判据方向(t *testing.T) { t.Parallel() for _, content := range []string{ "用户不在上海上班,目前所在城市为花溪(贵阳花溪区一带)", "主人希望被称呼为「叶酱」", "主人有上班日程,工作日需要早起,助手提醒早睡", } { if !IsPrivateContent(content) { t.Fatalf("IsPrivateContent(%q) = false, want true(已实证私事必须命中)", content) } } for _, content := range []string{ "用户喜欢冰美式", "住手!不要再改了", "用户喜欢睡懒觉的猫,周末会看猫的视频", "Cyrene 用 Go 编写,项目编号规则是 CY-日期-序号", } { if IsPrivateContent(content) { t.Fatalf("IsPrivateContent(%q) = true, want false(习惯与普通词不许误标)", content) } } } // TestSharedVisible_作用域与敏感度合取 钉住受限车道唯一判据的矩阵: // 只有「共享域 且 未标敏感」才可注入;persona 域无论标没标都不可注入 // (它本来就是主人私有的角色日常,design/03 §3/§5)。 func TestSharedVisible_作用域与敏感度合取(t *testing.T) { t.Parallel() cases := []struct { name string item MemoryItem want bool }{ {"user+非敏感", MemoryItem{Scope: ScopeUser}, true}, {"project+非敏感", MemoryItem{Scope: ScopeProject}, true}, {"user+敏感", MemoryItem{Scope: ScopeUser, Sensitive: true}, false}, {"project+敏感", MemoryItem{Scope: ScopeProject, Sensitive: true}, false}, {"persona+非敏感", MemoryItem{Scope: ScopePersona}, false}, {"persona+敏感", MemoryItem{Scope: ScopePersona, Sensitive: true}, false}, } for _, c := range cases { if got := c.item.SharedVisible(); got != c.want { t.Fatalf("%s: SharedVisible() = %v, want %v", c.name, got, c.want) } } } // TestAdd_私事按白名单补标 钉住写入侧兜底:模型(或人工调用方)漏标时,白名单 // 命中的内容落库后必须 sensitive=1;而 habit 类内容必须保持 0(否则冻结的 // 「用户习惯可查」裁决会被这次改动悄悄推翻)。 func TestAdd_私事按白名单补标(t *testing.T) { t.Parallel() s := newTestStore(t) private := baseItem() private.Scope = ScopeUser private.Content = "用户居住在花溪(贵阳花溪区),不在上海工作" storedPrivate := addItem(t, s, private) if !storedPrivate.Sensitive { t.Fatal("白名单命中的条目落库后 Sensitive = false, want true(写入侧兜底没生效)") } habit := baseItem() habit.Scope = ScopeUser habit.Content = "用户喜欢冰美式" storedHabit := addItem(t, s, habit) if storedHabit.Sensitive { t.Fatal("习惯类条目被误标成敏感 —— 受限车道会平白丢掉合法习惯") } // 只加不撤:调用方显式标 true 的习惯类内容必须保持 true(撤标是人工动作)。 explicit := baseItem() explicit.Scope = ScopeUser explicit.Content = "用户喜欢手冲咖啡" explicit.Sensitive = true if got := addItem(t, s, explicit); !got.Sensitive { t.Fatal("显式 sensitive=true 被改写成 false —— 写入路径不许撤标") } } // TestSensitive_十五条读路径回读 钉住「15 列读回路」:sensitive 必须在每一条 // 走 scanItem 的读路径上都读得回来。任一处 SELECT 少一列,这里 first 就会扫错列 // (报错或把别的列当成 sensitive),不会有静默通过的余地。 func TestSensitive_十五条读路径回读(t *testing.T) { t.Parallel() ctx := context.Background() s := newTestStore(t) personaPrivate := baseItem() personaPrivate.Content = "主人只愿在私下被叫作「叶酱」" stored := addItem(t, s, personaPrivate) userPrivate := baseItem() userPrivate.Scope = ScopeUser userPrivate.Content = "主人现居贵阳花溪区,不在上海" userStored := addItem(t, s, userPrivate) projectPlain := baseItem() projectPlain.Scope = ScopeProject projectPlain.ProjectID = "proj-1" projectPlain.Content = "Cyrene 用 Go 编写" projectStored := addItem(t, s, projectPlain) byID, err := s.Get(ctx, stored.ID) if err != nil { t.Fatalf("Get: %v", err) } if !byID.Sensitive { t.Fatal("Get 读回的 sensitive = false, want true(Get 的 SELECT 少一列?)") } byPersona, err := s.ListByPersona(ctx, personaPrivate.PersonaID) if err != nil { t.Fatalf("ListByPersona: %v", err) } if !containsSensitive(byPersona, stored.ID) { t.Fatal("ListByPersona 读回的条目丢掉了 sensitive 位") } byScope, err := s.ListScope(ctx, ScopeUser, personaPrivate.PersonaID) if err != nil { t.Fatalf("ListScope: %v", err) } if !containsSensitive(byScope, userStored.ID) { t.Fatal("ListScope 读回的条目丢掉了 sensitive 位") } byProject, err := s.ListProject(ctx, "proj-1") if err != nil { t.Fatalf("ListProject: %v", err) } if containsSensitive(byProject, projectStored.ID) { t.Fatal("ListProject 把非敏感的条目读成了敏感") } all, err := s.List(ctx) if err != nil { t.Fatalf("List: %v", err) } if !containsSensitive(all, stored.ID) || containsSensitive(all, projectStored.ID) { t.Fatal("List 读回的 sensitive 位与落库值不一致") } } // containsSensitive 报告 items 中 id 那条是否为 sensitive(找不到即失败)。 func containsSensitive(items []MemoryItem, id string) bool { for _, it := range items { if it.ID == id { return it.Sensitive } } return false }