check / check (push) Canceled after 0s
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
104 lines
8.8 KiB
Markdown
104 lines
8.8 KiB
Markdown
# 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 |
|