From 08d1a9c23033661ff9a8cedfb2490a48c0c3e625 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 13 Oct 2026 18:18:30 -0600 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX --- crates/ledger-core/src/reports.rs | 2 +- crates/ledger-core/tests/spec_arap.rs | 2 +- .../ledger-core/tests/spec_assets_payroll.rs | 6 +- crates/ledger-core/tests/spec_ledger.rs | 2 +- crates/ledger-core/tests/spec_reports.rs | 8 +-- docs/testing.md | 55 ++++++++++--------- 6 files changed, 40 insertions(+), 35 deletions(-) diff --git a/crates/ledger-core/src/reports.rs b/crates/ledger-core/src/reports.rs index dd941a1..305bff9 100644 --- a/crates/ledger-core/src/reports.rs +++ b/crates/ledger-core/src/reports.rs @@ -458,7 +458,7 @@ fn statement_lines( /// SCENARIOS: /// 1. IF a month was closed: SHOULD still report that month's earnings. /// BECAUSE closing doesn't make a period break even (R-9). -/// 2. IF `start` is after `end`: SHOULD refuse. (ASSUMED) +/// 2. IF `start` is after `end`: SHOULD refuse. (DESIGN: no standard governs a reversed range; docs/testing.md #1) /// ``` pub fn profit_and_loss_for_period( ledger: &Ledger, diff --git a/crates/ledger-core/tests/spec_arap.rs b/crates/ledger-core/tests/spec_arap.rs index 5dbbde5..b16981e 100644 --- a/crates/ledger-core/tests/spec_arap.rs +++ b/crates/ledger-core/tests/spec_arap.rs @@ -538,7 +538,7 @@ fn create_invoice_refuses_due_before_date_books_unchanged() { /// Closes January 2026 on `l`. /// -/// ASSUMED: closing a period with nothing in it still marks it closed +/// CONVENTION (docs/testing.md #4): closing a period with nothing in it still marks it closed /// (close_period returns Ok(None)); the precondition is asserted. fn close_january(l: &mut Ledger) { let p = l diff --git a/crates/ledger-core/tests/spec_assets_payroll.rs b/crates/ledger-core/tests/spec_assets_payroll.rs index de1ebfd..f8cd4b0 100644 --- a/crates/ledger-core/tests/spec_assets_payroll.rs +++ b/crates/ledger-core/tests/spec_assets_payroll.rs @@ -316,7 +316,7 @@ fn schedule_clamps_to_end_of_life() { /// life ends 2026-03-15 so Mar holds 1..14 = 14/31 → 140.00. 17/31 + 14/31 /// = one full month, and the total is exactly 620.00. /// -/// ASSUMED: life ends on purchase_date + useful_life_months and the end day +/// CONVENTION (docs/testing.md #9): life ends on purchase_date + useful_life_months and the end day /// itself is not held (matches the doc's "17/31 + 14/30" pairing for a /// purchase on the 15th). #[test] @@ -343,7 +343,7 @@ fn partial_first_and_last_months_make_one_full_month_b15() { /// Jan 17/31 → 170.00, Feb 310.00, Mar 310.00 (acc 790.00), Apr (30 days) /// trues up to 930 − 790 = 140.00, not 14/30 × 310 = 144.67. /// -/// ASSUMED: same end-of-life convention as the test above. +/// CONVENTION (#9): same end-of-life convention as the test above. #[test] fn last_month_trues_up_unequal_month_lengths_b15() { let a = asset("1030.00", "100.00", 3, date(2026, 1, 15)); @@ -867,7 +867,7 @@ fn run_payroll_posts_hand_worked_entry() { /// 750.00, state 250.00, SS 620 + 248 = 868.00, Medicare 145 + 58 = 203.00, /// other 200.00, net 3567.50 + 1697.00 = 5264.50. /// -/// ASSUMED: lines are aggregated per account ("zero totals are skipped" +/// CONFIRMED (docs/testing.md #10): lines are aggregated per account ("zero totals are skipped" /// implies totals), not one line per employee. #[test] fn run_payroll_aggregates_employees() { diff --git a/crates/ledger-core/tests/spec_ledger.rs b/crates/ledger-core/tests/spec_ledger.rs index 9106952..7f300ef 100644 --- a/crates/ledger-core/tests/spec_ledger.rs +++ b/crates/ledger-core/tests/spec_ledger.rs @@ -1235,7 +1235,7 @@ fn a_loss_closes_as_a_debit_to_equity() { } /// Oracle: close INVARIANT 5: nothing to close posts nothing and returns -/// None. ASSUMED: the period is still marked closed (step 3 of the doc). +/// None. CONVENTION (docs/testing.md #4): the period is still marked closed (step 3 of the doc). #[test] fn a_close_with_nothing_to_close_posts_no_entry() { let mut b = books(); diff --git a/crates/ledger-core/tests/spec_reports.rs b/crates/ledger-core/tests/spec_reports.rs index 1e3e08c..764951b 100644 --- a/crates/ledger-core/tests/spec_reports.rs +++ b/crates/ledger-core/tests/spec_reports.rs @@ -531,7 +531,7 @@ fn pnl_closed_month_still_reports_its_earnings() { assert_eq!(year.net_income(), c(7000)); } -/// Oracle: P&L SCENARIO 2 (ASSUMED in the spec itself) — start after end +/// Oracle: P&L SCENARIO 2 (DESIGN, docs/testing.md #1) — start after end /// refuses. #[test] fn pnl_period_start_after_end_refuses() { @@ -1009,7 +1009,7 @@ fn me_refuses_bad_shares() { /// earnings 100.01 -> A 50.01, B 50.00; period income 201.01 -> A 100.51, /// B 100.50. Total ending 2251.02 = BS equity. /// -/// ASSUMED: the beginning (unclosed prior earnings) and the period's income +/// CHANGE PENDING (docs/testing.md #7): the beginning (unclosed prior earnings) and the period's income /// are each split separately (INVARIANT 3 names them separately), not /// cumulative-then-differenced. #[test] @@ -1407,7 +1407,7 @@ fn sorted_amounts(lines: &[ledger_core::CashFlowLine]) -> Vec { /// 400; AR up 300 (-300), AP up 80 (+80); CFO 180; financing loan 300 + /// contribution 100 = 400; net change 580 = cash 1000 -> 1580. /// -/// ASSUMED: one working-capital / financing line per account that changed. +/// CHANGE PENDING (docs/testing.md #8): one working-capital / financing line per account that changed. #[test] fn cf_hand_computed_with_designated_debt() { let co = cf_example_books(); @@ -2418,7 +2418,7 @@ proptest! { /// NI + depreciation + working capital; with or without a close and a /// designated loan. /// - /// ASSUMED: with no registered fixed assets, depreciation and investing + /// CHANGE PENDING (docs/testing.md #8): with no registered fixed assets, depreciation and investing /// are zero (receivables are current, so working capital). #[test] fn prop_cf_sections_sum_to_change_in_cash(gens in arb_books(), (a, _m, e) in three_days(), month in opt_month(), designate in any::()) { diff --git a/docs/testing.md b/docs/testing.md index 1d1846d..5e62b8a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -25,7 +25,7 @@ the store's journal safety; the other crates come next (gap list). | 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` | `just mutants` | ~12 min | weekly | +| 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. @@ -56,32 +56,37 @@ laptop: | balance sheet | 47 µs | 1.04 ms | | one month's P&L | 25 µs | 248 µs | -## Assumed, not confirmed (reject any) +## Formerly assumed: checked against the standards (2026-10-13) -Each was inferred, not decided by a human; the tests that rely on them -say so in their doc comments. +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). -- ASSUMED: a P&L for a period whose start is after its end is refused. -- ASSUMED: a balance sheet omits accounts with a zero balance. -- ASSUMED: the lifetime `profit_and_loss` counts closing entries - (only period statements exclude them). -- ASSUMED: closing a period with nothing to close still marks it closed. -- ASSUMED: a sweep account (draws, distributions) is zeroed as of the - period end, like income. -- ASSUMED: reconciliation balances are debits minus credits (tested on - cash; liability accounts unspecified). -- ASSUMED: a members' equity statement allocates prior unclosed earnings - and the period's income separately; one statement's ending for a member - may differ by a cent from the next statement's beginning (only totals - are guaranteed to chain). -- ASSUMED: cash flow lists one working-capital or financing line per - account that changed; with no registered assets, investing is zero. -- ASSUMED: an asset's life ends on purchase date + life months, that day - not held. -- ASSUMED: a payroll entry has one line per account total, not per - employee. -- ASSUMED: a period with exactly the dates of an existing one nests in it - (is allowed). +| # | 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