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
Max-testing pass, ranks 4-6 and step 9 for ledger-core.
Fuzzing (rank 4): fuzz_op_sequence now drives balanced pairs, foreign
lines, periods, closes, and amounts from raw decimal bits, and asserts
the books stay valid (TB balances, posted entries validate, no draft in
a closed period); new fuzz_core_serde (every persisted core type
round-trips) and fuzz_store_open (a corrupt journal is an error, never a
panic); hangs count (-timeout=10). 54M executions; one crash:
- fuzz_journal_line: a number too big for an integer parsed as a float,
and its canonical form (which the hash chain covers) re-canonicalized
differently. The journal never holds floats (R-2), so the reader now
refuses them (JournalLine::parse, used by every journal reader);
regression a_journal_line_with_a_float_is_refused. Checked first: the
real books' journal has none. Rerun: 13.2M executions, clean.
Seeds in fuzz/seeds/ (synthetic, minimal); the grown corpora stay out of
git. `just fuzz <target> <seconds>`.
Miri (rank 4): the core's unit, bridge, and invariant tests under Miri
(our code has no unsafe; rust_decimal and chrono do): no undefined
behavior. `just miri`. A pinned nightly (nightly-2026-10-01, with miri)
is now in mise.toml.
Benchmarks (rank 6): criterion (new dev-dependency: the playbook's Rust
benchmark tool; none existed) in benches/books.rs; baselines in
docs/testing.md. The store's per-command rehearsal clone costs ~1 ms at
10k entries. No budget yet, so nothing gates on them. `just bench`.
CI: .github/workflows/check.yml runs `just check` on every push;
scheduled.yml runs proofs, the GnuCash oracle, fuzzing, and mutation
weekly. Tools come from mise.toml.
docs/testing.md: every layer, its oracle rank, command, runtime, and
gate; benchmark baselines; the ASSUMED invariants for Stephen to reject;
the gap list with triggers. docs/oracle.md's fuzzing paragraph had
claimed persistent corpora that didn't exist; corrected.
Also: ledger-import now inherits the workspace lints (nightly cargo
warned).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Every report a CPA expects, laid out the way US small-business books
read (QuickBooks as the reference):
- Exports: books.export gains general_ledger (by account, running
balances, splits), general_journal (the entry-ordered listing, which
the CPA package used to mislabel "general ledger"; alias journal),
members_equity, and trial_balance as of a date, in xlsx/csv/json, and
PDF for all five statements (P&L, balance sheet, cash flow, trial
balance, members' equity). books.trial_balance takes as_of.
- CPA package: every statement as PDF and XLSX, plus the general ledger
and general journal, all for the fiscal year to date.
- Layout (ledger-import::report): multi-column statements (Total;
Debit/Credit; one column per member plus Total), "$" on subtotals and
totals, a rule above every sum (none over an empty section), a double
rule under statement totals, zero as 0.00, negatives in parentheses
with the closing paren hanging so decimal points align.
- Terms: "Net income (loss)"; for a multi-member LLC, "Members' equity",
member contributions and distributions named in the cash flow, and the
balance sheet splitting retained earnings (the Retained Earnings
account plus earnings not yet closed from earlier years) from the
fiscal year's net income (loss); the cash flow's "Adjustments to
reconcile net income (loss) to net cash from operating activities" and
"Net increase (decrease) in cash".
- PDF: Inter replaces the typst-assets serif fonts (bundled, SIL OFL 1.1,
license in fonts/; from the official v4.1 release), centered title
block, column-header row, full-width rules, footer "Accrual basis ·
Prepared <date> · Journal seq N" with page numbers. Amounts on total
rows stay regular weight so bold figures can't leave the decimal
column. typst-assets dependency removed.
- XLSX: centered title block, header row, Excel accounting formats with
"$" attached on sums, general ledger and general journal workbooks.
- Config: wanderbooks.toml [[company.members]] (name, share "50%" or
"0.5", capital_account; shares must total 100%) and [books]
accounting_basis (accrual | cash).
- Core: RETAINED_EARNINGS_CODE names the "3999" that period close uses.
- Fix: "today" is local time, not UTC; in US evenings UTC is tomorrow,
so reports defaulting to "through today" included the next day and
footers carried the wrong date.
Tests: layout (currency and rule placement, retained earnings vs. this
year's net income for a member LLC, footer text); exact template data;
deterministic PDFs; config parsing (members, shares, basis); stdio:
trial balance as of, February general ledger (beginning 1,004.00, splits,
ending 1,003.50), members' equity (contributions 1,000.00, net income
3.50, ending 1,003.50), members' equity PDF, the CPA package's ten files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
The printpdf statements padded columns with spaces in a proportional font
(amounts didn't line up), had no company name, printed negatives as
"-207.68", and put everything on one page (a long statement was silently
cut off past ~70 lines). Now:
- PDFs are typeset by Typst from one template,
ledger-import/templates/statement.typ. The laid-out statement goes in as
JSON data (sys.inputs), so account names are never parsed as markup.
Fonts are bundled (typst-assets: Libertinus Serif), so output doesn't
depend on the machine and is byte-for-byte deterministic. Statements
paginate, with the company/title repeated on later pages and "Page n of
m" plus "Prepared from journal through seq N" in the footer.
- US accounting presentation, decided once in ledger-import::report and
applied by both PDF and XLSX: tabular figures; "$" on each section's
first amount and on every subtotal and total, pinned to the column's
left edge; negatives in parentheses with the closing paren hanging (its
width reserved on every row, so decimal points align); zero as a dash;
a single rule above sums (none over an empty section) and a double rule
under statement totals; amounts never bold, because bold figures are
wider and would leave the decimal column. XLSX uses Excel's own
Accounting formats for the same result.
- Cash flow: "Net change in cash" is a subtotal; only "Cash at end of
period" takes the double rule.
- Core fix: cash_flow walked the chart in HashMap order, so the order of
working-capital and financing lines could change between runs. Lines
now follow account code. Test: five owners, twenty runs, always
3000/3001/3003/3005/3009; fails without the sort.
New dependencies (ledger-import): typst, typst-layout, typst-pdf,
typst-assets (fonts), typst-as-lib (World/font plumbing), serde_json (the
template's data). Removed: printpdf. Release ledger-mcp grows by the
bundled fonts.
Tests: exact template data for a hand-built P&L (like a golden); same
statement renders to identical bytes; 200 rows of Typst markup
characters render literally and span several pages; currency-sign and
rule placement; amount splitting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu