feat(memory): 记忆 P2(项目索引物化 / decay 回写 / 作用域写规则 / 静默吞点 + loop 级只读红线测试)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 11:47:39 +08:00
1 parent 9b903a89bc
commit 509dc412eb
12 files changed
+660 -55

No files matched your search

+37
View File
@@ -11,6 +11,7 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory/projectindex"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/tool"
)
@@ -251,6 +252,42 @@ func TestKnowledge_RestrictedEnv_OnlySharedVisible(t *testing.T) {
}
}
// TestKnowledge_SurfacesLinkedProject 钉住审计 2026-10-05 §4:knowledge 工具此前
// 丢掉了 Result.Project,模型因此够不着「项目索引 → 项目描述」层级。修复后项目
// 作用域命中必须带出链接的项目条目(id/title/desc),非项目命中则不带该键。
func TestKnowledge_SurfacesLinkedProject(t *testing.T) {
t.Parallel()
sr := &stubRetriever{results: []memory.Result{
{
Item: memory.MemoryItem{ID: "m1", Content: "项目记忆:Cyrene 用 Go 编写", Kind: memory.KindWorldEvent, Scope: memory.ScopeProject},
Score: 0.7,
Project: &projectindex.Project{ID: "proj.1", Title: "Cyrene", Desc: "陪伴型 AI 助手"},
},
{
Item: memory.MemoryItem{ID: "m2", Content: "用户习惯:喜欢冰美式", Kind: memory.KindRelationship, Scope: memory.ScopeUser},
Score: 0.4,
},
}}
kt := knowledgeToolForTest(t, WithKnowledgeRetriever(sr))
ctx := loop.WithSessionEnv(tool.WithTurnPersona(context.Background(), "persona-a"), loop.OwnerEnv())
got, err := kt.Execute(ctx, map[string]any{"query": "Cyrene"})
if err != nil {
t.Fatalf("Execute: %v", err)
}
results := got.(map[string]any)["results"].([]map[string]any)
proj, ok := results[0]["project"].(map[string]any)
if !ok {
t.Fatalf("项目作用域命中未带出链接项目:%+v", results[0])
}
if proj["id"] != "proj.1" || proj["title"] != "Cyrene" || proj["desc"] != "陪伴型 AI 助手" {
t.Fatalf("project = %+v, want proj.1/Cyrene/陪伴型 AI 助手", proj)
}
if _, present := results[1]["project"]; present {
t.Fatalf("非项目命中不该带 project 键:%+v", results[1])
}
}
// TestKnowledge_OwnerEnv_AllScopes 钉住反向边界:owner(全权限)环境不做隔离过滤,
// 四类命中都照常返回 —— 修复只收窄群聊/陌生人,绝不误伤主人自己的完整读取。
func TestKnowledge_OwnerEnv_AllScopes(t *testing.T) {