Split kernel::entry_flows into entry_movements and flows_of so the
reversal-mirror proof fits CI; prove runs the fast harnesses, prove-slow
the 8 slow ones (own job in scheduled CI), prove-all both. Laws in
verification.md renumbered 1-31 with a table of all 55 harnesses.
MCP docs checked against the 115 tools (about 60 fixes) with a test that
every tool is documented; rules.md cross-references fixed; bookspec
statuses and loader tightened; viewer notes wrap and accounts sort by
code; STATUS.md says P3 is built, cutover pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
A schedule-convention setting, the two new tax categories, a disposal
before the in-service date and a depreciation catch-up each need their
feature now (tax_categories_gain_exempt, disposal_before_service,
depreciation_catch_up), so no write before consent leaves books P2 refuses
or misreads; replayed names are read with P2's TIN detector (B-28). The
cutover runs end to end through the real P2 and P3 binaries in a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Schema 13 is as shipped and the asset columns are schema 14 (shipped
migrations pinned by hash); the tax-basis K-1 leaves net income open (only
the return has it) and takes a contributed prepaid plan at its book amount
as its section 723 basis; a disposal of contributed property names its
704(c) gain or loss; aliases compare by meaning; fixture amounts made up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
A distribution declared to a payable and then paid is financing when paid,
the declaration disclosed (ASC 230-10-45-15(a)); a disposal's depreciation
lines count as depreciation in the cash flow and the tax-basis K-1; an
as-of asset list says the status of that day. Seam tests tie the members'
statement, K-1, party totals, cash flow, 1099s, depreciation and the CPA
package together on three dates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Book value is cost less what the books posted; the schedule's figure is
apart. An asset's acquired date comes from its entry. Contributed
property keeps its section 723 carryover basis apart from its depreciable
basis (the lower of value and adjusted basis), with its 704(c) built-in
gain and method. The K-1 worksheet reports tax-basis capital (Form 1065
Item L) beside the book figures, leaving open what only the CPA knows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Books an update could write a newer format for open on their old format
and are asked at every open; anything needing a new feature is refused
until the owner agrees with the passphrase, after a verified backup that
includes the keys. New books start on the current format. Each projection
schema has its own database file, so an older version still opens the
books until the owner agrees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The final month takes what the months before leave, spread over its days;
a disposed asset stops on its disposal day. A skipped month is caught up
in the month the run is made; a reversed run un-posts its month. New
schedules spread by day; equal-months schedules must start on the 1st.
Amounts round to the currency's own minor unit. Kani and Lean proofs; an
exhaustive search over runs, reversals and disposals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
A vendor's 1099 decision is its own dated fact (files NEC or MISC, not
reportable with its reason, or undetermined), applied by each payment's
date. Payment methods are recorded; card and network payments are left
out and listed; a member's check through financing counts; a bank payment
with no method counts (D-40); $2,000 for payments after 2025. One
worksheet for the tool, the viewer and the CPA package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Each party's part of an entry is signed as its own account would show it,
with a direction and its cash and in-kind amounts; a reversal is its
original negated. Totals on a cash or all basis: for a member, all equals
the members' equity statement and cash the K-1's cash. Money is cash,
cards and the financing accounts, and a liability credit never pays its
own creditor (creditor_of), so accruals and declared distributions count
once. tin_last4 from the TIN or W-9 facts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
books.rename_account; account names trimmed, bounded, refused when a
sibling's or TIN-shaped; books.show refuses what its target ignores. A
report asked for without dates covers this fiscal year so far, everywhere
(one rule for tools, descriptions and the viewer; the IIF and CPA package
keep their own dates). policy.require_tax_category warns or refuses; a
Form 4797 gain is shown apart on every return.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Direct method with the operating reconciliation and noncash schedules; it
foots by construction (Lean and Kani). Every non-cash account belongs to
one section; reversals net; a member or lender paying for the company is
cash borrowed and spent (D-42, constructive receipt and disbursement);
property a member contributes stays noncash. The bookspec seed: one loader
checks every example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The CPA package's general-journal.iif now holds every posted entry through
as_of (closing entries left out, D-36), written as plain ASCII with CR LF,
DOCNUM the entry id, QuickBooks Desktop's field limits and subaccount types
honoured, and every character written differently listed in the export's
warnings and the README (grouped by substitution, TINs masked). The 1099
worksheet stops at as_of too. PDFs print CJK, Arabic, Hebrew, Thai and
Devanagari (Noto fallbacks, OFL), and so does the viewer. has_full_tin reads
Unicode separators.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Members' shares of earnings are split cumulatively by largest remainder in
the closes, the members' equity statement, the K-1 worksheet and the
viewer, so a member's capital on a given day never moves by a cent when its
month is later closed. Old closes replay as they were; the first P3 close
carries any true-up. Feature members_cumulative. Kani and Lean proofs of
the bounded drift; bookspec examples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
`wanderbooks books put-aside ID DATA-DIR [--vault DIR --vault-key FILE]
[PATH...]` moves the old copies of adopted books (their data folder, their
TIN vault and its key, their settings file, the git history around them,
old exports) whole into numbered folders of their own in the root's
legacy/, a list of where each came from written and synced first. Nothing
is deleted. Refused unless the books hold everything the old copy does,
byte for byte (journal, stored files, vault records, key; what adoption
left behind and records the books sealed anew are listed and go aside
too); while a wanderbooks process holds it open for writing; when its
head moved since the preview; for links, paths named twice, inside the
root or missing, a vault or key given as a plain path, and paths on
another disk. Renames never replace anything, and a move cut short puts
back what it moved. Three review rounds of data safety.
The plan records the cutover's answers (Q-3: move the old copies aside;
the backup repository and folder).
Also: `connect`'s "the books fernhill' agent token" reads properly, and
the restart tests wait for a copied program to be runnable (a child forked
by another test while the copy was written made it "text file busy").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Mutation testing pauses until P12 (D-33, owner 2026-10-20), which reruns
the whole max-testing pass over everything since P1. Before the pause,
pass 1 on slices 3-6 and its triage gave tests in ledger-store,
ledger-package and ledger-mcp, and three real bugs, fixed:
- the command line took an admin answer longer than four MiB, or cut off
before its newline, whenever the bytes that came still parsed;
- The CPA package put a twin's number on the last dot of the whole path,
so in a folder with a dot the second file landed in another folder;
- a document or category named of dots alone ("..") could climb out of
its folder when the CPA package is unpacked.
The plan gains P13, bookspec (D-34): a formal, sourced accounting
specification first (rules with symbolic forms and worked examples), then
its own formal validation and a conformance harness any engine can be
tested against, seeded by P3's regression tests. docs/testing.md records
slices 3-8 and what P12 must cover; plan/STATUS.md says where P2 stands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
No Landlock, no seccomp, no helper: isolation is "same_user
(advisory)". The lease socket keeps two intents (close, format-upgrade
consent). The viewer holds the books open while it runs and gains a
Books menu: New books, Back up now, Restore (preview, then confirm),
Close, Connect Claude Code / Codex. New commands `wanderbooks books
back-up` and `wanderbooks connect claude|codex`. The scaffold
(`new`/`init` and its templates) is gone; install.sh needs no sudo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The server backs each book up itself: a per-book git archive, pushed
fast-forward only to a git remote and to a folder, when the books open,
after a quiet spell, before they close and on request; every commit,
push and drill is a record in ops/backup.jsonl. Drills clone each
target and verify the chain, the blobs, the vault and the head, weekly
and after a failure. `wanderbooks books restore` previews, then
restores beside or (with --replace) over, never moving books something
holds. The archive refuses anything but a plain bare repository and
pins git against the owner's configuration. books.backup and
books.back_up_now for the agent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
`wanderbooks gui [BOOK]` finds the data root, reads settings from the
journal, the vault of the book, presence through ops/, Save a copy /
Open on every document, Save the W-9, the Export menu, and colour
schemes. One builder for the CPA package and the 1099 worksheet
(ledger-package), shared by the server and the viewer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Files reach the books through the server: uploads by hash and size,
then filed; exports (the CPA package, the 1099 worksheet, W-9s) handed
over as downloads from the server, never read off disk by the agent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Agent tokens (wb_ and 64 hex digits), one book and one name each, kept
only as SHA-256 in a chain log and in the OS keyring; made, listed and
revoked only through the admin socket (`wanderbooks token ...`). The
door checks them, scopes cover every tool, every call is in the access
log (fail closed), and the owner passphrase is an Argon2id hash.
Setup's terminal test waits for its prompts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
One wanderbooks executable. The books are open while the viewer, an
agent session or a terminal holds them: a host process takes leases on
a socket, starts the server (the hidden mode `wanderbooks server`) with
a pipe on its input, and closes it 10 s after the last lease ends. No
background service. One host even in a race (run/host.lock).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
CI (GitHub's slower runners) failed every_scheme_paints_the_same_words_and_figures_readably
on the Documents page: thumbnails still rendering show "…" until their
picture comes, and a few were still loading when one scheme's frame was
read and done when the next one's was. The placeholder isn't a word of
the page: it is left out of the comparison, and its contrast is still
checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Owner, 2026-10-19: hovering a scheme for a moment switches to it live;
moving off goes to the next one hovered or back to the one in force, to
sample schemes without re-opening the menu.
A variant's row rested on for 50 ms is worn; off every row for 50 ms,
the scheme in force again; the menu closed without a click, the one in
force at once. Only a click chooses, and a preview is never kept.
Tested with frames 10 ms apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Owner, 2026-10-19: full color-scheme support for the viewer, the top
Neovim-style schemes, about twenty families each with a flyout of all its
variants, a light/dark/all filter, Ayu included, alphabetical, a whole row
to click.
- The sidebar's corner button (the light/dark toggle before) opens the
menu: the filter, then, alphabetically and all on screen at once (in
columns in a short window), Ayu, Catppuccin, Cyberdream, Dracula,
Everforest, GitHub, Gruvbox, Kanagawa, Material, Melange, Monokai Pro,
Night Owl, Nightfox, Nord, One Dark, Oxocarbon, Rosé Pine, Solarized,
Sonokai, Tokyo Night and Wanderbooks (the viewer's own Light and Dark):
80 schemes, each variant a row with a strip of its colors.
- Colors from each scheme's official source, checked value by value
(docs/color-schemes.md; the table names the sources).
- Every palette, the viewer's own included, keeps every text color
(figures, gains and losses, warnings, links, secondary and faint text)
at WCAG AA (4.5:1) on every ground it sits on, soft fills included; a
color that falls short keeps its hue and only changes lightness. Text,
secondary and faint text stay apart (each 1.2 times the next), gains,
losses and text and the charts' series shown together stay plainly
different (CIE76 ΔE 20).
- The scheme sets both egui styles and pins its mode, so the system
turning light or dark changes nothing (it made figures unreadable).
- Preferences now live in the platform's place for app settings
($XDG_CONFIG_HOME/wanderbooks-viewer on Linux), not eframe's default,
which was $XDG_DATA_HOME/wanderbooks: the data root's own place. The
old file is moved once.
- Tests: every page of thirteen painted in all 80 schemes, the same words
in the same places, each text readable on what was painted under it;
the menu's order, reach and whole-row clicks in a tall and a short
window; the system flip; preferences restored. Two review rounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The first scheduled run (2026-10-19) failed four ways: the viewer's
mutation job called the recipe retired in P2 slice 1 (owner, 2026-10-18:
no mutation runs on the GUI); the core and MCP runs were stopped at
GitHub's six hours; the import run missed six mutants.
- Every mutation recipe takes a shard (k/n, default all of it); the
workflow runs the core in 8 shards, the store in 4, the importers in 2,
the MCP server in 16, each its own job with a 350-minute limit.
- Test timeouts are three times each run's own baseline, never under the
recipe's old fixed value, so slower runners don't report false
timeouts.
- The mutants-gui job is gone.
- ledger-import: rule_marks_match_their_notes,
asset_register_xlsx_headings_and_totals_rule and
xlsx_missing_amounts_are_cells_only_on_ruled_rows catch the six
(the tax-basis rule marks, a heading's alignment, the totals row's
blank cells, and a blank amount cell only on ruled rows), each checked
against its mutant by hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
ledger_store::root: one private folder for every book, found without the
environment (--root, else the root setup recorded in a file only this
account can write, else the platform default under the home from the
password database). Every folder made 0700, files 0600; a root that is a
link, another user's, open to others, unmarked, or under a folder another
user could swap is refused; no error names it. Setup keeps a recorded
root, never makes a second root or moves books while one holds any, and
leaves a folder holding other things untouched. Books are created and
adopted one at a time; adoption reads the source under its writer lock
(changing nothing in it), copies into staging, re-hashes every stored
file against its name, checks every named file is there, opens and
verifies the copy with the source's head, imports settings for books
that keep none, syncs, and only then renames it into place.
wanderbooks setup makes the root and never prints where it is. Three
review rounds; mutation on the diff (docs/testing.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The server and the viewer are libraries with run(prog, args), returning
Exit(code) instead of ending the process; ledger-mcp and ledger-gui are
one-line shims (the live books keep using them until the cutover).
wanderbooks dispatches dev-stdio and gui to them in-process; a mode must
come first and owns its flags. The gui feature (default) leaves the
viewer out of the server-only build, which just server-only builds,
clippies, tests, and checks for window libraries. The viewer's mutation
recipe is retired (owner, 2026-10-18); its two musts are tested
directly. Two review rounds; mutation on the diff (docs/testing.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
The first company's P1 cutover found the K-1 worksheet printing a
member's share as typed: "50%" read from a settings file printed
0.50, the same share kept by the books as 0.5 printed 0.5. Shares now
print at least two places whatever their spelling.
Every company document now goes into the books (D-20, owner,
2026-10-17), and formation papers and plans print the company's EIN,
which the filing scan refused as a printed TIN. It now passes the
company's own EIN, as its settings hold it, printed 2-7, and nothing
else; a vault TIN is still refused (rules.md B-26). Its refusal no
longer suggests converting a file to get past it.
plan/served.md: D-20; correcting a record's source without changing
its words (P5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Many test helpers removed their temp dir only before the test, or only
on pass, so runs left them behind: 117,000 filled /tmp (RAM here) and
stopped a mutation run. Each now holds a guard that removes the dir on
drop, unwind included; servers started on a test dir hold its guard
until they stop. A full cargo test --workspace leaves 0 dirs (859
before). just sweep-test-tmp stays for runs killed mid-test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Settings, conventions (posting templates, approval rules, policies,
close steps, calendar, mappings, notes), decisions, open items and
entry approvals are journal events (journal format 2). Books that keep
their settings never read wanderbooks.toml or config.json; the server
and the viewer take the vault key from --vault-key-file instead.
books.import_settings moves a settings file in once, exactly as the
server reads it. Approval rules, when enforced, gate every posting
path. Replay refuses what it can't read as newer, never as corrupt.
Lean: a record changes no figure. Mutation on the whole diff: every
survivor killed by a test but two equivalent (docs/testing.md). The
Company page says a late open item is overdue in words, not colour
alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
CI failed on a_file_swapped_after_the_check_is_never_read: its filesystem
handed the deleted checked file's inode number to the file made in its
place, so device and inode matched for another file. The opened file
must now also match the checked file's size, modification time and change
time. The test now models a real swap (the replacement exists before the
original goes), the reused-inode case, an in-place rewrite, and that the
unchanged file is read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
- The 13K-character instructions (clients cut them short) become a
connect text of at most 1,400 characters whose first 512 stand alone,
plus books.handbook and books://handbook/{section}: the old guidance by
section, with no helper commands, home paths, pull requests or
file-reading suggestions (tool_text_lint).
- Every tool has a title and read-only/destructive hints; no
requiresUserInteraction anywhere (owner decision 2026-10-16).
- books.import_local_file reads only regular files inside the import
roots (the downloads folder by default, or --import-root), never the
data dir, books dir, vault or the key's folder, no symlink escapes or
hard links, and the file opened must be the file checked.
- The store lock is a tokio mutex taken off the runtime threads.
- get_info refreshes settings first; a bare books://digest reads.
Review fixes (P0 review, 2026-10-16):
- Fail closed: a tokio mutex never poisons and spawn_blocking caught the
panic, so after a panic mid-mutation the server kept serving a Store
behind its journal. A panic (the task, or while holding the store)
now stops the shared store; every later request on every session is
refused with a plain message; a restart re-verifies the journal. The
join error goes through redact_refusal and never carries the panic's
text.
- books.set_w9 w9_file_path read any path (a link named w9.pdf to the
vault key was stored and get_w9 wrote it out): it now goes through the
import roots' lockdown, its type judged on the canonical path.
- Sealed even inside a root: the key file in force, where a linked one
leads, both folders, and the default key folder whatever the settings
say. Refusals check the roots first, so outside them every path gets
one message (no probing what exists); inside, other books-dir files say
to copy them out first.
- The open is O_NOFOLLOW | O_NONBLOCK (a pipe swapped in after the check
can't hang a thread) and must match the checked file's device and
inode. New dependency: libc (unix only), for those two open flags,
which std doesn't name.
- Default import roots are the downloads folder only (the plan); files
with full TINs (get_w9, vendor_1099 include_tins, the CPA package with
TINs) are never written into an import root, and the server refuses to
import any file it wrote with TINs (by SHA-256).
- set_tin and set_w9 overwrite vault records: destructive_hint = true;
annotations_complete pins the key tools' hints. The read-only hint is
checked in behaviour on every spec_mcp call (journal and data-dir files
unchanged).
- Text: the connect text names the settings file as the one local file
to edit, says every entry balances on real accounts and names its
party, says invoice/bill/payment/payroll tools post unchecked, rewords
the exemptions, and shows the company name as one plain line; the
handbook takes section "all", lists its ids in the schema and says to
read contract first; books.settings drops the config.json path and no
error names the data dir or the key file. tool_text_lint covers the
whole tool JSON and resources and bans git/commit/FL-0001; tool
descriptions over 700 characters are pinned to today's eight (P5).
- digest: an empty since= is bare; the default date is pinned on every
day of a month.
Round-2 review fixes (2026-10-16):
- TIN files (B-26): the in-memory, per-session set of TIN-file hashes
became one registry beside the vault (vault/tin-files.sha256, hashes
only), shared by every session and every later server. It holds the
W-9 set_w9 reads (recorded as read), get_w9 copies, 1099 worksheets
with TINs, CPA packages with TINs and each file in one that holds a
TIN; a file is recorded before it is written, and a failure refuses.
import_local_file, attach_receipt and file_document (base64 was never
checked) refuse a recorded file, and a text file (by type, name, or
UTF-8/UTF-16 decoding) holding a full TIN. Not caught: an altered
binary copy; the docs say so (P2/P7 remove the path tools).
- The text check is narrow (owner/engineer decision, 2026-10-16: bank
exports are filed for reconciliation, and has_full_tin's "nine digits
however separated" refused their routing numbers, long references and
dates run into amounts). Text is refused when it holds (a) a TIN the
vault holds for a party, anywhere, as nine digits, 3-2-4 joined by
hyphens, spaces or dots, or 2-7 joined by a hyphen or space, compared
on the server against the decrypted vault and never returned or
logged (a vault that can't be opened leaves (b)); or (b) a stand-alone
printed SSN (123-45-6789) or EIN (12-3456789) shape: no digit, and no
hyphen or dot followed by a digit, beside it. Full-width digits and
Unicode hyphens and spaces read as ASCII. has_full_tin still guards
refusals and results (redact_refusal). Known limit, for P7: a refusal
under (a) tells the agent a text it chose holds a vault TIN.
New dependency: zeroize in ledger-mcp (already in the workspace
through ledger-vault, same major version), so the vault's TINs decoded
for the scan are wiped from memory on drop as the vault's own are.
- Mutation testing of the final P0 diff (docs/testing.md): tests for
each law a survivor broke (handbook titles, a dropped default root, a
.jpg W-9, the waker, the walk's folder flags against a link and a
pipe, the 40-link limit, save paths refused as such, the package
marking only its TIN files, a registry that can't be read, text types,
the short receipt rule's counts and a 12-character code). Simpler
code where only a race could tell: inspect places an existing path by
canonicalize itself (no second comparison), read_checked's read limit
is its one size check, openat fails on -1 as specified. The 25 MiB
upload test removes its 50 MB of books however it ends.
- The GUI's stale-rule prompt no longer says to open a pull request: it
asks the agent to find the figure from the rule's official authority,
confirm it with the human, pass it explicitly, and tell the human so it
can be reported upstream (as the handbook says); a lib test pins it and
bans files, git, GitHub and pull requests from every attention prompt.
- Import roots (local_files.rs INVARIANT 0): XDG_DOWNLOAD_DIR="$HOME/"
made $HOME (or /) the root. No root may be /, the home folder or hold
it, or hold the books, data dir, vault or a key: a named one stops the
server at startup, a default one is dropped; the default falls back to
~/Downloads only if that is a real folder other than $HOME; with no
root, an import says so.
- The read is a component walk from / with openat(O_NOFOLLOW |
O_DIRECTORY) per folder and O_NOFOLLOW | O_NONBLOCK for the file, so a
folder swapped for a link after the check fails; no step may land on a
protected inode (one renamed into a root); dev/ino and nlink stay. No
/proc. Portable to macOS. (libc's openat, two small unsafe blocks.)
- One refusal for missing, dangling and outside paths: resolved()
follows every link, dangling ones too, so a link's target existing or
not no longer changes the answer.
- Sealing: the key files (in force, default) and their targets always;
their folders only when not $HOME, not a root, not holding a root; and
~/.config/wanderbooks whatever XDG_CONFIG_HOME says.
- Agent text (I-12): RuleNotKnown names the authority (IRS, with its
address) and says to tell the human, never a repo path, PR or GitHub;
the digest's stale_rules drop the repo catalog and path for it; vault,
outside-books, STOPPED and local-file refusals name no git, path or
flag. tool_text_lint bans docs/, .md, /home, data dir, --data-dir,
--import-root, vault.key, .jsonl, sqlite, .gitignore, github, pull
request, and also lints every error template (scanned from source),
built error texts, and the server's refusal constants.
- The receipt rule told (ReceiptPolicy::rule) is the one enforced: the
exact threshold in the base currency (no "$", no rounding), required
and exempt accounts, reversals exempt; the connect text fits 1,400
characters by dropping reasons, then codes, never the rule.
- The connect text follows the parties setting; "offer books.digest"
everywhere. shown_name drops Unicode format characters (bidi, zero
width).
- read_only_tools_change_nothing compares file contents, the database
by every row, the vault and the settings file (raw database bytes did
not change either; rows are the stable comparison).
- import_local_file takes the name and type from the canonical path;
the digest's journal repair says when the tail was set aside, not the
journal's file name; "version-history messages" back in the TIN rule;
openWorldHint false on every tool, pinned in annotations_complete.
- Outside the MCP crate, for the same texts: ledger-core's DatedRule
gains `authority` (the statute or publication with its address) and
RuleNotKnown carries it; ledger-store's "sqlite error" reads "database
error"; ledger-config's ReceiptPolicy::rule takes the base currency.
New dev-dependency: rusqlite in ledger-mcp (already in the workspace,
same version and features as ledger-store), to read the database's
rows in read_only_tools_change_nothing.
- The filing scan is no TIN oracle (tin_files.rs INVARIANT 4): text
holding more than two TIN-shaped numbers (any rendering rule (a)
reads) that end in a known TIN's last four (the books' TIN and W-9
facts, via known_tin_last4, before the vault is opened; then the
vault's own) is refused with TIN_PROBE, the same words whether or not
one is the vault's TIN; only one or two candidates are compared
against the vault. Tests: unit
text_probing_for_a_tin_is_refused_alike_whether_or_not_it_holds_it,
one_or_two_candidates_are_compared_against_the_vault; over HTTP
text_probing_for_a_tin_is_refused_alike_by_every_filing_tool.
Plan: plan/served.md P0, section 6 "Immediate hole (P0)", 4.1, 4.3, 4.9.
Review round 3. S1: a panic stops the server for every session, so no
input may reach one. Dates are four-digit years (new ledger_core::
calendar, 0001-01-01..9999-12-31): the MCP's parse_date is strict
YYYY-MM-DD, periods are bounded, last_day_of_month returns a refusal
(server and store); schedules, assets, recurring entries, periods,
documents, disposals and entries refuse dates outside the calendar, and
replay refuses such journal dates. Every amount a command takes is at
most a line's (draft lines by size, document lines, payouts, payroll,
statements, shares, tax basis, proceeds, recurring templates), on
replay too, so sums are exact; the 1099 vendor part and the K-1
property share use checked products (TooLargeToCompute); a recurring
run makes at most 1,000 drafts. tests/hostile_args.rs drives every tool
with schema-derived hostile arguments and every resource with generated
URIs, asserting the server never stops; the audit's panics are pinned.
S2: a `.` or `..` path name is refused as NO_SUCH_FILE up front. S3:
NUL-interleaved UTF-16 without a byte-order mark is scanned both ways.
S4: resource-read refusals are TIN-redacted. S5: set_w9 records its W-9
only once the call passes every check, just before the vault write.
S8: idempotentHint only on verify, run_depreciation, run_schedules,
run_recurring, set_party_roles (a repeat leaves journal and vault
alone; spec_vault checks every candidate twice); every tool's hints
pinned in one table. C1: books.settings lists receipt_required_accounts
and receipt_exempt_accounts uncut. C2: the default company name reads
"a new company". C3: the parties rule names no-party reasons. C4/C5:
the handbook names the settings file only as books.settings' books_toml,
and restores "check books.digest and offer it". C6: an import root
inside the books, data dir or vault is refused at startup. The lint
catches `just` however it ends and where the books are;
digest_resource_bare_reads reads the server's month.
Review round 4 (2026-10-17): round 3's bounds ran in replay too, so
journals main wrote and reopened (an entry dated 0000-06-01 or
+10000-01-01, a draft line of 2e15, an asset ending past 9999, a basis
or share past the line limit, sub-cent document and payroll lines) no
longer opened. Posted is forever: the bounds are the commands' only.
- Replay applies main's rule exactly: parse_date takes any date chrono
reads; replay_add_line(_fx) has no size bound (add_line/add_line_fx
do); replay_register_asset/correct_asset (chrono overflow only),
replay_set_asset_tax_basis (check_journaled), replay_set_entry_parties
(check_shares_journaled), replay_register_recurring
(validate_recurring_journaled), replay_register_schedule (months 1 to
600), replay_create_period (order only), replay_dispose_asset, and no
line, date, payment or payroll range checks in replay_invoice/bill,
their payments and replay_payroll_run.
- Derived figures over such books refuse (TooLargeToCompute,
DateOutOfRange), never panic: amortization plans, depreciation into
chrono's last month (and the true-up for lives past 9999), intervals,
document totals and aging, net pay and payroll plans, money flows and
share resolution, the asset register, receipt-policy debits; the MCP
tools and the viewer show what can't be computed.
- tests/fixtures/old-books: the review's 13 journals, its control and 15
more edges, written by main's Store API with main's figures
(expected.json); every one opens in the writer and the viewer to those
figures, every derived figure computes or refuses, every MCP tool and
GUI page answers; hostile_args restarts the server after each
property; each bound has a command/replay test pair.
Final review: a schedule's plan refuses a total of 10^26 or more
(TooLargeToCompute) instead of letting Decimal round its cents silently,
and a null `remaining` in the schedule output is always explained by
plan_error. Replay is unaffected: no journaled schedule month can belong
to a total that large (the line limit bounds every posted month).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
An unknown event type on a whole, chained line, or a journal.format_set
declaring a newer journal format, now raises StoreError::NewerJournal
(update wanderbooks) instead of JournalCorrupt. Corruption (bad hash,
seq, or JSON) is still corruption. The gate runs in the store, the
Snapshot and the GUI, after each line's parse, seq and hash checks, and
never skips a line it can't read. Adds the journal.format_set event
(format 1 today; nothing writes it yet) and a registry of every event
type this version reads, tested against the replay and projection arms.
Review fixes (P0 review, 2026-10-16):
- A line's own hash is checked only by the next line's prev, and the
gate used to refuse a line before reading the next: a flipped bit in a
middle line's type (or a format_set edited from 1 to 2) was reported
as "newer, the books are not damaged". Now the reader settles every
refusal against the next line (format::settle): a next line that
doesn't chain makes it corrupt; one that chains makes it newer and
verified; with no next line the message says "newer or damaged; update,
then run verify", never "not damaged" (NewerJournal.verified).
- A newer format number is judged newer before its features are parsed:
a newer format may shape them differently.
- Snapshot treats a blank complete line as corrupt, as the writer does.
- docs/spec/rules.md B-27, which the code cites, now exists.
Round-2 review fixes (2026-10-16):
- The envelope {seq, ts, type, payload, prev} and its hash (SHA-256 of
the canonical JSON) are frozen across formats (rules.md B-27); new
fields go in payload. settle reads only the next line's prev, leniently,
so a newer format's next line (a float, a string seq, keys of its own)
is never called corrupt: no readable prev means unverified newer.
- A key beside the envelope's was silently dropped (and the chain then
broke as "corrupt"): it is now NewerJournal, hashed over the raw
canonical JSON so the next line still checks it; JournalLine::parse
refuses such a line.
- "the books are not damaged" overclaimed: a verified refusal says line N
is intact and this version can't check the lines after it.
- The writer read the journal with BufRead::lines (invalid UTF-8 was an
I/O error); it now splits bytes on newlines as the Snapshot does, so a
non-UTF-8 line is JournalCorrupt in both.
- Store::open truncated a torn tail before the gate ran, altering newer
books (perhaps mid-append) before refusing them. The gate now runs over
the complete lines first; the tail is set aside (and journaled) only
when they pass.
- Mutation testing (docs/testing.md, P0): the viewer and the writer say
the same words about the same damaged line (the newline is no part of
it); read_journal_and_format returns every line and the format in
force; the journal page tells a format declaration.
Plan: plan/served.md P0 and 5.1.
Review round 3 (S6, S7). Store::open replays every complete line in
memory (the first line's currency, then the ledger) before a cut-off
last line is set aside, so a complete line the books can't apply
refuses the open with the journal byte for byte as it was and nothing
set aside (B-27 SCENARIO 3; test
a_bad_complete_line_before_a_torn_tail_leaves_the_journal_as_it_was);
catch_up now only projects. The viewer
applies the writer's first-line rule (created_currency, shared) instead
of guessing USD: any other first line, or a store.created without a
sound base_currency, is corrupt to both (test
the_first_line_is_store_created_for_the_viewer_and_the_writer). fuzz_store_replay varies the
first line (14,696 runs clean).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
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
Every crumb above the page you're on links to where it names: the
section goes to its home (Parties › Tobin Harlow → Parties), and a
reconciliation's trail runs Accounts › its account › it. Same look; a
pointer and "Go to …" on hover. A crumb naming the current page isn't a
link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
The party page's "TIN on file" and the W-9 card's TIN copy the full,
formatted number on click (right-click: digits only). The viewer reads
it from the vault only at that moment, with the server's key file in
force, never creates a key, refuses a key inside the books dir, and
never draws more than the last four. A locked vault copies nothing and
says why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Regulators and registries that don't tax the company (a division of
corporations, a licensing board), distinct from tax_agency. Core Role,
MCP descriptions, viewer label/noun/icon, docs, and tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
An independent recompute of the real books found no numeric
disagreements; review found 15 defects, all fixed with spec-first
regression tests:
- k1: a prepaid credited straight to capital is property; a member
paying a vendor through 2100 is cash. Docs and B-13 match.
- parties: a reversal of a reversal counts on its root's side;
liability-to-liability payments (member pays the card) count as paid.
- viewer: party money uses the server's cash accounts and full calendar
year; per-party "What for" scales multi-party entries.
- reconciliations: refuse a withdrawn statement before journaling; new
books.void_statement; reconciled lines can't be un-cleared; one shared
reconcilable-accounts rule for digest and viewer.
- CPA package includes receipts linked to entries; a file can't be both
filed on and linked to one entry.
- parties = "require" covers payroll, tax remittance (agency) and
disposal (buyer); no-party marks follow reversal chains and clear when
a vendor is tagged.
- derived member shares are frozen to exact amounts when posted, so a
settings change never re-splits history.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Entries may name several parties, each with an exact amount, a percent,
or a share derived from the members' capital lines or [[company.members]]
shares (entry.parties_set); shares always sum exactly. The 1099
worksheet counts a vendor's share. A member's party page is their
partner capital statement, from the same ledger_core::k1 source as the
CPA package.
books.set_tin stores any party's TIN (members' SSNs for K-1s) in the
vault (party.tin_set, last four in the books). The CPA package carries a
K-1 member worksheet (k1-members.csv, with a members' total that ties to
Schedule L) and the 1099 worksheet with full TINs, saved outside the
books only. files_jointly_with notes spouses' two K-1s land on one 1040.
Adversarial review fixes: reversed contributions no longer inflate K-1
contributions and distributions; a K-1 row never takes another party's
TIN; TIN detection covers dot/slash/dash separators; refusals redact
TINs; vendor tagging can't silently replace shared parties; reversals
follow re-apportioning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
ledger_core::parties::entry_flow: cash lines count; liability lines count
except the part paired against cash in the same entry, which counts once
as paid to the liability's creditor (or received from a lender). A
reimbursement to the members no longer nets to zero. What-for breakdown
comes from the same function and sums to the net exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
MCP: books.link_attachment, unlink_attachment, attachments; link_to on
attach_receipt and file_document; record_statement, statements;
books.reconcile previews and, confirmed against a recorded statement,
records the reconciliation; void_reconciliation, reconciliations;
import_bank_csv links its source file; digest lists unreconciled
accounts.
TIN vault: books.set_w9 takes the TIN and the form into the encrypted
vault (last four in the books); books.vendor_1099 include_tins and
save_csv_to for the CPA's copy; books.get_w9; books.purge_tin. A leak
test scans every tool output, the journal, the database, and the blobs.
[books] parties = off | suggest | require (default suggest): every
preview names the entry's party or suggests likely ones; require refuses
to post until it names one or is marked as having none (entry.no_party).
Viewer: documents on every subject page; statements and reconciliation
history per account; reconciliation pages; reconcile and
no-counterparty attention items; party money by period (paid out,
received in, net, what for, by month); Needs attention gets its own
full-width section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Parties: one directory for vendors, contractors, customers, employees,
members, platforms, agencies, banks, landlords (ledger-core parties.rs).
W-9/W-4 facts without the number (last four only; any nine-digit run in a
party field is refused, Treas. Reg. 301.6109-4). Vendor and customer
records link to parties on replay; core tags every bill, invoice, payment,
and reversal with its counterparty. Journal events party.*,
entry.party_set; payroll lines name the employee party.
MCP: books.add_party, update_party, set_party_roles, set_w9, set_w4,
parties, party, set_entry_party; draft_entry party; run_payroll employee;
show party. Viewer: Parties directory and party pages, search, journal
text, W-9 attention, demo data.
One definition of a party's money (ledger_core::parties::activity) for
MCP and viewer. 1099 amounts count payments from cash and financing
accounts (a member paying for the company counts; a card doesn't; a
prepayment counts when made): vendor_1099_report_with, B-19.
Store: attachment links to any subject, statements, reconciliations
(schema v11), with replay and projection. ledger-vault: encrypted TIN and
W-9 records (ChaCha20-Poly1305, key file outside the books).
Spec-blind tests for parties (core, store, MCP, viewer) and attachments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
- core: TaxBasisRule (cost IRC 1012, carryover IRC 723, personal-use conversion Treas. Reg. 1.168(i)-4(b)), TaxBasis with the contributor's adjusted basis, Asset::tax_basis and ::built_in_gain (IRC 704(c)); the register gains tax rule, contributor basis, tax basis, built-in gain, total tax basis, missing count (rules.md B-25; sources vendored in docs/spec/sources/irs-partnership-basis.md)
- store: asset.tax_basis_set journaled, replayed, projected (schema v10)
- mcp: books.set_asset_tax_basis; register_asset takes tax_basis_rule and contributor_basis, checked before anything registers (found by the spec-blind suite); list_assets and the register export (json, csv) carry tax basis; instructions say to record it for every asset
- import: the register's XLSX and PDF gain the federal tax basis table with citations
- viewer: tax basis on the Assets page, the asset page (a Tax basis card), the report, the journal, and Needs attention for assets without one
- just: check and the mutation recipes sweep this user's test book dirs older than an hour (killed runs had left 23 GB in RAM-backed /tmp)
- docs: the parties, attachments, TIN vault, completeness engine, and updates designs (docs/parties-and-attachments.md)
- tests: spec-blind tax basis suites in core, store, MCP; register PDF/XLSX and viewer tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX