feat(bookspec): the five laws as Lean theorems (P13 step 4)

The first edition's five laws (debits equal credits; the accounting
equation; the trial balance balances; net income articulates to equity;
the cash-flow sections sum to the change in cash) are theorems in
bookspec/lean, ported from verify/lean (Basic, Entry, Statements, Closing,
Ledger under namespace Bookspec) and restated by name in Laws.lean, each a
statement about every reachable state of the books. Audit.lean prints
their axioms; the checker's lean.rs test fails on a sorry, a law not
audited, or any axiom beyond propext, Quot.sound and Classical.choice.
Money is Int, the exact count of the smallest unit: every law is a law of
addition, so the model is faithful to exact decimals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 12:51:30 -06:00
1 parent ce69d9d7bc
commit 055decf7e2
13 files changed
+2162

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
//! The five laws are Lean theorems in `bookspec/lean` (P13 step 4), with
//! no assumption beyond Lean's standard axioms.
use std::path::{Path, PathBuf};
use std::process::Command;
const LAWS: [&str; 5] = [
"law_debits_equal_credits",
"law_accounting_equation",
"law_trial_balance",
"law_net_income_articulates",
"law_cash_flow_is_change_in_cash",
];
fn lean_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../lean")
}
/// Every law is stated in `Laws.lean`, audited in `Audit.lean`, and no
/// file of the library says `sorry`.
#[test]
fn the_five_laws_are_stated_and_audited() {
let laws = std::fs::read_to_string(lean_dir().join("Bookspec/Laws.lean")).unwrap();
let audit = std::fs::read_to_string(lean_dir().join("Audit.lean")).unwrap();
for law in LAWS {
assert!(laws.contains(&format!("theorem {law}")), "{law} is stated");
assert!(
audit.contains(&format!("#print axioms {law}")),
"{law} is audited"
);
}
for entry in std::fs::read_dir(lean_dir().join("Bookspec")).unwrap() {
let path = entry.unwrap().path();
let text = std::fs::read_to_string(&path).unwrap();
for (i, line) in text.lines().enumerate() {
let code = line.split("--").next().unwrap();
assert!(
!code
.split(|c: char| !c.is_alphanumeric() && c != '_')
.any(|w| w == "sorry"),
"{}:{}: sorry",
path.display(),
i + 1
);
}
}
}
/// With Lean installed, the library builds and every law depends only on
/// `propext`, `Quot.sound` and `Classical.choice`. Without it the test
/// says so and passes: `mise install` provides Lean (`just prove` is
/// where the machine is known to have it).
#[test]
fn the_laws_use_standard_axioms_only() {
let built = Command::new("lake")
.arg("build")
.current_dir(lean_dir())
.output();
let Ok(built) = built else {
eprintln!("lake is not installed: the Lean build is not checked here");
return;
};
assert!(
built.status.success(),
"lake build:\n{}",
String::from_utf8_lossy(&built.stdout)
);
let out = Command::new("lake")
.args(["env", "lean", "Audit.lean"])
.current_dir(lean_dir())
.output()
.unwrap();
let text = String::from_utf8_lossy(&out.stdout);
assert!(out.status.success(), "audit:\n{text}");
for law in LAWS {
let line = text
.lines()
.find(|l| l.contains(&format!("Bookspec.{law}'")))
.unwrap_or_else(|| panic!("{law} is not audited:\n{text}"));
for axiom in line
.split('[')
.nth(1)
.unwrap()
.trim_end_matches(']')
.split(", ")
{
assert!(
["propext", "Quot.sound", "Classical.choice"].contains(&axiom.trim()),
"{law}: axiom {axiom}"
);
}
}
}