Files
Cyrene_v2/internal/runtime/loop/memory_isolation_test.go
T
AskaEth eeb5f26668
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(memory): 堵住 knowledge 工具绕过群聊/陌生人过滤的 P0 / owner 召回 hits 如实上报 / 静默吞点补来源日志
2026-10-05 11:30:51 +08:00

80 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package loop
import (
"context"
"strings"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
)
// TestMemoryRecallSpan_OwnerReportsRealHits 钉住审计 2026-10-05 §7 的
// 「owner 召回 hits 恒 0」:真实跑一轮后,memory 召回节点上报的命中条数
// 必须等于实际注入的记忆条数(3 条种子),而不是硬编码的 0。
func TestMemoryRecallSpan_OwnerReportsRealHits(t *testing.T) {
t.Parallel()
rec := newFlowRecorder()
ad := &mockAdapter{stream: []mgResp{{resp: model.Response{Model: "mock-1", Content: "好的"}}}}
td := dispatcherWith(t)
a := newTestAgent(t, ad, td)
store := newTestMemoryStore(t)
seedMemories(t, store)
l := New(Options{
Agent: a, Tools: td, MaxRounds: 8,
Tracer: trace.New(nil),
Flow: rec.publisher(),
Retriever: memory.NewRetriever(store),
PersonaID: "persona-a",
OnStream: func(model.StreamEvent) {},
})
if _, err := l.Run(context.Background(), "web", []model.Message{model.TextMessage(model.RoleUser, "go")}); err != nil {
t.Fatalf("Run: %v", err)
}
recall := nodesOfKind(rec.last(t, "web"), "memory")[0]
if got := recall.Props["scope"]; got != "owner" {
t.Fatalf("memory 召回 scope = %v,want owner(props:%+v)", got, recall.Props)
}
hits, ok := recall.Props["hits"].(int)
if !ok {
t.Fatalf("hits prop 类型 = %T,want int(props:%+v)", recall.Props["hits"], recall.Props)
}
if hits != 3 {
t.Fatalf("owner 召回 hits = %d,want 3(3 条种子全部命中)", hits)
}
}
// TestExtractMemoryAsync_NoStoreLogsSource 钉住审计 2026-10-05 §7 的静默吞点:
// 配置了 extractor 却没有 store 时,提取会被跳过 —— 但必须带来源记一条告警
// (source=runtime/loop),绝不静默吞掉。
func TestExtractMemoryAsync_NoStoreLogsSource(t *testing.T) {
t.Parallel()
log, buf := captureLoopLog()
l := New(Options{
Extractor: memory.NewManualExtractor("persona-a", "owner"),
PersonaID: "persona-a",
Log: log,
// Memory 故意不配置:提取结果无处可落。
})
ctx := WithSessionEnv(context.Background(), OwnerEnv())
l.extractMemoryAsync(ctx, []model.Message{model.TextMessage(model.RoleUser, "记住 我爱喝冰美式")}, 1)
found := false
for _, rec := range loopJSONRecords(t, buf) {
if rec["source"] != "runtime/loop" {
continue
}
if msg, _ := rec["msg"].(string); strings.Contains(msg, "no memory store configured") {
found = true
}
}
if !found {
t.Fatalf("缺带来源(source=runtime/loop)的「无 store 跳过提取」告警:%q", buf.String())
}
}