Commit Graph
297 Commits
Author SHA1 Message Date
swaitsandClaude Opus 5.5 e265ed4339 Parties, attachment links, statements, reconciliations (store), TIN vault crate
check / check (push) Waiting to run
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
2026-10-15 14:43:40 -06:00
swaitsandClaude Opus 5.5 0a62353767 new: ignore the database copies kept when the projection is rebuilt (books-data/wanderbooks.mismatch.*)
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 13:53:44 -06:00
swaitsandClaude Opus 5.5 4826a39a88 fix: books with an asset linked to a since-reversed entry failed to open (the projection read-back re-checked the link against today's books); links restore as recorded, Ledger::acquisition_reversed reports ones to relink; regression test
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 13:51:17 -06:00
swaitsandClaude Opus 5.5 27e78a0ed5 Assets can be corrected before depreciation: books.correct_asset (cost, salvage, life, in-service date, with a reason), journaled as asset.corrected with the old figures; refused once depreciation is posted
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 13:41:15 -06:00
swaitsandClaude Opus 5.5 6e6f9d65b2 Fixed assets carry federal tax basis beside book value; tests sweep stale temp dirs
check / check (push) Waiting to run
- 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
2026-10-15 13:30:09 -06:00
swaitsandClaude Opus 5.5 49c49aef33 Asset descriptions can change: books.label_asset description, journaled as asset.described (with the old one), replayed, projected, shown in the journal
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 12:43:28 -06:00
swaitsandClaude Opus 5.5 2e14c702c0 viewer: journal text for asset.acquisition_linked; a test that every event type the store writes has plain-language journal text
check / check (push) Waiting to run
The acquisition link was added after the viewer's asset work and showed its raw event name in recent activity. The new test reads the event types from the store's replay arms, so the next new event without text fails it (checked: it fails without this fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 12:35:39 -06:00
swaitsandClaude Opus 5.5 ee7e205f5c Fixed assets, fully featured: tags, serials, acquisition links, label sheets, the fixed asset register, and an Assets section in the viewer; fix a flaky verify
check / check (push) Waiting to run
- core: asset tags (generated PREFIX-0001 or the human's own; unique ignoring case; never reused by another asset) and serial numbers (rules.md B-24); a journaled link from an asset to the entry that acquired it (posted, unreversed, debiting the cost account); fixed_asset_register(as_of), reconciled to the GL (unposted depreciation, unregistered cost)
- store: asset.tagged, asset.serial_set, asset.acquisition_linked journaled, replayed, projected (schema v9); differs_from now also compares control roles and tag history
- config: [books] asset_tag_prefix, default the company's initials without legal-form words
- import: Avery 5160/8160 label sheets with QR codes (geometry vendored from Avery, docs/spec/sources/avery-5160.md); the register as XLSX and PDF
- mcp: register_asset tags (and links) automatically; books.label_asset, books.asset_labels; asset tools take a tag; books.show asset / assets page / register report; export fixed_asset_register (json, csv, xlsx, pdf) and in the CPA package
- viewer: Assets page (register, reconciliation), asset page (identity, schedule posted vs not, entries, documents), Fixed asset register report, global search by tag/serial/description, journal text, needs-attention items
- fix: verify compared snapshot amounts as strings; a zero total summed in hash-map order could print "0" or "0.00", so agreeing books failed verify at random. Now by value (regression test)
- tests: spec-blind suites for tags, register, prefix, store, MCP; label and register PDF/XLSX tests; viewer tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 12:18:00 -06:00
swaitsandClaude Opus 5.5 767fd469cd testing: mutation scores for control roles and settings reload
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 10:43:00 -06:00
swaitsandClaude Opus 5.5 1771d87e0e Control roles in the journal (books.assign_role, books.roles); the MCP reloads wanderbooks.toml on change and reports settings problems (books.settings, digest); new writes a full commented wanderbooks.toml
check / check (push) Waiting to run
- core: ControlAccount keys, Ledger::assign_role/check_assign_role/role_account (rules.md B-23): one account per role, of the role's type, balance sheet roles move only when empty; invoices, bills, payroll, remittance, and closes post to the assigned account; an assigned role frees its default code for renumbering
- store: role.assigned journaled and replayed (strict account_id)
- mcp: settings.rs reloads by content at the start of each request, keeps the last good settings on a broken edit; books.settings with chart problems; digest settings_problems; instructions say when to edit the toml
- viewer: journal text for role.assigned
- cli: full example wanderbooks.toml, tested to load with every example uncommented
- tests: spec_roles.rs (25, spec-blind), store roles.rs (6), MCP reload/problems/roles, settings unit tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-15 10:30:16 -06:00
swaitsandClaude Opus 5.5 efa370dfa3 mcp: tests for the settings beside the books, year earnings (open periods, last year's partial close), year 1, a failed flush; drop by-class P&L's dead placeholder guard; record config, MCP, GUI mutation scores
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 20:45:49 -06:00
swaitsandClaude Opus 5.5 ca3a279219 viewer: drop parties' bill arms (the ledger tags bill entries with their vendor, which wins); burn tests for one covered month and breaking even
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 19:00:47 -06:00
swaitsandClaude Opus 5.5 7a6cc154ae gitignore every mutants output folder
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 18:16:01 -06:00
swaitsandClaude Opus 5.5 612d807f98 viewer: fmt::plain refuses underscore and exponent forms (test)
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 18:15:50 -06:00
swaitsandClaude Opus 5.5 c1f92ed3e8 store journals payroll.taxes_remitted; viewer tests for the numbers mutation found unchecked
check / check (push) Waiting to run
A payroll tax remittance was journaled as a plain entry, so the viewer
labeled it "Journal entry" and, at a 0.00 threshold, flagged it as
missing a receipt (the server posts it without one, like a payroll
run). The store now names it with payroll.taxes_remitted, as it names
runs with payroll.run; replay and the projection pass over it, and the
viewer's journal page describes it.

tests/view_model.rs: the cash history is never empty when cash moved
and ends on the last day it did; the prior month runs to the same day;
burn averages over only the months the books cover, half away from
zero; cash accounts found by name when the settings name none; card
accounts; each entry's accounts debits first, once each; only what
arrived since the last refresh is fresh; integrity counts files again
without rereading them; the invoice's customer, the bill payment's
vendor, payroll and remittance origins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 18:03:10 -06:00
swaitsandClaude Opus 5.5 f0fc725ca2 mcp: P&L by class no longer counts closing entries; oracle tests for every tool's output
check / check (push) Waiting to run
profit_and_loss_by_class re-added every posted entry in the range,
closing entries included: after closing January, its by-class P&L for
January showed an unclassified row of the whole period's net income,
negated. Closing entries are left out, as profit_and_loss_for_period
leaves them out.

tests/oracle_mcp.rs (24 tests): every export (CSV, JSON, XLSX, PDF,
IIF) against ledger-core's reports on the same books; the CPA package's
contents; P&L by class; the 1099 worksheet; disposals; depreciation;
payroll remittance; the digest against the journal; books.show's viewer
commands; resources; receipts and documents round-tripping; the 25 MiB
upload limit; bank import sides; reference resolution; partial FX lines;
preview balances. Written against the mutation run's 126 survivors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 17:14:27 -06:00
swaitsandClaude Opus 5.5 d1cbd845c0 just mutants-gui: also the view-model oracle and copy tests
check / check (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 16:43:15 -06:00
swaitsandClaude Opus 5.5 2fa84a37ff mcp: balance sheet's year earnings no longer charge this year with closing last year's; tests for what mutation found untested
check / check (push) Waiting to run
year_earnings counted every closing entry dated this year against this
year's income, but a close takes all unclosed income through its end,
earlier years included. A partnership closing January after an unclosed
December printed "Net income (loss)" -969 for a year earning +730, and
the closed December as "Undistributed earnings, prior years". The
unclosed part is now the income after the last close, from no earlier
than the fiscal year's start (closes are cumulative).

Unit tests: the 25 MiB upload limit, YYYY-MM periods, config_mentions
for every setting naming a code, report members, year earnings before
and after a close, and which entries may lose their last receipt.
tests/startup.rs: --help, the default data dir under HOME, settings
read from beside the books, and a held lock refused after the wait
instead of waited on forever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 15:58:48 -06:00
swaitsandClaude Opus 5.5 47d301bcfe viewer: the overview's numbers checked against ledger-core
check / check (push) Waiting to run
Kpis gets a spec (cash, liabilities, receivables, month-to-date against
the same days of last month, fiscal year to date, burn). tests/view_model.rs
computes each from ledger-core on random books (month ends, leap days,
any fiscal start) along with the period ranges, cash history, account
labels, journal count, receipt rule, origins and parties, the blob
root, and integrity counts. Burn rounds half away from zero, like the
rest of wanderbooks (it was banker's rounding).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 15:53:57 -06:00
swaitsandClaude Opus 5.5 eba85f9c77 viewer: click to copy values, amounts, and identifiers
check / check (push) Waiting to run
Company details (EIN, state number, address, every profile value), an
entry's number and amount, an account's code and balance, a document's
file name and SHA-256 copy on click: a pointer and copy icon on hover,
a check for a moment after. Amounts copy as their exact plain decimal
(fmt::plain: no "$", commas, or parentheses; "-" for negatives); where a
click already drills down (report cells, register and transaction rows,
general ledger lines), a right-click menu copies the amounts, the entry
number, or the description. The sidebar's chain check copies the
journal head and data folder.

tests/copy.rs: every display style copies back to the exact value
(property), and clicking the EIN, an entry's amount, and its number puts
exactly that on the clipboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 15:38:05 -06:00
swaitsandClaude Opus 5.5 8ac4e5438d mutants-gui timeout 300 s (tests compete with other mutants' builds); read_only test restores permissions on failure and sweeps leftovers from killed runs
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 14:46:01 -06:00
swaitsandClaude Opus 5.5 122fed0a7a cargo: tests keep their books in /tmp when TMPDIR points elsewhere
check / check (push) Canceled after 0s
just mutants* points TMPDIR at ~/.cache for cargo-mutants' tree copies;
the tests inherited it and synced every journal append to disk, several
times slower per mutant. Cargo's [env] reaches the tests it runs, not
cargo-mutants, so the copies stay in the cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 14:19:27 -06:00
swaitsandClaude Opus 5.5 8de1965707 just mutants-gui: run the accuracy tests per mutant (unit + spec_gui), 60 s timeout; the read-only test renders every page and timed out under parallel load
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 14:12:40 -06:00
swaitsandClaude Opus 5.5 9a1f009f98 spec: ASC paragraphs read directly (360, 340, 210, 230, 606); all confirm the rules
check / check (push) Canceled after 0s
Read in the ASC basic view and vendored as short quotes with paragraph
numbers (docs/spec/sources/fasb-asc.md). Depreciation: any systematic and
rational allocation of cost less salvage (360-10-35-4); prepaids are
current assets with no prescribed expensing method (340-10-05-4,
210-10-45-1/2); cash flows gross and classified three ways, owners'
advances financing (230-10-45-7/10/14(b)/15(b)); a principal's revenue
is gross (606-10-55-37B). Citation fixed: 230-10-45-14(b), not (2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 14:11:59 -06:00
swaitsandClaude Opus 5.5 fe9625502f viewer: proven read-only; its database check no longer creates SQLite files
check / check (push) Canceled after 0s
tests/read_only.rs: with every file and folder of the books read-only on
disk, the viewer loads, follows live, and renders every page (receipts
included), and every file keeps its bytes, size, and mtime; on writable
books nothing appears beside the database either.

Found: with no writer running, the snapshot's read-only open of the WAL
database had to create -wal/-shm (a write into the books) and failed on
read-only books, showing a false "database unreadable". With no -wal
file no writer has it open, so check_disk now reads it immutable.

just mutants-gui sweeps only what the viewer must get right: the view
model's numbers (books.rs) and their formatting (fmt.rs); 179 mutants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 13:54:40 -06:00
swaitsandClaude Opus 5.5 13a52c5849 just mutants*: no proptest shrinking (a mutant only needs a failing test); GUI timeout 60 s (its suite takes ~10 s)
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 13:47:48 -06:00
swaitsandClaude Opus 5.5 50f22af695 just mutants*: scratch copies go to the user's cache, never /tmp
check / check (push) Canceled after 0s
cargo-mutants copies and builds the tree per job in TMPDIR; on Arch /tmp
is tmpfs (RAM, up to half of it), and a GUI run put 20 GB there. Every
recipe now uses $XDG_CACHE_HOME/wanderbooks/mutants (~/.cache/...).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 13:19:20 -06:00
swaitsandClaude Opus 5.5 9837c62117 mcp: HTTP concurrency test; state-machine suite tolerates a contended disk
check / check (push) Canceled after 0s
tests/http_concurrency.rs: six HTTP clients at once against one store
(main.rs serve_http shares it): every bare entry refused at threshold
0.00, every receipted one posted, each contested draft posted exactly
once, books verify and balance, a restart replays the same head.

spec_mcp.rs: the response timeout is 180 s (a mutation run's jobs
contend for a synced disk; the baseline timed out at 30 s), and
MCP_CASES sets the state machine's cases (just mutants-mcp uses 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 12:06:24 -06:00
swaitsandClaude Opus 5.5 e6c69a9d34 just mutants-config/-mcp/-gui, weekly in CI
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 11:41:20 -06:00
swaitsandClaude Opus 5.5 9110cd888d gui: spec-blind suite; search finds the best match, axis ticks never overflow
check / check (push) Canceled after 0s
tests/spec_gui.rs (31 spec-blind tests): exact money text round-trips,
file names never escape the folder or hit a Windows device name, history
against a browser model, fuzzy search against a subsequence model, axis
ticks, the view model against ledger-core, and the attention list.

Found and fixed:
- fuzzy matched greedily, so "ac c" scored 1 for "c" (the "c" inside
  "ac") instead of 5 for the word "c"; it now scores the best alignment.
- ticks accumulated float error (a last tick at -5.55e-17 for a top of
  0), gave more than n + 2 ticks at large magnitudes with tiny spans,
  and overflowed for spans near f64's limit. Ticks are now whole
  multiples of the step, checked, with [lo, hi] when f64 has no nice step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 11:39:41 -06:00
swaitsandClaude Opus 5.5 1291547fc4 lean: apportion's parts sum to the whole for any number of parts
check / check (push) Canceled after 0s
The ranks of n parts are a permutation of 0..n-1 (being ahead is a strict
total order; pigeonhole by hand, core Lean has no subperm), so exactly
u parts get a unit and one gets the fraction. apportion_sum and
apportion_sum_of_shortfall; the audit shows only the standard axioms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 10:17:37 -06:00
swaitsandClaude Opus 5.5 3f20df9700 config, mcp, gui: specs from sources, spec-blind suites, and the bugs they found
check / check (push) Canceled after 0s
ledger-config: parse gets a sourced spec. A fiscal year starts on the
first of a month (IRS Pub. 538); tax_classification is read by whole
words (Treas. Reg. 301.7701-3, Schedule C instructions; "business corp"
no longer reads as an S corp, "corporation or S corporation" is refused);
duplicate or blank members and shared capital accounts are refused; a
negative or non-grammatical receipt threshold is refused; codes trimmed.
tests/spec_config.rs (65 spec-blind tests, 14 properties), fuzz target
fuzz_config_parse (weekly). Mutation: 55 of 55 viable caught (from 25/38).

The receipt policy moves to ledger-config, with one loader
(effective_receipt_policy) and one decimal grammar, so the server and
the viewer enforce and show the same rule (the viewer had its own: only
expense-like debits, a silent $25 default, and no config.json).

ledger-mcp: a module spec (MCP 2025-06-18 tools, JSON-RPC 2.0, the
operating contract). Amounts in are exactly -?digits[.digits]; a number
that is one account's code and another's id (vendors, classes alike) is
refused, not guessed; the threshold is validated at startup. Fixed:
add_line with a bad class journaled the line before refusing;
profit_and_loss_by_class ignored `class`; aging buckets said "0". A wire
in front of rmcp answers malformed JSON with -32700 and invalid requests
with -32600 and their id (rmcp dropped the first, omitted the id).
tests/spec_mcp.rs (26 spec-blind tests: every tool's result against its
own outputSchema, a random-call state machine checking the journal is
untouched by every refusal, amount grammar, previews, the receipt policy).

ledger-gui: money, accounting, and dollars never round (they rounded to
cents, banker's style); export file names avoid Windows device names,
trailing dots and spaces, empty names, and 255-byte overflow; specs on
attention, history, fuzzy search, and axis ticks.

ledger-core: the lifetime profit_and_loss is renamed unclosed_earnings
(docs/testing.md #3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 10:13:05 -06:00
swaitsandClaude Opus 5.5 5b8910a1cd books.renumber_account: give an account a new code, history kept
check / check (push) Canceled after 0s
Ledger::renumber_account changes only the code (entries name accounts by
id): refuses an unknown account, a blank or used code, and a control code
for an account of the wrong type or a placeholder. Journaled as
account.renumbered (with the previous code), replayed, projected, shown
in the viewer's journal. The MCP tool lists wanderbooks.toml settings
that still name the old code. The server instructions now warn that the
engine can't tell a same-type account with another purpose at a control
code (a hosting expense at payroll's 6100).

Tests: core property (only the code changes; trial balance equal), core
refusals, the store's command property, MCP end to end with restart.
Mutation on the new code: 8 of 8 viable caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-14 08:00:58 -06:00
swaitsandClaude Opus 5.5 ccec4e9439 import: XLSX layout spec and tests; mutation 53% -> one equivalent left
check / check (push) Canceled after 0s
report_to_xlsx gains INVARIANTS 4-6 (header block, right-aligned amount
headings, spacer and account spacing, footer one empty row down, rules
exactly where rule_above says, exact text grouped in threes), tested by
reading the workbook's styles, merges and column widths. The general
ledger's footer now sits one empty row down like the other sheets.

Also killed: IIF transliteration arms, tax summary's optional sections,
and day-equals-month bank dates. `just mutants-import`, weekly in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 23:01:07 -06:00
swaitsandClaude Opus 5.5 abba6241e7 import: tests for the XLSX ledger and journal sheets, header and footer, tax summary differences, cash flow adjustments, owner limit
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 21:36:02 -06:00
swaitsandClaude Opus 5.5 11e33edc60 import: spec-blind suite (40 tests); POSIX two-digit years; exact sub-cent text
check / check (push) Canceled after 0s
tests/spec_import.rs, written from the import crate's specs and sources
alone: every statement foots as printed on random books (P&L, balance
sheet with and without members, cash flow, trial balance, members'
equity), format_amount round-trips, bank CSV round-trips in both layouts
with date-order scenarios, IIF transactions sum to zero with every
account declared and ASCII fields, XLSX cells read back through a small
zip/DEFLATE reader, PDF labels with Typst markup render as text and the
bytes are deterministic, chart CSV and templates.

Its two findings, both fixed:
- "1/15/69" parsed as 2069: chrono's %y cuts over at 70, POSIX strptime
  at 69 (69-99 are 1900s). Two-digit years are computed by POSIX now.
- An amount finer than a cent went into an XLSX as rounded text ("1.01"
  for 1.005); it is written as its exact text now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 21:17:02 -06:00
swaitsandClaude Opus 5.5 9f81fc0817 ci: weekly fuzz_import_csv; testing.md: import in progress
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 21:03:26 -06:00
swaitsandClaude Opus 5.5 38a24c7609 import: specs for the PDF, XLSX, and chart parsers; chart header on the first non-blank row
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 21:03:03 -06:00
swaitsandClaude Opus 5.5 e52b88433c import: templates match the engine's control accounts; report specs
check / check (push) Canceled after 0s
- Starter charts put "Platform and Payment Processing Fees" at 6100, the
  code payroll posts wages to, so a template company that ran payroll
  would book its wages as processing fees; their own payroll accounts
  (7000, 7050, 2300) were never used. Now 6100 Salary & Wage Expense and
  6110 Payroll Tax Expense (the engine's control accounts, with tax
  categories), fees at 6060, and the unused accounts gone (the
  withholding payables 2110-2150 are created when payroll first runs).
  New tests: every template imports with categories valid for its
  return; no template puts another account at a control code; a
  partnership names every partner and its shares sum to 100%.
- format_amount printed a negative zero ("(0.00)" for -0.004); it rounds
  half away from zero first now, like amount_parts.
- Specs (GOAL/SOURCE/INVARIANTS) for statement finishing, P&L, balance
  sheet, cash flow, trial balance, members' equity (each must foot as
  printed), format_amount, and the templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 21:01:51 -06:00
swaitsandClaude Opus 5.5 ea180d1d8e tax summary: Schedule K items stated separately; XLSX never shows a wrong amount
check / check (push) Canceled after 0s
From the IRS form instructions (now quoted in docs/spec/sources/
irs-tax-lines.md):
- On Forms 1065 and 1120-S, Schedule K takes "only taxable portfolio
  interest", and charitable contributions are among the items the entity
  "must state separately". The tax summary counted both in ordinary
  income and deductions; they now go in TaxSummary::separately_stated,
  outside book_net and tax_net, and the report, MCP export, and viewer
  list them under "Separately stated to owners (Schedule K)".
- Schedule C: "Do not include charitable contributions". The category no
  longer applies to Schedule C (a sole proprietor's gift is personal, an
  owner draw).
- interest_income's note: interest earned in the business itself is
  ordinary income (other_income).

XLSX: a cell number is a binary double and Excel honors 15 significant
digits (Microsoft, "Excel specifications and limits"), so an amount over
$9,999,999,999,999.99 or finer than a cent is written as its exact text,
right-aligned, instead of a number Excel would show wrong. The old
conversion also fell back to 0.0 silently; gone. Property test: every
whole-cent amount up to 15 digits round-trips exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 20:57:00 -06:00
swaitsandClaude Opus 5.5 c72a762ef1 import: grounded in Intuit's specs; atomic chart and bank imports
check / check (push) Canceled after 0s
New vendored sources (docs/spec/sources/): Intuit's bank CSV upload and
IIF formats, ECMA-376 / Excel number storage, and the IRS form lines the
tax categories map to.

IIF export (rewritten to Intuit's IIF Import Kit):
- declares every account first (!ACCNT NAME, ACCNTTYPE, ACCNUM): an
  undeclared account is created in QuickBooks "as a new bank account";
  the MCP export types them (cash accounts BANK, the control accounts AR
  and AP, registered fixed assets FIXASSET, the rest by type);
- subaccounts by colon path, parents first; colons in a name replaced;
- ASCII only (IIF is ASCII text), dashes and quotes transliterated;
- the kit's 2007+ journal layout and M/D/YYYY dates; an entry using an
  undeclared account is refused.

Bank CSV:
- the 4-column layout is money out then money in by position, which
  matches Intuit's "Credit, Debit" and banks' "Debit, Credit" both (the
  doc's "Expense, Income" labels were wrong, the parsing was not);
- a slash date's order comes from the file (a part over 12 settles it)
  or the caller's new date_order, never a guess: Intuit recommends
  dd/mm/yyyy, US banks write mm/dd/yyyy;
- fractions of a cent and double negatives ("(-5)") are refused; the
  header is recognized on the first non-blank row.

Chart CSV: a blank code or name is refused with its row (found by the
new fuzz_import_csv target), and the core now refuses a blank account
code or name too (replay keeps older books).

MCP:
- books.import_chart_csv and books.import_bank_csv rehearse the whole
  file on a copy of the ledger first: a bad row anywhere refuses the
  file (before, a duplicate within the file, a parent after its child,
  or a date in a closed period left earlier rows behind);
- bank rules: the longest matching pattern wins (they arrive in a map,
  so "first match" changed from run to run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 20:47:41 -06:00
swaitsandClaude Opus 5.5 f1bdac4bca store: last mutation survivors killed; 98% with four equivalent left
check / check (push) Canceled after 0s
Final store mutation run: 357 caught + 1 timeout of 365 viable (from 82%
this morning). Three of its seven survivors now have tests:
- replay refuses a re-chained journal whose schedule posting differs
  from what it re-derives (check_replayed_entry guards that event);
- a disposal refused for an account's type says the type it needed;
- a database whose last pages are damaged while its settings still read
  fails the integrity check and is rebuilt (verified to fail with the
  check disabled: "database disk image is malformed").
The four left are equivalent: sort_value's two arms, OpenFlags | vs ^,
and writing the long form of a sweepless retained-earnings close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:59:37 -06:00
swaitsandClaude Opus 5.5 7559b4c808 store: second mutation pass (345/373); trim what rehearsal covers; tests
check / check (push) Canceled after 0s
Store mutation rerun: 345 caught of 373 viable (92.5%, from 82%). The
survivors, triaged:

- Removed code the survivors showed could never matter: set_line_class's
  hand-written checks (its rehearsal refuses first), ensure_on (the
  engine creates missing control accounts itself), and verify's
  live-vs-replay halves of the asset, class, and payroll comparisons
  (the whole-ledger comparison above them already checks that; the
  SQLite read-back halves stay).
- New tests: disposal account types; depreciation periods must be real
  months; search_entries (filters, newest first, paging, no drafts);
  the database's invoice amount paid and status; replay refusing a
  re-chained journal whose closing entry differs from what it
  re-derives; a projection missing a table.
- Equivalent: sort_value's arms (serde_json's maps are already sorted
  without preserve_order, which nothing enables; the function stays as
  the guard if anything ever does), OpenFlags | vs ^ (disjoint bits), and
  writing the long form of a sweepless retained-earnings close (replay
  reads both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:46:00 -06:00
swaitsandClaude Opus 5.5 d67e897f1b store: a database with the wrong currency is rebuilt, not blamed on the journal
check / check (push) Canceled after 0s
Store::open refused books whose database recorded a base currency other
than the journal's, with JournalCorrupt, though the journal was fine and
the database is derived (a mutation-testing survivor: nothing tested
the case). Now the currency comes from the journal, and a database that
fails its integrity check or disagrees on currency goes through the same
path: kept aside for forensics and rebuilt (open SCENARIO 6).

Also tests: the viewer tells a database Behind the journal from one
Ahead of what it has read; the table check counts missing and
unexpected rows; a new journal creates its directory; a stored blob
exists and an unknown one doesn't.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:37:20 -06:00
swaitsandClaude Opus 5.5 0cf8fb842f store: tests for mutation survivors; drop pre-checks the rehearsal covers
check / check (push) Canceled after 0s
From the first store mutation run (324 caught of 396 viable):
- receive_payment and pay_bill repeated the engine's refusals by hand
  after rehearsing the engine itself, so those checks could never fire
  (their mutants survived). Removed: the rehearsal is the check.
- New hand-computed tests: a disposal's final catch-up after some
  depreciation was posted (200 of 300, gain 100); a disposal on the
  purchase date (allowed) or the day before (refused); a retained
  earnings close with a sweep replays it; a float inside an array is
  refused; the viewer's journal and head hash.
- The concurrency test's reader stops when the writer thread ends, not
  only when it says it's done, so a mutant that kills the writer fails
  the test at once instead of spinning to the five-minute timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:32:32 -06:00
swaitsandClaude Opus 5.5 db00b74e82 ci: weekly store mutation run and replay fuzzing
check / check (push) Canceled after 0s
`just mutants-store` (64 random cases per mutant, temp books on a RAM
disk in CI) runs beside the core's weekly; fuzz_store_replay joins the
weekly fuzz matrix. docs/testing.md lists the spec-blind store suite,
the refusal property's converse law, and the replay fuzz oracles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:26:27 -06:00
swaitsandClaude Opus 5.5 4d8d5daf37 store: refusals leave the live books untouched; per-filing names; spec suite
check / check (push) Canceled after 0s
The refusal property gained the converse law (an accepted command
journals something; only batch runs may find nothing to do) and checks
the store's class and payroll listings. It found a schedule registered
with an out-of-range total whose first month was refused after its entry
was begun: the live ledger kept a draft the journal never had.

- Core: compound commands (post_schedule_month, reverse_entry,
  close_period_to, create_invoice, create_bill, receive_payment,
  pay_bill, run_payroll, remit_payroll_taxes) run inside
  Ledger::atomically, so a refusal partway leaves the ledger exactly as
  it was.
- Core: a schedule total no line could carry, or finer than a cent, and
  an asset cost beyond the line range, are refused at registration.
  Replay keeps older schedules (replay_register_schedule).
- Store: add_line, add_line_fx, mark_cleared, set_fx_rate,
  post_schedule_month and close_period_to changed the live ledger before
  journaling; they now run on a copy, journal what it produced, then
  swap it in, so a failed write leaves the live books as journaled.

New spec-blind suite tests/spec_store.rs (57 tests written from the
store's specs alone). Its two findings, adjudicated:
- A file filed twice under different names listed the first name and
  type for both (the projection kept them per blob). Schema v8 gives
  each attachment its own; older databases backfill and heal by verify.
- Withdrawn attachments stay in the store's listing, marked (the MCP
  listing hides them): the spec and doc said "omit" and are corrected.

Also removes journal::truncate_to_last_good_seq: unused, and it dropped
everything after the first bad complete line, which the books now treat
as corruption for a person, never repaired automatically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:24:34 -06:00
swaitsandClaude Opus 5.5 8a49f0ff0e store: specs for the write path, open, verify, heal, receipts, viewer
check / check (push) Canceled after 0s
GOAL/SOURCE/INVARIANTS/SCENARIOS blocks, citing R-11, R-12, R-13a,
docs/receipt-store.md, IRS Pub. 583, and Rev. Proc. 98-25: the module
spec every command method follows (refuse whole, journal first, gapless
chained lines, replay equals live, multi-step commands rehearsed whole,
nothing deleted), Store::create, open, verify, verify_and_heal,
attach_receipt, link_receipt, unlink_receipt, withdraw_attachment, and
Snapshot::refresh. Also moves ensure_control_account's doc comment back
onto it (it had been stacked on rehearse).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:08:33 -06:00
swaitsandClaude Opus 5.5 2cbb6a263f store: benchmarks at 10k entries on disk
check / check (push) Canceled after 0s
benches/store.rs (criterion, in `just bench`): open 249 ms, viewer load
616 ms, full verify 809 ms, post one entry (four synced appends) 62 ms.
The books are built on a temp dir and copied under target/ so every
sync hits a real disk. Baselines in docs/testing.md; no budget, by
decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 19:06:11 -06:00
swaitsandClaude Opus 5.5 fb3b4ab476 closes: a run of members closes splits cumulatively
check / check (push) Canceled after 0s
Each close into members' capital rounded on its own, ties to the
earlier member, so twelve monthly 50/50 closes of odd-cent income could
leave one member several cents ahead of half the year (docs/testing.md
#12, found with the members' equity fix).

- CloseTo::Members now splits cumulatively over a run (closes back to
  back with the same members and shares): each close gives a member
  allocate(run total so far) - allocate(run total before). Periods
  record their close order and a cumulative close's members and net
  (Period::close_seq, member_close) so the next close continues the run
  and replay recomputes it exactly. A close to anything else, or other
  shares, ends the run.
- Journaled "rounding": "cumulative". The per-close rule is kept as
  CloseTo::MembersPerClose for closes journaled "largest_remainder";
  older closes still replay with the last member absorbing it.
- Lean: runPart_telescopes (a member's total from a run is their
  allocation of the run's total) and closePart_total (each close hands
  out exactly its net), audited to the standard axioms.
- Tests: hand-computed three one-cent months (per close A 0.03 / B 0;
  cumulative A 0.02 / B 0.01), a run ending, a property that each member
  stays within a cent of their share (fails on the per-close rule), and
  a store contract that each rule journals and replays as itself.

Wanderland's books have no closes yet, so all of theirs use the new rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 18:58:21 -06:00