fix: tell the agent the receipt policy the books actually use

The server's instructions hardcoded '$25.00'; they now state the
configured threshold, and a zero threshold reads 'every entry needs at
least one receipt'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 17:30:35 -07:00
1 parent 005a96055a
commit 01d41c0daf
3 files changed
+69 -1

No files matched your search

+16
View File
@@ -101,11 +101,27 @@ impl ReceiptPolicy {
/// Human-readable policy summary for previews and errors. /// Human-readable policy summary for previews and errors.
pub fn describe(&self) -> String { pub fn describe(&self) -> String {
if self.threshold().is_zero() {
"receipts required on every entry".to_string()
} else {
format!( format!(
"receipts required when entry debits are strictly greater than {}", "receipts required when entry debits are strictly greater than {}",
self.threshold() self.threshold()
) )
} }
}
/// The receipt rule, phrased for the agent's operating instructions.
pub fn rule(&self) -> String {
if self.threshold().is_zero() {
"every entry needs at least one receipt".to_string()
} else {
format!(
"any entry moving more than ${} in debits needs at least one receipt",
self.threshold().round_dp(2)
)
}
}
/// Normalize a description for split detection. /// Normalize a description for split detection.
fn normalize_description(desc: &str) -> String { fn normalize_description(desc: &str) -> String {
+5 -1
View File
@@ -4051,7 +4051,11 @@ impl ServerHandler for BooksServer {
.enable_resources() .enable_resources()
.build(), .build(),
) )
.with_instructions(INSTRUCTIONS) // Tell the agent the policy these books actually use.
.with_instructions(INSTRUCTIONS.replace(
"any entry moving more than $25.00 in debits needs at least one receipt",
&self.policy.rule(),
))
.with_server_info(Implementation::new( .with_server_info(Implementation::new(
"wanderbooks", "wanderbooks",
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
+48
View File
@@ -1586,3 +1586,51 @@ fn mcp_vendor_1099_over_stdio() {
assert_eq!(v["blobs_verified"], 1); assert_eq!(v["blobs_verified"], 1);
assert!(v["events"].as_u64().unwrap() > 10); assert!(v["events"].as_u64().unwrap() > 10);
} }
#[test]
fn mcp_receipts_always_required_when_threshold_is_zero() {
let dir = test_dir("always-receipts");
let toml = dir.join("wanderbooks.toml");
std::fs::write(&toml, "[books]\nreceipt_threshold = \"0.00\"\n").unwrap();
let mut mcp = Mcp::spawn_with(
&dir.join("books-data"),
&[std::ffi::OsStr::new("--books-toml"), toml.as_os_str()],
);
let init = mcp.request(
"initialize",
json!({
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "smoke-test", "version": "0.1.0"},
}),
);
let instructions = init["result"]["instructions"].as_str().unwrap();
assert!(
instructions.contains("every entry needs at least one receipt"),
"the agent is told the real policy: {instructions}"
);
assert!(!instructions.contains("$25.00"));
mcp.notify("notifications/initialized");
for (code, name, typ) in [("1000", "Bank", "asset"), ("6000", "Fees", "expense")] {
mcp.call(
"books.add_account",
json!({"code": code, "name": name, "account_type": typ}),
);
}
let e = mcp.call(
"books.draft_entry",
json!({"date": "2026-10-01", "description": "Bank fee"}),
);
let id = e["entry_id"].as_u64().unwrap();
for (account, side) in [("6000", "debit"), ("1000", "credit")] {
mcp.call(
"books.add_line",
json!({"entry_id": id, "account": account, "side": side, "amount": "5.00"}),
);
}
let err = mcp.call_err(
"books.post_entry",
json!({"entry_id": id, "confirmed": true}),
);
assert!(err.contains("receipt"), "a $5 entry needs a receipt too: {err}");
}