feat(bookspec): rule identifiers from B-1 to B-45 (P13)

Every rule of `docs/spec/rules.md` now has one bookspec identifier,
`BS-<area>-<n>`: eleven areas (entry, ledger, statement, period, partner,
asset, prepaid, arap, revenue, 1099, format), numbered in B-rule order
within its area. The table is `bookspec/spec/rules-index.md`, and each
stated rule carries its id under its heading in `docs/spec/rules.md`, so
a reader of either can find the other.

rules.md's DECISION status becomes bookspec's CHOICE: the specification
names the options and an engine declares its pick (`Claims::with_choice`),
which is what an example already meant by `status = "CHOICE"`. B-39 to
B-45 are allocated in `plan/served.md` 8.0.3 and not yet stated: they
carry an id and an area and no status, so a later unit fills them in
without moving anyone else's number.

The catalogue names the examples that demonstrate each rule (56 files,
each under at least one rule) and the rule each refusal reason belongs
to. A rule with no example cannot be claimed: a claim needs evidence.
`docs/architecture.md`, `docs/testing.md` (tests and the For-P12
mutation targets) and `docs/parity.md` record the new shape; the
bookspec README points at the checker, the runner and the adapter.
This commit is contained in:
swaits committed 2026-10-26 21:23:16 -06:00
1 parent b7be5a5472
commit cb4e955942
6 files changed
+285 -60

No files matched your search

