test(loop): 钉住 FilterSharedVisible 真的看了 sensitive(review-m63 P1-1)
r1 复审的变异实验证明了一个静默回退缺口:把 memory_filter.go 那行改回 scope-only(等价于本片被 revert),`go test ./internal/runtime/loop/` 全绿, 而群聊/陌生人的候选池会重新放进主人的私事 —— P0-1 的唯一集成点没有回归覆盖。 新增 memory_filter_sensitive_test.go(两个层级): - TestFilterSharedVisible_敏感共享行被排除:直接调真实过滤函数,断言 sensitive=1 的 user / project 行被排除、非敏感 user/project 行存活、 persona 行与 L4 意图记录照旧被排除(不本地复现过滤); - TestInjectMemory_RestrictedEnv_排除敏感私事:走完整 assembleContext 注入路径 (真实 Store + Retriever + 过滤),query 直译群里那句「她住哪」;先断言 owner 车道读得到「花溪」(前提不空转),再断言受限车道读不到、且非敏感记忆与 「群聊隔离」标记仍在。 变异验证(实测,已还原):把那行改回 scope-only 后 TestFilterSharedVisible_敏感共享行被排除 与 TestInjectMemory_RestrictedEnv_排除敏感私事 双双变红,而 loop 包其余用例仍绿 —— 缺口正是由本文件补上的。
This commit is contained in:
1 parent
44fbd29836
commit
81a47720be
1 file changed
+134
@@ -0,0 +1,134 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// 本文件钉住 **P0-1 的唯一集成点**:`FilterSharedVisible` 真的看了 `sensitive`
|
||||
// (切片 M63、审计 2026-10-30 §7 P0-1;r1 复审 P1-1)。
|
||||
//
|
||||
// 为什么必须单独有这一条:memors 包的用例钉的是 `MemoryItem.SharedVisible()` 这个
|
||||
// **判据**,它管不到 loop 有没有调用它。r1 复审的变异实验证明了缺口的具体形态 ——
|
||||
// 把 `memory_filter.go` 里那行改回 `sharedScope(r.Item.Scope)`(等价于本片被
|
||||
// revert),`go test ./internal/runtime/loop/` **全绿**,而群聊/陌生人的候选池会
|
||||
// 重新放进主人的私事。因此这里的用例必须走**真实函数/真实注入路径**,
|
||||
// 不许在本地复现过滤(memory 包那份 restrictedVisible 只作数据层判据用)。
|
||||
//
|
||||
// 两条用例分别守两层:
|
||||
// 1. TestFilterSharedVisible_敏感共享行被排除 —— 直接调真实 FilterSharedVisible;
|
||||
// 2. TestInjectMemory_RestrictedEnv_排除敏感私事 —— 走 assembleContext 的完整注入
|
||||
// 路径(真实 Store + 真实 Retriever + 真实过滤),并把「群里问『她住哪』」写成
|
||||
// query,同时断言 owner 车道照常读得到(本片红线:sensitive 不是权限系统)。
|
||||
|
||||
// resultIDs 返回命中集的 id 列表(顺序保留),供集合断言用。
|
||||
func resultIDs(results []memory.Result) []string {
|
||||
ids := make([]string, 0, len(results))
|
||||
for _, r := range results {
|
||||
ids = append(ids, r.Item.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// TestFilterSharedVisible_敏感共享行被排除 直接调真实过滤函数,钉住三条判据:
|
||||
// 作用域(persona 不在共享域)、敏感度(sensitive=1 一律排除)、意图记录(L4 原话)。
|
||||
func TestFilterSharedVisible_敏感共享行被排除(t *testing.T) {
|
||||
t.Parallel()
|
||||
results := []memory.Result{
|
||||
{Item: memory.MemoryItem{ID: "private-user", Scope: memory.ScopeUser, Sensitive: true,
|
||||
Content: "用户居住在花溪(贵阳花溪区),不在上海工作"}},
|
||||
{Item: memory.MemoryItem{ID: "habit-user", Scope: memory.ScopeUser,
|
||||
Content: "用户喜欢冰美式"}},
|
||||
{Item: memory.MemoryItem{ID: "plain-project", Scope: memory.ScopeProject,
|
||||
Content: "项目记忆:Cyrene 用 Go 编写"}},
|
||||
{Item: memory.MemoryItem{ID: "private-project", Scope: memory.ScopeProject, Sensitive: true,
|
||||
Content: "称呼约定:主人只叫「叶酱」"}},
|
||||
{Item: memory.MemoryItem{ID: "persona-daily", Scope: memory.ScopePersona,
|
||||
Content: "角色日常:周末喜欢爬山"}},
|
||||
{Item: memory.MemoryItem{ID: "intent-record", Scope: memory.ScopeUser,
|
||||
Content: "intent:isTask=true;taskKind=query;confidence=0.9;text=帮我看看这个"}},
|
||||
}
|
||||
|
||||
visible := FilterSharedVisible(results)
|
||||
got := resultIDs(visible)
|
||||
want := []string{"habit-user", "plain-project"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("FilterSharedVisible 保留 %v, want %v(敏感/角色日常/意图记录都必须被排除)", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("FilterSharedVisible 保留 %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seedSensitiveMemories 写入一条主人私事(会经 memory 包的白名单兜底标成
|
||||
// sensitive=1)、一条用户习惯、一条项目记忆。
|
||||
func seedSensitiveMemories(t *testing.T, store *memory.Store) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
seeds := []memory.MemoryItem{
|
||||
{PersonaID: "persona-a", Content: "用户居住在花溪(贵阳花溪区),不在上海工作",
|
||||
Scope: memory.ScopeUser, Kind: memory.KindWorldEvent},
|
||||
{PersonaID: "persona-a", Content: "用户习惯:喜欢冰美式",
|
||||
Scope: memory.ScopeUser, Kind: memory.KindRelationship},
|
||||
{PersonaID: "persona-a", Content: "项目记忆:Cyrene 用 Go 编写",
|
||||
Scope: memory.ScopeProject, Kind: memory.KindWorldEvent},
|
||||
}
|
||||
for _, it := range seeds {
|
||||
stored, err := store.Add(ctx, it)
|
||||
if err != nil {
|
||||
t.Fatalf("seed memory: %v", err)
|
||||
}
|
||||
if it.Content == seeds[0].Content && !stored.Sensitive {
|
||||
t.Fatalf("私事种子没有被标成 sensitive —— 用例前提不成立(白名单兜底没生效)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestInjectMemory_RestrictedEnv_排除敏感私事 是集成级对抗用例:query 直译群里那句
|
||||
// 「她住哪」,走完整的 assembleContext 注入路径。
|
||||
//
|
||||
// 断言顺序是刻意的:先证明 owner 车道**读得到**花溪(否则「受限车道读不到」可能只是
|
||||
// 相关度不够,用例空转),再断言受限车道读不到。
|
||||
//
|
||||
// 这条用例正是 r1 复审变异 A 的杀手:把那行改回 sharedScope(r.Item.Scope) 之后,
|
||||
// 受限块会重新出现「花溪」,本用例变红。
|
||||
func TestInjectMemory_RestrictedEnv_排除敏感私事(t *testing.T) {
|
||||
t.Parallel()
|
||||
store := newTestMemoryStore(t)
|
||||
seedSensitiveMemories(t, store)
|
||||
l := New(Options{Retriever: memory.NewRetriever(store), PersonaID: "persona-a"})
|
||||
|
||||
msgs := []model.Message{model.TextMessage(model.RoleUser, "她住在哪里?是上海还是花溪?")}
|
||||
|
||||
ownerView := l.assembleContext(WithSessionEnv(context.Background(), OwnerEnv()), msgs, 1)
|
||||
if len(ownerView) < 1 {
|
||||
t.Fatal("owner 车道没有注入记忆块,用例前提不成立")
|
||||
}
|
||||
ownerBlock := messageText(ownerView[0])
|
||||
if !strings.Contains(ownerBlock, "花溪") {
|
||||
t.Fatalf("owner 车道读不到这条私事(前提不成立,后续断言会空转):\n%s", ownerBlock)
|
||||
}
|
||||
|
||||
restrictedCtx := WithSessionEnv(context.Background(),
|
||||
SessionEnv{ActorEnv: restrict.ActorGroupMember, MemWritable: false})
|
||||
restrictedView := l.assembleContext(restrictedCtx, msgs, 1)
|
||||
if len(restrictedView) < 1 {
|
||||
t.Fatal("受限车道应当仍注入非敏感记忆块")
|
||||
}
|
||||
restrictedBlock := messageText(restrictedView[0])
|
||||
if strings.Contains(restrictedBlock, "花溪") {
|
||||
t.Fatalf("受限车道拿到了主人的住址 —— FilterSharedVisible 没有看 sensitive:\n%s", restrictedBlock)
|
||||
}
|
||||
if !strings.Contains(restrictedBlock, "群聊隔离") ||
|
||||
!strings.Contains(restrictedBlock, "喜欢冰美式") ||
|
||||
!strings.Contains(restrictedBlock, "Cyrene 用 Go 编写") {
|
||||
t.Fatalf("受限车道丢了非敏感记忆或隔离标记(用户习惯可查的裁决被推翻):\n%s", restrictedBlock)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user