docs(spec): every rule traced to an authority; public-domain sources vendored

Stephen asked that specs come from recognized documents, not from
anyone's memory. There is no single US bookkeeping bible; authority is
layered, and this records it:

- docs/spec/sources/: verbatim excerpts (whitespace normalized), each
  with URL, retrieval date, and the SHA-256 of the fetched file, of
  public-domain texts: Pacioli's 1494 treatise in Geijsbeek's 1914
  translation (double entry, the trial balance, one money, write only
  forward, correct by opposite entry, close into capital); IRS Pub. 583,
  538, 946, 15; the 1099-MISC/NEC instructions (2025 and 2026); 26 U.S.C.
  §6041, §6041A, §704; Treas. Reg. §1.263(a)-1(f) and -4(f); Notice
  2015-82; Rev. Proc. 98-25; Form 1065 instructions; 17 U.S.C. §105. The
  excerpts were extracted by matching each passage against the fetched
  file, so none can contain text the source doesn't.
- docs/spec/rules.md: the rule catalog, B-1 to B-22. Each rule says
  AUTHORITY, CONVENTION, or DECISION; cites its sources (FASB's
  Codification and Concepts Statement 8 and OpenStax quoted briefly,
  never copied: copyright and CC BY-NC-SA); and names the code that
  enforces it and the tests and proofs that check it.
- Spec blocks in the code now carry SOURCE: lines into the catalog;
  AGENTS.md makes sourcing rule 0 of the formal-methods bar.

Corrections the sources turned up: the de minimis limit is $500 in the
regulation and $2,500 by Notice 2015-82 (both now cited); 1099
thresholds after 2026 are inflation-indexed, so the engine's flat
$2,000 for 2027+ is an open question (B-19). Unverified ASC paragraphs
(360-10-35-4, 340-10, 230-10-45-10, 606-10-55-37B, 210) are listed in
docs/spec/README.md until someone with a FASB basic-view login reads
them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-13 12:18:57 -06:00
1 parent 0340f316ee
commit ad572ae89d
15 files changed
+849 -9

No files matched your search

+3
View File
@@ -198,6 +198,9 @@ impl AgingRow {
///
/// ```text
/// GOAL: say how overdue a balance is, as of a date.
/// SOURCE: docs/spec/rules.md B-18 (bucket edges: CONVENTION, ASC
/// 326-20-55-38 illustrative; due today is Current: DECISION,
/// Stephen 2026-10-13).
/// INVARIANTS:
/// 1. MUST put the whole balance in exactly one bucket; the others are
/// zero.
+15 -9
View File
@@ -8,21 +8,25 @@
//! laws it implements hold for books of any size. See
//! `docs/verification.md` for which proof covers which law.
//!
//! The laws, in the order they appear:
//! The laws, in the order they appear (each traced to its authority in
//! `docs/spec/rules.md`):
//!
//! * a line's signed amount is `+amount` for a debit, `-amount` for a credit;
//! * a line's signed amount is `+amount` for a debit, `-amount` for a credit
//! (B-2);
//! * an entry is acceptable when it has two or more lines, every amount is
//! positive and in range, and its debits equal its credits exactly;
//! * an acceptable entry nets to zero, account by account summed;
//! positive and in range, and its debits equal its credits exactly (B-1,
//! B-2, B-21);
//! * an acceptable entry nets to zero, account by account summed (B-1);
//! * a reversal flips every side, so it is acceptable exactly when the
//! original is, and the two together net every account to zero;
//! original is, and the two together net every account to zero (B-9);
//! * an account's balance is its movement read on its normal side, so the
//! balances satisfy the accounting equation whenever movements net to
//! zero;
//! zero (B-5, B-7);
//! * a trial balance puts each movement in exactly one column, so its
//! columns differ by the net of all movements;
//! * a closing line zeroes the account it closes;
//! * an allocation's parts sum to the whole, to the last unit.
//! columns differ by the net of all movements (B-6);
//! * a closing line zeroes the account it closes (B-11);
//! * an allocation's parts sum to the whole, to the last unit, by largest
//! remainder (B-13).
use crate::{AccountType, Side};
use rust_decimal::Decimal;
@@ -277,6 +281,8 @@ pub fn plug_last<T: Amount>(whole: T, parts: &mut [T]) -> Option<()> {
/// ```text
/// GOAL: split a whole into parts, each a whole number of units where
/// possible, summing to the whole exactly, without favoring anyone.
/// SOURCE: docs/spec/rules.md B-13 (shares: 26 U.S.C. §704(a); cent
/// rounding: DECISION, Stephen 2026-10-13), B-21 (exact totals).
/// INVARIANTS:
/// 1. MUST make the parts sum to `whole` exactly.
/// 2. MUST change each part by zero or one unit (the first in order
+2
View File
@@ -612,6 +612,8 @@ impl Ledger {
///
/// ```text
/// GOAL: decide whether `entry` may be reversed on `date`.
/// SOURCE: docs/spec/rules.md B-9 (Pacioli ch. 31, 36), B-10 (DECISION,
/// Stephen 2026-10-13), B-12 (closed periods).
/// INVARIANTS:
/// 1. MUST NOT mutate anything.
/// 2. NEVER accepts a reversal that `reverse_entry` would then refuse.