192 lines
5.4 KiB
Rust
192 lines
5.4 KiB
Rust
//! Tests that `ToolSummary` is indexed as kind `summary` and found by `recall`. Do not edit.
|
|
|
|
#[path = "support/logs.rs"]
|
|
mod logs;
|
|
#[path = "support/tmp.rs"]
|
|
mod tmp;
|
|
|
|
use bxcore::{CallId, DataClass, LogRecord, RecallArgs, Timestamp};
|
|
use logs::{assistant, result, user, write_log};
|
|
use memoryd::files::Store;
|
|
use memoryd::index::Index;
|
|
use memoryd::recall::recall;
|
|
use tmp::TempDir;
|
|
|
|
fn now() -> Timestamp {
|
|
Timestamp::parse("2026-09-24T21:00:00.000Z").unwrap()
|
|
}
|
|
|
|
fn summary(minute: u32, content: &str, untrusted: bool) -> LogRecord {
|
|
LogRecord::ToolSummary {
|
|
time: logs::at(minute),
|
|
call: CallId(1),
|
|
tool_call_id: "c0".to_string(),
|
|
content: content.to_string(),
|
|
class: DataClass::Public,
|
|
untrusted,
|
|
}
|
|
}
|
|
|
|
struct Setup {
|
|
dir: TempDir,
|
|
store: Store,
|
|
index: Index,
|
|
}
|
|
|
|
fn setup(tag: &str) -> Setup {
|
|
let dir = TempDir::new(tag);
|
|
let starter = dir.write("etc/soul.md", "Soul.\n");
|
|
let store = Store::new(&dir.path().join("memory"));
|
|
store.init(&starter).unwrap();
|
|
let index = Index::open(&dir.path().join("index/memory.sqlite3"), DataClass::Private).unwrap();
|
|
Setup { dir, store, index }
|
|
}
|
|
|
|
fn ask(s: &Setup, query: &str) -> memoryd::recall::Recalled {
|
|
let args = RecallArgs {
|
|
query: query.to_string(),
|
|
k: 10,
|
|
};
|
|
recall(&s.index, &s.store, &args, "mm-now", now()).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn a_tool_summary_is_found_as_kind_summary() {
|
|
let s = setup("summary-found");
|
|
write_log(
|
|
s.dir.path(),
|
|
"mm-a",
|
|
&[
|
|
user(2, "fetch the launch page"),
|
|
assistant(
|
|
3,
|
|
None,
|
|
&[("http_fetch", r#"{"url":"https://example.org"}"#)],
|
|
),
|
|
result(4, "raw html body about tea", DataClass::Public, false),
|
|
summary(5, "the page is about tea and the launch window", false),
|
|
],
|
|
);
|
|
s.index
|
|
.catch_up(&s.dir.path().join("sessions"), &s.store.path("notes.md"))
|
|
.unwrap();
|
|
let got = ask(&s, "tea");
|
|
assert!(
|
|
got.text
|
|
.contains("the page is about tea and the launch window"),
|
|
"{got:?}"
|
|
);
|
|
assert!(
|
|
got.text.contains("memory · session mm-a"),
|
|
"tagged as a session hit: {}",
|
|
got.text
|
|
);
|
|
assert!(!got.untrusted);
|
|
}
|
|
|
|
#[test]
|
|
fn the_raw_tool_result_is_still_not_indexed() {
|
|
let s = setup("summary-no-raw");
|
|
write_log(
|
|
s.dir.path(),
|
|
"mm-a",
|
|
&[
|
|
user(2, "fetch"),
|
|
assistant(3, None, &[("http_fetch", "{}")]),
|
|
result(4, "UNIQUE_RAW_BODY_XYZ", DataClass::Public, false),
|
|
summary(5, "a short account of the page", false),
|
|
],
|
|
);
|
|
s.index
|
|
.catch_up(&s.dir.path().join("sessions"), &s.store.path("notes.md"))
|
|
.unwrap();
|
|
let got = ask(&s, "UNIQUE_RAW_BODY_XYZ");
|
|
assert!(
|
|
got.text.contains("(no matches"),
|
|
"raw tool result stays unindexed: {}",
|
|
got.text
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn an_untrusted_summary_taints_the_recall() {
|
|
let s = setup("summary-taint");
|
|
write_log(
|
|
s.dir.path(),
|
|
"mm-a",
|
|
&[
|
|
user(2, "fetch"),
|
|
assistant(3, None, &[("http_fetch", "{}")]),
|
|
result(4, "page", DataClass::Public, true),
|
|
summary(5, "the fetched page mentions the launch window", true),
|
|
],
|
|
);
|
|
s.index
|
|
.catch_up(&s.dir.path().join("sessions"), &s.store.path("notes.md"))
|
|
.unwrap();
|
|
let got = ask(&s, "launch");
|
|
assert!(got.untrusted, "{got:?}");
|
|
assert!(got.text.contains("memory-tainted"), "{}", got.text);
|
|
}
|
|
|
|
#[test]
|
|
fn a_trusted_summary_does_not_taint() {
|
|
let s = setup("summary-trusted");
|
|
write_log(
|
|
s.dir.path(),
|
|
"mm-a",
|
|
&[
|
|
user(2, "fetch"),
|
|
assistant(3, None, &[("http_fetch", "{}")]),
|
|
result(4, "page", DataClass::Public, false),
|
|
summary(5, "the fetched page mentions the launch window", false),
|
|
],
|
|
);
|
|
s.index
|
|
.catch_up(&s.dir.path().join("sessions"), &s.store.path("notes.md"))
|
|
.unwrap();
|
|
let got = ask(&s, "launch");
|
|
assert!(!got.untrusted, "{got:?}");
|
|
assert!(got.text.contains("memory · session mm-a"), "{}", got.text);
|
|
assert!(!got.text.contains("memory-tainted"), "{}", got.text);
|
|
}
|
|
|
|
#[test]
|
|
fn a_secret_summary_is_skipped_when_the_index_is_private() {
|
|
let s = setup("summary-secret");
|
|
write_log(
|
|
s.dir.path(),
|
|
"mm-a",
|
|
&[
|
|
user(2, "fetch"),
|
|
assistant(3, None, &[("http_fetch", "{}")]),
|
|
LogRecord::ToolResult {
|
|
time: logs::at(4),
|
|
call: CallId(1),
|
|
tool_call_id: "c0".to_string(),
|
|
content: "secret page".to_string(),
|
|
class: DataClass::Secret,
|
|
untrusted: false,
|
|
truncated: false,
|
|
},
|
|
LogRecord::ToolSummary {
|
|
time: logs::at(5),
|
|
call: CallId(1),
|
|
tool_call_id: "c0".to_string(),
|
|
content: "SECRET_SUMMARY_PHRASE".to_string(),
|
|
class: DataClass::Secret,
|
|
untrusted: false,
|
|
},
|
|
],
|
|
);
|
|
s.index
|
|
.catch_up(&s.dir.path().join("sessions"), &s.store.path("notes.md"))
|
|
.unwrap();
|
|
let got = ask(&s, "SECRET_SUMMARY_PHRASE");
|
|
assert!(
|
|
got.text.contains("(no matches"),
|
|
"secret summary above max_class: {}",
|
|
got.text
|
|
);
|
|
}
|