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
Max-testing pass, steps 2-4.
Spec-blind tests (rank 1-2): four agents, each given only the specs and
the public interface (doc comments and signatures, every body removed,
never src/ or the existing tests), wrote tests/spec_reports.rs (62),
spec_ledger.rs (72), spec_arap.rs (42), spec_assets_payroll.rs (50):
hand-computed examples per scenario and a proptest law per invariant
(conservation, partition, invariance, scaling, reversal cancels,
"a refusal leaves the books unchanged" over random invalid commands).
They found:
1. A refused payroll run changed the books: an all-zero run passed
validation, then failed at post after creating seven payroll
accounts and using up an entry id. validate_payroll_lines now
refuses a run with no gross pay.
2. A refused add_account used up an account id (empty code, duplicate,
unknown parent). The id is now taken only when the account is added.
3. Stale prose: close_period_to and members_equity still described the
old last-member rounding.
4. Open (left #[ignore]d pending Stephen): depreciation_schedule's doc
says "through the month containing" the date; the code prorates that
month by days.
GnuCash oracle (rank 3, an oracle never the spec): `just oracle` builds
GnuCash 5.10 in a container pinned by base digest and package versions
(oracle/gnucash/) and runs tests/oracle_gnucash.rs: 200 seeded random
books built through wanderbooks (entries, foreign lines, reversals,
monthly closes), every account's balance at month ends and random dates
and each month's P&L, compared exactly: 15,800 comparisons. First run:
4,724 differences, all from foreign lines; without them, zero.
Adjudicated from principles (rules.md B-3): wanderbooks booked
foreign * rate unrounded ($108.34567); a ledger in one money holds no
fractions of its smallest unit, and its cash must match a bank. Stephen
decided: book in the base currency's minor unit, half away from zero.
add_line_fx now rounds; minor units per ISO 4217 list one (2026-09-17,
SIX), generated into Currency::minor_units and recorded in
docs/spec/sources/iso-4217.md. Replay now takes the journaled base
amount (replay_add_line_fx), so lines booked before replay exactly as
booked (contract: books_written_under_older_rules_still_open). Now:
zero differences, foreign lines included.
Tests: 449, from 223.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Stephen asked that specs come from recognized documents, not from
anyone's memory. There is no single US bookkeeping bible; authority is
layered, and this records it:
- docs/spec/sources/: verbatim excerpts (whitespace normalized), each
with URL, retrieval date, and the SHA-256 of the fetched file, of
public-domain texts: Pacioli's 1494 treatise in Geijsbeek's 1914
translation (double entry, the trial balance, one money, write only
forward, correct by opposite entry, close into capital); IRS Pub. 583,
538, 946, 15; the 1099-MISC/NEC instructions (2025 and 2026); 26 U.S.C.
§6041, §6041A, §704; Treas. Reg. §1.263(a)-1(f) and -4(f); Notice
2015-82; Rev. Proc. 98-25; Form 1065 instructions; 17 U.S.C. §105. The
excerpts were extracted by matching each passage against the fetched
file, so none can contain text the source doesn't.
- docs/spec/rules.md: the rule catalog, B-1 to B-22. Each rule says
AUTHORITY, CONVENTION, or DECISION; cites its sources (FASB's
Codification and Concepts Statement 8 and OpenStax quoted briefly,
never copied: copyright and CC BY-NC-SA); and names the code that
enforces it and the tests and proofs that check it.
- Spec blocks in the code now carry SOURCE: lines into the catalog;
AGENTS.md makes sourcing rule 0 of the formal-methods bar.
Corrections the sources turned up: the de minimis limit is $500 in the
regulation and $2,500 by Notice 2015-82 (both now cited); 1099
thresholds after 2026 are inflation-indexed, so the engine's flat
$2,000 for 2027+ is an open question (B-19). Unverified ASC paragraphs
(360-10-35-4, 340-10, 230-10-45-10, 606-10-55-37B, 210) are listed in
docs/spec/README.md until someone with a FASB basic-view login reads
them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX