P3: the cash flow statement by ASC 230 (defect 1, D-35, D-42)
check / check (push) Canceled after 0s

Direct method with the operating reconciliation and noncash schedules; it
foots by construction (Lean and Kani). Every non-cash account belongs to
one section; reversals net; a member or lender paying for the company is
cash borrowed and spent (D-42, constructive receipt and disbursement);
property a member contributes stays noncash. The bookspec seed: one loader
checks every example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-21 09:35:32 -06:00
1 parent 2c29d48ab9
commit eb4329bb2a
64 files changed
+12128 -1567

No files matched your search

+86 -4
View File
@@ -2379,8 +2379,11 @@ fn a_restarted_server_waits_for_the_old_one_to_let_go() {
}
/// `books.cash_flow` with no accounts passed reads the roles from
/// `[books]` in wanderbooks.toml (rules.md B-14): the owners' advance is
/// financing, gross; a loan to a member is investing.
/// `[books]` in wanderbooks.toml (rules.md B-14): the expense the owners
/// paid through 2100 is borrowed from them and spent on its date, each
/// line with `on_behalf` (D-42, rule 11); repaying the advance is
/// financing; a loan to a member is investing; the statement is the direct
/// method's, and it foots.
#[test]
fn cash_flow_reads_its_account_roles_from_the_books_settings() {
let dir = test_dir("cash-roles");
@@ -2448,10 +2451,89 @@ fn cash_flow_reads_its_account_roles_from_the_books_settings() {
.map(|l| l["amount"].as_str().unwrap().to_string())
.collect()
};
// Financing, gross and sorted by code: 2100 in and out, then capital.
assert_eq!(amounts("financing"), ["100.00", "-100.00", "1000.00"]);
// Financing by kind: the contribution, what the owners paid for the
// company (borrowed through 2100), then the repayment of 2100.
assert_eq!(amounts("financing"), ["1000.00", "100.00", "-100.00"]);
assert_eq!(cf["financing"][1]["entry"], 2);
assert_eq!(cf["financing"][1]["on_behalf"], "100.00");
assert!(cf["financing"][0].get("on_behalf").is_none());
let kinds: Vec<&str> = cf["financing_by_kind"]
.as_array()
.unwrap()
.iter()
.map(|k| k["kind"].as_str().unwrap())
.collect();
assert_eq!(
kinds,
[
"Contributions from owners — 3000 Capital",
"Proceeds from borrowing — 2100 Due to owners",
"Repayments of borrowing — 2100 Due to owners"
]
);
assert_eq!(amounts("investing"), ["-500.00"]);
assert_eq!(amounts("operating"), ["-100.00"]);
assert_eq!(cf["operating"][0]["on_behalf"], "-100.00");
assert_eq!(cf["operating"][0]["class"], "to_suppliers_and_employees");
assert_eq!(cf["method"], "direct");
assert_eq!(cf["net_change_in_cash"], "400.00");
assert_eq!(cf["reconciles"]["sections_sum"], "400.00");
assert_eq!(cf["reconciles"]["difference"], "0.00");
// Nothing noncash: the software is net income's only item.
assert_eq!(cf["noncash_disclosures"], json!([]));
let r = &cf["operating_reconciliation"];
assert_eq!(r["net_income"], "-100.00");
assert_eq!(r["lines"], json!([]));
assert_eq!(r["net_cash_from_operating"], "-100.00");
for gone in ["net_income", "depreciation", "working_capital"] {
assert!(cf.get(gone).is_none(), "{gone} is gone");
}
}
/// rules.md B-14 rule 2: roles that contradict the chart are refused,
/// naming the setting and the fix, never a statement on a wrong partition.
#[test]
fn cash_flow_refuses_contradictory_roles() {
let dir = test_dir("cash-refuse");
let mut mcp = Mcp::spawn(&dir);
mcp.request(
"initialize",
json!({
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "smoke-test", "version": "0.1.0"},
}),
);
mcp.notify("notifications/initialized");
for (code, name, typ) in [
("1000", "Bank", "asset"),
("1500", "Equipment", "asset"),
("2100", "Due to owners", "liability"),
] {
mcp.call(
"books.add_account",
json!({"code": code, "name": name, "account_type": typ}),
);
}
let err = mcp.call_err(
"books.cash_flow",
json!({"cash_accounts": ["1000"], "financing_accounts": ["1500"]}),
);
assert!(
err.contains("financing_accounts (from the call or books.financing_accounts) lists 1500"),
"{err}"
);
assert!(err.contains("financing accounts are liabilities"), "{err}");
let err = mcp.call_err(
"books.cash_flow",
json!({"cash_accounts": ["1000", "2100"]}),
);
assert!(err.contains("cash accounts are asset accounts"), "{err}");
let err = mcp.call_err(
"books.cash_flow",
json!({"cash_accounts": ["1000"], "investing_accounts": ["1000"]}),
);
assert!(err.contains("is in both cash_accounts"), "{err}");
}
/// A chart CSV with a bad row anywhere is refused whole: nothing is