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:
4 files changed
+55
-24
No files matched your search
@@ -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
|
||||
file per receipt: PDF preferred, an image is fine.
|
||||
- **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
|
||||
|
||||
If `books-data/journal.jsonl` is missing or empty, onboard before recording
|
||||
anything. Keep it conversational; ask a few things at a time.
|
||||
If the books are empty (`books.chart` lists no accounts), onboard before
|
||||
recording anything. Keep it conversational; ask a few things at a time.
|
||||
|
||||
1. Ask: company legal name, entity type (sole proprietorship or
|
||||
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.
|
||||
- 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
|
||||
them read access) and open it with `wanderbooks gui`, or `ledger-gui
|
||||
--data-dir books-data`. Nothing they do there can change the books.
|
||||
them read access) and open it with `wanderbooks gui`. Nothing they do
|
||||
there can change the books.
|
||||
|
||||
## Session start
|
||||
|
||||
@@ -222,14 +227,15 @@ human tells you their preference, write it there.
|
||||
search).
|
||||
- `books.digest` is the review surface — "what did you do with my books
|
||||
lately." Offer it; don't make the human ask.
|
||||
- Reports are pure functions of the journal. If a number looks wrong, the
|
||||
journal is the evidence — read it, don't guess.
|
||||
- Reports are pure functions of the journal. If a number looks wrong,
|
||||
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
|
||||
against the journal (rebuilding it if it drifted), and every stored
|
||||
file. Run it when a number looks wrong or the human asks.
|
||||
- Back up: `just backup` after every session that posted entries. The git
|
||||
history of this directory is the archive; `books-data/journal.jsonl` is
|
||||
the system of record and the SQLite database is disposable.
|
||||
history of this directory is the archive; the journal is the system of
|
||||
record and the database is disposable.
|
||||
"#;
|
||||
|
||||
const CLAUDE_MD: &str = "@AGENTS.md\n";
|
||||
|
||||
@@ -144,7 +144,6 @@ fn agents_md_carries_the_contract() {
|
||||
"Posted is forever",
|
||||
"reversing entries",
|
||||
"receipt",
|
||||
"journal.jsonl",
|
||||
"onboard",
|
||||
"chart_template",
|
||||
"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]
|
||||
fn rerun_refused_without_force_and_never_clobbers_the_profile() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
+12
-12
@@ -77,11 +77,18 @@ they are control roles in the journal (rules.md B-23).
|
||||
|
||||
## Transports
|
||||
|
||||
1. **stdio** (build first). For Claude Code, Claude Desktop, local agents.
|
||||
No auth — the OS user boundary is the auth.
|
||||
2. **Streamable HTTP** (later in the phase). For remote access (phone, travel
|
||||
laptop). Bearer token, binds 127.0.0.1 or the tailnet interface only.
|
||||
Binding 0.0.0.0 is an explicit human decision, never a default.
|
||||
- **stdio** (default): `ledger-mcp --data-dir PATH`. For Claude Code,
|
||||
Claude Desktop and other local agents. No auth: the OS user boundary is
|
||||
the only boundary.
|
||||
- **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.
|
||||
**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
|
||||
|
||||
@@ -725,13 +732,6 @@ posting.
|
||||
the agent downloads from Google Drive via the CLI to a temp path, then
|
||||
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)
|
||||
|
||||
Hand-rolled on `ServerHandler` (rmcp 3.x has no resource macros):
|
||||
|
||||
Reference in new issue
Block a user