refactor!: one way to serve books: the stdio server is gone (D-44)
check / check (push) Canceled after 0s

The owner (2026-10-23): "We should probably just delete the stdio path
altogether. THere's no point in that. And it sitting there unused will
just lead to bitrot. Nuke it?"

Books are served only by the books server (`wanderbooks server`),
reached by agents through the bridge (`wanderbooks mcp --book ID`),
which stays.

Removed: `wanderbooks dev-stdio`, the `ledger-mcp` binary, and
`ledger_mcp::run` (stdio and `--http` over a bare data dir), with all
that only that mode had:
- files the books make coming back inline: `HandedFile.content_base64`
  is gone, and `download` and `expires_at` are always there;
- books with no access log or backups: `books.access_log`,
  `books.backup` and `books.back_up_now` are always offered, by scope;
- the settings file read and reloaded (`legacy_files`), the vault key
  named by a file or `--vault-key-file`, and the books dir's
  key-inside-the-books and git checks (with `paths.rs`);
- `wire.rs` (the bridge frames an agent's lines itself);
- `books.settings`' file fields and `books.start`'s `settings_source`.

`BooksServer` is built one way, from `Served` (a book as the books
server serves it), in production and in the unit tests. An unknown tool
is now a protocol error (mcp-tools.md). Inline file input is unchanged.

Then the owner's three decisions (2026-10-23, the question tool):
(a) `books.import_settings` is removed entirely (118 tools): old books
come in whole by `wanderbooks books adopt`, which reads their settings
file once; ledger-config keeps its settings-file parsing only as far as
adoption uses it (`load` and the path-reading `effective_receipt_policy`
are gone), and `books.chart_template` gives its settings as
`books.set_setting` changes. (b) The viewer's loose-books-folder mode is
removed (`--data-dir`, `--books-toml`, `--vault-key-file`, the settings
file and config.json followed): it opens only books of the data root,
reading settings from the journal and the vault from the book's own
`vault/` and `keys/`. (c) `wanderbooks dev-stdio` has no stub: it is an
unknown command like any other word. Also: an upload's expiry past the
calendar is an error, never an empty string.

Tests: every suite that drove the stdio server moved to
crates/ledger-cli/tests/ and drives a real `wanderbooks server` on a
data root of its own (tests/common), handed files by their download;
mcp_stdio.rs is mcp_journeys.rs. Tests of what is gone are deleted and
listed in docs/testing.md. Docs: plan D-44 (D-18 superseded), STATUS,
testing, rules, mcp-interface, the handbook, README; justfile recipes.

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-23 19:45:18 -06:00
1 parent 0df7f52ac3
commit 7741e96eb3
87 files changed
+3477 -6476

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ You are the bookkeeping agent for these books. The engine enforces the accountin
- These tools are the only way to the books. Never look for the books (their journal, receipts or vault) in local files.
- Start every session with books.start: the rules in force, this company's conventions, what is open, and next steps. Follow the company's conventions (books.conventions).
- Settings, conventions, decisions and open items live in the books: change them only through these tools (the settings-and-conventions section). One exception, for books that still read a settings file (books.settings says source "file"): that file may be edited until books.import_settings moves it in. No other local file.
- Settings, conventions, decisions and open items live in the books: change them only through these tools (the settings-and-conventions section). No local file is yours to edit.
- Money is exact decimal. Always pass amounts as decimal strings ("123.45"), never floats. Amounts in every response are decimal strings: treat them as exact, and echo them to the human as written.
- Never post directly: draft, add lines, preview, show the human the preview, then post (the entries section).
- Every entry must balance: debits equal credits, exactly. Every line needs a real account from books.chart. The engine rejects anything else.
+1 -1
View File
@@ -1,6 +1,6 @@
# CPA handoff
- books.export_cpa_package carries every member's and 1099 payee's full TIN from the vault (the K-1 worksheet and the 1099 worksheet). It hands the zip over: run its download command with the folder the human wants it in (books served by `wanderbooks mcp`), then tell them where it is. The viewer's "Export for CPA" makes the same package.
- books.export_cpa_package carries every member's and 1099 payee's full TIN from the vault (the K-1 worksheet and the 1099 worksheet). It hands the zip over: run its download command with the folder the human wants it in, then tell them where it is. The viewer's "Export for CPA" makes the same package.
- Never file the package, or a file from it, into the books.
- tin_gaps lists each member or payee whose TIN it lacks and why: collect them (books.set_tin, books.set_w9) and export again.
- Everything in it is as of its as_of: the 1099 worksheet counts the year's payments through that day, and general-journal.iif (the QuickBooks Desktop file) every posted entry from the first through it, closing entries left out. That file is plain ASCII within QuickBooks Desktop's limits: warnings (and the README) say what it writes differently from the books ("Café" as "Cafe", a name cut to 31 characters, a subaccount of another type at the top level, a project's name as its QuickBooks class, the memos grouped); tell the human. Each line's project is its CLASS, the projects declared first as QuickBooks classes. It names no customer or vendor: the README lists the entries on accounts receivable or payable, where QuickBooks may ask the CPA for one.
+3 -3
View File
@@ -1,8 +1,8 @@
# Files in and out
- A file comes in one of two ways, named in the tool that files it (books.attach_receipt, books.file_document, books.set_w9, books.import_bank_csv, books.import_chart_csv):
- an upload, for any file up to 25 MiB (books served by `wanderbooks mcp`; on another connection only inline): books.begin_upload with the file's sha256 (`sha256sum`), size, type, name and purpose; run the command it returns with the file's path; then give its upload_id. Its bytes never pass through you. An upload lasts 15 minutes, is used once, and only by the token that began it.
- an upload, for any file up to 25 MiB: books.begin_upload with the file's sha256 (`sha256sum`), size, type, name and purpose; run the command it returns with the file's path; then give its upload_id. Its bytes never pass through you. An upload lasts 15 minutes, is used once, and only by the token that began it.
- inline, for a small file (at most 256 KiB): content_base64 with the file's sha256, so a byte lost on the way is caught.
- If begin_upload says the bytes are stored already (existing_attachment_id), link that attachment instead of filing a copy.
- Files the books make (books.export_cpa_package, books.vendor_1099 csv, books.get_w9) are handed over: run the download command each returns to save it in a folder (books served by `wanderbooks mcp`; elsewhere it comes back inline). They are kept 15 minutes, for the token that asked. No file with a full TIN goes into the books, however it is filed: the server refuses every file it read or wrote with full TINs (the W-9 books.set_w9 took, and each of those outputs, or a file taken out of a CPA package), and any text file holding one: a TIN the vault keeps, written as nine digits or in the usual groups (3-2-4 with hyphens, spaces or dots; 2-7 with a hyphen or a space), or a number printed as an SSN or EIN is (3-2-4 or 2-7 digits joined by hyphens, not part of a longer number). Other numbers pass, so bank exports (routing and account numbers, long references) go in. It can't recognize an altered copy of a scan, or a TIN the vault doesn't keep written any other way, so never file a W-9 or a TIN worksheet yourself.
- Receipts and documents come back out through books.get_receipt: view bytes gives a file of at most 256 KiB inline; a larger one gives the command (wanderbooks download) that saves a copy in a folder you name (only for books served by `wanderbooks mcp`).
- Files the books make (books.export_cpa_package, books.vendor_1099 csv, books.get_w9) are handed over: run the download command each returns to save it in a folder. They are kept 15 minutes, for the token that asked. No file with a full TIN goes into the books, however it is filed: the server refuses every file it read or wrote with full TINs (the W-9 books.set_w9 took, and each of those outputs, or a file taken out of a CPA package), and any text file holding one: a TIN the vault keeps, written as nine digits or in the usual groups (3-2-4 with hyphens, spaces or dots; 2-7 with a hyphen or a space), or a number printed as an SSN or EIN is (3-2-4 or 2-7 digits joined by hyphens, not part of a longer number). Other numbers pass, so bank exports (routing and account numbers, long references) go in. It can't recognize an altered copy of a scan, or a TIN the vault doesn't keep written any other way, so never file a W-9 or a TIN worksheet yourself.
- Receipts and documents come back out through books.get_receipt: view bytes gives a file of at most 256 KiB inline; a larger one gives the command (wanderbooks download) that saves a copy in a folder you name.
@@ -11,17 +11,6 @@ The books keep their own record of how they are kept. Every agent, anywhere, rea
- policy.require_tax_category is warn (unset: a revenue or expense account without a tax category is created, and the result and Needs attention say so) or refuse (books.add_account, books.import_chart_csv and books.set_tax_category won't leave one without; the accounts invoices, bills and payroll create are never refused, and their results name them). Before setting refuse, categorize with the human every account books.digest lists in uncategorized_accounts.
- books.export_settings writes the settings out as a settings file (TOML), each with who decided it. It is a copy: editing it changes nothing.
## Books that still read a settings file
If books.settings says source "file", these books predate settings in the books and still read the settings file books.settings names. Move it in once, with the human:
1. Read that file's text, whole and unedited, and pass it to books.import_settings as toml_text (and, if books.settings shows config_json_present, the receipt policy as config_json_text: {"receipt_policy": {"require_receipts_above", "receipt_required_accounts", "receipt_exempt_accounts"}} from books.settings). The import is refused unless the text is the file the server reads.
2. Show the human the preview: every change, the notes (what isn't a books setting and where it goes), anything unknown, and the file's comments, which are not imported: record what matters in them (where a value came from, a discrepancy, something pending) as a decision, an open item or a convention.
3. Confirm with confirmed: true, decided_by the human. The import is refused if any setting would read differently afterwards.
4. If the file named the vault's key file ([vault] key_file), the server must already be started naming the same file, and the viewer too, or the vault won't open: tell the human before the import.
From then on no file is read; books.set_setting changes settings. Until then, that file is the one local file you may edit, and books.set_setting is refused (it would drop the rest of the file).
## Conventions
How this company keeps its books, as the owners decided, is recorded as conventions (books.set_convention, read with books.conventions; books.start lists them):