P0: pin dev tools, honest transports doc, tools-only template

mise.toml: pin cargo-mutants 27.1.0 and cargo-fuzz 0.13.2 (the latest
published, and what is installed) instead of "latest", so mutation and
fuzz runs repeat (plan/served.md I-13, P0).

docs/mcp-interface.md: the spec had two Transports sections, and the
first claimed bearer-token auth on Streamable HTTP. There is none: the
server uses rmcp's default config, whose only guard is a Host check for
localhost, 127.0.0.1 and ::1, with no token and no Origin check. One
section now says so plainly and points to plan/served.md P2 for auth.

ledger-cli AGENTS.md template: "the journal is the evidence - read it,
don't guess" led an agent to cat the journal file instead of asking the
books. The template now says to trace a figure through books.get_entry,
books.search_entries, or books.export (journal or general_ledger), and a
new bullet says the books are reached only through the wanderbooks MCP
tools: never open, list or parse the books' files, not even to look; if
a tool can't answer, say so. The other lines that sent the agent to the
data dir go too: onboarding is triggered by an empty books.chart instead
of a missing journal.jsonl, the CPA paragraph drops the ledger-gui
--data-dir books-data command, and the backup note no longer names the
journal's path. Test agents_md_reaches_the_books_only_through_tools
pins this; agents_md_carries_the_contract no longer expects journal.jsonl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-16 18:18:05 -06:00
1 parent a870e11f50
commit 4f8047689f
4 files changed
+55 -24

No files matched your search

+15 -9
View File
@@ -126,12 +126,17 @@ a second. Everything else they see, you show them.
requires a receipt — the server enforces this, don't argue with it. One requires a receipt — the server enforces this, don't argue with it. One
file per receipt: PDF preferred, an image is fine. file per receipt: PDF preferred, an image is fine.
- **Show the numbers.** After every mutation, show the affected balances. - **Show the numbers.** After every mutation, show the affected balances.
When the human questions a figure, trace it to journal entries. When the human questions a figure, trace it to journal entries with the
tools (`books.get_entry`, `books.search_entries`).
- **Only through the tools.** The books are reached only through the
wanderbooks MCP tools. Never open, list or parse the books' files on
disk (journal, blobs, database, vault), not even to look. If a tool
can't answer, say so.
## First run: onboarding ## First run: onboarding
If `books-data/journal.jsonl` is missing or empty, onboard before recording If the books are empty (`books.chart` lists no accounts), onboard before
anything. Keep it conversational; ask a few things at a time. recording anything. Keep it conversational; ask a few things at a time.
1. Ask: company legal name, entity type (sole proprietorship or 1. Ask: company legal name, entity type (sole proprietorship or
single-member LLC, partnership or multi-member LLC, S corp, C corp), single-member LLC, partnership or multi-member LLC, S corp, C corp),
@@ -200,8 +205,8 @@ Bank feeds aren't automatic here: import the bank's CSV export
`books.verify` is clean. `books.verify` is clean.
- A CPA who wants to look around can get a read-only copy of this - A CPA who wants to look around can get a read-only copy of this
directory (it's a git repository: push it to a private remote and give directory (it's a git repository: push it to a private remote and give
them read access) and open it with `wanderbooks gui`, or `ledger-gui them read access) and open it with `wanderbooks gui`. Nothing they do
--data-dir books-data`. Nothing they do there can change the books. there can change the books.
## Session start ## Session start
@@ -222,14 +227,15 @@ human tells you their preference, write it there.
search). search).
- `books.digest` is the review surface — "what did you do with my books - `books.digest` is the review surface — "what did you do with my books
lately." Offer it; don't make the human ask. lately." Offer it; don't make the human ask.
- Reports are pure functions of the journal. If a number looks wrong, the - Reports are pure functions of the journal. If a number looks wrong,
journal is the evidence — read it, don't guess. trace it through the tools: `books.get_entry`, `books.search_entries`,
or `books.export` with report `journal` or `general_ledger`. Don't guess.
- Integrity: `books.verify` checks the journal's hash chain, the database - Integrity: `books.verify` checks the journal's hash chain, the database
against the journal (rebuilding it if it drifted), and every stored against the journal (rebuilding it if it drifted), and every stored
file. Run it when a number looks wrong or the human asks. file. Run it when a number looks wrong or the human asks.
- Back up: `just backup` after every session that posted entries. The git - Back up: `just backup` after every session that posted entries. The git
history of this directory is the archive; `books-data/journal.jsonl` is history of this directory is the archive; the journal is the system of
the system of record and the SQLite database is disposable. record and the database is disposable.
"#; "#;
const CLAUDE_MD: &str = "@AGENTS.md\n"; const CLAUDE_MD: &str = "@AGENTS.md\n";
+26 -1
View File
@@ -144,7 +144,6 @@ fn agents_md_carries_the_contract() {
"Posted is forever", "Posted is forever",
"reversing entries", "reversing entries",
"receipt", "receipt",
"journal.jsonl",
"onboard", "onboard",
"chart_template", "chart_template",
"to the cent", "to the cent",
@@ -156,6 +155,32 @@ fn agents_md_carries_the_contract() {
} }
} }
/// The agent reaches the books only through the MCP tools. The scaffolded
/// AGENTS.md once said "the journal is the evidence — read it", and an
/// agent `cat`ed the journal file instead of asking the books.
#[test]
fn agents_md_reaches_the_books_only_through_tools() {
let tmp = tempfile::tempdir().unwrap();
let books = tmp.path().join("b");
assert!(new(tmp.path(), &books, &[]).0);
let md = fs::read_to_string(books.join("AGENTS.md")).unwrap();
for phrase in [
"only through the",
"wanderbooks MCP tools",
"Never open, list or parse",
"books.get_entry",
"books.search_entries",
"general_ledger",
"can't answer, say so.",
] {
assert!(md.contains(phrase), "AGENTS.md says {phrase:?}");
}
// Nothing points the agent at the books' files.
for path in ["books-data", "journal.jsonl", "wanderbooks.db", "read it"] {
assert!(!md.contains(path), "AGENTS.md never mentions {path:?}");
}
}
#[test] #[test]
fn rerun_refused_without_force_and_never_clobbers_the_profile() { fn rerun_refused_without_force_and_never_clobbers_the_profile() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
+12 -12
View File
@@ -77,11 +77,18 @@ they are control roles in the journal (rules.md B-23).
## Transports ## Transports
1. **stdio** (build first). For Claude Code, Claude Desktop, local agents. - **stdio** (default): `ledger-mcp --data-dir PATH`. For Claude Code,
No auth — the OS user boundary is the auth. Claude Desktop and other local agents. No auth: the OS user boundary is
2. **Streamable HTTP** (later in the phase). For remote access (phone, travel the only boundary.
laptop). Bearer token, binds 127.0.0.1 or the tailnet interface only. - **Streamable HTTP**: `ledger-mcp --data-dir PATH --http 127.0.0.1:8080`.
Binding 0.0.0.0 is an explicit human decision, never a default. Serves MCP at `http://127.0.0.1:8080/mcp` via rmcp's tower integration.
**There is no authentication on this transport today**: no token and
no Origin check. The only guard is rmcp's default `Host` check, which
accepts `localhost`, `127.0.0.1` and `::1`. Any local process that can
reach the port can read and change the books. Bind it to loopback only;
binding a LAN, tailnet or public interface is an explicit human
decision, never a default. Auth, scopes and Origin validation arrive
with the served books (`plan/served.md`, P2).
## Server instructions ## Server instructions
@@ -725,13 +732,6 @@ posting.
the agent downloads from Google Drive via the CLI to a temp path, then the agent downloads from Google Drive via the CLI to a temp path, then
calls this. Keeps Drive credentials out of the server. calls this. Keeps Drive credentials out of the server.
## Transports
- **stdio** (default): `ledger-mcp --data-dir PATH`. For local agents.
- **Streamable HTTP**: `ledger-mcp --data-dir PATH --http 127.0.0.1:8080`.
Serves MCP at `http://127.0.0.1:8080/mcp` via rmcp's tower integration.
For network clients; bind to loopback unless you know what you're doing.
## Resources (read-only) ## Resources (read-only)
Hand-rolled on `ServerHandler` (rmcp 3.x has no resource macros): Hand-rolled on `ServerHandler` (rmcp 3.x has no resource macros):
+2 -2
View File
@@ -18,5 +18,5 @@ node = "26"
# verify/lean/lean-toolchain. # verify/lean/lean-toolchain.
"github:leanprover/lean4" = "4.34.1" "github:leanprover/lean4" = "4.34.1"
# Mutation testing and fuzzing (docs/oracle.md). # Mutation testing and fuzzing (docs/oracle.md).
"cargo:cargo-mutants" = "latest" "cargo:cargo-mutants" = "27.1.0"
"cargo:cargo-fuzz" = "latest" "cargo:cargo-fuzz" = "0.13.2"