Files
wanderbooks/docs/testing.md
T
swaitsandClaude Opus 5.5 08d1a9c230
check / check (push) Canceled after 0s
docs: the assumed list, checked against the standards; mutation score
Each formerly ASSUMED behavior is now CONFIRMED, CONVENTION, DESIGN, or
CHANGE (pending), with its source (Reg. S-X, ASC 230 via Deloitte DART,
IRS K-1 instructions, OpenStax). Two need engine work: members' equity
should allocate cumulatively so each member's beginning equals the prior
ending (#7), and the cash flow should show financing gross and classify
investing by account role (#8). Two questions for the CPA are listed.
The test doc comments that relied on them now cite the verdicts.

Core mutation run: 1,011 caught + 13 timeouts of 1,024 viable, 0 missed.
No benchmark budget, by decision.

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

8.8 KiB

Testing strategy

How wanderbooks knows its books are right. Strongest oracle first: a test's worth is where its idea of "correct" comes from, not what it runs. Formal proofs are in docs/verification.md; the authorities every rule comes from are in docs/spec/. Scope so far: ledger-core in full, and the store's journal safety; the other crates come next (gap list).

Layers

Layer Oracle (rank) Where Command Runtime Gates
Spec-derived examples written specs with sources (1) tests/spec_*.rs, invariants.rs, statements.rs, assets.rs, ... just check seconds every change
Regressions the bug, named (1) beside the code they cover, *_regression/named for the defect just check seconds every change
Compatibility contracts journals written under older rules must open (1) ledger-store/tests/store.rs (books_written_under_older_rules_still_open) just check <1s every change
Properties laws over all inputs (2) properties.rs, spec_*.rs, kernel_bridge.rs just check seconds every change
Metamorphic scaling, reversal, ordering invariance (2) spec_reports.rs just check seconds every change
Refusal law (store) a refused command never reaches the journal; the books reopen to the same reports (2, 3) store.rs refused_commands_never_reach_the_journal just check (STORE_CASES=5000 deeper) ~20s every change
Fault injection (store) a full disk or a cut-off append leaves whole, openable books (1) ledger-store/tests/faults.rs, store.rs (power_loss_at_any_byte_of_the_last_append_is_recovered) just check ~5s every change
Model-based stateful the Lean state machine, transcribed (3) tests/model.rs just check ~3s every change
Differential: Decimal vs i64 the kernel at two types (3) kernel_bridge.rs just check <1s every change
Differential: GnuCash another implementation, adjudicated (3) tests/oracle_gnucash.rs, oracle/gnucash/ just oracle ~2 min weekly
Formal proofs Kani (bounded) and Lean (unbounded) src/proofs.rs, verify/lean/ just prove ~3 min weekly
Fuzzing crash, hang, broken books (4) fuzz/fuzz_targets/, corpora in fuzz/corpus/ just fuzz <target> <secs> 10 min each weekly
Miri undefined behavior in our use of dependencies (4) the core's unit, bridge, invariant tests just miri ~2 min on demand
Golden tests/golden/fixture_company.json (5) invariants.rs just check <1s every change
Benchmarks a baseline (6) benches/books.rs just bench ~1 min none (no budget, by decision)
Mutation can the tests fail? ledger-core (2026-10-13: 1,011 caught + 13 timeouts of 1,024 viable, 0 missed) just mutants ~3 h weekly

CI: .github/workflows/check.yml runs just check on every push; scheduled.yml runs proofs, the oracle, fuzzing, and mutation weekly.

Seeds and failure databases: proptest's *.proptest-regressions files are checked in and replay on every run. The GnuCash oracle's books come from fixed seeds (SEED in oracle_gnucash.rs). Fuzz corpora live in fuzz/corpus/<target>.

Golden review: fixture_company.json is hand-verified (docs/oracle.md, Layer 1). The van depreciation golden was replaced on 2026-10-13 by a hand computation (van_schedule_is_the_hand_computed_one) after every row was checked against it.

Benchmarks (baseline 2026-10-13)

No budget, by decision (Stephen, 2026-10-13: the books do little work and are fast enough). The numbers are a baseline to spot a regression.

40-account chart, two-line entries across a year, on the development laptop:

1k entries 10k entries
clone the ledger (store rehearsal, per command) 56 µs 1.0 ms
post one more entry 1.0 µs 5.8 µs
trial balance 44 µs 1.07 ms
balance sheet 47 µs 1.04 ms
one month's P&L 25 µs 248 µs

Formerly assumed: checked against the standards (2026-10-13)

Stephen isn't an accountant and won't adjudicate these; each was checked against published sources instead (rank: FASB ASC via the Deloitte DART reproduction, SEC, IRS; then textbooks; software practice is evidence of convention only). Verdicts: CONFIRMED (a source supports it), CONVENTION (no standard governs; matches mainstream practice), DESIGN (not an accounting question), CHANGE (a source says otherwise; work pending).

# Behavior Verdict Source / note
1 A P&L whose start is after its end is refused DESIGN No standard governs a reversed range; refusing beats silently swapping a typo
2 A balance sheet omits zero-balance accounts CONFIRMED Reg. S-X 210.4-03(a): "No caption should be shown ... as to which the items and conditions are not present." A comparative statement must still show a zero beside a non-zero prior column
3 The lifetime profit_and_loss counts closing entries CONFIRMED Used as earnings not yet closed on the balance sheet; closed income is already in capital (OpenStax Principles of Financial Accounting 5.1). Its name misleads: rename to unclosed earnings (pending)
4 Closing a period with nothing to close still locks it CONVENTION A close is a lock (B-12); a dormant month must still lock and count for close-in-order
5 Draws and distributions are closed into capital at period end CONFIRMED OpenStax 5.1: dividends "are closed to the permanent account"; drawing accounts close to owner's capital. Monthly closes mean the year's K-1 Item L withdrawals sum across months
6 Reconciliation balances CONFIRMED Signed by the account's normal side (a card reads as owed), as statements show it; reconciling_a_card_reads_its_normal_side_...
7 A member's ending on one equity statement may differ by a cent from the next statement's beginning CHANGE (pending) A capital account is one running balance (Form 1065 K-1 instructions, Item L: beginning and ending capital "based on the partnership's books and records"). Allocate cumulatively so beginning equals the prior ending per member. CPA question below
8 Cash flow nets one line per account; investing comes only from registered assets CHANGE (pending) ASC 230-10-45-7: gross receipts and payments "more relevant" than net; 45-8 limits netting to quick-turnover items. Financing: contributions apart from distributions, borrowings apart from repayments. ASC 230-10-20/45-13: loans made and investments bought are investing, so classify by account role
9 An asset's life ends at purchase + life months, that day not held; day-count proration CONVENTION Exactly N months of service; day proration and the mid-month convention are both accepted book policies (ASC 360-10-35-4 not read: login)
10 A payroll entry has one line per account total CONFIRMED The per-employee detail the IRS requires ("Amounts and dates of all wage ... payments", Employment Tax Recordkeeping) is kept in the run; a summary entry is standard
11 A period with exactly the dates of an existing one is allowed DESIGN Nothing governs duplicate periods; harmless (the second close locks only). Refusing exact duplicates is an option

Questions for Krista Baker (CPA):

  1. Interim members' equity: is a per-member allocation of unclosed earnings acceptable on interim statements, or should the books close monthly so the capital accounts carry it?
  2. How should 2100 Due to Shareholders, and expenses the owners paid personally, appear on the cash flow statement: operating, financing, or as a non-cash disclosure (ASC 230-10-50-3, not yet read)?

Gap list: what isn't covered, and what would bring it in

Technique Status Trigger
Fault injection SKIP for the core (no I/O, clock, or network); DONE for the store's journal (full disk, cut-off append) the first I/O or clock in ledger-core
Integration SKIP for the core (no collaborators) the core gains one; covered at the store and MCP layers
End-to-end SKIP for the core (a library) MCP stdio tests cover the journeys
Security properties SKIP for the core (no roles, no secrets) the first permission concept, or a secret-bearing field
Concurrency stress SKIP for the core (no shared state) the first Arc, atomic, or lock in the core; the GUI and MCP server have threads (next)
Lean: apportion's sum law for any n OPEN Kani covers n <= 3, properties n <= 5; needs a permutation lemma
ASC paragraphs not yet read OPEN ASC 360-10-35-4, 340-10, 230-10-45-10, 606-10-55-37B, 210: needs a FASB basic-view login
GnuCash's own close OPEN the oracle books closes as ordinary entries; comparing GnuCash's Close Book needs its Scheme API
Store: differential replay vs. projection, concurrency (writer + snapshot reader), benchmarks at 10k, mutation NEXT then import, MCP, config, GUI, the same playbook