+9 -1
View File
@@ -13,6 +13,14 @@ Every example is checked against wanderbooks on every `cargo test`
with fails the build, so the examples can't rot. An expected value is worked with fails the build, so the examples can't rot. An expected value is worked
by hand from the cited source, never copied from an engine's output. by hand from the cited source, never copied from an engine's output.
The schema, the parser (`Example::from_toml`, every structure with
`deny_unknown_fields`) and the `Engine` trait an accounting engine
implements live in `bookspec/checker`; `bookspec/runner` runs any engine
through the examples and reports per rule: pass, fail or not claimed. The
wanderbooks adapter is `crates/ledger-core/tests/bookspec.rs`. The rule
identifiers are `BS-<area>-<n>`, tabled in `bookspec/spec/rules-index.md`
(P13 steps 1 to 3).
## The shape of an example ## The shape of an example
One TOML file per example in `examples/`, named for its `id`. One TOML file per example in `examples/`, named for its `id`.
@@ -99,7 +107,7 @@ declares `largest-remainder`, the rule a members close splits by.
authorities alone (a standard, a regulation, a statute, a court, the authorities alone (a standard, a regulation, a statute, a court, the
mechanics of double entry); `CONVENTION`, common practice where no mechanics of double entry); `CONVENTION`, common practice where no
authority governs (rules.md's CONVENTION); `CHOICE`, an owner's or the authority governs (rules.md's CONVENTION); `CHOICE`, an owner's or the
house's decision (a plan D-number, or a DECISION rules.md records) that house's decision (a plan D-number, or a CHOICE rules.md records) that
some expected value depends on, even when authorities carry the rest. An some expected value depends on, even when authorities carry the rest. An
example whose figures follow a decision cites it in `sources`. example whose figures follow a decision cites it in `sources`.
+28
View File
@@ -0,0 +1,28 @@
//! The documentation bookspec's identifiers live in: `docs/spec/rules.md`
//! carries each stated rule's id and speaks bookspec's `CHOICE`.
use std::path::Path;
use bookspec_checker::rules;
/// `docs/spec/rules.md` carries every stated rule's id under its heading,
/// and no longer calls a choice a decision (P13 step 3).
#[test]
fn rules_md_carries_every_id() {
let text = std::fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/spec/rules.md"),
)
.expect("docs/spec/rules.md");
for rule in rules().iter().filter(|r| r.status.is_some()) {
assert!(
text.contains(&format!("**bookspec:** `{}`", rule.id)),
"B-{} carries {}",
rule.b,
rule.id
);
}
assert!(
!text.contains("DECISION"),
"rules.md speaks bookspec's CHOICE"
);
}
+32
View File
@@ -134,3 +134,35 @@ tokio mutex never poisons. So the server fails closed: a panic in a
request (its task, or while it holds the guard) sets `stopped`, and every request (its task, or while it holds the guard) sets `stopped`, and every
later request, on every session, is refused until a restart, which later request, on every session, is refused until a restart, which
re-verifies the journal. re-verifies the journal.
## bookspec (`bookspec/`, P13)
bookspec is the formal accounting specification (D-34): every rule stated
normatively, sourced, with a symbolic form and concrete examples, and a
conformance harness any engine can be run against. It lives in its own
Cargo workspace inside this repository until the harness is stable enough
to move to its own repository (P13 step 6, D-34, Z-17):
- `bookspec/checker` is engine-independent: the example schema and its
parser (`Example::from_toml`, every structure `deny_unknown_fields`, the
cross-field shape checked by `Example::validate`), the rule catalogue
(`rules()`, `RuleId` `BS-<area>-<n>`, the refusal-to-rule table) and the
`Engine` trait (`load`, `apply`, `query`, `claims`, and `change` for a
`[[changes]]` entry, which posts no entry and so is neither a setup nor
an event).
- `bookspec/runner` drives any `Engine` through the examples and reports
per rule: pass, fail or not claimed. It compares the engine's answers
with the examples' exact expected values; it never asks the engine
whether its own answer is right.
- `bookspec/spec/rules-index.md` maps B-1 to B-45 to the bookspec ids;
`docs/spec/rules.md` carries each stated rule's id under its heading.
- `crates/ledger-core/tests/bookspec.rs` is the wanderbooks adapter: it
implements `Engine` over `Store` (a dev-dependency only, so no engine
crate depends on the specification) and remains the loader that checks
every figure. `ledger-package` answers the formats appendix's IIF
queries through the same adapter, as the package test does.
Neither bookspec crate depends on `ledger-core`; the dependency points
inward from the adapter. `just check` runs the bookspec workspace's
format, clippy and tests beside the wanderbooks workspace's, so an example
cannot rot (P13 step 1).
+10
View File
@@ -71,3 +71,13 @@ do everything the GSheet+GDrive system does today? Tracked separately in
`docs/capability-parity.md`, with its own cutover rule (parallel run + `docs/capability-parity.md`, with its own cutover rule (parallel run +
the owner's approval). The two catalogs converge: when both are green, the the owner's approval). The two catalogs converge: when both are green, the
sheet retires. sheet retires.
### P13 bookspec (2026-10-26)
P13 is a specification, not a feature, so no row of the semantic catalog
moves and the floor stands. It adds the checker crate (`bookspec/checker`:
the example schema, the rule identifiers `BS-<area>-<n>` and the `Engine`
trait), the runner (`bookspec/runner`: pass, fail or not claimed per rule),
and the rule index from B-1 to B-45 (`bookspec/spec/rules-index.md`). Every
existing bookspec example still runs on every `cargo test`, through both
the loader's own checks and the `Engine` trait.
+138 -59
View File
@@ -3,19 +3,24 @@
Every rule the engine enforces, the authority for it, and what checks it. Every rule the engine enforces, the authority for it, and what checks it.
Status is **AUTHORITY** (a source states the rule), **CONVENTION** (common Status is **AUTHORITY** (a source states the rule), **CONVENTION** (common
practice no authority states; the best reference is given), or practice no authority states; the best reference is given), or
**DECISION** (a human chose, and when). Vendored sources are in **CHOICE** (a human chose, and when; bookspec's CHOICE: the specification
names the options, an engine declares its pick). Vendored sources are in
`sources/`; FASB and OpenStax are quoted briefly under their copyright and `sources/`; FASB and OpenStax are quoted briefly under their copyright and
license (see `README.md`). Quotes from FASB's Codification were read in license (see `README.md`). Quotes from FASB's Codification were read in
Deloitte's DART roadmaps, which reproduce it ("FASB Accounting Standards Deloitte's DART roadmaps, which reproduce it ("FASB Accounting Standards
Codification: Copyright © 2005 - 2026 by Financial Accounting Codification: Copyright © 2005 - 2026 by Financial Accounting
Foundation"). Foundation").
Spec blocks in the code cite these as `SOURCE: rules.md B-n`. Spec blocks in the code cite these as `SOURCE: rules.md B-n`. Every rule
also has a bookspec identifier, `**bookspec:** BS-<area>-<n>`: the table
from B-1 to B-45 is `bookspec/spec/rules-index.md` (P13, D-34).
--- ---
## B-1 Every entry is a double entry: debits equal credits ## B-1 Every entry is a double entry: debits equal credits
**bookspec:** `BS-entry-1`
**Status:** AUTHORITY **Status:** AUTHORITY
- Pacioli ch. 36 (`sources/pacioli-1494.md`): "All entries made in the - Pacioli ch. 36 (`sources/pacioli-1494.md`): "All entries made in the
@@ -28,7 +33,7 @@ Spec blocks in the code cite these as `SOURCE: rules.md B-n`.
**Enforced:** `kernel::check_lines` via `JournalEntry::validate` (two or **Enforced:** `kernel::check_lines` via `JournalEntry::validate` (two or
more lines, debits = credits exactly). A line's account must exist and more lines, debits = credits exactly). A line's account must exist and
take postings when the line is added, not only at post: DECISION take postings when the line is added, not only at post: CHOICE
(the owner, 2026-10-13), `Ledger::add_line` (the owner, 2026-10-13), `Ledger::add_line`
(`a_line_on_an_unknown_or_placeholder_account_is_refused_when_added`). **Checked:** Kani (`a_line_on_an_unknown_or_placeholder_account_is_refused_when_added`). **Checked:** Kani
`check_lines_is_exact`, `accepted_entry_nets_to_zero`; Lean `check_lines_is_exact`, `accepted_entry_nets_to_zero`; Lean
@@ -36,6 +41,8 @@ take postings when the line is added, not only at post: DECISION
## B-2 A line's amount is positive; its side gives the direction ## B-2 A line's amount is positive; its side gives the direction
**bookspec:** `BS-entry-2`
**Status:** CONVENTION **Status:** CONVENTION
Pacioli keeps amounts in a debit column and a credit column (ch. 36: "All Pacioli keeps amounts in a debit column and a credit column (ch. 36: "All
@@ -50,6 +57,8 @@ that every line reads one way (2026-10-13, found by the formal pass).
## B-3 One money through the ledger; foreign amounts at the transaction-date rate ## B-3 One money through the ledger; foreign amounts at the transaction-date rate
**bookspec:** `BS-ledger-1`
**Status:** AUTHORITY **Status:** AUTHORITY
- Pacioli ch. 36: "the money that you reckon by at the beginning should be - Pacioli ch. 36: "the money that you reckon by at the beginning should be
@@ -62,7 +71,7 @@ that every line reads one way (2026-10-13, found by the formal pass).
A foreign line is booked in the base currency's smallest unit (ISO 4217 A foreign line is booked in the base currency's smallest unit (ISO 4217
minor units, `sources/iso-4217.md`), half away from zero; a ledger in one minor units, `sources/iso-4217.md`), half away from zero; a ledger in one
money holds no fractions of its smallest unit, and its cash can then money holds no fractions of its smallest unit, and its cash can then
match a bank statement: DECISION (the owner, 2026-10-13), after the GnuCash match a bank statement: CHOICE (the owner, 2026-10-13), after the GnuCash
cross-check found wanderbooks kept $108.34567 where GnuCash booked cross-check found wanderbooks kept $108.34567 where GnuCash booked
$108.35. Lines booked before then replay as booked. $108.35. Lines booked before then replay as booked.
@@ -84,6 +93,8 @@ GnuCash oracle.
## B-4 Five elements, closed list ## B-4 Five elements, closed list
**bookspec:** `BS-ledger-2`
**Status:** AUTHORITY (definitions); the five-type chart is CONVENTION **Status:** AUTHORITY (definitions); the five-type chart is CONVENTION
FASB Concepts Statement No. 8, Ch. 4 (2021), which replaced Concepts FASB Concepts Statement No. 8, Ch. 4 (2021), which replaced Concepts
@@ -97,11 +108,13 @@ or carrying out other activities." E81 expenses "are outflows or other
using up of assets … or incurrences of its liabilities …". using up of assets … or incurrences of its liabilities …".
**Enforced:** `AccountType` (five variants). A child account has its **Enforced:** `AccountType` (five variants). A child account has its
parent's type: DECISION (the owner, 2026-10-13), `Ledger::add_account` parent's type: CHOICE (the owner, 2026-10-13), `Ledger::add_account`
(`ParentTypeMismatch`). **Checked:** `a_child_account_has_its_parents_type`. (`ParentTypeMismatch`). **Checked:** `a_child_account_has_its_parents_type`.
## B-5 Normal balances ## B-5 Normal balances
**bookspec:** `BS-ledger-3`
**Status:** CONVENTION **Status:** CONVENTION
No standard defines normal balances. OpenStax, *Principles of Accounting, No standard defines normal balances. OpenStax, *Principles of Accounting,
@@ -117,6 +130,8 @@ with a credit, their 'normal' balance is a credit." Pacioli ch. 12: capital
## B-6 The trial balance balances ## B-6 The trial balance balances
**bookspec:** `BS-statement-1`
**Status:** AUTHORITY **Status:** AUTHORITY
Pacioli ch. 36: "the total of the credits — I do not say creditors — Pacioli ch. 36: "the total of the credits — I do not say creditors —
@@ -129,6 +144,8 @@ Lean `trial_balance_iff`, `Reachable.trial_balance`,
## B-7 The accounting equation ## B-7 The accounting equation
**bookspec:** `BS-statement-2`
**Status:** AUTHORITY **Status:** AUTHORITY
CON 8 Ch. 4 ¶E62 (cited): equity "is the difference between the entity's CON 8 Ch. 4 ¶E62 (cited): equity "is the difference between the entity's
@@ -141,6 +158,8 @@ of equity (B-11).
## B-8 Posted is forever: the book is written only forward ## B-8 Posted is forever: the book is written only forward
**bookspec:** `BS-ledger-4`
**Status:** AUTHORITY **Status:** AUTHORITY
- Pacioli ch. 36: "You should not write backward, but always forward — - Pacioli ch. 36: "You should not write backward, but always forward —
@@ -158,6 +177,8 @@ hash-chained. **Checked:** Lean `Step.extends`, `Steps.extends`; property
## B-9 A mistake is corrected by an opposite entry, never erased ## B-9 A mistake is corrected by an opposite entry, never erased
**bookspec:** `BS-ledger-5`
**Status:** AUTHORITY **Status:** AUTHORITY
Pacioli ch. 31 (the *storno*): "you shall make another entry opposite this Pacioli ch. 31 (the *storno*): "you shall make another entry opposite this
@@ -172,7 +193,9 @@ Lean `accepted_reversal_iff`, `movement_with_reversal`.
## B-10 An entry is reversed at most once; a closing entry never ## B-10 An entry is reversed at most once; a closing entry never
**Status:** DECISION (the owner, 2026-10-13) **bookspec:** `BS-ledger-6`
**Status:** CHOICE (the owner, 2026-10-13)
Follows from B-9 (a correction corrects once) and B-11 (a closed period Follows from B-9 (a correction corrects once) and B-11 (a closed period
stays closed). **Enforced:** `Ledger::check_reversal`. **Checked:** stays closed). **Enforced:** `Ledger::check_reversal`. **Checked:**
@@ -180,6 +203,8 @@ stays closed). **Enforced:** `Ledger::check_reversal`. **Checked:**
## B-11 Revenue and expenses close into profit and loss, and that into capital ## B-11 Revenue and expenses close into profit and loss, and that into capital
**bookspec:** `BS-period-1`
**Status:** AUTHORITY (mechanics); the account the net lands in depends on **Status:** AUTHORITY (mechanics); the account the net lands in depends on
the entity the entity
@@ -197,13 +222,15 @@ the entity
## B-12 A closed period takes no new postings; periods close in order ## B-12 A closed period takes no new postings; periods close in order
**bookspec:** `BS-period-2`
**Status:** CONVENTION **Status:** CONVENTION
Periods themselves: IRS Pub. 538 (`sources/irs-periods-and-methods.md`) Periods themselves: IRS Pub. 538 (`sources/irs-periods-and-methods.md`)
defines the tax year, calendar and fiscal. Locking a closed period and defines the tax year, calendar and fiscal. Locking a closed period and
closing in order are practice (and follow from B-8: a closed period's closing in order are practice (and follow from B-8: a closed period's
book is not rewritten). Periods nest (a month in a quarter in a year) book is not rewritten). Periods nest (a month in a quarter in a year)
and never partially overlap: DECISION (the owner, 2026-10-13), and never partially overlap: CHOICE (the owner, 2026-10-13),
`Ledger::create_period` (`PeriodsOverlap`; `Ledger::create_period` (`PeriodsOverlap`;
`periods_nest_but_never_partially_overlap`). **Enforced:** `closed_period_containing`, `periods_nest_but_never_partially_overlap`). **Enforced:** `closed_period_containing`,
`CloseOutOfOrder`, `Store::rehearse`. **Checked:** Lean `Steps.frozen`; `CloseOutOfOrder`, `Store::rehearse`. **Checked:** Lean `Steps.frozen`;
@@ -212,10 +239,12 @@ closed period).
## B-13 Partners share income by the partnership agreement; capital accounts carry it ## B-13 Partners share income by the partnership agreement; capital accounts carry it
**bookspec:** `BS-partner-1`
**Status:** AUTHORITY (shares; capital accounts; a count ends where the **Status:** AUTHORITY (shares; capital accounts; a count ends where the
members or shares change: §706(d)(1), Treas. Reg. §1.706-4(a)); DECISION members or shares change: §706(d)(1), Treas. Reg. §1.706-4(a)); CHOICE
(cent rounding, the owner 2026-10-13); DECISION (counted from the company's (cent rounding, the owner 2026-10-13); CHOICE (counted from the company's
first close, D-10, 2026-10-16); DECISION (the statement splits unclosed first close, D-10, 2026-10-16); CHOICE (the statement splits unclosed
earnings the same running way, D-38, 2026-10-20) earnings the same running way, D-38, 2026-10-20)
- 26 U.S.C. §704(a) (`sources/irs-partnerships.md`): "A partner's - 26 U.S.C. §704(a) (`sources/irs-partnerships.md`): "A partner's
@@ -238,7 +267,7 @@ earnings the same running way, D-38, 2026-10-20)
worksheet carries the book capital analysis, and beside it the tax-basis worksheet carries the book capital analysis, and beside it the tax-basis
capital as far as the books know it (B-36). capital as far as the books know it (B-36).
- **No authority** says how to round a share to the cent. Largest - **No authority** says how to round a share to the cent. Largest
remainder, ties to the member listed first: DECISION (the owner, remainder, ties to the member listed first: CHOICE (the owner,
2026-10-13). Closes made before then replay with the last member 2026-10-13). Closes made before then replay with the last member
absorbing it. absorbing it.
- **Members closes split cumulatively, counted from the company's first - **Members closes split cumulatively, counted from the company's first
@@ -323,7 +352,7 @@ earnings the same running way, D-38, 2026-10-20)
service contract credited straight to capital is property (a contract service contract credited straight to capital is property (a contract
handed over), while a prepaid service they paid a vendor for on the handed over), while a prepaid service they paid a vendor for on the
company's behalf, booked through Due to Shareholders (Dr prepaid / Cr company's behalf, booked through Due to Shareholders (Dr prepaid / Cr
2100, then Dr 2100 / Cr capital), is cash (DECISION, the owner, 2100, then Dr 2100 / Cr capital), is cash (CHOICE, the owner,
2026-10-15); and it is shared among 2026-10-15); and it is shared among
members credited in one entry in proportion to their credits members credited in one entry in proportion to their credits
(`k1` INVARIANTS 3, 4, 6). (`k1` INVARIANTS 3, 4, 6).
@@ -364,7 +393,9 @@ earnings the same running way, D-38, 2026-10-20)
## B-14 Statements: P&L for a period; cash flows by the direct method ## B-14 Statements: P&L for a period; cash flows by the direct method
**Status:** AUTHORITY, except where marked CONVENTION or DECISION **bookspec:** `BS-statement-3`
**Status:** AUTHORITY, except where marked CONVENTION or CHOICE
The P&L covers a period; a closing entry is not income or spending The P&L covers a period; a closing entry is not income or spending
(B-11), so period statements exclude it. (B-11), so period statements exclude it.
@@ -575,7 +606,7 @@ in public sources that reproduce the Codification, `sources/fasb-asc.md`):
close has no cash line. close has no cash line.
11. **What a member or a lender pays, or receives, for the company is 11. **What a member or a lender pays, or receives, for the company is
cash the company borrowed (or a member contributed) and spent** cash the company borrowed (or a member contributed) and spent**
(DECISION, D-42, the owner, 2026-10-20, revising D-35's first point; (CHOICE, D-42, the owner, 2026-10-20, revising D-35's first point;
bookspec's CHOICE: the standard leaves it open). bookspec's CHOICE: the standard leaves it open).
*Source.* The Codification does not address a payment a third party *Source.* The Codification does not address a payment a third party
makes on the entity's behalf (PwC Viewpoint FSP 6.9.3: "ASC 230 does makes on the entity's behalf (PwC Viewpoint FSP 6.9.3: "ASC 230 does
@@ -648,7 +679,7 @@ in public sources that reproduce the Codification, `sources/fasb-asc.md`):
contribution), and none of the guides read presents an expense contribution), and none of the guides read presents an expense
someone paid on the entity's behalf as noncash. The books' own K-1 someone paid on the entity's behalf as noncash. The books' own K-1
worksheet already counts it as a cash contribution (`k1.rs` worksheet already counts it as a cash contribution (`k1.rs`
INVARIANT 3, DECISION 2026-10-15), so the two agree. INVARIANT 3, CHOICE 2026-10-15), so the two agree.
- **Card purchases** follow the card's classification (D-35, - **Card purchases** follow the card's classification (D-35,
unchanged): on a card listed as financing, the issuer is a lender unchanged): on a card listed as financing, the issuer is a lender
that paid the merchant, so the purchase is borrowed and spent and the that paid the merchant, so the purchase is borrowed and spent and the
@@ -761,6 +792,8 @@ the tools' `ledger-cli` `tests/p3_seams.rs`).
## B-15 Depreciation: straight line, salvage never depreciated, partial months by days ## B-15 Depreciation: straight line, salvage never depreciated, partial months by days
**bookspec:** `BS-asset-1`
**Status:** CONVENTION (book method); AUTHORITY for tax conventions, which **Status:** CONVENTION (book method); AUTHORITY for tax conventions, which
wanderbooks does not apply wanderbooks does not apply
@@ -770,7 +803,7 @@ life of the asset," with "(Cost – salvage value)/Useful life"; partial
periods: "A common method is to allocate depreciation expense based on the periods: "A common method is to allocate depreciation expense based on the
number of months the asset is owned in a year." wanderbooks prorates number of months the asset is owned in a year." wanderbooks prorates
partial months by days, including a schedule through a mid-month date partial months by days, including a schedule through a mid-month date
(DECISION, the owner 2026-10-13). ASC 360-10-35-4 (read 2026-10-14, (CHOICE, the owner 2026-10-13). ASC 360-10-35-4 (read 2026-10-14,
`sources/fasb-asc.md`) prescribes no method: cost less salvage is spread `sources/fasb-asc.md`) prescribes no method: cost less salvage is spread
over the useful life "in a systematic and rational manner," as equitably over the useful life "in a systematic and rational manner," as equitably
as possible to the periods served; straight line by days is one such as possible to the periods served; straight line by days is one such
@@ -948,6 +981,8 @@ dates, `accumulated_posted` and its edges,
## B-16 Prepaid expenses and deferred revenue are recognized over the period benefited or earned ## B-16 Prepaid expenses and deferred revenue are recognized over the period benefited or earned
**bookspec:** `BS-prepaid-1`
**Status:** AUTHORITY (revenue); CONVENTION (prepaids) **Status:** AUTHORITY (revenue); CONVENTION (prepaids)
ASC 606-10-45-2 (cited): payment before transfer → "the entity shall ASC 606-10-45-2 (cited): payment before transfer → "the entity shall
@@ -965,13 +1000,13 @@ spreads by the day from its start (the first day covered), as
depreciation does (B-15 rules 1-4: full months equal, the first and last depreciation does (B-15 rules 1-4: full months equal, the first and last
partial months by their days, the last taking what the others leave, partial months by their days, the last taking what the others leave,
rounded to the minor unit): a 12-month policy from March 15 posts rounded to the minor unit): a 12-month policy from March 15 posts
thirteen month ends, a small March and a small final March (DECISION, thirteen month ends, a small March and a small final March (CHOICE,
the owner, D-11 confirmed in D-37, 2026-10-20). Schedules registered before the owner, D-11 confirmed in D-37, 2026-10-20). Schedules registered before
P3 keep equal months, the last absorbing rounding, forever: a posted P3 keep equal months, the last absorbing rounding, forever: a posted
month's replay re-derives its plan. `books.schedule_convention` sets the month's replay re-derives its plan. `books.schedule_convention` sets the
convention new schedules take (unset: daily). An equal-months schedule convention new schedules take (unset: daily). An equal-months schedule
starts on the 1st of a month, for prepaid expenses and deferred revenue starts on the 1st of a month, for prepaid expenses and deferred revenue
alike (DECISION, the owner, D-37, 2026-10-20): revenue is never recognized alike (CHOICE, the owner, D-37, 2026-10-20): revenue is never recognized
before it is earned (ASC 606-10-25-23), and one rule for both is simpler before it is earned (ASC 606-10-25-23), and one rule for both is simpler
and never early; schedules an earlier version took mid-month replay as and never early; schedules an earlier version took mid-month replay as
they were. A reversed month is un-posted, so it is due again; a month in they were. A reversed month is un-posted, so it is due again; a month in
@@ -1008,6 +1043,8 @@ B-16 cites them (P3 depreciation review R7).
## B-17 Platform payouts are recorded gross when the business is the principal ## B-17 Platform payouts are recorded gross when the business is the principal
**bookspec:** `BS-revenue-1`
**Status:** AUTHORITY **Status:** AUTHORITY
ASC 606-10-55-37 (cited): "An entity is a principal if it controls the ASC 606-10-55-37 (cited): "An entity is a principal if it controls the
@@ -1024,17 +1061,19 @@ CPA confirms.
## B-18 Aging buckets: Current, 1–30, 31–60, 61–90, 90+ days past due ## B-18 Aging buckets: Current, 1–30, 31–60, 61–90, 90+ days past due
**Status:** CONVENTION (bucket edges); DECISION (due today is Current) **bookspec:** `BS-arap-1`
**Status:** CONVENTION (bucket edges); CHOICE (due today is Current)
ASC 326-20-55-37/38 (cited) illustrates "one way" to age trade ASC 326-20-55-37/38 (cited) illustrates "one way" to age trade
receivables with buckets "current … 1–30 days past due … 31–60 … 61–90 … receivables with buckets "current … 1–30 days past due … 31–60 … 61–90 …
more than 90 days past due"; it is illustrative, not a rule. The FFIEC more than 90 days past due"; it is illustrative, not a rule. The FFIEC
Call Report instructions (bank regulators, not binding here): "The past Call Report instructions (bank regulators, not binding here): "The past
due status … should be determined in accordance with its contractual due status … should be determined in accordance with its contractual
repayment terms." A document due today is not yet past due: DECISION repayment terms." A document due today is not yet past due: CHOICE
(the owner, 2026-10-13). Aging as of a date shows only documents issued by (the owner, 2026-10-13). Aging as of a date shows only documents issued by
then, less only payments made by then; and a payment never predates its then, less only payments made by then; and a payment never predates its
invoice or bill (a real prepayment is a deposit): DECISIONS (the owner, invoice or bill (a real prepayment is a deposit): CHOICES (the owner,
2026-10-13). What a document owes as of a date is read from its ledger 2026-10-13). What a document owes as of a date is read from its ledger
lines that stand then (B-38): a void invoice is in no bucket, a payment lines that stand then (B-38): a void invoice is in no bucket, a payment
reversed by then no longer counts, one reversed later still does; the reversed by then no longer counts, one reversed later still does; the
@@ -1050,8 +1089,10 @@ buckets partition what the documents owe, exactly.
## B-19 1099 reporting thresholds, payment methods, and decisions ## B-19 1099 reporting thresholds, payment methods, and decisions
**bookspec:** `BS-1099-1`
**Status:** AUTHORITY (`sources/irs-1099-thresholds.md`, **Status:** AUTHORITY (`sources/irs-1099-thresholds.md`,
`sources/irs-1099-payment-method.md`), with the DECISIONs named below. `sources/irs-1099-payment-method.md`), with the CHOICEs named below.
$600 through 2025; $2,000 for payments made after Dec. 31, 2025 (26 U.S.C. $600 through 2025; $2,000 for payments made after Dec. 31, 2025 (26 U.S.C.
§6041(a), §6041A(a)(2), as amended by Pub. L. 119-21 §70433; §70433(f): §6041(a), §6041A(a)(2), as amended by Pub. L. 119-21 §70433; §70433(f):
@@ -1107,7 +1148,7 @@ card's account (a business_card method's), or other. First match wins:
|---|---| |---|---|
| the entry's method pays through this line's account, card or network | card (excluded) | | the entry's method pays through this line's account, card or network | card (excluded) |
| the entry's method pays through this line's account, bank or a member's own cash or check | counts | | the entry's method pays through this line's account, bank or a member's own cash or check | counts |
| a cash account (no method, or one naming another account) | counts: presumed check, ACH or transfer (DECISION D-40, owner, 2026-10-20); tag a debit-card payment with the card | | a cash account (no method, or one naming another account) | counts: presumed check, ACH or transfer (CHOICE D-40, owner, 2026-10-20); tag a debit-card payment with the card |
| a financing account, or a member's or platform method's account (no method, or one naming another account) | undetermined until the method is recorded | | a financing account, or a member's or platform method's account (no method, or one naming another account) | undetermined until the method is recorded |
| a company card's account (a business_card method's) | card (excluded) | | a company card's account (a business_card method's) | card (excluded) |
| anything else (an expense, payables, prepaid) | no payment line | | anything else (an expense, payables, prepaid) | no payment line |
@@ -1148,7 +1189,7 @@ employee's wages go on Form W-2 (§1.6041-2(a)(1)). Paid by card is never a
vendor's reason: it is a property of each payment. Every new decision vendor's reason: it is a property of each payment. Every new decision
names who made it and when (`settings::Source`). names who made it and when (`settings::Source`).
**Dated decisions** (DECISION D-40, owner, 2026-10-20: "legally this has **Dated decisions** (CHOICE D-40, owner, 2026-10-20: "legally this has
to be 2 dated statuses"). Each decision takes effect on a date (a W-9's, to be 2 dated statuses"). Each decision takes effect on a date (a W-9's,
or a tax election's: Form 8832's effective date, §301.7701-3(c)(1)(iii)), or a tax election's: Form 8832's effective date, §301.7701-3(c)(1)(iii)),
or from the beginning; each payment takes the decision in force on its or from the beginning; each payment takes the decision in force on its
@@ -1203,7 +1244,7 @@ named beside another vendor (`add_vendor`), checked by
`a_named_party_gaining_a_vendor_record_keeps_one_vendor_per_entry`. `a_named_party_gaining_a_vendor_record_keeps_one_vendor_per_entry`.
Shares that don't resolve count the whole payment toward the vendor (the Shares that don't resolve count the whole payment toward the vendor (the
conservative answer) and the entry is listed (`Vendor1099Report:: conservative answer) and the entry is listed (`Vendor1099Report::
unresolved`; a `books.vendor_1099` note). DECISION (the owner, unresolved`; a `books.vendor_1099` note). CHOICE (the owner,
2026-10-15: shares). 2026-10-15: shares).
**The quieter note.** Untagged posted entries are named only when they **The quieter note.** Untagged posted entries are named only when they
@@ -1229,7 +1270,7 @@ contradict.
(`ledger_core::rules::THRESHOLD_1099`, known through 2026, review from (`ledger_core::rules::THRESHOLD_1099`, known through 2026, review from
2026-11-01). For a later year the engine refuses with `RuleNotKnown` 2026-11-01). For a later year the engine refuses with `RuleNotKnown`
rather than guess; the MCP digest and the viewer's "Needs attention" list rather than guess; the MCP digest and the viewer's "Needs attention" list
flag the rule once it is due (DECISION, the owner, 2026-10-13). The flag the rule once it is due (CHOICE, the owner, 2026-10-13). The
bucket table is `kernel::payment_bucket` (Kani `payment_bucket_is_the_table`, bucket table is `kernel::payment_bucket` (Kani `payment_bucket_is_the_table`,
`payment_split_is_exact`, `net_buckets_keeps_the_total`). **Checked:** `payment_split_is_exact`, `net_buckets_keeps_the_total`). **Checked:**
`an_unknown_year_threshold_is_never_guessed`, `an_unknown_year_threshold_is_never_guessed`,
@@ -1266,6 +1307,8 @@ MCP tests through a real books server (ledger-cli); bookspec
## B-20 Every transaction has supporting documents; records are kept ## B-20 Every transaction has supporting documents; records are kept
**bookspec:** `BS-ledger-7`
**Status:** AUTHORITY (`sources/irs-records.md`) **Status:** AUTHORITY (`sources/irs-records.md`)
IRS Pub. 583: "In addition, you must keep supporting documents"; they IRS Pub. 583: "In addition, you must keep supporting documents"; they
@@ -1277,6 +1320,8 @@ generally; 7 for bad debts).
## B-21 Exact amounts in the books; rounding only on returns ## B-21 Exact amounts in the books; rounding only on returns
**bookspec:** `BS-entry-3`
**Status:** AUTHORITY (for where rounding happens) **Status:** AUTHORITY (for where rounding happens)
Form 1065 instructions (`sources/irs-partnerships.md`): "If two or more Form 1065 instructions (`sources/irs-partnerships.md`): "If two or more
@@ -1289,6 +1334,8 @@ cents (and FX precision) exactly (R-2).
## B-22 Expense or capitalize (book policy, mirrors tax) ## B-22 Expense or capitalize (book policy, mirrors tax)
**bookspec:** `BS-asset-2`
**Status:** AUTHORITY (`sources/irs-capitalization.md`) **Status:** AUTHORITY (`sources/irs-capitalization.md`)
Treas. Reg. §1.263(a)-1(f)(1)(ii)(D) sets $500 per invoice or item for a Treas. Reg. §1.263(a)-1(f)(1)(ii)(D) sets $500 per invoice or item for a
@@ -1301,8 +1348,10 @@ taxable years beginning on or after January 1, 2016. The 12-month rule:
## B-23 Control roles: one account each, of the right kind, moved only when empty ## B-23 Control roles: one account each, of the right kind, moved only when empty
**bookspec:** `BS-ledger-8`
**Status:** CONVENTION (a control account agrees with the records it **Status:** CONVENTION (a control account agrees with the records it
controls); DECISIONS (the owner, 2026-10-13 and 2026-10-15) controls); CHOICES (the owner, 2026-10-13 and 2026-10-15)
Invoices, bills, payroll, and closes post to accounts the engine picks by Invoices, bills, payroll, and closes post to accounts the engine picks by
role: receivables, revenue, payables, general expense, retained earnings, role: receivables, revenue, payables, general expense, retained earnings,
@@ -1310,9 +1359,9 @@ salary and payroll tax expense, and five payroll withholdings. A role uses
its default code (1200, 4000, 2000, 6999, 3999, 6100, 6110, 2110–2150) its default code (1200, 4000, 2000, 6999, 3999, 6100, 6110, 2110–2150)
until the human assigns it one of the chart's own accounts; the assignment until the human assigns it one of the chart's own accounts; the assignment
is journaled, so replay never depends on a setting outside the journal is journaled, so replay never depends on a setting outside the journal
(DECISION, 2026-10-15). A role's account is postable and of the role's (CHOICE, 2026-10-15). A role's account is postable and of the role's
type (B-4), and the engine refuses one that isn't rather than renumber type (B-4), and the engine refuses one that isn't rather than renumber
the chart (DECISION, 2026-10-13). No account fills two roles. A the chart (CHOICE, 2026-10-13). No account fills two roles. A
receivables, payables, withholding, or retained earnings account must keep receivables, payables, withholding, or retained earnings account must keep
agreeing with what it controls (open invoices, open bills, taxes withheld, agreeing with what it controls (open invoices, open bills, taxes withheld,
closed earnings): such a role moves only when its account holds no closed earnings): such a role moves only when its account holds no
@@ -1340,7 +1389,9 @@ frees its code); the control guard and `Ledger::control_check` (B-38).
## B-24 Asset tags: one label per asset, never reused ## B-24 Asset tags: one label per asset, never reused
**Status:** DECISION (the owner, 2026-10-15) **bookspec:** `BS-asset-3`
**Status:** CHOICE (the owner, 2026-10-15)
Each fixed asset may carry a tag, the label stuck on the thing itself Each fixed asset may carry a tag, the label stuck on the thing itself
(generated `PREFIX-0001` or the human's own: 1–32 ASCII letters, digits, (generated `PREFIX-0001` or the human's own: 1–32 ASCII letters, digits,
@@ -1363,8 +1414,10 @@ the books' figure, never the schedule's (P3, defect 13).
## B-25 Tax figures beside book value: the §723 basis, the depreciable basis, the §704(c) built-in gain ## B-25 Tax figures beside book value: the §723 basis, the depreciable basis, the §704(c) built-in gain
**Status:** AUTHORITY (`sources/irs-partnership-basis.md`); DECISION **bookspec:** `BS-asset-4`
(the owner, 2026-10-15: the register carries tax basis); DECISION D-41
**Status:** AUTHORITY (`sources/irs-partnership-basis.md`); CHOICE
(the owner, 2026-10-15: the register carries tax basis); CHOICE D-41
(the owner, 2026-10-20: the depreciable basis of property a member (the owner, 2026-10-20: the depreciable basis of property a member
contributed from personal use is the lesser of its fair market value and contributed from personal use is the lesser of its fair market value and
the member's adjusted basis, the CPA to confirm at year end; the the member's adjusted basis, the CPA to confirm at year end; the
@@ -1440,8 +1493,10 @@ never assumed). **Checked:** `ledger-core` `tests/spec_asset_basis.rs`,
## B-26 Parties: one directory, no full TIN in the books ## B-26 Parties: one directory, no full TIN in the books
**bookspec:** `BS-partner-2`
**Status:** AUTHORITY (truncated TINs: `sources/irs-truncated-tin.md`); **Status:** AUTHORITY (truncated TINs: `sources/irs-truncated-tin.md`);
DECISIONS (the owner, 2026-10-15) CHOICES (the owner, 2026-10-15)
Everyone the company deals with is a party, with roles (vendor, Everyone the company deals with is a party, with roles (vendor,
contractor, customer, employee, member, payout platform, tax agency, contractor, customer, employee, member, payout platform, tax agency,
@@ -1451,12 +1506,12 @@ not archived, ignoring case and surrounding space. A W-9 is recorded as its
facts and the TIN's last four digits (the part a truncated TIN shows, facts and the TIN's last four digits (the part a truncated TIN shows,
Treas. Reg. §301.6109-4(a)); no party field may hold a nine-digit number, Treas. Reg. §301.6109-4(a)); no party field may hold a nine-digit number,
and the full TIN lives only in the encrypted vault, outside the journal and the full TIN lives only in the encrypted vault, outside the journal
(DECISION). W-9 facts need a 1099 role (contractor, landlord); W-4 facts (CHOICE). W-9 facts need a 1099 role (contractor, landlord); W-4 facts
need the employee role; payroll lines that name their employee name a need the employee role; payroll lines that name their employee name a
party with that role. A project's text (its name, description, links and party with that role. A project's text (its name, description, links and
notes: B-37) holds no full TIN either (`settings::line_text`, notes: B-37) holds no full TIN either (`settings::line_text`,
`long_text`). A link is a web address, so its rule reads it as one `long_text`). A link is a web address, so its rule reads it as one
(`settings::holds_printed_tin_in_link`, DECISION of this rule, so store (`settings::holds_printed_tin_in_link`, CHOICE of this rule, so store
listings can be kept): the printed SSN (3-2-4) and EIN (2-7) forms and a listings can be kept): the printed SSN (3-2-4) and EIN (2-7) forms and a
bare nine digits standing alone are refused, but nine digits joined to a bare nine digits standing alone are refused, but nine digits joined to a
letter or to `/ = ? & #` are an id in a path or query (an app store letter or to `/ = ? & #` are an id in a path or query (an app store
@@ -1471,13 +1526,13 @@ under the book's own key, `keys/legacy.key` beside it): `books.set_w9`
journals only the last four and the vault record's hash; the signed W-9 journals only the last four and the vault record's hash; the signed W-9
goes there too, never to the attachment store. Any party may have its goes there too, never to the attachment store. Any party may have its
TIN in the vault, whatever its roles: every member's (for their K-1) and TIN in the vault, whatever its roles: every member's (for their K-1) and
every 1099 payee's belongs there (DECISION, the owner, 2026-10-15: "In the every 1099 payee's belongs there (CHOICE, the owner, 2026-10-15: "In the
vault, that's the WHOLE reason we built it. And it needs to be in the vault, that's the WHOLE reason we built it. And it needs to be in the
damn CPA package."). `books.set_tin` stores one for any party; the books damn CPA package."). `books.set_tin` stores one for any party; the books
journal its TIN facts (type, last four, the record's hash: journal its TIN facts (type, last four, the record's hash:
`party.tin_set`), which never disagree with its W-9 facts' last four. No `party.tin_set`), which never disagree with its W-9 facts' last four. No
tool output, error, or log holds a full TIN except `books.vendor_1099` tool output, error, or log holds a full TIN except `books.vendor_1099`
with `include_tins` (DECISION, the owner, 2026-10-15: for the CPA, when with `include_tins` (CHOICE, the owner, 2026-10-15: for the CPA, when
asked): a refusal that quotes its input masks any TIN in it asked): a refusal that quotes its input masks any TIN in it
(`parties::redact_full_tins`, checked by `refusals_never_echo_a_tin`), (`parties::redact_full_tins`, checked by `refusals_never_echo_a_tin`),
and a party text field refuses nine digits however they are separated and a party text field refuses nine digits however they are separated
@@ -1498,10 +1553,10 @@ over as an export the asking token fetches with `wanderbooks download`
(kept 15 minutes), never inline (D-44; since D-46 no tool takes or (kept 15 minutes), never inline (D-44; since D-46 no tool takes or
gives any file's bytes inline: files come in only by upload and go out gives any file's bytes inline: files come in only by upload and go out
only by download). (Until slice 6 they were only by download). (Until slice 6 they were
written only outside the books dir: DECISION, P0 review, 2026-10-16, so written only outside the books dir: CHOICE, P0 review, 2026-10-16, so
none could be filed back in as a receipt and read out again.) No file none could be filed back in as a receipt and read out again.) No file
holding a full TIN is filed into the books holding a full TIN is filed into the books
(DECISION, P0 review round 2, 2026-10-16): the server keeps, beside the (CHOICE, P0 review round 2, 2026-10-16): the server keeps, beside the
vault, the SHA-256 of every file it read or wrote with full TINs (the W-9 vault, the SHA-256 of every file it read or wrote with full TINs (the W-9
`books.set_w9` reads, recorded once the call has passed every check, just `books.set_w9` reads, recorded once the call has passed every check, just
before the vault keeps it, so a refused call marks no file; each before the vault keeps it, so a refused call marks no file; each
@@ -1526,9 +1581,9 @@ or dot followed by a digit, on either side,
except the company's own EIN as its settings hold it, printed 2-7: it is except the company's own EIN as its settings hold it, printed 2-7: it is
the company's, no person's, the settings already hold it in full (B-29), the company's, no person's, the settings already hold it in full (B-29),
and every company document goes into the books, its formation papers and and every company document goes into the books, its formation papers and
plans printing it (DECISION, the owner, 2026-10-17); (a) still refuses it plans printing it (CHOICE, the owner, 2026-10-17); (a) still refuses it
should the vault hold that number for a party. Full-width digits and should the vault hold that number for a party. Full-width digits and
Unicode hyphens and spaces count as their ASCII kin (`tin_files.rs`). The text check is narrow on purpose (DECISION, Unicode hyphens and spaces count as their ASCII kin (`tin_files.rs`). The text check is narrow on purpose (CHOICE,
the owner, 2026-10-16): bank exports are filed for reconciliation, and the owner, 2026-10-16): bank exports are filed for reconciliation, and
their routing numbers, long references and dates next to amounts are their routing numbers, long references and dates next to amounts are
not TINs; the broad `has_full_tin` reading stays on what the server says not TINs; the broad `has_full_tin` reading stays on what the server says
@@ -1536,7 +1591,7 @@ not TINs; the broad `has_full_tin` reading stays on what the server says
answer would tell an agent whether a text it chose holds a vault TIN; answer would tell an agent whether a text it chose holds a vault TIN;
the tools show each TIN's last four, so an agent filing texts full of the tools show each TIN's last four, so an agent filing texts full of
nine-digit numbers ending in those four could find the TIN in about 17 nine-digit numbers ending in those four could find the TIN in about 17
filings. So (a) answers for at most two candidates a filing (DECISION, filings. So (a) answers for at most two candidates a filing (CHOICE,
P0 review, 2026-10-16): before the vault is opened, a text holding more P0 review, 2026-10-16): before the vault is opened, a text holding more
than two distinct TIN-shaped numbers (in any rendering (a) reads; one than two distinct TIN-shaped numbers (in any rendering (a) reads; one
number written several ways counts once) that end in the last four of a number written several ways counts once) that end in the last four of a
@@ -1593,10 +1648,12 @@ unit tests in `tin_files.rs`.
## B-27 The journal format gate: newer is not corrupt ## B-27 The journal format gate: newer is not corrupt
**Status:** DECISION (the owner, 2026-10-16; `plan/served.md` 5.1 and P0); **bookspec:** `BS-format-1`
DECISION (P0 review, 2026-10-16: a line's own hash is checked before the
**Status:** CHOICE (the owner, 2026-10-16; `plan/served.md` 5.1 and P0);
CHOICE (P0 review, 2026-10-16: a line's own hash is checked before the
line is called newer; the envelope and its hash are frozen across line is called newer; the envelope and its hash are frozen across
formats); DECISION (the owner, 2026-10-20, D-39, and `plan/served.md` formats); CHOICE (the owner, 2026-10-20, D-39, and `plan/served.md`
3.9: older books move to a newer format only with the owner's go-ahead, 3.9: older books move to a newer format only with the owner's go-ahead,
asked at every open until given) asked at every open until given)
@@ -1789,11 +1846,13 @@ schema); `ledger-mcp` `local.rs` (`format_upgrade_waits_for_consent`,
## B-28 What new commands take: four-digit years, amounts a line can carry ## B-28 What new commands take: four-digit years, amounts a line can carry
**bookspec:** `BS-entry-4`
**Status:** CONVENTION (ISO 8601's basic calendar dates have four-digit **Status:** CONVENTION (ISO 8601's basic calendar dates have four-digit
years, as do the spreadsheet and PDF formats the books export to); years, as do the spreadsheet and PDF formats the books export to);
DECISION (P0 review round 3, 2026-10-16, finding S1: the server stops CHOICE (P0 review round 3, 2026-10-16, finding S1: the server stops
for every session when a request panics, so no input may reach one); for every session when a request panics, so no input may reach one);
DECISION (P0 review round 4, 2026-10-17: posted is forever, so the CHOICE (P0 review round 4, 2026-10-17: posted is forever, so the
bounds are the commands', never replay's) bounds are the commands', never replay's)
**New commands.** Every date a command takes or derives is in **New commands.** Every date a command takes or derives is in
@@ -1904,7 +1963,9 @@ pinned; every tool over every old book) and `server.rs`
## B-29 The books keep how they are kept ## B-29 The books keep how they are kept
**SOURCE:** DECISION (plan/served.md I-3, owner decisions of 2026-10-16: **bookspec:** `BS-ledger-9`
**SOURCE:** CHOICE (plan/served.md I-3, owner decisions of 2026-10-16:
"the journal holds all books state"; light governance: decisions are "the journal holds all books state"; light governance: decisions are
recorded with who made them and when, not as paperwork); IRS Pub. 538 and recorded with who made them and when, not as paperwork); IRS Pub. 538 and
the rules of B-1 to B-28 for what each setting means. the rules of B-1 to B-28 for what each setting means.
@@ -1978,12 +2039,14 @@ checked to change no figure or entry; `approval_rules_off_by_default`,
## B-30 A report asked for without dates covers this fiscal year so far ## B-30 A report asked for without dates covers this fiscal year so far
**bookspec:** `BS-period-3`
**Status:** AUTHORITY for the period (IRS Pub. 538, **Status:** AUTHORITY for the period (IRS Pub. 538,
`sources/irs-periods-and-methods.md`: "A tax year is an annual accounting `sources/irs-periods-and-methods.md`: "A tax year is an annual accounting
period for keeping records and reporting income and expenses"; 26 U.S.C. period for keeping records and reporting income and expenses"; 26 U.S.C.
§6041(a), "in any calendar year", and §6041A(a)(2), "during such calendar §6041(a), "in any calendar year", and §6041A(a)(2), "during such calendar
year", `sources/irs-1099-thresholds.md`, for the calendar-year counts); year", `sources/irs-1099-thresholds.md`, for the calendar-year counts);
DECISION for using it as the default (no authority says what a tool's CHOICE for using it as the default (no authority says what a tool's
omitted dates mean; the owner, 2026-10-20, `plan/served.md` P3 "Settled in omitted dates mean; the owner, 2026-10-20, `plan/served.md` P3 "Settled in
design": "a report asked for without dates covers this fiscal year so design": "a report asked for without dates covers this fiscal year so
far, everywhere, and an account's register opens on it with 'Show all far, everywhere, and an account's register opens on it with 'Show all
@@ -2048,7 +2111,9 @@ generated descriptions (`described`) and parameter docs; `ledger-gui`
## B-31 Renaming an account changes its label, nothing else ## B-31 Renaming an account changes its label, nothing else
**Status:** DECISION (the owner, `plan/served.md` 4.9 and P3 "Settled in **bookspec:** `BS-ledger-10`
**Status:** CHOICE (the owner, `plan/served.md` 4.9 and P3 "Settled in
design", 2026-10-20: "a new account named like a sibling (ignoring design", 2026-10-20: "a new account named like a sibling (ignoring
capitals), or holding a nine-digit number, is refused"); B-8 (posted is capitals), or holding a nine-digit number, is refused"); B-8 (posted is
forever); AUTHORITY for keeping the old name (Rev. Proc. 98-25 §5.01, forever); AUTHORITY for keeping the old name (Rev. Proc. 98-25 §5.01,
@@ -2111,13 +2176,15 @@ bookspec `examples/rename-changes-no-amount.toml`.
## B-32 Every revenue and expense account maps to a line of the return ## B-32 Every revenue and expense account maps to a line of the return
**bookspec:** `BS-ledger-11`
**Status:** AUTHORITY for why it matters (IRS Pub. 583, **Status:** AUTHORITY for why it matters (IRS Pub. 583,
`sources/irs-records.md`: "Your books must show your gross income, as `sources/irs-records.md`: "Your books must show your gross income, as
well as your deductions and credits"; IRS Pub. 538: "you must maintain well as your deductions and credits"; IRS Pub. 538: "you must maintain
records that will enable you to file a correct return"; Rev. Proc. 98-25 records that will enable you to file a correct return"; Rev. Proc. 98-25
§5.01: the relationship "between the total of the amounts in the §5.01: the relationship "between the total of the amounts in the
taxpayer's machine-sensible records by account and the taxpayer's taxpayer's machine-sensible records by account and the taxpayer's
return"); DECISION for warn versus refuse (Pub. 583: "the law does not return"); CHOICE for warn versus refuse (Pub. 583: "the law does not
require any specific kind of records"; the owner, 2026-10-20: warn by require any specific kind of records"; the owner, 2026-10-20: warn by
default, header accounts exempt, the first company set to refuse at the default, header accounts exempt, the first company set to refuse at the
P3 cutover). P3 cutover).
@@ -2170,11 +2237,13 @@ bookspec `examples/tax-category-required.toml`.
## B-33 A package as of a day shows the books through that day ## B-33 A package as of a day shows the books through that day
**bookspec:** `BS-statement-4`
**Status:** CONVENTION (a report as of a day reports the books on that **Status:** CONVENTION (a report as of a day reports the books on that
day: balances through it, a period's activity from the period's start day: balances through it, a period's activity from the period's start
through it; nearest authorities the accounting period, IRS Pub. 538, through it; nearest authorities the accounting period, IRS Pub. 538,
`sources/irs-periods-and-methods.md`, and the information returns' `sources/irs-periods-and-methods.md`, and the information returns'
calendar year, `sources/irs-1099-thresholds.md`); DECISION (owner, calendar year, `sources/irs-1099-thresholds.md`); CHOICE (owner,
2026-10-20, plan D-36: the 1099 worksheet stops at as_of too) 2026-10-20, plan D-36: the 1099 worksheet stops at as_of too)
Nothing in the CPA package made as of a day (`books.export_cpa_package`, Nothing in the CPA package made as of a day (`books.export_cpa_package`,
@@ -2226,6 +2295,8 @@ and `bookspec/examples/1099-worksheet-through-as-of.toml` (`ledger-core`
## B-34 The QuickBooks Desktop file (IIF): plain ASCII, every entry through as_of, closes left out, classes carried ## B-34 The QuickBooks Desktop file (IIF): plain ASCII, every entry through as_of, closes left out, classes carried
**bookspec:** `BS-format-2`
**Status:** AUTHORITY (Intuit, `sources/intuit-iif.md`: IIF files are **Status:** AUTHORITY (Intuit, `sources/intuit-iif.md`: IIF files are
ASCII text, tab-separated, with no escape; an account is known by its ASCII text, tab-separated, with no escape; an account is known by its
NAME, a subaccount's NAME its path joined by colons; lists come before NAME, a subaccount's NAME its path joined by colons; lists come before
@@ -2243,7 +2314,7 @@ QuickBooks Desktop, as an account's); CONVENTION (a
QuickBooks integrator and an IIF importer, same file: a longer name is QuickBooks integrator and an IIF importer, same file: a longer name is
cut on a chart import but not in a journal's lines; a double quote is a cut on a chart import but not in a journal's lines; a double quote is a
text delimiter; Conductor's QuickBooks Desktop API: a class's name text delimiter; Conductor's QuickBooks Desktop API: a class's name
case-insensitive, so no two alike ignoring case); DECISION (owner, 2026-10-20, plan D-36: plain ASCII, what isn't case-insensitive, so no two alike ignoring case); CHOICE (owner, 2026-10-20, plan D-36: plain ASCII, what isn't
ASCII transliterated and listed; CR LF; DOCNUM the entry's id; closing ASCII transliterated and listed; CR LF; DOCNUM the entry's id; closing
entries left out; every entry since the company began until the CPA entries left out; every entry since the company began until the CPA
says otherwise); Unicode Standard Annex #14 (the mandatory line breaks) says otherwise); Unicode Standard Annex #14 (the mandatory line breaks)
@@ -2386,8 +2457,10 @@ appendix: the file line by line and its changes, derived by hand);
## B-35 A party's part of an entry, and a party's totals ## B-35 A party's part of an entry, and a party's totals
**bookspec:** `BS-partner-3`
**Status:** CONVENTION (the sign: B-2's, a line on the party's own **Status:** CONVENTION (the sign: B-2's, a line on the party's own
account); DECISION (the owner, 2026-10-15: `entry_flow`, money paid down account); CHOICE (the owner, 2026-10-15: `entry_flow`, money paid down
counted once; the owner, 2026-10-20, P3 design settled with each review's counted once; the owner, 2026-10-20, P3 design settled with each review's
recommendation: a member's entry counts what moved their capital, money recommendation: a member's entry counts what moved their capital, money
is cash, cards, the members' financing and loans, and a liability credit is cash, cards, the members' financing and loans, and a liability credit
@@ -2615,10 +2688,12 @@ distributions declared or of depreciated property, the properties), Kani
## B-36 The K-1 worksheet reports tax-basis capital beside book capital ## B-36 The K-1 worksheet reports tax-basis capital beside book capital
**bookspec:** `BS-partner-4`
**Status:** AUTHORITY (`sources/irs-partnerships.md`: Instructions for **Status:** AUTHORITY (`sources/irs-partnerships.md`: Instructions for
Form 1065 (2025), Item L, "Tax-basis method"; required from tax year Form 1065 (2025), Item L, "Tax-basis method"; required from tax year
2020: Instructions for Form 1065 (2020), Item L, "you must use the tax 2020: Instructions for Form 1065 (2020), Item L, "you must use the tax
basis method this year"); DECISION D-41 (the owner, 2026-10-20: "the basis method this year"); CHOICE D-41 (the owner, 2026-10-20: "the
K-1 worksheet reports tax-basis capital ... the contributed property at K-1 worksheet reports tax-basis capital ... the contributed property at
its §723 carryover basis, beside the book figures") its §723 carryover basis, beside the book figures")
@@ -2670,7 +2745,7 @@ book capital reconciles to tax capital:
account of a prepaid-expense schedule: a service contract the member account of a prepaid-expense schedule: a service contract the member
handed over, B-13) with no fixed asset for it enters at its book handed over, B-13) with no fixed asset for it enters at its book
amount, with a note: a contract's carryover basis is what the books amount, with a note: a contract's carryover basis is what the books
recorded for it (DECISION, P3 review 2026-10-21; the CPA to confirm). recorded for it (CHOICE, P3 review 2026-10-21; the CPA to confirm).
- What the books can't know is never guessed. The share of net income - What the books can't know is never guessed. The share of net income
"as figured for tax purposes" is the return's, always: the books are "as figured for tax purposes" is the return's, always: the books are
kept on their own basis, and every book-tax difference (Schedule M-1) kept on their own basis, and every book-tax difference (Schedule M-1)
@@ -2711,11 +2786,13 @@ package); the odd cents, `ledger_core::tax_capital::RunningSplit` on
## B-37 A project is a class with its facts; its figures are its tagged lines ## B-37 A project is a class with its facts; its figures are its tagged lines
**Status:** DECISION (the owner, 2026-10-21: "a Projects page that treats **bookspec:** `BS-statement-5`
**Status:** CHOICE (the owner, 2026-10-21: "a Projects page that treats
a class as a project ... Projects are entities too and can have name, a class as a project ... Projects are entities too and can have name,
description, whatever other metadata you think they should have"; the description, whatever other metadata you think they should have"; the
convention `link-to-projects` in the books: "whenever applicable, we link convention `link-to-projects` in the books: "whenever applicable, we link
things to [projects]"); DECISION (the owner, 2026-10-22, plan D-43: an invoice's or bill's things to [projects]"); CHOICE (the owner, 2026-10-22, plan D-43: an invoice's or bill's
lines take a project, each line one or none, item 8); CONVENTION (job lines take a project, each line one or none, item 8); CONVENTION (job
costing, items 3 and 8; the snapshot, item 7: a presentation choice from costing, items 3 and 8; the snapshot, item 7: a presentation choice from
management accounting, not GAAP); AUTHORITY (on management accounting, not GAAP); AUTHORITY (on
@@ -2877,7 +2954,9 @@ proptests `prop_project_nets_partition_the_pnl`,
## B-38 A document's figures are its ledger lines that stand; a control account is its documents ## B-38 A document's figures are its ledger lines that stand; a control account is its documents
**Status:** DECISION (the owner, 2026-10-23, plan D-45: "we kinda need **bookspec:** `BS-arap-2`
**Status:** CHOICE (the owner, 2026-10-23, plan D-45: "we kinda need
stronger invoice support. Like make it a first class citizen"; hand stronger invoice support. Like make it a first class citizen"; hand
entries to receivables and payables refused, the owner's answer 5); entries to receivables and payables refused, the owner's answer 5);
CONVENTION (a control account agrees with its subledger, as B-23; the CONVENTION (a control account agrees with its subledger, as B-23; the
+68
View File
@@ -2847,3 +2847,71 @@ nine-digit check), `full_tin_runs`'s `extend(grouped_tin_runs(..))`;
Deleting any one of them leaves a TIN that the old scanner passed Deleting any one of them leaves a TIN that the old scanner passed
either readable or unmasked, which the truth table and the property either readable or unmasked, which the truth table and the property
catch. catch.
## P13: bookspec, the checker crate and the runner (2026-10-26)
bookspec's example schema moved out of the loader into
`bookspec/checker`, its own Cargo workspace inside the repository (P13
step 1, D-34). `crates/ledger-core/tests/bookspec.rs` is now the
wanderbooks adapter: it imports `Example`, `Setup`, `Event`, `Expect`,
`Query`, `Refusal` and the rest from the checker by a path dev-dependency,
and it is unchanged in what it proves. The loader parses every example
with `Example::from_toml` (the parser plus the schema's own cross-field
checks), so a malformed example fails before an engine sees it.
Step 2 adds the `Engine` trait (`bookspec/checker/src/engine.rs`: `load`,
`apply`, `query`, `claims`, and `change` for a `[[changes]]` entry), the
runner (`bookspec/runner`), and the adapter's implementation over `Store`.
The runner runs all 56 examples, one fresh engine each, compares every
answer with the example's exact values and every refusal with the reason
the example names, and reports per rule: pass, fail or not claimed. The
adapter claims the rules the catalogue gives an example; a rule with no
example is not claimed, never silently passed.
Step 3 adds the rule identifiers: `BS-<area>-<n>` for the eleven areas,
`bookspec/spec/rules-index.md` as the table from B-1 to B-45, and each
stated rule's id under its heading in `docs/spec/rules.md`. `DECISION`
there is bookspec's `CHOICE`: the specification names the options, an
engine declares its pick (`Claims::with_choice`). B-39 to B-45 are
allocated (`plan/served.md` 8.0.3) and not yet stated; they carry an id and
an area and no status.
- **`bookspec/checker` `tests/schema.rs`:** `every_seed_example_parses_and_validates`
(all 56, the file named for its id, ids unique), `an_unknown_key_is_refused`,
`the_status_is_authority_convention_or_choice`, `a_line_amount_is_exact_and_positive`,
`an_event_is_exactly_one_kind`, `reverses_counts_events_from_one`,
`the_members_shares_sum_to_one`, `a_share_is_a_fraction_above_zero`,
`every_refusal_reason_names_a_rule`, `an_unknown_query_is_refused`,
`a_period_may_be_refused`, `a_command_shows_no_values`,
`a_figure_query_takes_exact_decimals`, `a_query_does_not_carry_its_expected_values`,
`a_format_query_is_of_the_formats_appendix`, `the_rule_index_lists_every_stated_rule`.
- **`bookspec/checker` `tests/rules.rs`:** `every_b_rule_has_one_id`,
`the_index_matches_the_catalogue`, `every_example_is_listed_under_a_rule`,
`a_claimed_rule_has_an_example`, `an_id_has_one_spelling`,
`a_choice_rule_declares_its_option`.
- **`bookspec/checker` `tests/docs.rs`:** `rules_md_carries_every_id`.
- **`bookspec/runner` `tests/runner.rs`:** `a_passing_engine_passes`,
`a_wrong_answer_fails`, `an_engine_that_claims_nothing_is_not_claimed`,
`a_claim_without_an_example_fails`, `a_refused_query_fails`,
`an_example_listed_under_no_rule_fails`, `a_refused_change_fails`. The
fake engine proves the runner judges answers, not the engine's belief.
- **`ledger-core` `tests/bookspec.rs`:** `the_runner_reports_every_claimed_rule_green`
(all 56 examples through the trait, every claimed rule pass) and
`every_bookspec_example_holds` (unchanged: the loader's own checks).
- **`ledger-package` `tests/package.rs`:** `the_bookspec_iif_example_matches_the_engine`
unchanged; the adapter answers the formats appendix through
`ledger_package::export_iif`, the same code the package test checks.
**For P12 (mutation targets):** `rules::build`'s area ordinals (a rule in
the wrong area); `RuleId::parse`'s prefix, area, digit, leading-zero and
zero checks; `rule_for_refusal`'s mapping arms; `schema::is_exact_decimal`
(each character class and the single point), `is_date`, `is_month`,
`is_month_day` and `check_amount`'s positivity; `Example::validate`'s
closed-list arms, the one-kind rule, `reverses < n`, the shares summing to
1, `after` bounds, the command's accepted-or-refused exclusivity and the
`only` table; `Query::is_complete`'s membership; `Refusal::of_reason`'s
fallback; the runner's `compare` (key equality, missing and extra keys,
list and row equality), `Outcome`'s mapping and `run`'s "claimed but no
example" arm; the adapter's `event_of`, its `depreciation_schedule` month
span (acquisition through as-of, 0.00 where the schedule reaches no month)
and every `answer_*` figure against the loader's same check (the two must
agree on all 56 examples).>>>>>>> 382e9406 (feat(bookspec): rule identifiers from B-1 to B-45 (P13))