From f80842640c98ceb9202ff07899b0dbdbd4bea812 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Fri, 2 Oct 2026 20:11:01 -0700 Subject: [PATCH] review: a secret session's recall and ask_memory still query Honcho Spec 13 requires that a secret session's query is sent unless the query itself holds a known-prefix secret. serve.rs already ignores MemoryCall taint on both read tools; the serve tests always sent Private. A skip of Honcho for Secret would not have failed. Each suite now has one call at class secret that asserts the fake received the search or chat. Co-Authored-By: Grok 4.6 --- crates/memoryd/tests/honcho_ask_serve.rs | 38 ++++++++++++++++++++- crates/memoryd/tests/honcho_recall_serve.rs | 33 +++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/crates/memoryd/tests/honcho_ask_serve.rs b/crates/memoryd/tests/honcho_ask_serve.rs index a2995d4..f9b4509 100644 --- a/crates/memoryd/tests/honcho_ask_serve.rs +++ b/crates/memoryd/tests/honcho_ask_serve.rs @@ -60,6 +60,16 @@ impl Home { /// One call on `brokerd`'s socket and its answer, within 20 s; how long it took. fn call(socket: &Path, tool: &str, arguments: &str) -> (ToolResponse, Duration) { + call_at(socket, tool, arguments, DataClass::Private) +} + +/// `call`, with the session's class on the `MemoryCall`. +fn call_at( + socket: &Path, + tool: &str, + arguments: &str, + taint: DataClass, +) -> (ToolResponse, Duration) { let started = Instant::now(); let mut stream = UnixStream::connect(socket).unwrap(); stream @@ -76,7 +86,7 @@ fn call(socket: &Path, tool: &str, arguments: &str) -> (ToolResponse, Duration) tool: tool.to_string(), arguments: arguments.to_string(), }, - taint: DataClass::Private, + taint, untrusted: false, }), }; @@ -230,3 +240,29 @@ fn an_unknown_tool_is_refused() { "memoryd runs only recall, memory and ask_memory" ); } + +/// Spec 13: a `secret` session's question still reaches Honcho; only a question holding a secret +/// is refused. +#[test] +fn a_secret_session_still_asks_honcho() { + let fake = FakeHoncho::start(); + let home = Home::serve("ask-serve-secret", Some(&fake.url)); + let (content, class, untrusted) = result( + call_at( + &home.config.broker_socket(), + "ask_memory", + QUESTION, + DataClass::Secret, + ) + .0, + ); + assert!( + fake.requests().iter().any(|r| r.path.ends_with("/chat")), + "Honcho was not asked: {content}" + ); + assert!( + content.starts_with("[1] memory · own · private\n Based on what I found in memory, "), + "{content}" + ); + assert_eq!((class, untrusted), (DataClass::Private, false)); +} diff --git a/crates/memoryd/tests/honcho_recall_serve.rs b/crates/memoryd/tests/honcho_recall_serve.rs index b28bc25..000c016 100644 --- a/crates/memoryd/tests/honcho_recall_serve.rs +++ b/crates/memoryd/tests/honcho_recall_serve.rs @@ -71,6 +71,16 @@ impl Home { /// One call on `brokerd`'s socket and its answer, within 20 s; how long it took. fn call(socket: &Path, tool: &str, arguments: &str) -> (ToolResponse, Duration) { + call_at(socket, tool, arguments, DataClass::Private) +} + +/// `call`, with the session's class on the `MemoryCall`. +fn call_at( + socket: &Path, + tool: &str, + arguments: &str, + taint: DataClass, +) -> (ToolResponse, Duration) { let started = Instant::now(); let mut stream = UnixStream::connect(socket).unwrap(); stream @@ -87,7 +97,7 @@ fn call(socket: &Path, tool: &str, arguments: &str) -> (ToolResponse, Duration) tool: tool.to_string(), arguments: arguments.to_string(), }, - taint: DataClass::Private, + taint, untrusted: false, }), }; @@ -239,3 +249,24 @@ fn without_honcho_recall_answers_as_before() { lines.lock().unwrap() ); } + +/// Spec 13: a `secret` session's query still reaches Honcho; only a query holding a secret is +/// refused. +#[test] +fn a_secret_session_still_asks_honcho() { + let fake = FakeHoncho::start(); + let home = Home::new("recall-serve-secret", Some(&fake.url)); + let _lines = home.serve(); + let (content, class, untrusted) = result( + call_at( + &home.config.broker_socket(), + "recall", + r#"{"query":"tea","k":10}"#, + DataClass::Secret, + ) + .0, + ); + assert!(!reads(&fake).is_empty(), "Honcho was not asked: {content}"); + assert!(content.contains("tea"), "{content}"); + assert_eq!((class, untrusted), (DataClass::Private, true)); +}