From 880278ea0eb34eb1c0d370b7a542f60288e625d0 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 16:53:56 -0600 Subject: [PATCH] fix(mcp): a close still offers books.verify; the test harness says why a server didn't start GitHub's check on 794807ab: P8's fit-up replaced the step after `books.close_period` (`books.verify`) with the audit and the attestation, and P5's month_end_close eval, which follows only what the books offer, lost the step it follows. The close now offers the chain check first, then the audit the attestation rests on. The portable macOS job failed every over-the-wire test with "the server didn't say ready" and nothing else: the harness sent the server's stderr to a file in the temp home and never showed it. It now panics with that log, so the next run says what macOS refused. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- crates/ledger-cli/tests/common/mod.rs | 8 +++++++- crates/ledger-mcp/src/next_steps.rs | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/ledger-cli/tests/common/mod.rs b/crates/ledger-cli/tests/common/mod.rs index 3fb84a1..027eb9d 100644 --- a/crates/ledger-cli/tests/common/mod.rs +++ b/crates/ledger-cli/tests/common/mod.rs @@ -209,7 +209,13 @@ impl Server { BufReader::new(child.stdout.take().unwrap()) .read_line(&mut line) .unwrap(); - assert_eq!(line, "ready\n", "the server didn't say ready"); + if line != "ready\n" { + // Say why (the portable jobs run this on macOS and Windows, + // where the only evidence is what the server wrote). + let said = + std::fs::read_to_string(home.dir.path().join("server.log")).unwrap_or_default(); + panic!("the server didn't say ready (got {line:?}); its log:\n{said}"); + } Self { child } } diff --git a/crates/ledger-mcp/src/next_steps.rs b/crates/ledger-mcp/src/next_steps.rs index e77e146..59aaa64 100644 --- a/crates/ledger-mcp/src/next_steps.rs +++ b/crates/ledger-mcp/src/next_steps.rs @@ -309,6 +309,15 @@ pub(crate) fn tool_steps( .get("period_id") .or_else(|| args.and_then(|a| a.get("period_id"))) .and_then(Value::as_u64); + // The chain first (cheap, and what every close has always + // offered: the month_end_close eval follows it), then the + // audit the attestation rests on (P8). + steps.push(Step::tool( + "books.verify", + None, + "Verify the journal's chain and the projection after the close.", + false, + )); steps.push(Step::tool( "books.audit", Some(json!({ "scope": "month" })),