Files
wanderbooks/docs/spec/rules.md
T
swaits 3e65c83f96 docs: B-26's TIN separators and P7's mutation targets (P7)
rules.md B-26 now says what the scanner reads: a comma or a parenthesis
between a TIN's 3-2-4 or 2-7 groups, and any run of spaces counting
once, while an amount's comma-grouped thousands stay an amount. The
filing scan's (a) and (b) renderings gain commas. docs/testing.md gains
the P7 section and the For-P12 mutation targets (D-33), and plan/
served.md's P7 As-built block records the three departures.
2026-10-26 20:29:15 -06:00

3027 lines
184 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rule catalog
Every rule the engine enforces, the authority for it, and what checks it.
Status is **AUTHORITY** (a source states the rule), **CONVENTION** (common
practice no authority states; the best reference is given), or
**DECISION** (a human chose, and when). Vendored sources are in
`sources/`; FASB and OpenStax are quoted briefly under their copyright and
license (see `README.md`). Quotes from FASB's Codification were read in
Deloitte's DART roadmaps, which reproduce it ("FASB Accounting Standards
Codification: Copyright © 2005 - 2026 by Financial Accounting
Foundation").
Spec blocks in the code cite these as `SOURCE: rules.md B-n`.
---
## B-1 Every entry is a double entry: debits equal credits
**Status:** AUTHORITY
- Pacioli ch. 36 (`sources/pacioli-1494.md`): "All entries made in the
ledger have to be double entries — that is, if you make one creditor,
you must make some one debtor."
- IRS Pub. 583 (`sources/irs-records.md`): "It is self-balancing because you
record every transaction as a debit entry in one account and as a credit
entry in another. Under this system, the total debits must equal the
total credits after you post the journal entries to the ledger accounts."
**Enforced:** `kernel::check_lines` via `JournalEntry::validate` (two or
more lines, debits = credits exactly). A line's account must exist and
take postings when the line is added, not only at post: DECISION
(the owner, 2026-10-13), `Ledger::add_line`
(`a_line_on_an_unknown_or_placeholder_account_is_refused_when_added`). **Checked:** Kani
`check_lines_is_exact`, `accepted_entry_nets_to_zero`; Lean
`Accepted.net_zero`; property `prop_posted_entries_balance`.
## B-2 A line's amount is positive; its side gives the direction
**Status:** CONVENTION
Pacioli keeps amounts in a debit column and a credit column (ch. 36: "All
the creditors must appear in the Ledger at the right hand side, and all the
debtors at the left"); the side, not a sign, says which way money moves. No
authority forbids a negative amount outright; wanderbooks refuses them so
that every line reads one way (2026-10-13, found by the formal pass).
**Enforced:** `kernel::check_lines` (`NegativeAmountLine`, `ZeroAmountLine`).
**Checked:** Kani `check_lines_is_exact`; test
`negative_amount_line_is_rejected`.
## B-3 One money through the ledger; foreign amounts at the transaction-date rate
**Status:** AUTHORITY
- Pacioli ch. 36: "the money that you reckon by at the beginning should be
the same all through the Ledger."
- ASC 830-20-30-1 (cited): "each asset, liability, revenue, expense, gain,
or loss arising from the transaction shall be measured initially in the
functional currency of the recording entity by use of the exchange rate
in effect at that date."
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
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
cross-check found wanderbooks kept $108.34567 where GnuCash booked
$108.35. Lines booked before then replay as booked.
**Enforced:** `Ledger::add_line` (base currency only, no fraction of the
minor unit), `Ledger::add_line_fx` (base amount = foreign amount × rate,
rounded to the minor unit; original kept), `Ledger::post_entry` (every
line, whatever made it: payroll, closes, disposals; `FinerThanMinorUnit`,
since 2026-10-13 after a payroll run posted $65.27794). Journals from
before replay as booked (`replay_post_entry`). A new asset's cost and
salvage are in the minor unit too (P3: every depreciation and disposal
line derives from them; `validate_new_asset`), and depreciation and daily
schedules round to it (B-15, B-16); assets an earlier version took finer
replay as journaled. **Checked:**
`an_asset_cost_finer_than_a_cent_is_refused`,
`a_foreign_line_is_booked_in_whole_base_units`,
`no_entry_posts_a_fraction_of_the_minor_unit`,
`foreign_currency_line_is_rejected`, `fx_overflow_is_an_error`; the
GnuCash oracle.
## B-4 Five elements, closed list
**Status:** AUTHORITY (definitions); the five-type chart is CONVENTION
FASB Concepts Statement No. 8, Ch. 4 (2021), which replaced Concepts
Statement 6 (cited): E16 "An asset is a present right of an entity to an
economic benefit." E37 "A liability is a present obligation of an entity to
transfer an economic benefit." E61 equity is "the residual interest in the
assets of an entity that remains after deducting its liabilities." E80
revenues "are inflows or other enhancements of assets … or settlements of
its liabilities … from delivering or producing goods, rendering services,
or carrying out other activities." E81 expenses "are outflows or other
using up of assets … or incurrences of its liabilities …".
**Enforced:** `AccountType` (five variants). A child account has its
parent's type: DECISION (the owner, 2026-10-13), `Ledger::add_account`
(`ParentTypeMismatch`). **Checked:** `a_child_account_has_its_parents_type`.
## B-5 Normal balances
**Status:** CONVENTION
No standard defines normal balances. OpenStax, *Principles of Accounting,
Vol. 1* §3.1 (cited; "Access for free at openstax.org"): "As assets and
expenses increase on the debit side, their normal balance is a debit…
Since liabilities, equity (such as common stock), and revenues increase
with a credit, their 'normal' balance is a credit." Pacioli ch. 12: capital
"must always be placed as creditor … and the cash always debtor."
**Enforced:** `AccountType::normal_side`, `kernel::normal_balance`.
**Checked:** Kani `normal_balance_is_signed`, `normal_side_increases`; Lean
`balance_after_line`.
## B-6 The trial balance balances
**Status:** AUTHORITY
Pacioli ch. 36: "the total of the credits — I do not say creditors —
should be equal to the total of the debits — I do not say debtors. If they
were not equal there would be a mistake in the Ledger." IRS Pub. 583 (B-1).
**Enforced:** follows from B-1. **Checked:** Kani `trial_balance_columns`;
Lean `trial_balance_iff`, `Reachable.trial_balance`,
`Reachable.trial_balance_through`; properties.
## B-7 The accounting equation
**Status:** AUTHORITY
CON 8 Ch. 4 ¶E62 (cited): equity "is the difference between the entity's
assets and its liabilities." On a live book, income not yet closed is part
of equity (B-11).
**Checked:** Kani `accounting_equation`; Lean `accounting_equation`,
`Reachable.accounting_equation[_through]`; property
`prop_balance_sheet_balances`.
## B-8 Posted is forever: the book is written only forward
**Status:** AUTHORITY
- Pacioli ch. 36: "You should not write backward, but always forward —
that is, go forward as the days go, which never come back. If you do
otherwise, the book would be untrue."
- German Commercial Code (HGB) §239(3) (cited; official English
translation): "An entry or a documented record may not be modified such
that it is no longer possible to establish its original content."
- Rev. Proc. 98-25 (`sources/irs-records.md`): machine-sensible records
must keep the audit trail.
**Enforced:** posted entries immutable; the journal is append-only and
hash-chained. **Checked:** Lean `Step.extends`, `Steps.extends`; property
`prop_posted_entries_immutable`; store `journal_tamper_is_detected`.
## B-9 A mistake is corrected by an opposite entry, never erased
**Status:** AUTHORITY
Pacioli ch. 31 (the *storno*): "you shall make another entry opposite this
one in the credit for the same amount … After you have made this
correction it is just as if you had written nothing in the debit column."
Ch. 36: "Mark with a cross or with an "H" that special entry, and then make
an entry on the opposite side under the same account."
**Enforced:** `Ledger::reverse_entry` (same lines, sides flipped, linked by
`reversal_of`). **Checked:** Kani `reversal_mirrors`, `reversal_cancels`;
Lean `accepted_reversal_iff`, `movement_with_reversal`.
## B-10 An entry is reversed at most once; a closing entry never
**Status:** DECISION (the owner, 2026-10-13)
Follows from B-9 (a correction corrects once) and B-11 (a closed period
stays closed). **Enforced:** `Ledger::check_reversal`. **Checked:**
`an_entry_is_reversed_at_most_once`, `a_closing_entry_is_never_reversed`.
## B-11 Revenue and expenses close into profit and loss, and that into capital
**Status:** AUTHORITY (mechanics); the account the net lands in depends on
the entity
- Pacioli ch. 34: "you shall close this account into the capital account";
ch. 27: profit and loss "must then be transferred for its closing (saldo)
into the capital account."
- OpenStax §5.1 (cited): "Temporary (nominal) accounts are accounts that
are closed at the end of each accounting period." Corporations close into
retained earnings; partnerships into each partner's capital (B-13).
**Enforced:** `Ledger::close_period_to`. **Checked:** Kani
`closing_line_zeroes`, `closing_entry_balances`; Lean
`closingEntry_zeroes`, `closingEntry_accepted`, `close_zeroes`,
`close_credits_net_income`; `a_close_sweeps_only_through_the_period_end`.
## B-12 A closed period takes no new postings; periods close in order
**Status:** CONVENTION
Periods themselves: IRS Pub. 538 (`sources/irs-periods-and-methods.md`)
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
book is not rewritten). Periods nest (a month in a quarter in a year)
and never partially overlap: DECISION (the owner, 2026-10-13),
`Ledger::create_period` (`PeriodsOverlap`;
`periods_nest_but_never_partially_overlap`). **Enforced:** `closed_period_containing`,
`CloseOutOfOrder`, `Store::rehearse`. **Checked:** Lean `Steps.frozen`;
store property `refused_commands_never_reach_the_journal` (no draft in a
closed period).
## B-13 Partners share income by the partnership agreement; capital accounts carry it
**Status:** AUTHORITY (shares; capital accounts; a count ends where the
members or shares change: §706(d)(1), Treas. Reg. §1.706-4(a)); DECISION
(cent rounding, the owner 2026-10-13); DECISION (counted from the company's
first close, D-10, 2026-10-16); DECISION (the statement splits unclosed
earnings the same running way, D-38, 2026-10-20)
- 26 U.S.C. §704(a) (`sources/irs-partnerships.md`): "A partner's
distributive share of income, gain, loss, deduction, or credit shall,
except as otherwise provided in this chapter, be determined by the
partnership agreement."
- Treas. Reg. §1.704-1(b)(2)(iv)(b) (`sources/irs-partnerships.md`): each
partner's capital account "is increased by … (3) allocations to him of
partnership income and gain" and "is decreased by … (7) allocations of
partnership loss and deduction", beside the money and property
contributed and distributed. A capital account is one running balance of
contributions, allocations and distributions: the closes are the
allocations.
- Form 1065 Schedule M-2: "Show what caused changes during the tax year in
the partners' tax-basis capital accounts." Schedule K-1 Item L reports
each partner's capital account "using the tax-basis method"; that
analysis "is based on the partnership's books and records" (the Partner's
Instructions' Basis Limitations section, `sources/irs-partnerships.md`;
not Item L, which an earlier version of this rule spliced in). The K-1
worksheet carries the book capital analysis, and beside it the tax-basis
capital as far as the books know it (B-36).
- **No authority** says how to round a share to the cent. Largest
remainder, ties to the member listed first: DECISION (the owner,
2026-10-13). Closes made before then replay with the last member
absorbing it.
- **Members closes split cumulatively, counted from the company's first
close** (P3 defect 17; D-10). A *count* is the members closes with one
set of (capital account, share) pairs, from the first such close since
the last members close with another set: closes to retained earnings or
to one account are skipped (they allocate nothing to the members), and
neither the members' order nor their distribution accounts matter. Each
close gives member *i* allocate(C + net)*ᵢ* − given*ᵢ*: their split of
everything the count has closed (C before it, plus its own net), to the
base currency's minor unit (cents, whole yen, thousandths of a dinar;
B-3; for a code ISO 4217 gives none, such as gold, the finest a line
carries, `kernel::MAX_LINE_SCALE` places, so every amount is still whole
units), less what the count's earlier closes gave them under whatever
rule they were made. A tied odd unit goes to the member the count's
first close listed first, whatever order later closes list the members
in, so a reorder of the members moves no cent; the statement below
breaks ties the same way, on a day before a count's first close in the
order of the count the next members close begins. Therefore each close hands out
exactly its net; after every close that posts, each member's total from
the count is one split of the count's total, under one minor unit from
their exact share; and rounding by closes made under earlier rules is
trued up by the first close that posts (D-10: from inception; listed in
the close's `members`). The true-up is under a unit per member for each
break in an earlier run and each per-close close; a last-absorbs close
rounded each member but the last to within half a unit, and the last
absorbed up to half a unit for each of the others (a unit with three
members, more with four or more).
- A change of members or shares starts a new count (26 U.S.C.
§706(d)(1): a year's distributive shares are determined by a method
"which takes into account the varying interests of the partners in the
partnership during such taxable year"; Treas. Reg. §1.706-4(a)(3)(vii):
the partnership "shall treat each segment as though the segment were a
separate distributive share period"). Income closed under
the old shares is never split again under the new ones. So the bound
holds per count: across k changes a member's total can be up to one
unit per count from the sum of their exact shares (the D-10 lifetime
bound holds while members and shares are unchanged).
- A true-up rides only on a close that posts an entry (income to close,
or a distribution to sweep): a close that posts nothing credits no one,
and the true-up waits for the next close that posts (close INVARIANT
5: never post a closing entry when there is nothing to close).
- The bound's precondition: every amount is a whole number of minor
units (B-3: every posted line is), and every product |total| × share
is exact. A product Decimal would round (a share of many decimal places
times a large total) is refused, never split
(`periods::allocate_cumulative` INVARIANT 4). Replaying a journal from
before B-3, a sub-unit fraction can make the bound two units.
- A year's income per member is the difference of two running totals,
each under a unit from exact, so under two units from share × the
year's income; the return is in whole dollars ("Rounding on the
return", `sources/irs-partnerships.md`).
- A period closes at the shares in force at the close: a share change
inside a period isn't split at its date (§706(d) and §1.706-4 would);
close the period at the change.
- Journaled `"rounding": "cumulative_since"` with `"since_seq": N`, the
close the count starts at, under the journal feature
`members_cumulative` (B-27); replay derives N again and refuses a close
journaled with another. Closes journaled `"cumulative"` (the run rule of
2026-10-13 to P3: a back-to-back run with the same members in the same
order, restarted by any other close, so a tied cent could go to the
same member at every restart), `"largest_remainder"` (each close on its
own) and none (the last member absorbs) replay under their own rules,
never changed (B-8).
- A capital account is one running balance (§1.704-1(b)(2)(iv)(b)). So the
statement of members' equity, the K-1 worksheet and the viewer split
undistributed earnings U as of each date the way the count's next close
would split them: allocate(C(d) + U(d)) − allocate(C(d)), C(d) being what
the count's closes dated on or before d closed (D-38; P3 review R-1). A
member's capital on a date never moves when a period ending on or before
it is later closed; a true-up of rounding by closes made under an earlier
rule shows only once a close posts it, dated at that close. A
statement's beginning is as of the day before its period, its ending as
of its last day: a member's ending on one statement is their beginning on
the next, to the unit, and a member's period income is the roll-forward's
remainder (their share within a rounding unit at each end).
- A correction undoes what it corrects (B-9): an entry and its reversal
both in a statement's span are neither contributions nor
distributions (`reports::cancelled_in_span`; the K-1 worksheet reads
the same). The K-1 worksheet's contributed property (Item L) is what
the member owned and handed over, with its own basis (§723): a prepaid
service contract credited straight to capital is property (a contract
handed over), while a prepaid service they paid a vendor for on the
company's behalf, booked through Due to Shareholders (Dr prepaid / Cr
2100, then Dr 2100 / Cr capital), is cash (DECISION, the owner,
2026-10-15); and it is shared among
members credited in one entry in proportion to their credits
(`k1` INVARIANTS 3, 4, 6).
**Enforced:** `CloseTo::Members`, `CloseTo::MembersCumulative`,
`Ledger::members_regime`, `Regime::allocate`,
`periods::allocate_cumulative`, `kernel::apportion_cumulative`,
`periods::allocate`, `kernel::apportion`, `members_equity`,
`Store::close_period_to` (the feature it needs, B-27), the store's
`close_to_from_journal`. **Checked:** Kani `apportion_is_exact`,
`apportion_is_fair`, `apportion_cumulative_is_exact`,
`cumulative_close_never_drifts_a_unit_at_halves`, `_at_tenths`,
`_at_near_thirds`,
`cumulative_closing_entry_balances`; Lean `runPart_telescopes`,
`closePart_total`, `trueUp_total`, `trueUp_running`,
`closePart_eq_trueUp`, `cumulative_drift`, `statement_after_close`,
`statement_unmoved_by_close`, `Apportion.rem_pos_of_rank_lt`,
`apportion_drift`, `allocW_sum`, `allocW_drift`,
`allocW_cumulative_drift`, `allocW_cumulative_total`; properties
`prop_split_*`, `each_members_capital_chains_across_statements`,
`each_member_stays_within_a_cent_of_their_share`,
`members_capital_never_drifts_a_cent_from_their_share`,
`each_members_capital_chains_and_holds_through_closes`,
`period_ends_rise_with_close_order`, `apportion_cumulative_agrees`,
`allocate_cumulative_agrees_with_the_integer_model`;
`the_audit_odd_cent_never_piles_up_across_closes` and the rest of
`ledger-core` `tests/odd_cent.rs`; `a_run_of_closes_never_piles_up_rounding`,
`a_different_close_ends_a_legacy_run`,
`a_different_close_no_longer_restarts_the_count`,
`members_closes_replay_under_the_rule_they_were_journaled_with`,
`ledger-store` `tests/member_closes.rs` and the old-books fixture
`x_member_closes`; `closes_journaled_under_the_old_rule_replay_with_it`;
`ledger-mcp` `close_period_shows_each_members_running_split`,
`old_books_name_each_closes_rule_and_true_up_at_the_next_close`;
`ledger-gui` `a_members_page_shows_the_same_capital_before_and_after_a_close`,
`a_yen_members_page_splits_in_whole_yen`; the bookspec examples
`members-close.*`.
## B-14 Statements: P&L for a period; cash flows by the direct method
**Status:** AUTHORITY, except where marked CONVENTION or DECISION
The P&L covers a period; a closing entry is not income or spending
(B-11), so period statements exclude it.
The statement of cash flows (P3 defect 1, D-35, D-42; the paragraphs were read
in public sources that reproduce the Codification, `sources/fasb-asc.md`):
1. **Cash receipts and payments.** The statement reports "the cash
effects" of operations, investing and financing (ASC 230-10-45-1; its
objective, 10-1: "cash receipts and cash payments"). An entry
contributes to the body only its net movement of the cash accounts,
and what a member or a lender paid or received for it (rule 11, which
nets to zero within the entry); an entry that moves neither
contributes nothing; a transfer between two cash accounts is no flow. The period is [start, end], inclusive. The
cash accounts are every account holding cash, cash equivalents or
restricted cash: the statement explains the change in their total
(45-4, as ASU 2016-18 amended it), and transfers among them "are not
reported as cash flow activities" (45-5). Which accounts those are is
the books' `books.cash_accounts`.
2. **Partition.** Every receipt and payment is investing, financing or
operating (45-10), so every non-cash account is in exactly one section:
investing (the cost and accumulated-depreciation accounts of every
registered fixed asset, and `books.investing_accounts`); financing
(equity, `books.financing_accounts`, and a liability that holds
distributions declared, below); operating (every other balance-sheet
account, and revenue and expense, but see rule 4). A liability left
operating that, in the posted entries dated through the period's end
(closes and reversals aside), only entries debiting nothing but equity
credit, at least once (Dr Distributions / Cr Distributions Payable; a
corporation's Dr Retained Earnings / Cr Dividends Payable), holds
distributions declared and unpaid: paying it is a distribution
(45-15(a): "payments of dividends or other distributions to owners"),
listed as "Distributions to members — <the payable>" (the statement
reads no parties, so not by member), and the declaration an exchange
inside financing, disclosed (rule 7; 50-3). A liability credited by any
other entry as well (what the members advanced, an accrued expense,
money borrowed) stays where it is (P3 seam review, 2026-10-21: before,
a distribution declared then paid read as "Cash paid to suppliers and
employees", the declaration as "Other noncash items" beside an increase
in the payable; `a_distribution_declared_then_paid_is_a_distribution_paid`,
`a_distribution_declared_then_paid_is_financing_when_paid`). The
roles must agree with the chart and each other, or the statement is
refused with the setting to change: cash and investing accounts are
assets; financing accounts are liabilities (equity listed is accepted:
it is financing anyway); no code twice in a list or in two lists; no
registered asset's account as cash; at least one cash account. One
check (`cashflow::role_problems`) is behind both the refusal and
`books.settings`' problems.
3. **Only the cash portion** of a part-cash transaction is reported
(50-5). An entry's cash `c` is applied to the sections moving against
it, in the order operating, investing, financing, each up to its own
movement; what is left of a section's movement is the entry's noncash
part, disclosed (rule 7). CONVENTION: the order (the ASC is silent on
allocating within one entry); it matters only for an entry with a
noncash source and two or more sections paid by cash, and is disclosed.
Within a section the same order splits the cash among its kinds (rule
5).
4. **Gains and losses on investing assets are investing**: receipts from
sales of PP&E are investing (45-12(c)), so the whole proceeds are, and
the gain or loss is taken out of net income (45-28(b)). In an entry that
credits a registered cost account or an investing account, the revenue
and expense lines are investing, except interest, received or paid (tax
categories `interest_income` and `interest_expense`): interest is
operating in any entry (45-16(b), 45-17(d)). The trigger is broad (a
return, a reclass or an impairment credits the cost account too), so the
reconciling line reads "(Gain) loss and other noncash changes in
investing assets".
5. **Gross lines by kind** (45-7; investing and financing inflows and
outflows "reported separately", 45-26). One line per entry, section and
kind, with the entry, date, label and the kind's accounts. Operating
lines carry the class 45-25 requires "at a minimum" (a)-(g), kind by
kind within the entry, by the direction of the cash: the cash an
entry's `interest_income` lines take is (b) interest received, and its
`interest_expense` lines' (e) interest paid, whatever else the entry
settles (a loan payment on an operating payable is (d) for the
principal and (e) for the interest, never all (e)); the rest of its
operating cash, by its other operating lines: a receipt is (a) if a
line is revenue or the receivables control, or the entry's party is a
customer, else (c); a payment is (g) if every such line is on an
account whose tax category is `taxes_licenses`, else (d) if a line is
an expense, a liability or a non-receivables asset, else (g); then,
of a payment that is (d), what went to a government is (g): the
entry names a party with the `government_agency` or `tax_agency`
role (its chain root's parties, for a reversal), on any account. ASC
230-10-45-17 lists "Cash payments to governments for taxes, duties,
fines, and other fees or penalties" (c) apart from "Cash payments to
other suppliers and employees for other goods or services" (b) (EY FRD
p. 43, which reproduces the paragraph), and 45-25 names only income
taxes, (f), among them, so the rest are other operating cash payments.
The payee decides because the account need not: a state's filing fee
for forming the company is an organization cost (tax category
`organization_startup`), still paid to a government. Every party a
government: the whole payment is (g). A government and others named
together: the payment is split by their resolved shares (B-35), the
government's part (g), the rest (d), because 45-22 has "each
separately identifiable source or each separately identifiable use"
classed "on the basis of the nature of the underlying cash flows" (EY
p. 44; it speaks of the three activities, read here for the classes
too, CONVENTION), and the shares identify each part; the government's
part is the payment × its share ÷ the whole, rounded half to even to
the base currency's places, the rest exact, so the two always sum to
the payment and a reversal mirrors them (CONVENTION). Shares that
don't resolve leave the whole payment (d). Not a government's: a
government selling goods or services (postage, a park's entry fee) is
45-17(b)'s supplier "for other goods or services", so such a payee
is added as a vendor (the `vendor` role, books.add_vendor), never
given `government_agency` (the books can't tell what a payment
bought; the role says it). Payroll keeps its
structure (CONVENTION): an entry with a line on the salary expense or
a payroll payable control account (B-23's roles: `salary_expense`,
`federal_withholding`, `state_withholding`, `social_security_payable`,
`medicare_payable`, `other_deductions_payable`), or on an account of
a pay tax category (`salaries_wages`, `officer_compensation`,
`employee_benefits`, `pension_profit_sharing`), stays (d) whoever it
names, because taxes withheld and remitted are the employees' pay
(45-17(b), "to ... employees") and the payables mix them with the
employer's own taxes, which the remittance can't tell apart; the
employer's payroll tax paid on its own expense account is a tax, (g).
As before this change, a remittance from the payables is (d); a
payroll liability kept at a code of the chart's own needs its role
(books.assign_role) to be read so. P3 cutover review, open point 2,
2026-10-22: before, only the tax category counted, so a filing fee on
an organization-costs account read as cash paid to suppliers
(`a_payment_to_a_government_is_other_operating`,
`a_government_payee_moves_a_fee_on_any_account`,
`a_shared_government_payment_splits_to_the_cent`,
`a_payroll_remittance_to_a_tax_agency_stays_pay`,
`a_government_selling_services_is_a_supplier`,
`a_reversal_takes_its_originals_payee`,
`a_shared_government_payment_a_member_paid_splits_both_ways`, all in
ledger-core `tests/spec_cash_flow.rs`;
`a_government_part_rounds_half_to_even` and
`a_government_part_sums_and_mirrors` in ledger-core
`cashflow::tests`; bookspec `cash-flow.government-fees`). A
government's payment a member paid on the company's behalf (rule 11)
is classed and split the same way, its `on_behalf` part with it. (Interest paid back out
is (g); interest refunded to the company, (c).) The operating cash is
split among these kinds as a section's among its kinds (rule 3), in
the order: the rest, interest received, interest paid (CONVENTION).
Class (f) stays empty until a tax category marks an entity-level income
tax (a
partnership pays none "as such", IRC §701, but can under §6225 or a
state pass-through election). Investing and financing kinds (D-35):
purchases and sales of property and equipment, payments for and
collections on each investing account, contributions from and
distributions to each member (by name; equity not a member's by
account), proceeds of and repayments of each financing liability. An
original's direction is its nature (45-15(a): cash out of equity is a
distribution). The printed statement and the viewer show operating one
row per class, and investing and financing one row per kind with its
total, each transaction listed under it (D-35); `books.cash_flow` and
the exports carry every line. Known limits: an entry's other operating
lines take one class, so its mixed receipts and payments other than
interest net within it; class (b) relies on portfolio interest being
categorized `interest_income` (interest receivable collected is (c)).
6. **Reversals.** A reversal takes its chain root's sections, class and
kind, with its own sign (`Ledger::reversal_chain`). Pairing walks each
chain from its root: an entry and its reversal both in the period drop
out of every line and disclosure, when their flows are exact negatives
(proved, and checked at runtime), so a reversal of a reversal whose
partner is paired is shown. A reversal shown alone is labelled
"Reversal of #N — <N's description>", a reinstatement "Reinstatement
of #A (reverses #B)", listed under the original's kind: never a label
from a reversal's sign (D1-c).
7. **Noncash investing and financing activities are disclosed** (50-3,
50-4; placement, 50-6). Disclosed: every entry not netted whose noncash
part moves financing, or moves investing through an account that is
neither accumulated depreciation nor a rule-4 P&L line, or that
exchanges inside investing and financing without cash (50-4's
"converting debt to equity", a note for equipment, a trade-in): `x =
max(0, min(D - out, C - in))` over the net debits `D` and credits `C`
of its investing and financing balance-sheet accounts, `in` and `out`
the cash those two sections took in and paid out, what a member or
lender handled for the entry included (rule 11: equipment a bank paid
for is 1,000 in and 1,000 out, nothing exchanged). Amount: `max(Σ
max(r_k, 0), x)`. Depreciation is a
reconciling item, not an activity. Limit: a trade-in within one cost
account nets away.
8. **Net income reconciled to operating cash**, a separate schedule with
the direct method (45-30; major classes of reconciling items, 45-29;
what is removed, 45-28): operating = NI + G + X + W, over the period's
non-closing entries. NI, net income; G, the rule-4 P&L lines; X, each
entry's operating noncash part, as "Depreciation" (its investing and
financing balance-sheet lines all accumulated depreciation), "Noncash:
through <A>" (those lines, accumulated depreciation aside, all on A),
or "Other noncash items"; W, minus each
operating balance-sheet account's movement ("Increase in" / "Decrease
in"). An identity over balanced entries (Lean
`operating_reconciliation_ties`), checked at runtime. G's lines on a
registered asset's depreciation expense account (a disposal's
depreciation still due through its day, or what the month's run posted
past it, credited back: B-15 rule 5) are shown in the "Depreciation"
line, not the gain or loss, so that line is the P&L's depreciation
expense for the period (45-28(a)'s depreciation is the expense
recognized; the sum NI + G + X + W is unchanged). P3 seam review,
2026-10-21: before, a disposal that credited back 25.00 of a month's run
showed depreciation 25.00 above the P&L's and the loss 25.00 below the
entry's (`a_disposals_depreciation_credit_back_is_depreciation`,
`depreciation_is_one_figure_everywhere`).
9. **The sections foot** (45-24: the statement reconciles beginning and
ending cash). `reconciles {sections_sum, net_change, difference}`; any
difference, a section unequal to its lines, or a reconciliation unequal
to operating is `CashFlowDoesNotFoot`, an engine error: nothing is
produced. It can't happen on posted entries (Lean
`cashflow_sections_sum_to_cash_change`, `Reachable.cashflow_foots`).
10. **Closing entries are never in the statement** (B-11). A close moves
only revenue, expense and equity, and cash accounts are assets, so a
close has no cash line.
11. **What a member or a lender pays, or receives, for the company is
cash the company borrowed (or a member contributed) and spent**
(DECISION, D-42, the owner, 2026-10-20, revising D-35's first point;
bookspec's CHOICE: the standard leaves it open).
*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
not address these situations"; BDO Blueprint 3.4: "While ASC 230 is
silent"; EY FRD 4.6.2: "judgment will be needed"; KPMG Handbook
Q8.3.10), so 105-10-05-2 sends the question to nonauthoritative
guidance. There the firms present it by substance, as constructive
receipt and disbursement: PwC 6.9.3.1, "a reporting entity should
include cash flows received or paid by a third party on behalf of the
reporting entity" (a bank paying a vendor: "a financing inflow ... and
an operating outflow"); DART Roadmap 7.2 (third-party financing,
Example 7-2: a lender paying an insurance premium, an operating outflow
and a financing inflow; the contrast, a supplier's own finance arm, is
AICPA TQA 1300.16 and the SEC staff's 2005 view: noncash); BDO 3.4
(Example 3-5: a bank paying for equipment, an investing outflow and a
financing inflow; it names "a parent might pay on behalf of a
subsidiary expenses"); EY 5.2.4 and 5.3.1 (third-party financing,
unlike the seller's, is a financing inflow and an operating or
investing outflow). KPMG Q8.3.10 also accepts a noncash presentation
for equipment a lender pays for. A CPA would present it as the firms
do; the change in cash is the same either way. The rule, entry by
entry, after its own cash (rule 3; `kernel::constructive`):
- **Paid for the company.** A financing liability
(`books.financing_accounts`: the members' financing account such as
Due to Members, a loan, a card listed as financing) credited against
operating or investing lines debited paid for them: a financing
inflow under the liability's kind ("Proceeds from borrowing — 2100
Due to Members", 45-14(b)) and the operating outflow (its class by
rule 5) or the investing outflow (45-13(c)), both on the entry's
date. A member's capital credited against revenue or expense debited
paid for them likewise, a contribution ("Contributions from members
— <member>", 45-14(a)): the expense-to-capital case below.
- **Received for the company.** A financing liability or a member's
capital debited against revenue, expense or the receivables control
credited (a customer paying a member, a vendor refunding to a
member's card): an operating inflow, and a repayment or a
distribution (PwC 6.9.3.1: cash "received or paid").
- Each line's `on_behalf` is the part of its cash so handled. The parts
net to zero within the entry, so the sections still sum to the
change in cash (rule 9; Kani `constructive_nets_to_zero`). Kinds take
it in order, operating before investing and, within operating,
balance-sheet lines before revenue and expense (CONVENTION, as rule
3's: it matters only for an entry whose sources cover part of its
uses, and then leaves capital the revenue and expense it can pay
for). A reversal mirrors its root (rule 6; Kani
`constructive_mirrors`), so a payment and its reversal net out, and
a reimbursement is the repayment it is (45-15(b)).
- **Stays noncash**, disclosed (rule 7): property a member contributes
that it already owned (capital credited against an asset, a prepaid
included: no cash moved, to anyone, 50-4); property handed over in
kind (an asset credited against a financing liability or capital
debited); a debt exchanged for another or for equity (50-4's
"converting debt to equity"); interest and guaranteed payments
charged into what the company owes the lender or member (tax
categories `interest_expense`, `interest_income`,
`guaranteed_payments`: their own charges, accrued, paid to no one;
"interest paid", 45-25(e), is cash paid); and a purchase on the
seller's own credit, an operating payable (50-4, "acquiring assets by
assuming directly related liabilities"; EY 5.3.1), flagged
`asset_on_operating_payable` (rule 12).
- **An expense a member pays into capital** (Dr expense / Cr the
member's capital, no cash) is a constructive contribution and an
operating outflow. The member paid cash to a vendor for the company;
whether the company owes it back (Due to Members) or not (capital)
is only the form of the financing, not the substance of the payment,
which the guides follow ("the same economic result", DART 7.2). BDO
3.4 lists a parent paying a subsidiary's expenses among the cases
for constructive cash flows without asking how the subsidiary
records what it owes back (a parent's payment is often booked as a
contribution), and none of the guides read presents an expense
someone paid on the entity's behalf as noncash. The books' own K-1
worksheet already counts it as a cash contribution (`k1.rs`
INVARIANT 3, DECISION 2026-10-15), so the two agree.
- **Card purchases** follow the card's classification (D-35,
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
card payment a repayment; on a card that is an operating payable, the
purchase is an accrual and its payment operating (d).
- *Limits* (the books can't tell who was paid). A lender's or member's
own charge credited to what the company owes them, other than
interest and guaranteed payments (a card's annual fee, rent a member
charges), reads as paid for the company: its operating outflow shows
when charged, its repayment as financing (each section's total over
the charge and its payment is right; the timing is the charge's).
Post what the company owes a member for their own goods or services
to a payable, not to the members' financing account. A note to the
seller listed in `books.financing_accounts` reads as a lender's loan
(borrowed and invested, where 50-4 makes it noncash): keep a seller's
note out of it (its principal payments then show as operating, where
45-13(c) makes them financing; flagged). An expense a member
contributes in kind (an item already owned, expensed at once) reads
as paid: post it to an asset first. A debt a member or a new lender
pays off directly is a noncash exchange (disclosed), where PwC
6.9.3.1 and BDO Example 3-6 would gross it up. Debt issue costs, a
financing outflow (45-15(f)), posted to an expense account read as
operating: post them to a contra-liability listed as financing, so a
fee the lender withholds is a noncash exchange inside financing and
the loan reads as its net proceeds. An entry that moves revenue or
expense straight to capital by hand, as a close does, reads as paid
or received for the company: close periods with `books.close_period`,
whose entries are never in the statement (rule 10).
12. **Equipment paid for, or sold, later** (D-35; 45-12(c), 45-13(c)). A
bill payment carries its bill's split, equipment part first: the
payables line's equipment part is investing, so its payment is an
investing outflow, the reconciliation's payables change leaves it out,
and the purchase is disclosed only for what is unpaid at the period's
end (a payment reversed by then is unpaid again; EY FRD 5.3.3; DART
6.1.3; bills post to the general-expenses
control today, so this waits for bills with account lines). A card
purchase follows the card's classification: on an operating payable it
is disclosed and flagged (`asset_on_operating_payable`), on a financing
one it is borrowed and invested (rule 11) and its payment a repayment. A sale for an amount owed on an
operating receivable is disclosed and flagged with the fix
(`asset_sold_on_operating_receivable`: post it to an investing
account). The flags are the statement's `warnings`, and items in the
viewer's Needs attention. Limits: payments more than "soon after" purchase are
financing by 45-13(c) when the seller financed it, which the books
can't tell from a bill; unregistered equipment is operating (the asset
register's `unregistered_cost` flags it).
Scope: single-currency books (R-5). A foreign-currency cash account's
exchange effect would be a separate reconciling line (830-230-45-1); a
hand-posted revaluation of a cash account shows as an operating receipt.
**Enforced:** `profit_and_loss_for_period`; `cashflow::cash_flow` (with
`kernel::entry_flows`, `apply_cash`, `constructive`, `exchanged`),
`role_problems`, `foot`; `ledger_package::cash_flow_for` (the server, the
package and the viewer). **Checked:** Lean `statements_add_up`,
`cashflow_sections_sum_to_cash_change`, `entry_flows_sum`,
`flows_reversal`, `constructive_nets`, `constructive_neg`,
`constructive_sections_net`, `stmt_flows_sum`, `stmt_flows_reversal`,
`operating_reconciliation_ties`, `cashflow_reversal_nets`,
`Reachable.cashflow_foots`, `Reachable.cashflow_is_change_in_cash`; Kani
`entry_flows_*`, `no_cash_no_flow`, `take_is_odd_and_bounded`,
`apply_cash_mirrors`, `apply_cash_spends_exactly`,
`exchanged_is_bounded`, `constructive_nets_to_zero`,
`constructive_mirrors`; `prop_period_pnl_partitions`,
`prop_cash_flow_laws` (5,000 cases, reversals of reversals, rule 11's
shapes), `prop_pure_entries_sections_are_movements`; regressions
`defect1_reversed_contributions_foot_regression`,
`defect1_two_member_llc_first_year_regression`,
`defect1_two_member_llc_as_of_march_18_regression` (and bookspec
`cash-flow.*`, `cash-flow.paid-on-the-companys-behalf` for rule 11,
`cash-flow.government-fees` for rule 5's governments);
`disposal_proceeds_all_investing`,
`an_expense_a_member_paid_is_borrowed_and_spent`,
`reimbursing_a_member_is_a_repayment_of_borrowing`,
`equipment_a_lender_paid_for_is_borrowed_and_invested`,
`an_expense_a_member_paid_into_capital_is_contributed_and_spent`,
`property_a_member_contributes_stays_noncash`,
`equipment_on_the_sellers_credit_stays_noncash_and_flagged`,
`a_payment_made_for_the_company_and_its_reversal_net`,
`part_paid_by_a_member_and_a_refund_a_member_received`,
`a_lender_pays_balance_sheet_lines_first_and_a_customer_pays_a_member`,
`interest_and_guaranteed_payments_charged_to_a_creditor_stay_noncash`,
`a_fee_withheld_from_a_loan_follows_its_account`,
`odd_shapes_foot_and_net`, `operating_classes_follow_asc_230_10_45_25`,
`a_payment_to_a_government_is_other_operating`,
`a_government_payee_moves_a_fee_on_any_account`,
`a_shared_government_payment_splits_to_the_cent`,
`a_payroll_remittance_to_a_tax_agency_stays_pay`,
`a_government_selling_services_is_a_supplier`,
`a_reversal_takes_its_originals_payee`,
`a_shared_government_payment_a_member_paid_splits_both_ways`,
`a_reversal_of_a_reversal_is_shown_and_foots`,
`a_reversal_three_levels_down_is_labelled_a_reversal`,
`operating_cash_is_classed_kind_by_kind`,
`two_kinds_of_one_class_are_one_line`,
`interest_paid_in_a_disposal_stays_operating`,
`a_loss_on_disposal_on_a_revenue_or_expense_account_is_investing`,
`an_investments_gain_is_investing`, `cash_goes_to_the_sections_in_order`,
`cash_goes_to_a_sections_kinds_in_order`,
`converting_member_debt_to_capital_is_disclosed`,
`a_bill_payment_carries_its_bills_split`,
`a_reversed_bill_payment_leaves_the_purchase_unpaid`, the role refusals,
`foot_refuses_any_difference`,
`a_distribution_declared_then_paid_is_a_distribution_paid`,
`a_disposals_depreciation_credit_back_is_depreciation`
(`crates/ledger-core/tests/spec_cash_flow.rs`); the seam review's year
of books (`ledger-package` `tests/p3_seams.rs`, its books
`tests/seams/books.rs`; the viewer's `ledger-gui` `tests/p3_seams.rs`;
the tools' `ledger-cli` `tests/p3_seams.rs`).
## B-15 Depreciation: straight line, salvage never depreciated, partial months by days
**Status:** CONVENTION (book method); AUTHORITY for tax conventions, which
wanderbooks does not apply
OpenStax §11.3 (cited): "Straight-line depreciation is a method of
depreciation that evenly splits the depreciable amount across the useful
life of the asset," with "(Cost – salvage value)/Useful life"; partial
periods: "A common method is to allocate depreciation expense based on the
number of months the asset is owned in a year." wanderbooks prorates
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,
`sources/fasb-asc.md`) prescribes no method: cost less salvage is spread
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
allocation.
Tax conventions (half-year, mid-month, mid-quarter): IRS Pub. 946
(`sources/irs-depreciation.md`), for the CPA.
The rule, exactly (P3, defect 22; the engineer settled it from ASC
360-10-35-4, 2026-10-20):
1. The life is `[in-service date, in-service date + life months)` (chrono
clamps a day past a month's end: Jan 31 plus one month is Feb 28), cut
into the calendar months it touches.
2. Through day `k` of a month but the life's last, the depreciation is
(cost − salvage) / life × (the months before, each as days held over
days in the month, + `k` / days in this month): the monthly rate by
days.
3. The life's last month takes what the months before it leave, evenly
over its days: through its last day the depreciation is exactly cost
− salvage, and through an earlier day of it, prorated by days, never
plugged to the whole. (Without clamping its daily rate is the first
month's: the remainder is that month's missing days.) Before P3 any
schedule through a day of the final month, and every disposed asset's,
plugged to the whole: a disposal in the final month posted too much
depreciation and too large a gain.
4. Each cumulative figure is rounded half away from zero to the
currency's minor unit (cents; whole yen; a thousandth of a dinar;
ISO 4217, `sources/iso-4217.md`), in exact integer arithmetic
(`kernel::prorated_through`); a month's amount is the difference of
consecutive ones. Through every month end this is the per-month
running total books posted before P3, except where the exact figure is
a half-cent tie, which the old 28-digit Decimal quotients could round
either way; P3 rounds it up, exactly, moving that cent to the month
before.
5. A disposed asset depreciates through its disposal day, held
(inclusive): its schedule, accumulated depreciation and book value
stop there, and equal what the disposal took. The disposal's book
value is cost less the schedule through that day whatever the runs
posted: depreciation still due posts as the final depreciation;
depreciation the disposal month's run (dated its end) posted past the
day is credited back to depreciation expense in the disposal entry
(`excess_depreciation`); and the disposal clears what the runs carry,
so the asset leaves the accumulated depreciation account at zero. A
disposal is refused unless every run carries at the end of the
disposal's month what it carries now (the disposal clears what they
carry now): while a run of a later month stands (reverse it first:
credited back at the disposal day, it would leave the disposal month's
accumulated depreciation a debit and its expense short), or while a
reversal (or re-instatement) of an earlier run dated after the month
isn't undone (the disposal would charge that run again, and the
reversal take it away only later; undone on any open day, so a closed
month never blocks a disposal for ever). A change a run carries only
at later month ends (a later month's run reversed after its month) is
that run's error and its correction, dated when made, and the disposal
leaves it as it is.
6. A month's run posts what the schedule says is due through the
month's end, less what the books carry then, but never so much that
the books would carry more than the schedule at a later month end
(`kernel::catch_up`). What the books carry is read entry by entry, each
in its own month (`assets::depreciation_effects`): a run adds its
amount in its month, a reversal of it takes it away in the reversal's
month. So a month skipped, or a run reversed, is caught up in the month
the run is made (plan/served.md P3, settled in design, 2026-10-20: an
immaterial miss; a material one is restated under ASC 250, a
correction for a human and the CPA), and a month whose run was
reversed after it ended is never run twice (it carried the month at
its end; the run of the reversal's month catches it up). A month a
later run caught up, or that rounds to nothing, posts nothing (an entry
carries no zero line); a run after the life posts what is still due.
A reversed run is un-posted (its month due again, when reversed within
it); re-instating one (reversing its reversal) is refused when it would
carry the books past the schedule (a later run caught its month up:
`assets::catch_up_room`); a disposal, or a run its disposal took as
final, can't be reversed; and a run, or a reversal of one, can't be
reversed on a day before its own (a correction comes after what it
corrects, B-9: a run of March reversed on Jan 31 would leave January
and February with negative depreciation; commands only, journals
replay as written). The viewer and the store read every run the books
recorded (`Ledger::depreciation_runs`: each `asset.depreciation_posted`,
as replay records it), not a month's latest (a run re-instated after its
month was run again and that run reversed is the month's run).
7. Two dates (P3, defect 13). Depreciation starts on the day the asset is
placed in service, never the day it was bought: ASC 360-10-35-4
spreads cost over the periods the asset serves, and an asset serves no
period before it is in service. (For tax, Pub. 946's "ready and
available for a specific use", `sources/irs-partnership-basis.md`, is
the CPA's.) The register holds an asset from the day the company
acquired it: the date of the entry it is linked to (its purchase or
contribution), never later than the in-service date, or the in-service
date when no entry is linked (settled in design, 2026-10-20: "an
asset's acquired date comes from the entry it is linked to"; one
source of truth, so the register and the general ledger can't
disagree, B-24). A new link to an entry dated after the in-service date
or the disposal is refused, as is a new in-service date before the
linked entry's date; a disposal can't come before the acquired date (a
command's bound; replay accepts what earlier versions did, B-28), and
one before the in-service date takes no depreciation.
8. Posted and scheduled. What the schedule says is due through a date
(`assets::accumulated_scheduled_through`, the tools'
`accumulated_scheduled_through`) and what the books carry
(`Ledger::accumulated_posted`: the runs read entry by entry as rule 6
reads them, each on its own day: a run adds its amount, a reversal of
it takes it away, a reversal of that reversal adds it back, B-8, B-9;
zero once the asset is disposed, its disposal entry having cleared it)
are two figures, never one: book value is cost less the posted figure
(`assets::scheduled_book_value` is the schedule's own, for the
schedule only). An asset's depreciation expense over a span is its
runs' (`Ledger::depreciation_posted_between`) plus its disposal entry's
lines on its expense account (rule 5): the P&L's, the cash flow
reconciliation's "Depreciation" (B-14 rule 8), the tax-basis K-1's
open item (B-36) and the IIF's all show that one figure
(`depreciation_is_one_figure_everywhere`). `books.list_assets` as of a
day says "disposed" only for a disposal dated by then, as `held` and
the figures do (B-33; before the seam review a later disposal showed
on an earlier day).
**Enforced:** `assets::depreciation_schedule`, `assets::run_amount`,
`assets::catch_up_room`, `assets::depreciation_effects`,
`kernel::prorated_through`, `kernel::catch_up`, `kernel::true_up`,
`calendar::month_parts`, `Store::run_depreciation`,
`Store::dispose_preview` (`check_nothing_after_disposal`),
`Store::check_reverse_entry`, `Ledger::asset_acquired_date`,
`set_asset_acquisition` (`replay_set_asset_acquisition` for replay),
`check_correct_asset`, `dispose_asset` (strict: the acquired date),
`record_depreciation_run` / `check_depreciation_run`,
`accumulated_posted`, `live_depreciation_run`. **Checked:**
`schedule_sums_exactly_to_depreciable_amount`,
`salvage_value_is_never_depreciated`, `prop_depreciation_schedule_exact`,
`defect22_depreciation_through_mid_final_month_is_prorated_not_plugged`,
`defect22_final_month_drift_is_spread_by_days`,
`defect22_a_disposed_assets_schedule_stops_at_its_disposal_day`,
`defect22_a_disposal_in_the_final_month_posts_depreciation_by_days`,
`defect22_a_disposal_inside_a_posted_month_takes_back_the_extra_days`,
`a_run_reinstated_after_its_rerun_was_reversed_still_counts`,
`the_viewer_counts_a_run_reinstated_after_its_rerun_was_reversed`,
`a_reversal_dated_before_the_entry_it_reverses_is_refused`,
`a_disposal_is_taken_once_a_late_change_is_undone_after_its_month_closed`,
`a_disposal_whose_month_has_run_charges_what_is_still_due_in_its_entry`,
`every_order_of_runs_reversals_disposals_and_closes_keeps_the_books_right`,
`prop_any_sequence_keeps_the_books_right`,
`prop_the_viewer_reads_an_asset_as_the_store_does`,
`prop_run_amounts_are_each_months_run_amount`,
`jpy_depreciation_posts_whole_yen`, `a_skipped_month_is_caught_up_in_the_month_run`,
`a_reversed_depreciation_month_can_be_run_again`,
`a_run_reversed_after_its_month_is_caught_up_in_the_reversals_month`,
`reinstating_a_run_a_later_run_caught_up_is_refused`,
`a_disposal_before_a_later_months_run_is_refused_until_it_is_reversed`,
`a_disposal_before_a_later_reversal_of_an_earlier_run_is_refused`,
`a_disposal_entry_cannot_be_reversed`,
`prop_the_final_month_takes_the_remainder_by_its_days`,
`depreciation_effects_read_each_entry_in_its_month`,
`prop_depreciation_is_monotone_and_bounded`,
`prop_disposed_schedule_equals_what_disposal_computed`,
`prop_month_end_schedules_match_the_legacy_arithmetic_except_half_cent_ties`;
`the_charge_never_falls_and_is_continuous_over_real_calendars`; Kani
`prorate_parts_sum_to_total`, `prorate_last_part_absorbs_the_remainder`,
`a_months_charge_is_within_half_a_cent`,
`a_months_charge_in_tenths_is_within_half_a_cent`,
`the_last_months_charge_is_within_half_a_cent`,
`the_last_months_charge_in_tenths_is_within_half_a_cent`,
`prorated_through_refuses_malformed_input`,
`catch_up_never_overtakes_the_schedule`,
`catch_ups_is_every_suffixs_catch_up`, `true_up_reaches_the_target`;
Lean `Prorate.kernel_parts_total`, `Prorate.kernel_parts_nonneg`,
`Prorate.part_near`, `Prorate.cum_near`, `Prorate.chargeMid_eq_cum`,
`Prorate.chargeLast_eq_cum`, `Prorate.chargeLast_start`,
`Prorate.chargeLast_mono`, `Prorate.cum_mono` (`docs/verification.md`);
`ledger-core` `tests/spec_asset_basis.rs` (the in-service and acquired
dates, `accumulated_posted` and its edges,
`a_reinstated_run_counts_and_is_live`); `ledger-store`
`tests/asset_tags.rs` (`runs_replay_reload_and_verify_catches_a_tampered_run`,
`the_viewer_and_writer_agree_on_a_bad_depreciation_posted_payload`);
`ledger-cli` `tests/spec_assets.rs` (`a_reversed_run_is_not_posted`,
`list_assets_book_value_is_what_the_books_carry`).
## B-16 Prepaid expenses and deferred revenue are recognized over the period benefited or earned
**Status:** AUTHORITY (revenue); CONVENTION (prepaids)
ASC 606-10-45-2 (cited): payment before transfer → "the entity shall
present the contract as a contract liability"; 606-10-25-23: "An entity
shall recognize revenue when (or as) the entity satisfies a performance
obligation by transferring a promised good or service." Prepaids: OpenStax
§4.2 (cited); ASC 340-10-05-4 and 210-10-45-1(g), -2 (read 2026-10-14,
`sources/fasb-asc.md`): a prepaid is an asset paid in advance of its use,
current within the operating cycle; the ASC prescribes no expensing
method, so monthly expensing stays a convention. Tax: the 12-month rule, Treas. Reg. §1.263(a)-4(f)
(`sources/irs-capitalization.md`).
Conventions (P3, defect 9): `daily` or `monthly_even`. A new schedule
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
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
thirteen month ends, a small March and a small final March (DECISION,
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
month's replay re-derives its plan. `books.schedule_convention` sets the
convention new schedules take (unset: daily). An equal-months schedule
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
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
they were. A reversed month is un-posted, so it is due again; a month in
a closed period, or one whose posting was reversed after the month ended
(posting it again on its own date would recognize it twice until the
reversal; a schedule posts its plan's months, so it can't catch one up
later), is left by a run for a human, listed with why, never a failed run.
A month's posting can't be reversed on a day before its own (a
correction comes after what it corrects, B-9; commands only). Not yet
vendored: ASC 606-10-25-31 (progress toward satisfying an obligation over
time), 606-10-25-32 (one method, applied consistently), 606-10-55-20 (time
elapsed as an input method): to read from the owner's ASC login before
B-16 cites them (P3 depreciation review R7).
**Enforced:** `schedules` (`daily_plan`, `amortization_plan`,
`validate_schedule`), `kernel::prorate_by_days`, `Ledger::reverse_entry`,
`Ledger::schedule_month_hold`.
**Checked:** `a_prepaid_expense_is_expensed_month_by_month`,
`deferred_revenue_is_earned_to_the_cent`,
`defect9_a_new_schedule_spreads_by_the_day`,
`defect9_deferred_revenue_daily_is_earned_to_the_cent`,
`defect9_legacy_schedules_stay_monthly_even`,
`an_equal_months_schedule_starts_on_the_first`,
`a_jpy_daily_schedule_posts_whole_yen`, `a_kwd_daily_schedule_rounds_to_the_fils`,
`a_reversed_schedule_month_is_due_again`,
`a_reversal_dated_before_the_entry_it_reverses_is_refused`,
`a_schedule_month_reversed_after_it_ended_is_left_for_a_human`,
`a_month_that_rounds_to_nothing_is_never_a_posting`,
`a_schedule_month_posted_again_waits_for_month_reposting_and_replays`,
`daily_plans_pin_month_end_clamping`,
`prop_daily_plan_sums_exactly_and_lands_on_month_ends`,
`prop_monthly_even_plan_is_unchanged`; `ledger-cli`
`mcp_daily_schedule`.
## B-17 Platform payouts are recorded gross when the business is the principal
**Status:** AUTHORITY
ASC 606-10-55-37 (cited): "An entity is a principal if it controls the
specified good or service before that good or service is transferred to a
customer." 606-10-55-38: an agent "recognizes revenue in the amount of any
fee or commission to which it expects to be entitled." 606-10-55-37B
(read 2026-10-14, `sources/fasb-asc.md`): a principal recognizes revenue
"in the gross amount of consideration." Whether an app
developer is principal for app-store sales is a judgment under 55-39; the
CPA confirms.
**Enforced:** `payouts::payout_plan`. **Checked:**
`a_payout_is_grossed_up_to_revenue_and_fees`.
## B-18 Aging buckets: Current, 1–30, 31–60, 61–90, 90+ days past due
**Status:** CONVENTION (bucket edges); DECISION (due today is Current)
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 …
more than 90 days past due"; it is illustrative, not a rule. The FFIEC
Call Report instructions (bank regulators, not binding here): "The past
due status … should be determined in accordance with its contractual
repayment terms." A document due today is not yet past due: DECISION
(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
invoice or bill (a real prepayment is a deposit): DECISIONS (the owner,
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
reversed by then no longer counts, one reversed later still does; the
buckets partition what the documents owe, exactly.
**Enforced:** `arap::aging_bucket`, `Ledger::ar_aging`/`ap_aging`
(through `Ledger::doc_state`), `receive_payment`/`pay_bill`
(`PaymentBeforeDocument`). **Checked:**
`aging_boundaries`, `aging_is_exclusive_and_only_ages`,
`aging_as_of_a_date_sees_only_what_existed_then`,
`a_payment_never_predates_its_document`, `aging_as_of_ignores_a_later_reversal`,
`a_voided_invoice_never_ages`, `status_is_a_function_of_journal_and_date`.
## B-19 1099 reporting thresholds, payment methods, and decisions
**Status:** AUTHORITY (`sources/irs-1099-thresholds.md`,
`sources/irs-1099-payment-method.md`), with the DECISIONs named below.
$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):
"shall apply with respect to payments made after December 31, 2025"); for
calendar years after 2026, indexed to inflation and rounded to the nearest
$100 (§6041(h)). §70433 sets the one amount for Form 1099-NEC
(§6041A(a)(2), §70433(c)) and the §6041(a) payments on 1099-MISC alike.
Gross proceeds paid to an attorney (a payment in connection with legal
services that §6041 doesn't cover, a settlement paid to a claimant's
attorney, say: attorneys' fees for the company's own legal work are
§6041's, on Form 1099-NEC) stay at $600 (Treas. Reg. §1.6045-5(a)(1),
(c)(4)), and royalties report at $10 (§6050N(a)); neither is modeled, and
the worksheet says so.
**Who pays.** The payor is the person engaged in a trade or business who
makes the payment in its course (Treas. Reg. §1.6041-1(a)(1)(i)). A person
paying on behalf of another is the payor only if it performs management
or oversight functions in connection with the payment or has a
significant economic interest in it; "mere administrative or ministerial
functions such as writing checks at another's direction" are neither
(§1.6041-1(e)(1)). A member who pays a company bill from their own funds
at the company's direction, the company bearing the cost through Due to
Members, does only that: the company stays the payor, so a member's own
cash, check or transfer through a financing account counts.
**What is paid by card or network.** A transaction reportable under both
§6041 and §6050W "[is] reported under section 6050W and not section
6041", and for that test the third-party-network de minimis is
disregarded (§1.6041-1(a)(1)(iv); the 1099-MISC/NEC instructions, "Form
1099-K"). A payment card is "any card, including any stored-value card"
(§1.6050W-1(b)(3)); the IRS names credit, debit and stored-value cards
("Understanding your Form 1099-K"). The exclusion follows the instrument,
whoever's card it was: a member's personal card is excluded too. A payment
card transaction is one in which the card "is accepted as payment"
(§1.6050W-1(b)(1)) by the payee: a card that only funded a transfer the
payee didn't take as a card payment is not one (a member's
friends-and-family transfer is their cash). Automated clearing houses
"do not qualify as TPSOs" (Instructions for Form 1099-K): ACH, bill-pay
and Zelle are bank payments, not network payments.
**The rule (`form1099::vendor_1099_report`, `kernel::payment_bucket`).**
Payment methods are recorded once (`instrument.added`: bank,
business_card, member_card, member_cash_or_check, platform, each with the
account it pays through) and entries tagged with the one they were paid
with (`entry.instrument_set`, the reversal chain together). Each line of
a vendor-tagged posted entry has a role from its account: cash
(`books.cash_accounts`), financing (`books.financing_accounts`, and the
account of a member_card, member_cash_or_check or platform method: only
the entry's method says how a payment through it was made), a company
card's account (a business_card method's), or other. First match wins:
| The line | Bucket |
|---|---|
| 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 |
| 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 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) |
| anything else (an expense, payables, prepaid) | no payment line |
Each entry's buckets are netted (`kernel::net_buckets`, P3 review R1):
money moved between the company's own sources pays no one, so the card
bill (Dr card / Cr bank), a member reimbursed (Dr Due to Members / Cr
bank) and a loan's principal net to zero; when the signs differ, the net
goes to the one bucket with its sign, else to undetermined. A bill is not
paid until it is; a prepayment counts when made (payments made in the
calendar year, §6041(a)); a worksheet as of a day (the CPA package's,
B-33) counts the year's payments from January 1 through that day; a
refund and a reversal net. Money received from a vendor for something else (the entry nets money
in and credits, beside its payment lines, anything a refund of what was
paid doesn't: revenue, receivables, a registered asset's cost account,
equity, or a liability other than payables; a sale, a customer's payment,
a disposal, a loan) is left out and listed, with its reversals (P3 review
R6); a refund credits only an expense, payables, or an asset no money is
paid out of and no one owes (prepaid, a deposit).
Books with no cash accounts named are refused (never "every asset", P3
review R5), as are books in another currency without a threshold given.
With cash accounts named and no methods or financing accounts, what counts
is exactly the old figure (credits minus debits to cash), but for money
received for something else.
**The decision.** Each vendor's 1099 decision is nec, misc,
not_reportable with a reason kind and the facts in words, or undetermined;
"not reportable" is never "undetermined" (plan defects 18, 23). No return
is required for payments of bills for merchandise, telephone, freight,
storage and similar (§1.6041-3(c)), or to a corporation (§1.6041-3(p)(1),
except attorneys' fees and medical and health care; a payor may treat a
payee as a corporation by its name "Incorporated, Inc., Corporation,
Corp., P.C., (but not Company or Co.)", a W-9 or a corporate resolution:
§1.6049-4(c)(1)(ii)(A)), a tax-exempt organization ((p)(2)), the United
States ((p)(3)), a State or its political subdivision ((p)(4)), a foreign
government ((p)(5)) or a documented foreign payee (§1.6041-4(a)(1)); an
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
names who made it and when (`settings::Source`).
**Dated decisions** (DECISION 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,
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
reversal chain's first entry's date. An LLC electing corporate taxation
keeps its EIN and its vendor record and changes on the election's date; a
payee that incorporates is a new legal person, a new vendor with its own
W-9. A decision written before decisions were kept (no date, no source)
is in force from the beginning, and an old null reads undetermined (plan
5.5). A dated decision with nothing decided before it is refused (it would
leave every earlier payment undetermined); one the same as the decision
before its date takes out any decision recorded for that date. A change
reaching payments in a closed period, or in a year whose 1099s were due
(January 31 of the next year), warns in `books.set_vendor_1099` and
`books.set_entry_instrument`.
**Status** (per vendor and year; T the threshold, F the payments made
while decided for a 1099, U those while undetermined, lo/hi each with
every reversal chain's unknown settled low or high, P3 review R3):
nothing in F or U is not_reportable when something was paid while not
reportable or the year-end decision is, else under_threshold; anything in
U is undetermined when hi(F) + max(hi(U), 0) ≥ T (a decision is needed),
else under_threshold; otherwise reportable when lo(F) ≥ T, under_threshold
when hi(F) < T, else undetermined. `reportable` is `status = reportable`.
A W-9 is on file by the vendor's flag or its party's W-9 facts.
An entry naming several parties (a payment shared between a contractor
and a member; `ledger_core::shares`) counts only the vendor's share
toward its 1099: what was paid to the payee (§6041(a)), not what the
entry moved for others. The share is resolved (`shares::resolve`)
against the entry's target (B-35: its money, or, when it moved none, its
parties' own-account movement; a reversal's is its original's negated),
the same target the MCP server checks shares against, then scaled to the
1099 payment (a ratio, so B-35's signs change no 1099 figure: a reversal
negates both the payment and the target): the share unchanged when the
payment equals the target (the usual case, an expense paid from cash or
by a member), else payment × share ÷ target, rounded to the nearest cent,
half away from zero (so a part on a card no method names scales out). The
vendor's part is split across the entry's netted buckets in proportion,
each rounded down to the cent and the cents short handed out by largest
remainder (`kernel::apportion`, P3 review R12), so they sum to it
exactly. A percent split's leftover cent goes to the party named first
(shares INVARIANT 8). A reversal carries the shares and subtracts the
same part, and re-apportioning either one re-apportions the other
(`Ledger::set_entry_parties` INVARIANT 5), so the pair nets to zero. The
vendor tag follows the parties: on an entry naming several,
`set_entry_vendor` never replaces their shares (refused unless the
vendor's party is named), and a vendor record never links to a party
named beside another vendor (`add_vendor`), checked by
`tagging_the_vendor_keeps_a_shared_entrys_parties`,
`clearing_the_vendor_on_a_shared_entry_is_refused`,
`re_apportioning_a_reversed_entry_re_apportions_its_reversal`,
`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
conservative answer) and the entry is listed (`Vendor1099Report::
unresolved`; a `books.vendor_1099` note). DECISION (the owner,
2026-10-15: shares).
**The quieter note.** Untagged posted entries are named only when they
paid money out (netted counts plus undetermined above zero) for something
(a debit to an expense, to payables, or to an asset no money is paid out
of and no one owes), leaving out payroll runs, entries marked as having
no counterparty, entries naming only parties with no 1099 role, and
entries reversed in the year (P3 review R7): one count, the entries when
ten or fewer, and the tool to tag them.
**Cards and party totals.** The worksheet's card accounts are the
business-card methods' accounts only. Party money's cards are the same
once one is recorded (B-35 item 5: `parties::card_accounts`), and, while
none is, the liabilities named "card"; a charge to such a card leaves no
cash or financing account, so the worksheet counts it for no one either
way, and lists it as a card's only once the card is recorded. The
worksheet is never a party's totals: B-35 item 9 says how the two relate
(a member paying a payee through the members' financing is paid to the
payee in party totals, and the company's payment here), and they never
contradict.
**Enforced:** `form1099::default_threshold`, a dated rule
(`ledger_core::rules::THRESHOLD_1099`, known through 2026, review from
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
flag the rule once it is due (DECISION, the owner, 2026-10-13). The
bucket table is `kernel::payment_bucket` (Kani `payment_bucket_is_the_table`,
`payment_split_is_exact`, `net_buckets_keeps_the_total`). **Checked:**
`an_unknown_year_threshold_is_never_guessed`,
`dated_rules_go_stale_on_their_review_date`,
`the_report_needs_the_cash_accounts_and_dollars`,
`payments_come_from_cash_and_financing_only`,
`undecided_is_not_not_reportable`,
`a_members_personal_card_payment_is_not_a_1099_payment`,
`card_account_payments_are_listed_not_dropped`,
`a_debit_card_payment_from_the_bank_is_excluded_when_tagged`,
`a_tag_naming_another_account_is_undetermined`,
`paying_down_a_financing_liability_pays_no_vendor`,
`paying_the_card_bill_pays_no_vendor`,
`a_negative_unknown_keeps_a_reportable_vendor_undetermined`,
`a_sale_to_a_contractor_does_not_lower_its_1099`,
`money_from_a_vendor_that_is_no_refund_does_not_lower_its_1099`,
`a_status_change_mid_year_applies_by_payment_date`,
`two_status_changes_in_a_year_and_a_reversal_across_them`,
`an_incorporation_on_december_30_is_a_new_vendor`,
`an_llc_electing_corporate_taxation_keeps_its_vendor`,
`a_dated_decision_records_a_change`,
`a_decision_change_names_the_payments_it_reaches`,
`an_undated_old_decision_is_in_force_from_the_beginning`,
`payment_bucket_is_the_table_in_all_16_cases`,
`a_shared_payment_splits_across_buckets_to_the_cent`, the properties
`no_instruments_no_financing_is_the_old_report`,
`buckets_partition_each_payment`, `a_reversal_nets_every_bucket_to_zero`,
`the_worksheet_ties_to_the_ledger`, `party_totals_are_the_1099s_buckets`
(ledger-core `tests/form1099.rs`);
`the_untagged_note_counts_only_money_out`,
`the_other_receipts_note_says_what_each_entry_did` (ledger-package); the
MCP tests through a real books server (ledger-cli); bookspec
`bookspec/examples/1099-*.toml`.
## B-20 Every transaction has supporting documents; records are kept
**Status:** AUTHORITY (`sources/irs-records.md`)
IRS Pub. 583: "In addition, you must keep supporting documents"; they
"support the entries in your books and on your tax return"; keep records
"until the period of limitations for that return runs out" (3 years
generally; 7 for bad debts).
**Enforced:** the MCP receipt policy (not the engine; R-13).
## B-21 Exact amounts in the books; rounding only on returns
**Status:** AUTHORITY (for where rounding happens)
Form 1065 instructions (`sources/irs-partnerships.md`): "If two or more
amounts are added to figure the amount to enter on a line, include cents
when adding the amounts and round off only the total." The books keep
cents (and FX precision) exactly (R-2).
**Enforced:** `Decimal` money, `Amount` checked arithmetic, line range.
**Checked:** Kani `sums_are_exact`; bridge tests.
## B-22 Expense or capitalize (book policy, mirrors tax)
**Status:** AUTHORITY (`sources/irs-capitalization.md`)
Treas. Reg. §1.263(a)-1(f)(1)(ii)(D) sets $500 per invoice or item for a
taxpayer without an applicable financial statement "or other amount as
identified in published guidance"; Notice 2015-82 raised it to $2,500 for
taxable years beginning on or after January 1, 2016. The 12-month rule:
§1.263(a)-4(f)(1). The election is made yearly on the return.
**Enforced:** bookkeeper policy (books' AGENTS.md), not the engine.
## B-23 Control roles: one account each, of the right kind, moved only when empty
**Status:** CONVENTION (a control account agrees with the records it
controls); DECISIONS (the owner, 2026-10-13 and 2026-10-15)
Invoices, bills, payroll, and closes post to accounts the engine picks by
role: receivables, revenue, payables, general expense, retained earnings,
salary and payroll tax expense, and five payroll withholdings. A role uses
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
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
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
receivables, payables, withholding, or retained earnings account must keep
agreeing with what it controls (open invoices, open bills, taxes withheld,
closed earnings): such a role moves only when its account holds no
balance. For a withholding or retained earnings role the bookkeeper
first moves the balance with an entry, which the journal shows. Revenue
and expense roles move at any time; earlier postings stay where they
are. Receivables and payables are different (B-38, 2026-10-23): their
documents read their figures from their lines, so their control accounts
take no line by hand and equal their documents by construction. Their
balance moves with its documents only (plan P4b S7); until then a
receivables or payables role whose control account holds a balance stays
where it is, and the refusal says so (it names no hand entry, which the
control guard would refuse). Such a role also never moves onto an
account holding a line that is no document's, and the account it leaves
stays guarded while it holds its documents' lines. The account at a
role's default code is the role's control account only once a document
posts there (or the role is assigned to it): before that, a chart's own
1200 Equipment or a card at 2000 is the chart's.
**Enforced:** `Ledger::check_controls`, `Ledger::assign_role` /
`check_assign_role`, `Ledger::check_renumber_account` (an assigned role
frees its code); the control guard and `Ledger::control_check` (B-38).
**Checked:** `ledger-core` `tests/spec_roles.rs`, `tests/spec_documents.rs`;
`ledger-store` replay of `role.assigned`.
## B-24 Asset tags: one label per asset, never reused
**Status:** DECISION (the owner, 2026-10-15)
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,
`- _ . /`), and the manufacturer's serial number. Tags are unique ignoring
case and are never reused by another asset, even after a retag or a
disposal, so an old label can never point at the wrong thing. Both are
journaled. The fixed asset register lists every asset acquired by a date
(B-15) and reconciles to the general ledger's cost and accumulated
depreciation accounts: cost per register and per books
(`unregistered_cost`), accumulated depreciation by schedule and per
books (`unposted_depreciation`: run depreciation for it), and from the
assets' runs and per books (`unattributed_accumulated`: an entry made by
hand). Book value on the register is cost less the depreciation posted,
the books' figure, never the schedule's (P3, defect 13).
**Enforced:** `Ledger::set_asset_tag` / `check_asset_tag_for`,
`assets::check_asset_tag`, `assets::check_serial`. **Checked:**
`ledger-core` `tests/spec_asset_tags.rs`; `ledger-store`
`tests/asset_tags.rs`; `ledger-cli` `tests/spec_assets.rs`.
## 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
(the owner, 2026-10-15: the register carries tax basis); DECISION D-41
(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
the member's adjusted basis, the CPA to confirm at year end; the
§704(c) method is traditional, recorded as a choice the CPA confirms;
the contributed property's values on the contribution date are the
books' recorded amounts)
Book value and tax figures are different numbers for the same asset. The
book value of a member's contribution is its fair market value (Treas.
Reg. §1.704-1(b)(2)(iv)(d)(1), cited; Treas. Reg. §1.704-3(a)(3)(i):
"book value is equal to fair market value at the time of contribution").
Three tax figures, each named for what it is, follow from the rule and
the facts recorded (`asset.basis_set`: the rule, the contributing
member's adjusted basis, the fair market value at contribution, the
§704(c) method if chosen, and who gave them, B-29):
- **Purchase** (`purchase`): "The basis of property shall be the cost of
such property" (IRC §1012(a)); depreciable basis is the cost. Not
§704(c) property: no §723 basis and no built-in gain.
- **§723 basis** of contributed property: "the adjusted basis of such
property to the contributing partner at the time of the contribution"
(IRC §723; Pub. 541: "the partnership's basis for determining
depreciation, depletion, gain, or loss"). Adjusted basis is cost plus
improvements, less depreciation allowed but not less than allowable
(IRC §1016(a)(1)-(2)). wanderbooks does not model §721(b) gain (an
investment-company partnership only), so the §723 basis is the
member's adjusted basis.
- **Depreciable basis:** for business property contributed
(`carryover_723`), the §723 basis; for property a member contributed
from personal use (`personal_use_conversion`), "the lesser of its fair
market value or its adjusted depreciable basis" (Treas. Reg.
§1.168(i)-4(b)(1); Pub. 946, "Property changed from personal use";
D-41).
- **§704(c) built-in gain or loss at contribution:** fair market value
less the §723 basis ("the excess of the property's book value over the
contributing partner's adjusted tax basis upon contribution", Treas.
Reg. §1.704-3(a)(3)(ii)); fixed at contribution, so correcting the book
cost later never moves it. A built-in loss counts only for the
contributing member (IRC §704(c)(1)(C)). The §704(c) method
(traditional, traditional with curative allocations, remedial: Treas.
Reg. §1.704-3(b)-(d)) is chosen property by property (§1.704-3(a)(2)).
The figures are at contribution (or purchase): tax depreciation reduces
the basis afterwards (§1016(a)(2)) and the built-in gain or loss "is
thereafter reduced" (§1.704-3(a)(3)(ii)); both are the CPA's (MACRS,
conventions, elections: `sources/irs-depreciation.md`). The facts are
kept, never the figures: the figures are derived when read, under the
rule in force, so a later change to a rule changes the reports without
refusing a line an earlier version wrote (B-28). A record made before
P3 (`asset.tax_basis_set`, with the old names `cost` and `carryover`)
has no fair market value recorded: it reads as the book cost, which
reproduces that version's figures exactly, and Needs attention asks for
the value. wanderbooks' book depreciation is GAAP straight line, not
§704(b) book depreciation, which must keep the tax depreciation's ratio
(Treas. Reg. §1.704-1(b)(2)(iv)(g)(3), cited): neither is the other.
**Enforced:** `kernel::basis_figures` (Kani `basis_figures_laws`),
`AssetBasis::check` (a purchase takes no contribution facts; a
contribution needs the adjusted basis and the fair market value, each
non-negative, in cents, at most one quadrillion) and its replay pairs
`check_journaled` and `check_legacy_journaled`, `Ledger::set_asset_basis`
/ `check_asset_basis` / `replay_set_asset_basis` /
`replay_set_asset_tax_basis`, `Asset::basis_figures`,
`reports::fixed_asset_register` (per-row tax fields,
`total_tax_basis_723`, `total_depreciable_basis`, `missing_basis`);
`ledger-store` `Store::set_asset_basis` (the journal feature
`asset_basis`); `ledger-mcp` `books.set_asset_tax_basis` and
`books.register_asset` (a contribution's fair market value is required,
never assumed). **Checked:** `ledger-core` `tests/spec_asset_basis.rs`,
`tests/kernel_bridge.rs` (`basis_figures_agree`), `tests/properties.rs`
(`prop_basis_figures_laws`); `ledger-store` `tests/asset_tags.rs`;
`ledger-cli` `tests/spec_assets.rs`.
## B-26 Parties: one directory, no full TIN in the books
**Status:** AUTHORITY (truncated TINs: `sources/irs-truncated-tin.md`);
DECISIONS (the owner, 2026-10-15)
Everyone the company deals with is a party, with roles (vendor,
contractor, customer, employee, member, payout platform, tax agency,
financial institution, landlord). Vendor and customer records link to
their party; entries are tagged with theirs. Names are unique among parties
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,
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
(DECISION). W-9 facts need a 1099 role (contractor, landlord); W-4 facts
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
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
(`settings::holds_printed_tin_in_link`, DECISION of this rule, so store
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
letter or to `/ = ? & #` are an id in a path or query (an app store
listing's `.../id284882215`) and are kept. A project's name keeps the
plain rule: a nine-digit job or PO number is written by its last four,
as everywhere else in the books.
The full TIN, when the books need it for the 1099 worksheet, lives in the
TIN vault (`vault/` in the book's own folder in the data root, encrypted
under the book's own key, `keys/legacy.key` beside it): `books.set_w9`
`tin` writes it there and
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
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
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
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
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
asked): a refusal that quotes its input masks any TIN in it
(`parties::redact_full_tins`, checked by `refusals_never_echo_a_tin`),
and a party text field refuses nine digits however they are separated
(`has_full_tin`: up to three spaces, hyphens, dots, slashes or
underscores between neighbors, in any script's digits, a run of spaces
counting once however long it is; and a comma or a parenthesis between
a TIN's 3-2-4 or 2-7 groups (DECISION, P7, 2026-10-26), so
`123,45,6789` and `(123) 45-6789` read as a TIN while an amount's
comma-grouped thousands do not, `1,234,567.89` being 1-3-3-2; past ASCII
every Unicode space and dash and the fullwidth and small forms count, and
invisible characters such as a zero-width space or a right-to-left mark
don't count toward the three: `sources/unicode-tin-text.md`, Unicode
17.0). Files holding one (`books.get_w9`, `books.vendor_1099`'s
`csv` with `include_tins`, and the CPA package, whose K-1 and 1099
worksheets carry every member's and payee's TIN by default) are never
written to a path: no tool takes one (D-29, P2 slice 6). Each is handed
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
gives any file's bytes inline: files come in only by upload and go out
only by download). (Until slice 6 they were
written only outside the books dir: DECISION, P0 review, 2026-10-16, so
none could be filed back in as a receipt and read out again.) No file
holding a full TIN is filed into the books
(DECISION, 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
`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
`books.get_w9` copy;
a 1099 worksheet with TINs; a CPA package with TINs and each file in it
that holds one), shared by every session and every later server for the
books, and every way a file enters the books (`books.attach_receipt`,
`books.file_document`, by upload, the only way since D-46) refuses those
exact files,
and refuses any text file (by its type or name, or because it decodes as
UTF-8 or UTF-16, with a byte-order mark or, NUL-interleaved, without one)
holding (a) a TIN the vault holds for a party of the
books, anywhere in the text, as nine digits in a row, as 3-2-4 digits
joined by hyphens, spaces, dots or commas, or as 2-7 digits joined by a
hyphen, a space or a comma (compared on the server against the decrypted
vault, never returned or logged; a vault that can't be opened leaves (b)
alone), or
(b) a number printed as an SSN or EIN is, standing alone: 3-2-4 digits
joined by hyphens or commas (123-45-6789, 123,45,6789) or 2-7 joined by a
hyphen or a comma (12-3456789, 12,3456789), with no digit, and no hyphen
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
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
plans printing it (DECISION, the owner, 2026-10-17); (a) still refuses it
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,
the owner, 2026-10-16): bank exports are filed for reconciliation, and
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
(refusals and results). Because (a) compares against the vault, its
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
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,
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
number written several ways counts once) that end in the last four of a
TIN the books know (each party's TIN facts and W-9 facts, read from the
books; once the vault is opened, its own records' last fours too) is
refused with words of its own ("more than two numbers shaped like a
TIN"), the same byte for byte whether or not one of them is a vault TIN
and whether or not the vault is open; only a text with no more than two
is compared against the vault. With the last four known, a TIN is one of
100,000 numbers, so finding one this way takes on the order of 50,000
filings, each one refused and seen. What this can't catch: a changed
copy of a binary file (a W-9 PDF re-saved or re-scanned), a TIN in an
image, or a TIN the vault doesn't hold written other than (b);
`plan/served.md` P2 and P7 take the path tools and full TINs out of the
agent's reach. A known limit, open until then: (a) still answers for two
candidates a filing, so an agent that makes those 50,000 refused filings
can find a TIN; P2's access log records every call, refusals included,
and P7's human-only TIN channel takes full TINs, and with them this
check, off the agent's path. A bank export holding three numbers that
happen to end in a known last four is refused the same way (filed as a
PDF instead).
A locked vault or a missing TIN never fails
the package: it says which TINs are missing. `books.purge_tin` deletes a
party's vault records and journals that it did. Wherever the books show a
party's last four (the directory, `books.party`, the 1099 and K-1
worksheets, the purge preview) it is the TIN facts', else the W-9
facts' (`Party::tin_last4`; the two never disagree; exactly four digits
or nothing, however the facts were built), kept after a purge
(P3 defect 11: the directory read W-9 facts only, so a member's read as
missing).
**Enforced:** `crate::parties` (has_full_tin, check_text, W9Facts::check,
W4Facts::check), `Ledger::add_party`, `update_party`, `set_party_role`,
`set_party_w9`, `set_party_w4`, `set_party_tin` (TinFacts::check; TIN and
W-9 facts agree), `set_entry_party`, `validate_payroll`; `ledger-core`
`k1` (the K-1 worksheet); `ledger-vault` (Tin::new, the encrypted
records, records_held, purge_records); `ledger-mcp` `books.set_w9`,
`books.set_tin`, `books.get_w9`, `books.purge_tin`, `books.vendor_1099`,
`books.export_cpa_package` (open_vault opens the book's own key;
vault_tin checks the vault against the books); `ledger-mcp` `tin_files`
(the registry, the text
scan and its probe bound, on every filing tool; `known_tin_last4` gives
the books' last fours).
**Checked:** `ledger-core` `tests/spec_parties.rs`; `ledger-store`
`tests/parties.rs`; `ledger-vault` `tests/vault.rs`; `ledger-cli`
`tests/spec_parties.rs`, `tests/spec_vault.rs` (every response, every
file handed over, the server's log, and every file in the books' folder
outside `vault/` and the files kept for their download scanned for the
TINs in any formatting), `tests/tin_files.rs`
(`a_tin_bearing_file_is_refused_from_every_session_and_after_a_restart`,
`text_with_a_vault_tin_or_a_printed_one_is_refused_and_a_bank_export_is_not`,
`text_probing_for_a_tin_is_refused_alike_by_every_filing_tool`), and the
unit tests in `tin_files.rs`.
## B-27 The journal format gate: newer is not corrupt
**Status:** DECISION (the owner, 2026-10-16; `plan/served.md` 5.1 and P0);
DECISION (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
formats); DECISION (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,
asked at every open until given)
Every journal line has a type, and a version of wanderbooks reads a fixed
set of them (`ledger_store::format::EVENT_TYPES`). A newer version may
write a type an older one doesn't know, or declare a newer journal format
with a `journal.format_set {format, features}` line before the first event
that needs it. An older reader stops at the first line it can't read and
says so: it never skips a line (the books it showed would differ from the
record without saying so), and it never calls newer books corrupt.
What every journal format shares is frozen, so that an older reader can
always follow the chain one line past the first line it can't read. A
line is the envelope `{seq, ts, type, payload, prev}` and nothing beside
it; its hash (what the next line's `prev` holds) is the SHA-256 of its
canonical JSON: keys sorted at every depth, no whitespace. A newer format
puts its new fields inside `payload`, never in the envelope, and never
changes how a line is hashed. A line with a key beside the envelope's is
refused as newer, never read with the key dropped, and its hash covers
the key, so the next line still checks it.
Damage stays corruption. A line's seq and `prev` are checked before the
gate, and its own hash is checked by the next line's `prev`; so when the
gate refuses a line, the reader reads the next one first, taking only its
`prev` (any JSON object with a string `prev`: it may use floats, a string
seq or keys of a format this version doesn't read). If that `prev` doesn't
match, the refused line was altered after it was written (a flipped bit in
its type reads like an unknown type): corrupt. If it matches, the refused
line is intact; the reader says so, and that it can't check the lines
after it. If there is no next line, or it has no `prev` this version can
read, nothing checks the refused line: the reader says it needs a newer
wanderbooks or the line is damaged, and to run verify after updating, and
never that it is intact. A newer format number is newer whatever the rest
of its declaration looks like (a newer format may shape its features
differently); a malformed declaration of a format this version reads is
corrupt. A blank line, or one that isn't UTF-8 JSON, is damage, to the
writer and the viewer alike: both read the journal as bytes split on
newlines. A writer never declares a format it couldn't read back, or
lowers the format in force. Opening the books checks every complete line
before it changes anything: a cut-off last line is set aside only when the
lines before it pass, so an older version never alters newer books.
A line an earlier version can't read is announced by a journal feature
(`ledger_store::format::KNOWN_FEATURES`): a new event type, or a field or
value on an event type an earlier version knows but would misread. The
books declare it in a `journal.format_set` before the first line that
needs it, so an earlier reader stops at the declaration; this version
declares every feature it knows at once (below), never one at a time on
the way. P3's: `members_cumulative` (B-13: a members close counted from
its count's first close, `"rounding": "cumulative_since"`),
`account_renamed` (an `account.renamed`, B-31), `require_tax_category` (a
`settings.set` naming `policy.require_tax_category`, B-32),
`tax_categories_gain_exempt` (an `account.tax_set` naming
`form_4797_gain_loss` or `tax_exempt_income`, categories an earlier version
refuses), `disposal_before_service` (an `asset.disposed` dated before the
asset's in-service date, on or after the day it was acquired: an earlier
version refuses a disposal before its purchase date), `depreciation_catch_up`
(an `asset.depreciation_posted` whose amount isn't its month's own: months
skipped or reversed caught up in it, B-15; an earlier version reads it as the
month's own, shows the months it caught up as not yet run, and running them
there posts them twice),
`party_creditors` (`party.creditor_set`: defect 24, B-35),
`payment_instruments` (`instrument.added`, `entry.instrument_set`:
defect 12, B-19), `vendor_1099_decisions` (`vendor.1099_set` with who
decided, a `not_reportable` decision, its reason or the date it takes
effect, and `vendor.w9_flag_set`: defects 18 and 23, D-40, B-19),
`schedule_convention` (a daily schedule's `schedule.registered` carries
`convention: daily`; an older version would read it as equal months,
B-16; and a `settings.set` naming `books.schedule_convention`, a key an
earlier version refuses), `month_reposting` (a schedule month or a depreciation period
posted again after a reversal un-posted it; an older version would refuse
the second `schedule.posted`, and its projection ignore the second
`asset.depreciation_posted`, B-15), `asset_basis` (an
`asset.basis_set`, a new event type: an asset's basis facts with the
value at contribution and the §704(c) method, B-25) and `projects` (a
`project.set`, a new event type: a class's project facts; an
`attachment.linked` to the subject kind `project`, which an older version
would call corrupt; a `decision.recorded` with `projects`, which an older
version would drop silently: B-37). After P3: `document_line_projects`
(an `invoice.created` or `bill.created` line carrying `class_id`, its
project, B-37 item 8; an older version would restore the document without
it, silently). It is a feature of its own, not part of `projects`: books
that declared `projects` before it existed wait for their owner's next
upgrade before a document's line names a project. A command asks for
the feature it needs (`Store::ensure_feature`) before its first journaled
line, and only when the line it is about to write uses it, so a write
refused for waiting on the upgrade journals nothing, never an entry
without the line that says what it is
(`a_write_waiting_for_the_upgrade_journals_nothing`), and a write that
needs nothing new is never refused. A tool journaling
several commands as one asks before the first of them, never between
them (a refusal there would leave the tool's work half done):
`books.set_w9` asks for `vendor_1099_decisions` before the W-9 when the
vendor's W-9 flag follows it. A value in a line of a known type that only
a newer version writes (a 1099 decision or reason kind, a payment
method's kind) is newer, never corrupt.
**The other way round: the format upgrade.** This version's format is
the newest it reads with every feature it knows (`format::current`). Books
it creates declare it in their second line, so they wait for nothing.
Books an older version wrote declare less (format 1, written before the
gate, declares nothing): they *wait for a format upgrade*, which is a fact
of their journal alone (`JournalFormat::upgrade`). They open and replay as
they are, byte for byte; everything that needs nothing new is written as
ever; a write that would need the newer format or a feature they haven't
declared is refused before anything is journaled
(`StoreError::FormatUpgradePending`, guard `format_upgrade_pending`), never
declared on the way. Every setting key and tax category an earlier version
didn't read needs a feature (`format::setting_feature`,
`format::tax_category_feature`: the P3 data-safety review, 2026-10-21,
found `books.schedule_convention`, both new tax categories, a disposal
before the in-service date and a depreciation catch-up journaled without
one, each leaving books P2 refused or misread before consent;
`tests/p2_compat.rs`). Only the owner's go-ahead moves them, with the owner
passphrase (the server checks it, Argon2id): in the app, which asks at
every open until it is given, or with `wanderbooks books upgrade ID` at a
terminal, whose commands that open the books also ask; never an agent, and
no tool. Before the upgrade the server commits the books to their own
backup repository and proves the copy holds them exactly as they are
(journal, stored files, vault and keys, byte for byte), all under the
books' lock; a backup that can't be made or proven upgrades nothing. The
upgrade is then one `journal.format_set` declaring the whole format, so a
book's format moves exactly once per version; books already in it are left
alone. An adoption's copy is new books of this version and moves to its
format as it is made (the original keeps its own). The upgrade never
weakens the gate: books declaring a feature this version doesn't know are
newer, as above.
**Each projection schema in a file of its own** (the lead's decision,
2026-10-21, for 3.9's "before consent, the way back is always there"). The
books' SQLite projection is derived from the journal, and its schema moves
with the version (`schema::SCHEMA_VERSION`), but an older version refuses a
database of a newer schema (`SchemaTooNew`). So each schema keeps its own
file, `wanderbooks.v{N}.db` (`store::db_file_for`): a version opens or
creates only its own; its own missing, it is built from the journal; the
legacy `wanderbooks.db` (every version through P2, schema 12) is used only
while it already holds this version's schema, so books needing no
migration aren't rebuilt for nothing; a file of any other schema, older or
newer, is never migrated, written, refused or deleted, and stays byte for
byte for the version that wrote it (going back finds it as it left it, or
builds its own). A file named for this version's schema that holds another
is damaged, kept aside and rebuilt, as a corrupt one is. `books.verify`
(`database`, `other_databases`) and `wanderbooks doctor` say which file is
in use and which others are there; none is deleted for being stale.
**Enforced:** `ledger_store::format` (`JournalFormat::admit`, `declared`,
`gate`, `settle`, `declaration`, `current`, `JournalFormat::upgrade`,
`require_format`, `require_feature`), `journal::ENVELOPE_KEYS`,
`JournalLine::parse`, `journal::read_journal_and_format`,
`Snapshot::read_new`, `Store::open`, `Store::create`, `store::db_file_for`,
`store::database_in_use` (and `Snapshot::check_disk`, which checks the file
it names), `Store::database_file`, `Store::other_databases`,
`Store::declare_journal_format`, `Store::upgrade_format`,
`Store::ensure_feature`, `Root::adopt`, `StoreError::NewerJournal` (its
`verified` flag picks the message), `StoreError::FormatUpgradePending`,
`ledger_store::backup::prove_archive`, ledger-mcp `Backups::upgrade_format`
and `local.rs` (the passphrase), ledger-host `host.rs` (the lease intent,
handed to the server) and `commands.rs` (`books upgrade`, asking at open),
ledger-gui `owner.rs` (the notice and the dialog).
**Checked:** `ledger-store` `tests/format.rs` (`old_reader_gets_newer_journal`,
`a_damaged_line_that_looks_newer_is_corrupt_when_the_next_line_checks_it`,
`an_unreadable_last_line_is_reported_as_newer_or_damaged`,
`a_newer_line_the_next_line_chains_onto_is_newer_and_intact`,
`a_newer_format_frames_its_lines_its_own_way`,
`an_unknown_envelope_key_is_newer_and_checked_through`,
`a_refused_line_followed_by_a_garbled_line_is_unverified`,
`a_line_that_is_not_utf8_is_corrupt_for_the_viewer_and_the_writer`,
`an_old_reader_never_repairs_newer_books`,
`a_blank_line_is_corrupt_for_the_viewer_and_the_writer`,
`damaged_journals_are_still_corrupt`) and the unit tests in `format.rs`;
`ledger-gui` `tests/newer_journal.rs`; `ledger-cli` `tests/startup.rs`;
the `fuzz_store_replay` target (the writer and the viewer agree on newer
versus bad). The upgrade: `ledger-store` `tests/format_upgrade.rs` (new
books in this version's format, a change waiting for the upgrade, every
old-books fixture upgrading to its figures, a newer feature still
refused), the unit tests in `format.rs` and `backup.rs`
(`the_archive_proves_to_hold_the_book_as_it_is_keys_and_all`),
`tests/settings.rs` (`a_change_needs_journal_format_2`), `tests/root.rs`
(adoption), `tests/schema_files.rs` (a P2-shaped books dir, its
`wanderbooks.db` used at its schema and left byte for byte past it; an
older schema's file left byte for byte and whole while this version's is
built and kept up; a newer schema's file left alone, not refused; a damaged
file of this version's schema rebuilt; new books' file named for their
schema); `ledger-mcp` `local.rs` (`format_upgrade_waits_for_consent`,
`no_upgrade_without_a_backup`,
`no_upgrade_unless_the_backup_just_made_is_proven`); `ledger-host`
`lease.rs` (`a_consent_never_shows_its_passphrase`); `ledger-cli`
`tests/format_upgrade.rs`
(from end to end); `ledger-gui` `tests/format_upgrade.rs`.
## B-28 What new commands take: four-digit years, amounts a line can carry
**Status:** CONVENTION (ISO 8601's basic calendar dates have four-digit
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
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
bounds are the commands', never replay's)
**New commands.** Every date a command takes or derives is in
0001-01-01 through 9999-12-31 (`ledger_core::calendar`): a date given
to a tool is exactly YYYY-MM-DD, a real day in that range (no sign, no
space, no fifth year digit); a period is a month of it; a new schedule's
last month (a daily one's last day), a new asset's last day of
depreciation (so a life ending on 9999-12-31 is taken, and one a day
later refused: P3 settled the plan's defect 22 question this way, lives
past 9999 refused at registration, the bound the life's last day held;
ones an earlier version took open, and their months after 9999-12 show
as scheduled and never post), a new recurring
entry's start and end, a new period, a new invoice's or bill's date and
due date, a disposal's date stay in it. Every amount a command takes,
not only a posted line's, is at most a journal line's
(1,000,000,000,000,000, `kernel::MAX_LINE_AMOUNT`): a draft line (by
size; its precision is checked when it posts), a document line, a
payout's lines and deposit, every payroll figure, a statement's
balances, a party's share, a contributor's basis, disposal proceeds, a
recurring template's lines. One run of the recurring entries creates at
most 1,000 drafts (`MAX_RECURRING_RUN`); a later date is run in steps.
**Replay accepts every line an earlier version accepted.** The bounds
above came after books were written, and posted is forever (B-8): a
journal an earlier wanderbooks wrote opens, in the writer and the
viewer alike, and replays to the same figures, forever. Replay applies
each check exactly as the version before the bound did: a journaled date
is any date chrono reads as `%Y-%m-%d` (chrono's own range, about
262,000 years either way, written "+10000-01-01" past 9999); a draft
line, a recurring template, a party's share, a contributor's basis, a
document line, a payment, a payroll run and disposal proceeds carry no
upper bound beyond what their own rules always said (positive, cents,
balanced, and so on); a schedule's months are 1 to 600 and an asset's
life ends on a date chrono holds. The checks split in two: the
command's (`Ledger::add_line`, `register_asset`, `AssetBasis::check`,
`shares::check_shares`, ...) and replay's (`replay_add_line`,
`replay_register_asset`, `AssetBasis::check_journaled`, `AssetBasis::check_legacy_journaled`,
`shares::check_shares_journaled`, ...), and every bound has its pair of
tests: the command refuses past it; replay of an old line past it is
accepted. The full-TIN rule (B-26) is one of these bounds: P3's detector
finds a TIN across every Unicode space and dash and invisible characters,
P2's only across ASCII separators, the no-break space and a few dashes, so
replay finds one as P2 did (`ledger_core::parties::replaying`, around every
replayed line), and a name, reason or fact P2 took opens
(`a_name_p2_took_replays_though_the_command_now_refuses_it`; the P3
data-safety review, 2026-10-21, found P3 refusing to open such books). What an earlier version left beside its journal is kept for it
the same way: its projection database, in its own schema's file, is never
migrated or written by a later version (B-27, "each projection schema in a
file of its own"), so the earlier version, gone back to, opens its books
as it left them.
**Derived figures refuse, never panic.** Because replay accepts what
the commands now refuse, every figure the books derive is computed with
checked arithmetic: a sum or product exact arithmetic can't hold is
refused with "too large to compute exactly" (`TooLargeToCompute`), and
a date past chrono's last day with `DateOutOfRange`, at the point of
computation (a draft's debits, a document's total and balance, aging,
net pay, a payroll plan, an entry's money flow and its shares'
resolution, the fixed asset register, an amortization plan), never
wrapped, rounded early or panicked on, and the server stays up. Date
walks reach chrono's last day without stepping past it (an asset's
depreciation in December 262142, an interval ending at
`NaiveDate::MAX`). A replayed line whose own figures overflow (a
recurring template whose debits Decimal can't sum: no version could
write it, and the last one panicked replaying it) is refused as the
books open, never a panic.
**Enforced:** `ledger_core::calendar`; `Ledger::try_create_entry`,
`add_line`, `add_line_fx`, `validate_new_asset`, `dispose_asset`,
`schedules::validate_schedule`, `validate_recurring`,
`periods::validate_period_range`, `arap::validate_lines`,
`validate_due_date`, `payroll::check_payroll_ranges`, `payout_plan`,
`shares::check_shares`, `AssetBasis::check`, `reconciliation_report`; for
replay, `replay_add_line`, `replay_add_line_fx`,
`replay_register_asset`, `replay_correct_asset`,
`replay_dispose_asset`, `replay_set_asset_tax_basis`, `replay_set_asset_basis`,
`replay_set_entry_parties`, `replay_register_recurring`,
`replay_register_schedule` (`validate_schedule_shape`),
`replay_create_period` (`validate_period_order`), `replay_invoice`,
`replay_bill`, `replay_invoice_payment`, `replay_bill_payment`,
`replay_payroll_run`, `validate_new_asset_journaled`,
`validate_recurring_journaled`, `check_shares_journaled`,
`AssetBasis::check_journaled`, `AssetBasis::check_legacy_journaled`; derived: `Invoice::total`/`balance`,
`Bill::total`/`balance`, `AgingRow::total`, `ar_aging`, `ap_aging`,
`PayrollLine::net_pay`, `payroll_entry_plan`, `parties::entry_flow`,
`shares::share_target`, `shares::resolve`, `fixed_asset_register`,
`schedules::amortization_plan`, `month_end`, `depreciation_schedule`,
`analysis::intervals`, `form1099::vendor_part`, `k1::share_property`,
`ReceiptPolicy::entry_debits`; `ledger-store` `parse_date` (replay:
chrono's range), `parse_period`, `last_day_of_month`,
`record_statement`, `dispose_preview`, `run_recurring`; `ledger-mcp`
`parse_date`, `parse_period_mcp`, `last_day_of_month`.
**Checked:** `ledger-core` `tests/spec_bounds.rs` (each bound and its
replay pair), `calendar.rs`, the unit tests of `amortization_plan`,
`intervals`, `vendor_part` and `share_property`, `assets.rs`
`registration_rejects_date_overflow`, `invariants.rs`
`out_of_range_amount_is_rejected`; `ledger-store` `tests/store.rs`
`books_an_earlier_version_wrote_open_to_its_figures` (every book in
`tests/fixtures/old-books`, written by main 188c90ea, opens in the writer
and the viewer and replays to exactly the figures main computed) and
`every_figure_over_old_books_is_computed_or_refused`, `tests/bounds.rs`;
`ledger-cli` `tests/hostile_args.rs` (every tool with generated hostile
arguments and every resource with generated URIs: the server never
stops, and the books reopen after; the inputs that once panicked,
pinned; every tool over every old book) and `server.rs`
`periods_are_a_year_and_a_month`, `dates_are_four_digit_years_and_real_days`;
`ledger-gui` `tests/old_books.rs`.
## B-29 The books keep how they are kept
**SOURCE:** DECISION (plan/served.md I-3, owner decisions of 2026-10-16:
"the journal holds all books state"; light governance: decisions are
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.
A company's settings, its conventions (how it posts each kind of
transaction, its policies and close steps), the decisions its owners made,
what is open, and approvals of draft entries are facts about the books,
kept in the journal like every other fact, each with who decided it and
when (journal format 2: `settings.set`, `convention.set`,
`convention.retired`, `decision.recorded`, `open_item.raised`,
`open_item.resolved`, `entry.approved`). A settings file is only text to
import from or export to; no server reads one (D-44).
- A setting is one of the known keys, checked by its key and kept in one
spelling; a change is all or nothing, and the settings after it hold
together. Replay takes any value of the right shape (B-28).
- No setting, convention id, convention, decision, open item or approval
holds a number that reads as a full TIN (nine digits standing alone,
bare or printed 3-2-4 or 2-7 with a dash or a space), except the
company's own EIN, shown masked everywhere but reports, packages and the
viewer, and a D-U-N-S number in its own key.
- A record of how the books are kept changes no entry, draft, period or
account (proved for the model: `Step.record_changes_no_figure`; checked
on the engine after every record in `settings_replay_is_pure`). Settings
that say how the same entries are presented or later allocated (the
fiscal year, the cash flow roles, the members) present them by the
settings in force; the fiscal year can't move once a period is closed
under it (a changed tax year needs the IRS's consent, Form 1128).
- Books keep their own settings, and only `books.set_setting` changes
them; old books bring their settings file in once, whole, as they are
adopted (`wanderbooks books adopt`), and nothing reads one after.
- A setting key or convention kind this version doesn't know is a newer
version's: the books are "newer", never "damaged" (B-27).
- A posting template's lines split the amount exactly on each side, odd
cents by largest remainder (B-13's rule); a slot (`{expense}`) takes the
account each draft names.
- Approval rules block anything only when the company enforces them
(`policy.approval_rules = enforce`; off by default). Then a draft a rule
covers posts only once its approver approved it as it stands (its
fingerprint: date, description, every line and class), and an operation
that would post such an entry at once (an invoice, a bill, a payment,
payroll, a remittance, a disposal) is refused: it is drafted instead.
Reversals, closes, depreciation and amortization follow from entries
already posted and are exempt. A rule names accounts that take postings,
and an account a rule names can't be renumbered until the rule is
revised.
**Enforced:** `ledger_core::settings` (`check`, `Settings::check_changes`,
`check_whole`, `shape`, `apply`), `ledger_core::conventions` (`check_new`,
`check_decision`, `check_open_item`, `split`), `Ledger::check_settings`,
`check_convention`, `approvals_missing`, `template_lines`; `ledger-store`
`Store::change_settings`, `adopt_settings`, `set_convention`,
`draft_from_template` (rehearsed whole), `record_decision`; the format gate
(format 2 declared before the first such line); `ledger_config::codec`
(`import`, `export`); `Store::post_entry` and `approval_gate`
(`ApprovalRequired`, `ApprovalNeedsDraft`), `check_renumber_account`;
`ledger-mcp` adoption (`local.rs` `settings_to_move`: a settings file,
whole, through `codec::import`), `books.post_entry`
(`approval_required`).
**Checked:** `ledger-core` settings and conventions unit tests;
`ledger-store` `tests/settings.rs` (`settings_replay_is_pure`, 256 cases
per check, 2,000 by hand, with posts between records and every record
checked to change no figure or entry; `approval_rules_off_by_default`,
`approvals_cover_every_post`, `the_fiscal_year_stays_under_closed_periods`,
`a_newer_key_or_kind_is_newer`;
`books_written_under_p1_open_to_their_record`, fixture
`old-books/p1_record`); `ledger-config` `tests/codec_props.rs`
(`exports_round_trip`) and codec unit tests; `ledger-cli`
`tests/spec_kept.rs`; Lean `Step.record_changes_no_figure`,
`Reachable.records`; fuzz `fuzz_settings_replay`.
## B-30 A report asked for without dates covers this fiscal year so far
**Status:** AUTHORITY for the period (IRS Pub. 538,
`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.
§6041(a), "in any calendar year", and §6041A(a)(2), "during such calendar
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
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
far, everywhere, and an account's register opens on it with 'Show all
dates' one click away").
The books' annual period is the fiscal year (`company.fiscal_year_start`,
B-29). Every place a date may be omitted resolves it by one rule
(`ledger_core::calendar::PeriodDefaults`), so the agent's report tools,
`books.show`, the viewer and the tool descriptions can't say different
things:
| Kind | Used for | Both dates omitted | Only `end` given | Only `start` given |
|---|---|---|---|---|
| A period | profit and loss (and by class), cash flow, tax summary, members' equity, general ledger, general journal (csv, json, xlsx), an account's register, `books.show` of an account or a report | the first day of the fiscal year that holds today, through today | the first day of the fiscal year that holds `end`, through `end` | `start` through today |
| An instant | balance sheet, trial balance, A/R and A/P aging, fixed asset register, the IIF's `as_of` (every posted entry from the first through it, B-34), the CPA package's `as_of` | today | | |
| A calendar year | party totals (`books.parties`, `books.party`, the viewer's party pages), the viewer's 1099 worksheet | today's calendar year | | |
- A resolved start after the resolved end is refused, whether the caller
gave both dates (`InvalidDateRange`) or only a start after today, the
end an omitted end means (`StartAfterToday`, whose message says the end
was defaulted: "pass end"). It is never swapped or clamped to fit.
- A derived start before 0001-01-01 is the calendar's first day (only an
end early in year 1 under a non-January fiscal year, B-28).
- The first year: when the company was formed after its fiscal year's
first day, the default start comes before formation. The amounts are
the same, and the start is not moved to `company.formed`: start-up and
organizational costs (IRC §195, §709) can be dated before formation.
- "Today" is the company's (`company.timezone`, B-29).
- The CPA package's period and the viewer's "Year to date" preset are
the fiscal year that holds their day (`calendar::fiscal_year_start_on`),
the fact itself, never "what an omitted date means": they don't follow
this default if it ever changes.
- Not covered (no default period): search filters, the activity digest
(its `since` is required; the `books://digest` resource reads this
month so far), the `through` of `books.run_schedules` and
`books.run_recurring` (today: an operation, not a period), and the
viewer's drill-downs from a balance, which run from the books' first
entry because a balance is cumulative. The IIF is never a period (B-34):
it refuses `start` and `end`, so the general journal's default period
never shortens it, nor anything in the CPA package, which gives its
statements and ledgers its own fiscal year to date.
**Enforced:** `ledger_core::calendar` (`period_defaults`,
`PeriodDefaults::statement`, `statement_range`, `as_of`,
`calendar_year`, `fiscal_year_start_on`, `PeriodKind::describe`,
`START_DOC`, `END_DOC`, `AS_OF_DOC`, `YEAR_DOC`); `ledger_package`
`statement_range`, `as_of_date`, `pnl_statement`, `bs_statement`,
`cash_flow_statement`, `build_package`; `ledger-mcp` `BooksServer::periods`,
`statement_range`, `as_of`, every report tool, `books.show`, the
generated descriptions (`described`) and parameter docs; `ledger-gui`
`Books::periods`, `default_period`, `fiscal_ytd`, `App::show`,
`RegisterRange`. **Checked:** `ledger-core` `calendar.rs` unit tests and
`tests/properties.rs` `prop_period_defaults_resolve`; `ledger-mcp`
`server.rs` `period_tools_describe_the_one_default`; `ledger-cli`
`tests/mcp_journeys.rs` `one_default_period_everywhere_defect_16` and
`the_default_period_never_shortens_the_iif_or_the_package`,
`tests/oracle_mcp.rs`
`show_sends_the_dates_it_names_defect_16`,
`show_refuses_start_after_end_defect_15`,
`show_refuses_a_derived_inverted_range_defect_15`; `ledger-gui`
`tests/show.rs`; bookspec `examples/default-period-fiscal-year-to-date.toml`.
## B-31 Renaming an account changes its label, nothing else
**Status:** DECISION (the owner, `plan/served.md` 4.9 and P3 "Settled in
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
forever); AUTHORITY for keeping the old name (Rev. Proc. 98-25 §5.01,
`sources/irs-records.md`: "the relationship (i.e., audit trail): (a)
between the total of the amounts in the taxpayer's machine-sensible
records by account and the account totals in the taxpayer's books");
Intuit IIF (`sources/intuit-iif.md`: "Accounts are identified by NAME")
for refusing a sibling's name.
Entries name accounts by id, so a rename changes no entry, line, hash,
balance or statement amount: every report, past periods included, prints
the new name. The journal keeps the old one (`account.renamed {account_id,
code, name, previous, reason}`), so a report printed under the old name
can be tied to the new. A rename is refused when the account is unknown,
the name is blank, the name (trimmed) is the one it has, another account
under the same parent (top-level accounts are siblings too) has it
ignoring capitals and surrounding space (QuickBooks would merge the two),
it holds a run of nine digits (a full SSN, EIN or account number, B-26:
every report prints a name), or it is not one line of at most 200
characters (`account::MAX_ACCOUNT_NAME`, the bound of the company's own
name: no line break, tab or other control character, which no report, CSV
or IIF line can hold). The name is kept trimmed. The same name rules hold
for a new account (`books.add_account`, `books.import_chart_csv`), which
is journaled with its name trimmed too; accounts the engine
creates for a control role keep their fixed names and are never refused
for one. Approval rules, roles, settings and closed periods name accounts
by code or id, and a name is no posting, so none of them blocks a rename.
Existing charts are untouched: the rules are the commands', never replay's
(B-28).
A rename can still change what the books pick by name (no amount): with
`books.cash_accounts` unset, the cash accounts are the asset accounts
whose names say they hold cash, and, while the books record no
business-card payment method, the card accounts are the liabilities
named "card" but not "gift" (`ledger_core::parties::card_accounts`, B-35
item 5); the result says
when the new name moves the account into or out of either. It also lists
conventions whose text names the old name. QuickBooks matches accounts
by name, so a CPA who imported an earlier IIF is told.
**Enforced:** `Ledger::rename_account` / `check_rename_account`,
`check_account_name` (also in `add_account`), `account::MAX_ACCOUNT_NAME`,
`check_rename_reason`,
`replay_rename_account` (only that the account exists),
`ChartOfAccounts::set_name`, `Ledger::add_control_account`;
`Store::rename_account` (journal feature `account_renamed`, which the
books declare before their first rename, B-27); `books.rename_account` (scope
`books:post`, like renumbering). **Checked:** `ledger-core`
`tests/spec_ledger.rs` (`rename_account_*`), `tests/model.rs`
(`Cmd::Rename`); `ledger-store` `tests/store.rs`
(`rename_account_changes_only_the_name_defect_15`,
`prop_rename_changes_no_figure`, `a_journaled_rename_replays_past_the_command_rules`),
`tests/format.rs`; `ledger-cli` `tests/mcp_journeys.rs`
(`rename_account_end_to_end_defect_15`), `tests/hostile_args.rs`;
`ledger-core` `tests/spec_ledger.rs`
(`a_new_name_is_one_trimmed_line_of_bounded_length`), `ledger-store`
`tests/store.rs` (`a_new_accounts_name_is_journaled_trimmed`); Lean
`Step.record` (a name is outside the model: `docs/verification.md` law 15);
bookspec `examples/rename-changes-no-amount.toml`.
## B-32 Every revenue and expense account maps to a line of the return
**Status:** AUTHORITY for why it matters (IRS Pub. 583,
`sources/irs-records.md`: "Your books must show your gross income, as
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
§5.01: the relationship "between the total of the amounts in the
taxpayer's machine-sensible records by account and the taxpayer's
return"); DECISION for warn versus refuse (Pub. 583: "the law does not
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
P3 cutover).
A tax category is the account's link to the return (`ledger_core::tax`,
`sources/irs-tax-lines.md`). An account needs one when it is a revenue or
expense account and not a header (a header takes no postings, so nothing
of its own reaches the return: no entry posts to one, a replayed one
included, and an account's header flag never changes).
- Needs attention and the digest (`uncategorized_accounts`) list every
account that needs a category and has none, with activity or not.
- `policy.require_tax_category`: `warn` (unset) or `refuse`. Under `warn`,
`books.add_account` and `books.import_chart_csv` create the account and
their results say so. Under `refuse`, they refuse it (the file whole),
naming the account and where the categories are listed; both take the
category at creation, so the refusal is always met in one call; and
`books.set_tax_category` won't clear a category an account needs.
- The accounts invoices, bills and payroll create for a control role
(Revenue 4000, General Expenses 6999, Salary & Wage Expense 6100,
Payroll Tax Expense 6110) are never refused, so an invoice is never
refused over a chart label: their results warn, and Needs attention
lists them. The policy is "no uncategorized account made through the
chart tools", not "never any".
- Turning the policy to `refuse` changes no existing account: it applies
to new commands (B-28). Needs attention keeps listing the gaps.
- Every account a tool can require has a category that fits it on every
return: the gain or loss account of a disposal (`form_4797_gain_loss`,
Form 4797), tax-exempt income (`tax_exempt_income`, Schedule K), and
each control role's. A gain or loss on business property is figured on
Form 4797 from the tax basis, and its split (ordinary gain, net section
1231 gain) decides the line, never Schedule C: the tax summary lists it
apart (`TaxSummary::form_4797`), at its book amount, with no tax amount
and in no total, on every return.
**Enforced:** `tax::needs_category`, `account_needs_category`,
`uncategorized_accounts`, `CATEGORIES`; `Ledger::check_tax_category_required`,
`check_tax_category_kept` (commands only, never `set_account_tax`, which
replay calls); `settings::TaxCategoryPolicy`, `Settings::require_tax_category`;
`Store::add_account_with_tax`, `add_account`, `set_account_tax`
(journal feature `require_tax_category`, which the books declare before
their first `settings.set` naming the key, B-27); `ledger-mcp` `books.add_account`,
`books.import_chart_csv`, `books.digest`; `ledger-gui` Needs attention
(Info under warn, Todo under refuse). **Checked:** `ledger-core`
`tests/tax.rs`; `ledger-store` `tests/store.rs`
(`prop_tax_policy_refuses_exactly`, `a_journaled_clear_replays_under_refuse`);
`ledger-cli` `tests/spec_tax_policy.rs`; `ledger-gui` `tests/view_model.rs`
(`needs_attention_flags_every_uncategorized_pl_account_defect_8`);
bookspec `examples/tax-category-required.toml`.
## B-33 A package as of a day shows the books through that day
**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
through it; nearest authorities the accounting period, IRS Pub. 538,
`sources/irs-periods-and-methods.md`, and the information returns'
calendar year, `sources/irs-1099-thresholds.md`); DECISION (owner,
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`,
the viewer's Export for CPA) reflects anything dated after it: no figure,
entry, receipt or worksheet row. The statements and the ledgers cover
the fiscal year containing as_of, through it; the balance sheet, the
trial balance and the fixed asset register are as of it (the register
only when an asset was bought by then); receipts/ holds the evidence of
the year's entries through it; the members' equity statement and the
K-1 worksheet, the year to date; general-journal.iif, every posted entry
from the first through it (B-34). The 1099 worksheet counts the payments
of as_of's calendar year from January 1 through as_of: information
returns add up what is paid "in any calendar year" (26 U.S.C. §6041(a);
"during such calendar year", §6041A(a)(2)), so a worksheet as of June 30
is the year to date, and a vendor not yet at the threshold is not yet
reportable; a December 31 package counts the whole year, as before. The
chart of accounts and documents/ are as they stand when the package is
made. One list of cash accounts and one of financing accounts serve the
whole package (the request's, else the settings':
`ledger_package::Settings::with_accounts`): the cash flow, the K-1
worksheet, the IIF's bank accounts and the 1099 payments read the same.
So the package's 1099 worksheet is `books.vendor_1099`'s CSV for the
same `cash_accounts`, `financing_accounts` and `through` = as_of, byte
for byte; the tool, given no accounts, uses the settings', as a package
given none does.
**Enforced:** `ledger_package::build_package` (INVARIANTS 1, 6, 7),
`ledger_package::Settings::with_accounts`, `ledger_package::export_iif`,
`ledger_package::vendor_1099_worksheet` and `ledger_core::vendor_1099_report`
(`through`), `books.vendor_1099` (`cash_accounts`, `financing_accounts`,
`through`).
**Checked:** `ledger-package` `tests/package.rs`
(`every_file_in_an_as_of_package_stops_at_as_of`: books the same through
as_of, with and without activity after it (a 1099 payment with its
receipt, an asset bought, a reversal of an entry dated as_of, the
members' contributions, an earlier asset sold), make the same package
byte for byte, every file and which files there are, with and without a
fixed asset register, mid-year and at year end; `as_of_package_iif_stops_at_as_of`,
`the_iif_through_as_of_at_its_edges`,
`the_1099_worksheet_stops_at_as_of`,
`one_list_of_cash_accounts_per_package`,
`payments_to_a_vendor_come_from_the_packages_accounts`; the property
`iif_ties_to_the_trial_balance_as_of`, 2,000 cases); `ledger-cli`
`oracle_mcp.rs` (`cpa_package_holds_the_year_and_its_evidence`: the
package's CSV is the tool's for the same accounts and `through`); the
bookspec examples `bookspec/examples/as-of-package-stops-at-as-of.toml`
and `bookspec/examples/1099-worksheet-through-as-of.toml` (`ledger-core`
`tests/bookspec.rs`).
## B-34 The QuickBooks Desktop file (IIF): plain ASCII, every entry through as_of, closes left out, classes carried
**Status:** AUTHORITY (Intuit, `sources/intuit-iif.md`: IIF files are
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
what uses them; DOCNUM is "the transaction's number"; the kit's 2007+
example file ends every line CR LF; QuickBooks Desktop holds an account
name of 31 characters, an account number of 7 and a memo of 4,095; "The
parent and subaccount must be the same account type"; a journal entry
to accounts receivable or payable needs a customer or vendor, and takes
one such account a transaction; a class list's keyword is CLASS, "Only
the NAME column is required", a subclass's NAME its parents' first,
joined by colons, and the kit declares its classes before the
transactions that name them, line by line in CLASS; Intuit's qbXML
Onscreen Reference, `ClassAdd`: a class's Name at most 31 characters in
QuickBooks Desktop, as an account's); CONVENTION (a
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
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
ASCII transliterated and listed; CR LF; DOCNUM the entry's id; closing
entries left out; every entry since the company began until the CPA
says otherwise); Unicode Standard Annex #14 (the mandatory line breaks)
1. **One transaction per posted entry dated on or before as_of**, from
the first, in the order posted, **except period-closing entries**:
QuickBooks keeps no closing entries (it works out retained earnings
itself; its "closing date" only locks a period), so an imported close
would zero its period's profit. The file's balances are the trial
balance as of as_of without the closes, and its income and expenses
each period's profit and loss; the members' shares are in the
package's members' equity statement and K-1 worksheet.
`books.export` iif takes as_of (default today), never a range.
Whether the file should hold only the fiscal year instead goes to the
CPA before the 2027 package (D-36); for the first year the two are
the same.
2. **DOCNUM is the entry's id** in the books, on the TRNS and every SPL,
so the CPA can trace a QuickBooks transaction to its entry and its
receipts.
3. **The file is ASCII and every line ends CR LF.** In a field (a NAME,
an ACCNUM, a MEMO) a tab or a mandatory line break (LF, VT, FF, CR,
NEL, U+2028, U+2029) is a space; any other control character is '?';
printable ASCII is itself, but a double quote, which is a single one
(QuickBooks reads a double quote as a text delimiter); any other
character is its transliteration by deunicode 1.6.0, pinned exactly
("René" "Rene", "北京" "Bei Jing", "—" "--", "“" "'", "😀" "grinning";
a transliterated word's trailing space dropped at the end or before a
space), or '?' when it has none. Every other file in the package keeps
the books' text (the PDFs carry Inter and Noto fallbacks for Chinese,
Japanese, Korean, Arabic, Hebrew, Thai and Devanagari, Arabic and
Hebrew set right to left; no emoji font, so an emoji prints as an
empty box).
4. **An account's own name**, so written, has each ':' replaced by '-'
(a fullwidth colon too), is trimmed ('?' if nothing is left) and cut
to 31 characters (QuickBooks Desktop's limit; cut by QuickBooks, the
ACCNT line and the transactions' would no longer match); its NAME is
its parents' NAMEs and its own, joined by ':'. A subaccount whose
QuickBooks type differs from its parent's (a bank account under a
header that is no bank, an asset's cost under a "Fixed assets" header)
is written at the top level, its own subaccounts with it: QuickBooks
gives a subaccount its parent's type, and the type decides where its
balance shows. Each parent is declared before its subaccounts, the
accounts otherwise in the order of their codes. Accounts whose NAMEs
would match ignoring case, which QuickBooks would merge, get
" (code)" after their own names (the name cut to make room, the code
written like a name, "no number" for a blank one), level by level from
the top and again until no two match; a match left after that (two
codes written alike, ignoring case, or alike in their first 30
characters) is refused.
5. **ACCNUM is the account's code**, written as a field and trimmed, or
left blank (ACCNUM is optional; the NAME identifies the account) when
it is longer than 7 characters (QuickBooks Desktop's limit) or another
account's would be written the same, ignoring case. **A MEMO** is cut
to 4,095 characters (QuickBooks Desktop's limit).
6. **Nothing changes silently.** Each account name, number or place and
each memo the file writes differently from the books (`Iif::changes`)
is said, both texts quoted and escaped, one line each, in the
`warnings` of `books.export` and `books.export_cpa_package` and in the
package README (the viewer's note counts them): each account's name,
number and place a line; the memos grouped by what is written
differently, each run of changed characters once with every entry it
is in (`"—" is written "--" in the memos of entries 3, 5, 9`), then
the memos cut. No line holds a full TIN (B-26): a run of nine digits,
in any script's digits and separators, is masked, and so is every
numeral of a text that holds one once written as a field (digits a
tab apart, letters deunicode writes as digits); a memo line whose
digits are masked says so.
7. **No customer or vendor NAME.** Intuit's IIF help makes NAME
optional and states no need of it in a journal; QuickBooks Desktop
asks for a customer or vendor on a journal entry to accounts
receivable or payable, and takes one A/R or A/P account a transaction
(Intuit staff and help, `sources/intuit-iif.md`). Writing names would
need the customer and vendor lists declared as well, with names unique
across them. So the file names none, and the package README names the
entries with a line on an A/R or A/P account (`Iif::receivable_payable`),
so the CPA can add the name if QuickBooks asks.
8. **Each line's class (project, B-37) is its CLASS**, and every class in
the books is declared in a `!CLASS` list (NAME) after the accounts and
before the transactions, as the kit declares its lists; books with no
class write no list. A class's NAME is written as an account's own
name is (rules 3 and 4: plain ASCII, a ':' a '-' since a colon starts
a subclass, trimmed, '?' if nothing is left, cut to 31 characters);
classes whose NAMEs would match ignoring case get " (id)" after their
names (the name cut to make room), round after round until none does,
as accounts get " (code)", except that among names that match, those
already written differently from the books (made safe, or tagged)
yield first, another " (id)" each time need be (CONVENTION: the CPA sees
the books' own names wherever they can stand): a class's name
written exactly as the books have it is kept unless another class's
own name is the same ignoring case (classes 1 "Café", 2 "Cafe" and 3
"Cafe (1)": "Cafe (1) (1)", the other two kept; review of the P4 follow-ups, finding 4,
2026-10-22: before, all three were renamed; and a fourth "Cafe (1) (1)"
makes class 1 "Cafe (1) (1) (1)", where a cap of two tags once renamed
class 4 instead). A tag that would change nothing (a name cut to 31
characters, ending in its own " (id)") isn't made: the name it meets
is tagged instead. Each class name written differently is
listed with rule 6's (`class 2's name "Café rebrand" is written "Cafe
rebrand"`), and the package README's "written differently" paragraph
says class names are held to 31 characters, no two alike. A line
whose class isn't declared is refused, never written classless. Before
2026-10-22 the file left CLASS empty on every line and the README
didn't say so (the P3 cutover review's open point 1).
**Enforced:** `ledger_import::iif` (`field`, `iif_accounts`,
`iif_classes`, `journal_to_iif`, `Iif::warnings`), `ledger_package::export_iif` and
`build_package`, `ledger-mcp` `books.export` and
`books.export_cpa_package`.
**Checked:** `ledger-import` `iif.rs` unit tests (every one of the
1,112,064 Unicode scalar values written as printable ASCII, alone and
beside a letter, and kept by a second pass; the transliteration table
pinned); `tests/spec_import.rs` (`iif_docnum_is_the_entry_id`,
`iif_writes_plain_ascii_and_lists_every_change`,
`iif_every_line_ends_cr_lf`, `iif_distinct_accounts_never_share_a_name`,
`iif_a_subaccount_of_another_type_stands_at_the_top_level`,
`iif_declares_each_parent_before_its_subaccounts`,
`iif_fits_quickbooks_desktop_limits`,
`iif_warnings_group_the_memos_by_what_is_written_differently`,
`iif_warnings_never_hold_a_tin`,
`iif_change_warnings_are_one_line_each`,
`iif_keeps_a_tin_readable_to_the_package_scan`,
`iif_writes_each_lines_class_and_declares_the_classes_first`,
`iif_class_names_fit_quickbooks_and_every_change_is_listed`,
`iif_class_names_written_as_the_books_have_them_are_kept`,
`iif_refuses_an_undeclared_or_unfit_class`, the properties
`iif_round_trips_random_books`,
`iif_names_never_clash_and_every_change_is_listed` and
`iif_class_names_never_clash`); `ledger-package`
`tests/package.rs` (`iif_leaves_out_period_closes`,
`the_readme_lists_what_the_iif_writes_differently`,
`the_readme_names_the_entries_on_receivables_and_payables`,
`the_package_iif_carries_each_lines_project`,
`the_bookspec_iif_example_matches_the_engine`, and B-33's); the bookspec
example `bookspec/examples/iif-quickbooks-journal-file.toml` (formats
appendix: the file line by line and its changes, derived by hand);
`ledger-cli` `oracle_mcp.rs`
(`iif_declares_accounts_by_role_and_balances_every_transaction`,
`iif_without_as_of_is_as_of_today_and_ties_to_the_trial_balance`),
`mcp_journeys.rs` (`mcp_import_export`,
`the_default_period_never_shortens_the_iif_or_the_package`).
## B-35 A party's part of an entry, and a party's totals
**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
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
is cash, cards, the members' financing and loans, and a liability credit
never counts as paying that liability's own creditor); AUTHORITY for what
a member's capital counts (Treas. Reg. §1.704-1(b)(2)(iv)(b), (c);
Form 1065 Schedule K-1 instructions, Item L:
`sources/irs-capital-accounts.md`) and for a promise not being payment
(Helvering v. Price, 309 U.S. 409 (1940):
`sources/helvering-v-price-1940.md`). Not ASC 230's cash (see item 8) and
not the books' accounting method (`books.accounting_basis`).
1. **Sign.** A party's part of an entry is signed as a line on the
party's own account would be (debit-positive, B-2): positive, the
company paid it or gave it value; negative, the company received from
it. A bill is negative (owed to the vendor), an invoice positive.
2. **Direction, cash, in kind.** Every part carries its direction (paid,
received, neither) and what of it moved as money (cash) and as property
(in kind); what is neither is a promise still to be settled (a bill, an
invoice, a fee on the party's own card, a distribution still owed) or
moves nothing with anyone (a close), and counts in no total. A part's
cash and in kind have its sign and together never exceed it; what of a
paid part is neither (a distribution partly still owed) is a promise,
counted when it is paid.
3. **What the shares split** (`shares::target`), in order:
1. A period's closing entry: its parties' own-account movement (the
members' equity, when only members are named), else its total
debits; neither (B-11, B-13: a close allocates income, it moves
nothing with anyone).
2. A reversal: its original's target, negated, paid and received
swapped, from the root original down (B-9: "a mistake is corrected
by an opposite entry"); never re-read from its own lines, because
`entry_flow` isn't odd when one liability pays down another (a
member paying the card from their own funds, reversed, read as a
second payment: P3 design §1.4).
3. Every party named has the member role in the directory and the
entry moves equity other than retained earnings (E): E, a
contribution received (E < 0) or a distribution paid (E > 0).
- Of a contribution, in kind is the property in it, at most |E|:
what asset accounts that are neither cash nor receivables took in
(the K-1 worksheet's test, k1 INVARIANT 3), so property
contributed subject to a loan is the member's net property
(§1.704-1(b)(2)(iv)(b)(2)). The rest is cash, as the K-1 counts
it: money, or what the member paid for the company (an expense
booked straight to capital, a debt to them converted,
§1.704-1(b)(2)(iv)(c)(1)).
- Of a distribution, cash is the money it paid (case 4's net, at
most E: §1.704-1(b)(2)(iv)(b)(4)) and in kind the property it
gave up, at most what is left, as the books carry it (the
statement's figure; (b)(5) counts its fair market value, which
stays with the CPA). The rest, credited to a liability that
isn't money for the entry (distributions payable, or what the
members advanced once they are named its creditors), is still
owed: it counts as nothing until it is paid (Helvering v. Price;
before the P3 review a distribution declared to distributions
payable counted when declared and again when paid, and a
distribution of money with depreciated property read the money
as property).
Read from journaled facts only (roles, the chart, creditors), so a
member leaving `[[company.members]]` never changes a posted entry;
the settings' members matter only to a derived split among several,
which the server journals as amounts when the entry posts.
4. Money moved (`parties::entry_flow` net, item 5's accounts less the
liabilities owed to a party named): the net, paid (+) or received
(−), all of it cash.
5. Otherwise its parties' own-account movement (payables, receivables,
a liability owed to a party named), else the total debits; neither.
The parts split the target as `shares` INVARIANTS 7-12 say: a
reversal's parts are exactly its original's negated, and a member's in
kind is shared as the K-1 worksheet shares property (in proportion,
leftover cents by largest remainder, ties to the member listed first).
4. **Totals: `basis` cash (the default) or all, always labelled.** Cash
counts each part's cash; all adds what moved in kind (Item L counts
money and property alike). Each part counts on its root original's
side, so a reversal nets what it reverses, and reversing the reversal
brings it back. A bill or invoice counts on neither basis (a promise is
not payment: Helvering v. Price); `billed`, `invoiced`, `owed` and
`receivable` report those. An entry that counts nothing is never
unresolved (its target is found before its split). For a member, over
the entries that name only members and move their capital, plus the
payments of distributions declared to them: basis all's received and
paid are the statement of members' equity's contributions and
distributions for the same dates, and basis cash's received is the
K-1 worksheet's cash contributions, when
- each such entry names exactly the members whose capital it moves
(derived from their capital lines, or amounts equal to them) and
moves every one of them the same way (capital moved from one member
to another moves nothing with the company: neither, though the
statement shows a distribution and a contribution);
- no distribution declared in the span is still unpaid at its end (the
statement counts it when declared, party totals when paid); and
- no reversal in the span undoes an entry outside it (the statement
then counts the reversal by its side; party totals net it on the
original's side).
Over any span at all, even one a reversal straddles, received less
paid still agrees with contributions less distributions, given the
first two conditions (the property
`member_totals_are_the_members_equity_statement` checks every span).
5. **Money** (P3 defect 24): the cash accounts (`[books] cash_accounts`;
every asset account but receivables when none are set), the cards
(`parties::card_accounts`, the one rule the reconciliation reminders
and the viewer read too: the liability accounts of the business-card
payment methods the books record, B-19; while they record none, the
liability accounts whose names say "card" but not "gift card"; never
the account of a member's card, a member's cash or check, or a
payment platform, which pay through the members' financing account or
their own balance), and `[books] financing_accounts` (what the members
advanced, loans). Every other liability (payables, the payroll withholdings, an
accrued expense, deferred revenue, gift cards the company sold) is a
promise: counted when it is settled, never also when it is booked
(before P3 an accrual through any liability but payables and payroll
counted twice). So, before any business card is recorded, a card
account whose name doesn't say "card" is not money until it is listed
in `financing_accounts` or a business-card method names it, and with
`financing_accounts` unset what the members
advanced is not money either: a purchase through either then counts
for no one until it is paid off (the handbook tells the agent to list
them).
6. **A party's own liabilities.** A party may be named the creditor of
the liabilities the company owes it itself (`creditor_of`: a card's
issuer, a loan's lender, each member for what the members advanced;
`party.creditor_set`, a journal feature). For an entry naming it, a
credit to one never counts as paying it (a fee on its card, interest on
its loan, equipment on the seller's note: neither, until paid; a
distribution credited to the members' financing: still owed), and a
debit to one is what the money paid for (a card payment, a
reimbursement: paid, as before).
7. **Known limits**, stated in the tools' text: an entry that both repays
a member and distributes to them counts the distribution only; money a
member lends under their own name through the members' financing and
later converts to capital counts both times (received as a loan, then
as a contribution), as does a distribution declared through it while
the members aren't named its creditors (paid when declared, and again
when paid); an entry naming a payee together with the creditor of the
liability that paid it (a vendor and the member who paid it through
the members' financing) reads neither for both; an expense a member
paid that is booked straight to capital and tagged with the vendor
reads neither (the vendor isn't counted as paid; tagged with the
member, it is the member's cash contribution). Record them apart, and
a member's payment for the company through the members' financing,
tagged with the payee: the house conventions already do.
8. **How party totals relate to the cash flow statement** (B-14, D-42,
P3 defect 1). The statement reports the company's cash, ASC 230's: a
member paying a vendor for the company is cash borrowed from the member
and spent, on that date (a financing inflow and an operating or
investing outflow); paying the member back is a financing outflow;
property a member contributes, and a debt to them converted to
capital, are noncash (disclosed); a distribution declared to a
distributions payable is a distribution on the statement when it is
paid (B-14 rule 2), listed under the payable ("Distributions to
members — 2200 Distributions Payable"), not by member: the statement
reads accounts, not parties, so the member a payment from the payable
went to is party totals' (the payment's own parties) and the
declaration's (the statement of members' equity, at its date). So over
a span, the statement's distributions (by member and under the payable)
are the statement of members' equity's less what is declared and still
unpaid at the span's end, and party totals' paid to the members on the
entries that move their capital or pay a declared distribution, exactly
(`the_members_figures_agree_on_every_date`). Party totals report what moved with
each party: the vendor is paid when the member pays it (the same date
and amount as the statement's outflow); the member is paid back when
the statement shows the financing outflow; and a member's capital
counts when it moves, a conversion as the cash contribution the K-1
calls it (the money they paid for the company), property in kind; a
distribution counts when it is paid, as on the statement. Where both
show a payment they agree on its amount and date. The statement shows
more than party totals do in two places: the financing inflow from a
member who pays a vendor is in no party's totals (the entry names the
vendor; the member's totals show only the repayment), and an expense
booked straight to capital and tagged with the vendor isn't counted
as paid to the vendor (item 7). "Cash" in party totals is money
through the company's cash accounts, cards and financing (and, on a
member's contribution, the K-1's cash), not the statement's ASC 230
cash.
9. **How party totals relate to the 1099 worksheet** (B-19; P3 defects
12, 18, 23; D-40). They answer different questions, and neither is
read for the other (`books.party` gives both, labelled). Party totals
report what moved with each party, whoever handled the money; the
1099 worksheet reports what the company, as the payor, paid a payee
toward its information return. A member paying a contractor for the
company through the members' financing account, the entry tagged
with the contractor: in party totals it is paid to the contractor on
that date, in money (the members' financing is money, item 5), and
nothing moves with the member until the company pays them back, which
is paid to the member. On the 1099 worksheet it is the company's
payment to the contractor (Treas. Reg. §1.6041-1(e)(1): the member only
writes the check at the company's direction): it counts toward the
contractor's 1099 when it was the member's own cash, check or
transfer, is excluded when it was a card, the member's own included
(§1.6041-1(a)(1)(iv): Form 1099-K), and is undetermined until its
payment method is recorded; the reimbursement pays no payee (money
between the company's own sources). A company card's charge is paid
in party totals and excluded on the 1099 alike. So the figures never
contradict: over a calendar year, for a payee every entry of which
names it alone, in books whose cash accounts are named, whose company
cards are recorded as business-card payment methods, and that pay only
out of cash accounts, cards and financing accounts, its party totals'
paid less received (basis cash) is exactly its 1099 worksheet's
`paid` + `excluded_card` + `undetermined` + `not_reportable`, less what
the worksheet lists as money received from it for something else
(a refund is netted on the worksheet and received in party totals).
The bookspec example `1099-and-party-totals-for-a-members-payment`
checks a year of it, and the property
`party_totals_are_the_1099s_buckets` checks it over random books. A
bill counts in neither until it is paid, and a payee's dated decision
changes only the 1099 worksheet.
**Enforced:** `kernel::party_target`, `kernel::reverse_target`,
`shares::{target, split, resolve}`, `parties::{flow, money_accounts,
card_accounts, MoneyAccounts::for_parties, activity_with, Activity::totals,
Basis}`, `Ledger::set_party_creditor`, `Store::set_party_creditor` (the
`party_creditors` journal feature, asked for before its first line);
`ledger-mcp` `books.get_entry` (`resolved`, `direction`, `cash`,
`in_kind`), `books.party` and `books.parties` (`basis`), `books.update_party`
(`creditor_of`); the viewer's party pages and entry page.
**Checked:** `ledger-core` `tests/spec_party_totals.rs` (the audit's
table, the members' equity and K-1 identities over every span, the
reversed card payment, accruals, a gift card, a party's own liabilities,
distributions declared or of depreciated property, the properties), Kani
`party_target_direction_is_its_sign`, `party_target_is_odd`,
`reverse_target_is_an_involution`, bridge `party_target_agrees`; Lean
`reverseTarget_involutive`, `capitalTarget_odd`, `partyTarget_odd`,
`sumOver_movement_reversal`; `ledger-store` `tests/party_creditors.rs`;
`ledger-cli` `tests/spec_party_totals.rs`; `ledger-gui` `tests/parties.rs`.
## B-36 The K-1 worksheet reports tax-basis capital beside book capital
**Status:** AUTHORITY (`sources/irs-partnerships.md`: Instructions for
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
basis method this year"); DECISION D-41 (the owner, 2026-10-20: "the
K-1 worksheet reports tax-basis capital ... the contributed property at
its §723 carryover basis, beside the book figures")
Schedule K-1 Item L reports each member's capital account on the tax
basis, by the transactional approach: beginning capital (last year's
ending), "the amount of cash plus the adjusted tax basis of all property
contributed", the member's share of income "as figured for tax
purposes", other increases and decreases "for tax purposes", and "the
amount of cash plus the adjusted tax basis of all property distributed".
The K-1 worksheet shows, per member, each Item L line on the book basis
(the members' equity roll-forward, B-13) beside the tax basis, and how
book capital reconciles to tax capital:
- Property contributed enters at its §723 basis (B-25): the member's
share of the property's book value less their share of its built-in
gain or loss at contribution, shared within the entry as its book value
is (B-13). To the cent, these shares (and those of property without a
basis, of a prepaid at book, and of property distributed) run
cumulatively, D-38's rule (the owner, 2026-10-22): each member's
running share is the largest-remainder rounding of their exact running
share (ties to the member listed first), less what earlier entries
gave them, so it is never a cent from exact, and tied odd cents
alternate rather than all going to one member (three equal odd-cent
properties at 50/50 leave the members a cent apart, never three). How
the count runs differs from the closes': a close splits by the
members' shares among all the members, so its count starts afresh when
members or shares change; here each entry splits by its own amounts
(each member's property in it, or what each took) among the members
with a part in it. So there is one count for each set of members with
a part in an entry, and it never restarts: shares don't enter it, and
a member outside the set is never moved by it (Item L's capital
contributed is the basis of the property that member contributed, so
a member who contributed nothing in an entry takes none of its
cents). The count takes every posted entry from the books' first in
date order (ties in posting order), whatever span the worksheet is
for, and an entry's reversal takes the original's split back out of
the count, each member exactly their part: an entry's cents are the
same in every span it is in, and next year's beginning is this year's
ending to the cent. The book property each member contributed in an
entry (B-13's share of the entry's property among the members'
credits) stays split entry by entry, ties to the member listed first:
each member's property there must stay within their own credit in the
entry, which a running true-up could break; its odd cents arise only
in an entry part cash and part property whose credits don't divide
the property evenly.
Property contributed with no basis in the books (no
registered asset linked to the entry holds a contribution basis for it)
is open. Property contributed to a prepaid account (the balance
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
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).
- 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
kept on their own basis, and every book-tax difference (Schedule M-1)
is the CPA's; so is the ending, and the beginning of any year after
the first (Item L: "the partner's ending capital account as
determined for last year"). The line names the differences the books
do see: the tax depreciation of every fixed asset in service (MACRS,
and for contributed property the §704(c) method's allocation: the
CPA's, B-15), and the tax gain on a disposal, with a contributed
property's built-in gain or loss at contribution, which that gain must
take account of (IRC §704(c)(1)(A)). Also open: the adjusted tax basis
of property distributed, and the tax treatment of a change in book
capital the books can't explain. Each such line is left blank and
marked open, with what the books know so far and the book amount the
CPA's figure replaces.
The book depreciation the tax figure replaces is the period's
depreciation expense, the P&L's: each asset's runs and what its
disposal entry posts to its expense account (B-15 rule 8).
- The reconciliation: book ending less tax ending (as far as the books
know it) = beginning (book less tax) + the built-in gain or loss share
+ property without a basis (at book) − property distributed (at book),
exactly.
**Enforced:** `ledger_core::tax_capital::tax_basis_capital`;
`ledger-package` `k1_tax` (`k1-tax-basis-capital.csv` in the CPA
package); the odd cents, `ledger_core::tax_capital::RunningSplit` on
`kernel::apportion_cumulative`. **Checked:** `ledger-core`
`tests/tax_capital.rs` (`tied_odd_cents_of_property_basis_run_cumulatively`,
`a_member_outside_an_entry_never_takes_its_cents`,
`an_entrys_odd_cent_is_the_same_in_every_span`,
`the_split_runs_in_date_order`, the law
`a_running_split_never_drifts_a_cent`), the unit tests in
`src/tax_capital.rs`, `bookspec/examples/partnership-k1-tax-basis-odd-cents.toml`;
`ledger-package` `tests/package.rs`
(`the_k1_worksheet_reports_tax_basis_capital`), `tests/p3_seams.rs`
(`depreciation_is_one_figure_everywhere`,
`the_cpa_package_as_of_each_date_is_the_tools_figures`).
## 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
a class as a project ... Projects are entities too and can have name,
description, whatever other metadata you think they should have"; the
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
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
management accounting, not GAAP); AUTHORITY (on
spec, item 4: ASC 450-30-25-1; ASC 340-40-25-5 with B-22; directly
related costs, item 8: ASC 340-40-25-7;
`sources/fasb-asc.md`, both quoted through Deloitte's DART roadmaps and
not yet re-read in the ASC basic view: an open item)
1. **A project is a class.** Same id and name; every class is one (a
class an earlier version wrote has the default facts: active, kind not
recorded). Its facts, the few a job cost sheet carries: a
description (its scope of work), a status (`active`, `paused`,
`finished`, `abandoned`), a kind (`internal`: the company's own
product; `client`: work for a customer; `on_spec`: work done hoping to
be paid; `other`; or not recorded), a party (who it is for or with),
the dates it started and ended (an end only when finished or
abandoned, never before the start; informational: no figure filters by
them), up to eight links (web addresses, never opened) and notes. Names
are one line, unique ignoring capitals (in any script) and surrounding
space, and never digits only (a project is named by id or name, so such
a name would read as an id); no field holds a full TIN (B-26). A
change that keeps a project's own name never checks the name again, so
old books (replay never checks names) holding a duplicate can still
change either project, and a fact-only change never renames. Journaled as `class.added`, then the whole
record as `project.set` when there is more than a name (journal feature
`projects`, B-27). Replay checks only what it can't do without (the
class and the party exist), never the text rules, so a rule tightened
later never breaks books already kept. No budget, estimate, contract
value or percent complete: each is a document linked to the project, a
revenue-recognition question the books don't face, or the CPA's.
2. **Links.** A line is tagged with a project (its class); a receipt or
document is linked to a project (attachment subject `project`); a
decision names the projects it is about. Whether to link is the
company's convention; how is this rule.
3. **Figures (job costing).** For a period: the revenue and expense lines
tagged to it, dated in the period, closing entries left out (B-11),
each on its normal side; net is revenue less expenses. To date: every
such line through the day, from the first day the books keep, whatever
the project's dates. A tagged balance-sheet line counts nothing (an
asset bought for it counts as it is depreciated or expensed, tagged
then). The books allocate no cost: a shared cost counts toward
projects only as allocation entries of their own, made by the
company's allocation convention (item 8). The projects with the
untagged lines
partition the P&L. By month, the period's last 240 months at most.
4. **On spec.** Work done hoping to be paid: its costs are expenses when
incurred (B-22; ASC 340-40-25-5 makes a cost of an anticipated
contract an asset only if, among its criteria, it is expected to be
recovered, which on spec it isn't). The hoped-for payment is a gain
contingency: no receivable and no income until received (ASC
450-30-25-1). A repayment is revenue tagged to the project (a
reimbursement credited to the expense it repays, tagged, comes to the
same). Its unrecovered cost, "if repaid", is expenses less revenue to
date, never below zero: a figure, never an asset.
5. **Status.** Finished and abandoned projects still take lines (a late
cost, a final invoice); `books.add_line`, `books.record_payout`,
`books.create_invoice` and `books.create_bill` say so when one is
tagged.
6. Omitted dates mean what B-30 says (`books.project`'s period); to date
is always stated as such, beside it.
7. **Snapshot (a presentation choice, not GAAP).** A project's net is
shown as its *contribution before overhead*: its revenue less its
direct costs (every expense line tagged to it), with no company
overhead allocated, since item 3 allocates none. The owner's label is
"contribution"; what it means is revenue less the costs traceable to
the project, fixed and variable alike (the books don't split costs by
behavior), before the common costs no project causes. That is
Garrison, Noreen and Brewer's *segment margin* (*Managerial
Accounting*, segment reporting: a segment's sales less its variable
expenses and its traceable fixed expenses, the defining source here),
who warn against allocating common costs to judge a segment. It is
not Horngren's *contribution margin* (revenue less variable costs
only; Horngren, Datar and Rajan, *Cost Accounting: A Managerial
Emphasis*), which the books can't compute and the viewer doesn't
claim; "contribution" here is only the everyday sense, what the
project contributes toward overhead. So a project in the black can
still leave the company in the red. Its margin ("contribution
margin" on the page, `margin_pct` in the tools) is that contribution
as a percent of revenue, one place, half to even, and none without
revenue (zero or less). "In the black" is a contribution above zero,
"in the red" below, "break-even" lines tagged that net to zero (a
cost and its reversal). The running net by month is the net to date
through each month's end; its last is the net to date. No GAAP
standard defines or requires these figures (ASC 280's segment
reporting is for public entities' operating segments, not projects);
they come from item 3's, one function the tools and the viewer share.
8. **Invoices and bills (the owner's decision, 2026-10-22, D-43).** Each
line of an invoice or bill names one project or none. A document that
spans N projects is N lines, each with its own amount; there is no
percentage split, so every figure is exact, as entered, with nothing
to round. A cost truly shared among projects is not split on the
document: its line names none, and it reaches the projects, if at
all, only as allocation entries of their own, on the stated basis of
the company's monthly allocation convention when `books.conventions`
has one; without one the line stays untagged and the agent asks the
human. The reasoning: job costing (item 3) charges a job the costs
traceable to it, its direct costs, whole, and reaches a shared
(indirect) cost only by an allocation on a stated basis, never by
guessing at the document. ASC 340-40-25-7 counts as relating
directly to a contract (25-5(a)) both the costs traceable to it
(direct labor and materials, costs explicitly chargeable to the
customer, costs incurred only because of the contract, such as
subcontractors: 25-7(a), (b), (d), (e)) and allocations of costs that
relate directly to the contract or its activities (25-7(c)); 25-8
sends general and administrative costs to expense unless the contract
makes them chargeable to the customer (`sources/fasb-asc.md`). The
books carry the first kind whole on the document's line and the
second as entries under the allocation convention on its basis;
neither is a percentage on the document, and 25-7(c) is what the
allocation convention models. An invoice line naming an on-spec
project (item 4) is taken, and the tools ask whether there is now an
agreement: on spec, a hoped-for payment is no receivable until it is
received. Its revenue (an
invoice's) or cost (a bill's) then counts toward the project exactly
as a tagged line of a hand-built entry does: the document's entry
posts one revenue (expense) line per project its lines name, in the
order each is first named, each the sum of that project's lines, and
the untagged lines' sum as one line (`arap::document_parts`), so a
document naming none posts exactly the one line every document posted
before. The receivable (payable) line is never tagged: it is a
balance-sheet line (item 3 counts it nothing), and so are the
payments (`books.receive_payment`, `books.pay_bill`: cash against the
receivable or payable), which take no project. Nothing else moves: the
P&L, the 1099 worksheet (B-19: payments made) and a party's totals
(B-35: what moved the money) read the same with or without the
projects; only the projects' figures and the P&L by class differ. A
line naming a finished or abandoned project is taken, and the tools
say so (item 5); one naming a project the books don't have is refused,
the document whole. Journaled as the line's `class_id` in
`invoice.created` or `bill.created` and an `entry.line_class_set` right
after its revenue or expense line's `entry.line_added`, behind the
journal feature `document_line_projects` (B-27).
**Enforced:** `ledger_core::classes::{ProjectFacts, ProjectStatus,
ProjectKind}`, `Ledger::{check_set_project, set_project,
replay_set_project, projects_of_party}`, `reports::{project_figures,
project_to_date, project_entries, contribution_margin_pct}`,
`ProjectToDate::{net, margin_pct, any_tagged}`, `ProjectFigures::{months_net_to_date,
period_margin_pct, period_lines}`, `kernel::unrecovered_cost`;
`arap::document_parts`, `Ledger::{create_invoice, create_bill}` (item
8); `Store::{add_project, set_project, subject_feature}` (feature
`projects`), `Store::{create_invoice, create_bill}` (feature
`document_line_projects`); `books.add_project`, `books.update_project`,
`books.projects`, `books.project`, `books.create_invoice` and
`books.create_bill` (`class` on a line); the viewer's Projects pages and
an invoice's or bill's page (each line's project).
**Checked:** `ledger-core` `tests/spec_projects.rs` and
`tests/spec_document_projects.rs`, `ledger-store` `tests/projects.rs` and
`tests/document_projects.rs`, `ledger-cli` `tests/spec_projects.rs`,
`ledger-gui` `tests/projects.rs`, `ledger-cli` `tests/projects_parity.rs`
and `tests/format_upgrade.rs` (`books_p2_served_cut_over_whole`); bookspec
`projects-net-is-its-tagged-lines`,
`projects-on-spec-costs-are-expenses` and
`projects-invoice-and-bill-lines-count-by-project`; proptest
`prop_document_parts_partition_the_total`; Kani `unrecovered_cost_laws`;
proptests `prop_project_nets_partition_the_pnl`,
`prop_project_set_round_trips`, `prop_project_contribution_is_the_net_to_date`
(core) and `prop_snapshot_contribution_is_the_net_to_date` (viewer).
## 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
stronger invoice support. Like make it a first class citizen"; hand
entries to receivables and payables refused, the owner's answer 5);
CONVENTION (a control account agrees with its subledger, as B-23; the
subledger tie-out is the first thing a CPA checks); sources cited and not
yet vendored, so this rule keeps the CONVENTION label (the Open list in
`README.md`): ASC 310-10 (receivables), ASC 405-20-40-1 (a liability is
removed only when paid or released), ASC 606-10-45-4 (a receivable is a
right to consideration that is unconditional).
1. **Tags.** Every ledger line on the receivables or payables control
account carries the document it belongs to (an invoice or a bill) and
what it does to it: its origin, a payment (later: a discount, a
write-off, an adoption, a role move). The tag lives beside the line,
like its cleared flag; the line and its hash never change (B-8). The
document commands write it in the step that posts the line; replay
derives it from the document events already journaled (an invoice's
or bill's own entry: its one line on the role's account as the role
stood at that point of the journal; a payment: its first untagged
control line for the payment's amount, so several payment events
naming one entry tag its lines in order), each on the side its
movement posts (an origin on the owed side, a payment on the other, so
an older payment deposited into receivables itself pays with its
credit); a reversal's mirror line carries its original's.
2. **Figures.** What a document owes as of a day is the signed sum of
the lines tagged with it dated on or before that day (a debit to
receivables counts plus for an invoice, a credit to payables for a
bill); what it was paid is minus the same sum over its payment lines.
Its status: Void when its own entry doesn't stand on that day (B-9;
an entry stands while an even number of the reversals after it in its
chain are dated by then, counted in chain order: a reversal counts
only once what it reverses does), else Unattached when its own entry's
control line carries no tag (an older entry replay couldn't match),
else Overpaid when it owes less than nothing (a credit balance, owed
back: never a receivable or payable, ASC 310-10), else Written off,
Paid, Partly paid or Open by what it owes. Overdue is a flag (owed past
the due date), not a status. No figure is stored: a reversal, a void
or a bounced payment is right by construction, and the aging as of a
day (B-18) shows what was owed then; it leaves out a document owing
less than nothing, which the control check lists with the unattached
ones for attention.
3. **The control guard.** A control account is one the receivables or
payables role is assigned to, or one holding a line of one of the
role's documents (the role's default code once a document posted
there; an account the role moved off). A line on one that carries no
document is refused, when added and when posted, whatever writes it
(a hand entry, a template, a recurring entry); a recurring template
naming one is refused when registered. The one exception is the
mirror of an untagged line in a reversal that takes the line off the
books (the hand line itself, or a re-instatement of it, reversed):
reversing an old hand line is how it is cleaned up, and reversing that
cleanup, which would put it back, is refused. The role never moves
onto an account holding an untagged line (B-23). Replay never refuses
(B-28). A chart's own account at a default code (1200 Equipment, a
card at 2000) takes hand lines until a document posts there; lines it
held before are listed as unattached from then on.
4. **Bounds.** At every day, 0 ≤ owed ≤ total, for every document
written under these rules. A payment can't exceed what is owed on any
day from its date on; a reversal that would break the bound where it
held (a void under a payment that stands; re-instating a payment
beside its replacement) is refused, naming what to reverse first;
reversing a payment is always taken. Books an earlier version left out
of bounds take any step that makes no day worse, so the remedy is
accepted (a step may carry a day across the range, overpaid to
over-owed, so long as it lands nearer the range). A reversal of a
document's entry is never dated before that entry.
5. **Payments.** The deposit or payer account is a postable asset or
liability that holds no role: a bank, a card, a member's payable. A
control account (item 3) holds its role; another role is held by its
assigned account, or by the account at its code of its type.
The payment method and the processor's reference go on the payment's
entry in the same step (B-19, D-40).
6. **Voids.** Voiding a document reverses its own entry, described
"Void invoice 7: reason"; it keeps its id (and number), and reversing
the void re-instates it (B-10).
7. **Journal.** A payment accepted only because an earlier payment on
its document was reversed needs the journal feature
`document_standing` (B-27): an older version, which adds every
payment event to the document's paid amount (its line tagged or not),
would show it paid above its total. Everything else is journaled as before, and a void is the
`entry.reversed` it is.
```text
GOAL: invoices and bills are the subledgers of the receivables and
payables control accounts; every figure about a document is read
from the ledger lines tagged with it.
INVARIANTS:
1. For every control account A and day D:
GL(A, D) = Σ_docs outstanding(d, D) + unattached(A, D).
No command adds an untagged control line; unattached changes only by
reversing (or, later, adopting) a legacy line.
2. 0 ≤ outstanding(d, D) ≤ total(d) at every D for every document
written under these rules; replay accepts what was written.
3. Reversing a tagged entry changes outstanding by exactly the opposite
of its tagged lines, from the reversal's day; reversing that
reversal restores it.
4. A document's state is a function of (the journal, the day).
5. Aging as of D partitions Σ outstanding(·, D) over the documents
owing something; a credit balance (only in books an earlier version
left out of bounds) is left out and listed by the control check.
6. Every refusal leaves the books and the journal unchanged; NEVER panics.
SCENARIOS:
1. Invoice 100, payment 100, payment reversed: Open, owing 100, A/R 100.
2. Invoice reversed with nothing paid: Void, not in aging, id kept.
3. Void with a payment standing: SHOULD refuse, naming the payment.
4. As of a day between a payment and its reversal: counted as paid.
5. Wrong payment reversed, right one recorded: accepted (feature
document_standing declared first).
6. Hand Dr 1200 / Cr 4000: SHOULD refuse with the document tools
named; reversing a legacy hand line on 1200: accepted, unattached
shrinks.
7. (S7) Half paid, rest written off: Written off, 50 paid / 50 written off.
8. receive_payment into 1200 or 4000: SHOULD refuse.
```
**Enforced:** `ledger_core::arap::{DocRef, DocTag, Movement, DocState,
DocStatus, doc_status, PaymentDetails, ControlCheck}`,
`kernel::{running_bounds, bounds_kept}`, `Ledger::{line_doc, doc_lines,
doc_state, invoice_state, bill_state, stands_on, in_effect,
control_role_of, control_check, payments_recorded, standing_payments,
receive_payment_with, pay_bill_with, void_document, check_void_document,
check_reversal, replay_reverse_entry, ar_aging, ap_aging}` (the guard in
`add_line`, `add_line_fx`, `post_entry` and `register_recurring`);
`Store::{receive_payment_with, pay_bill_with, void_document}` (feature
`document_standing`); `Ledger::check_assign_role` (B-23); the projection
(schema 15) keeps no document figure; the viewer reads what a document
owes and whether it is late from `doc_state` (as of today, or of its own
date when it is dated ahead).
**Checked:** `ledger-core` `tests/spec_documents.rs` (a test per
scenario and finding of its reviews; proptests
`subledger_equals_control`, `aging_partitions_what_documents_owe`,
`reversal_twice_restores_state`); `ledger-store`
`tests/arap_standing.rs` (the fixture an earlier version wrote, its
golden figures adjudicated; the feature gate; replay equals live),
`tests/arap_props.rs` (proptest `status_is_a_function_of_journal_and_date`:
live, replayed and snapshot books agree at every date, closed months
included), the store fuzz test's voids and payments with details;
Kani `bounds_check_matches_definition`, `bounds_kept_is_the_definition_on_books_in_bounds`;
Lean `Documents.control_agrees`, `Documents.Step.document_unattached`,
`Documents.Step.reverse_moves`, `Documents.Reachable.unattached_zero`
(through role assignments and releases),
`Documents.Reachable.control_is_documents`. The model's limits: it
starts from empty books (legacy lines are covered by the identity
`control_agrees` alone), it names documents by one id space (an injective
coding of `DocRef`), and an account becoming a control account by its
first document, holding lines from before, is outside it (those lines are
listed as unattached).