P3: odd cents split the same running way everywhere (defect 17, D-38)
check / check (push) Canceled after 0s

Members' shares of earnings are split cumulatively by largest remainder in
the closes, the members' equity statement, the K-1 worksheet and the
viewer, so a member's capital on a given day never moves by a cent when its
month is later closed. Old closes replay as they were; the first P3 close
carries any true-up. Feature members_cumulative. Kani and Lean proofs of
the bounded drift; bookspec examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-20 21:35:00 -06:00
1 parent 2390dc6553
commit a7f3cc09a9
44 files changed
+6636 -195

No files matched your search

+34 -9
View File
@@ -1134,7 +1134,10 @@ posting.
financing_accounts?, investing_accounts? }` → `{ content, content_encoding? }`.
- Reports: `profit_and_loss`, `balance_sheet`, `cash_flow` (needs
`cash_accounts`), `trial_balance`, `members_equity` (statement of
changes in members' equity; members from `wanderbooks.toml`),
changes in members' equity; members from `wanderbooks.toml`; earnings
not yet closed are split the way the next members close will split
them, so a member's capital on a day never changes when its month is
closed: rules.md B-13, D-38),
`general_ledger` (by account: beginning balance, postings with running
balance and split, ending balance), `general_journal` (alias
`journal`: every entry, line by line, in date order).
@@ -1258,15 +1261,37 @@ R-FUTURE-3 and rides the engine roadmap, not the MCP layer.
- `books.create_period { name, start, end }` → `{ period_id }`.
Dates are YYYY-MM-DD, inclusive.
- `books.close_period { period_id, confirmed? }` → without confirmation:
`{ status: "preview", lines: [...] }`: the exact closing entry (the close
run on a copy of the ledger), posting nothing; with `confirmed: true`:
posts it and marks the period closed. The entry zeroes revenue and
expense balances as of the period end; the net goes to `3999` Retained
Earnings, or, with `[[company.members]]` in `wanderbooks.toml`, to each
member's capital account by share. Periods close in order. Closed periods
reject new entries.
`{ status: "preview", period_id, closing_entry: null, lines: [...],
members }`: the exact closing entry (the close run on a copy of the
ledger), posting nothing; with `confirmed: true`: `{ status: "closed",
period_id, closing_entry, lines, members }`, posting it and marking the
period closed. The entry zeroes revenue and expense balances as of the
period end; the net goes to `3999` Retained Earnings, or, with
`[[company.members]]` in the settings, to each member's capital account
by share, cumulatively (rules.md B-13; D-10): each close gives a member
their share of everything closed to these members and shares so far, to
the currency's minor unit (odd units by largest remainder, ties to the
member listed first when the count began), less what earlier closes gave
them, so no member is ever a minor unit from their exact share. A close
may carry a one-time true-up of rounding by closes made under an earlier
rule, which can run against the period's result by a cent. `members`
(null for a close not to the members) is `{ since_close, members: [{
name, capital_account, share, this_close, running, exact }] }`: the
close the count starts at (the first members close with these members
and shares; 1 is the books' first close of all), and per member, in the
settings' order, their capital account's code, share ("0.50"), what this
close credits them (negative: debits), what the count's closes have
credited them in all, and their exact share of everything the count
closed (unrounded, e.g. "0.005"). The preview's `members` is exactly what
confirming posts. Periods close in order. Closed periods reject new
entries.
- `books.list_periods {}` → `{ periods: [{ id, name, start, end, status,
closing_entry }] }`.
closing_entry, close_rule }] }`. `close_rule` says which rounding rule a
period closed to the members was closed under:
`"members_cumulative_since:N"` (the rule since P3, counted from close N),
`"members_run"` (2026-10-13 to P3), `"members_per_close"` (2026-10-13) or
`"members_last_absorbs"` (before 2026-10-13); null for an open period or
one closed to anything else.
## Tools — bank reconciliation
+12 -6
View File
@@ -63,9 +63,11 @@ requirement conflict, the requirement wins.
(loss), distributions (debits), ending — plus a total that equals
balance-sheet equity. Earnings not yet distributed (retained earnings and
unclosed income) and the period's income are allocated by ownership share
to the cent by largest remainder (the odd cent goes to the largest
remainder, ties to the earlier member; the owner, 2026-10-13), so every
row's total is exact. Any equity change the roll-forward doesn't explain is shown as
to the currency's minor unit by largest remainder (the odd cent goes to
the largest remainder, ties to the earlier member; the owner, 2026-10-13),
earnings not yet closed the way their close will split them (D-38,
rules.md B-13), so every row's total is exact and a member's capital on a
day never changes when its month is closed. Any equity change the roll-forward doesn't explain is shown as
"other", never hidden.
- **R-9d — Tax categories.** Revenue and expense accounts map to stable
tax categories (`ledger_core::tax::CATEGORIES`: key, label, income /
@@ -127,9 +129,13 @@ requirement conflict, the requirement wins.
end** (later-dated entries belong to later periods) and puts the net in
`to`: `CloseTo::RetainedEarnings` (`3999`, the corporate convention;
`close_period` is shorthand) or `CloseTo::Members` (each member's capital
account by share, to the cent, odd cents by largest remainder: the
partnership convention; closes journaled before 2026-10-13 replay with
the rule they were made under, the last member absorbing rounding), or `CloseTo::Account` (any equity account, e.g.
account by share, to the currency's minor unit, odd units by largest
remainder, cumulatively from the first members close with these members
and shares, so no member is ever a unit from their exact share: the
partnership convention, rules.md B-13; closes journaled under earlier
rules replay with the rule they were made under: `CloseTo::MembersRun`,
the run rule of 2026-10-13 to P3, `CloseTo::MembersPerClose`, and
before 2026-10-13 the last member absorbing rounding), or `CloseTo::Account` (any equity account, e.g.
a sole proprietor's capital). Every target can sweep owner-pay equity
accounts — draws, distributions, dividends — into it: a member's
distributions account into their own capital. The members' equity
+136 -30
View File
@@ -206,34 +206,109 @@ closed period).
## B-13 Partners share income by the partnership agreement; capital accounts carry it
**Status:** AUTHORITY (shares); DECISION (cent rounding)
**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."
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 is the book capital analysis, which the CPA converts for
Item L.
- **No authority** says how to round a share to the cent. Largest
remainder, ties to the earlier member: DECISION (the owner, 2026-10-13).
Closes made before then replay with the last member absorbing it.
- Rounding never piles up: a run of members closes (same members and
shares, back to back) splits cumulatively, each close giving a member
their allocation of the run's total so far less their allocation before
it, so each member's total from the run is within a cent of their exact
share (follows from the running capital account below; found
2026-10-13: closing each month on its own gave every tied cent to the
earlier member). Journaled as `"rounding": "cumulative"`; closes
journaled `"largest_remainder"` replay one at a time.
- A capital account is one running balance: Form 1065 Schedule K-1
instructions, Item L: "The partnership must report your beginning
capital account and ending capital account for the year", "based on the
partnership's books and records." So a statement of members' equity
splits undistributed earnings cumulatively, as of each date (beginning
as of the day before the period, ending as of its last day), never one
period at a time: a member's ending on one statement is their beginning
on the next, to the cent, and a member's period income is the
roll-forward's remainder (their share within a rounding cent).
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
@@ -247,15 +322,39 @@ closed period).
members credited in one entry in proportion to their credits
(`k1` INVARIANTS 3, 4, 6).
**Enforced:** `CloseTo::Members`, `periods::allocate`,
`kernel::apportion`, `members_equity`. **Checked:** Kani
`apportion_is_exact`, `apportion_is_fair`; Lean `runPart_telescopes`,
`closePart_total`; properties `prop_split_*`,
`each_members_capital_chains_across_statements`,
`each_member_stays_within_a_cent_of_their_share`;
`a_run_of_closes_never_piles_up_rounding`,
`members_closes_replay_under_the_rule_they_were_journaled_with`;
`closes_journaled_under_the_old_rule_replay_with_it`.
**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 declaration), 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 flow by the indirect method
@@ -716,6 +815,13 @@ 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 feature (`ledger_store::format::KNOWN_FEATURES`) is a field or value on
an event type an older version knows but would misread; the writer declares
it with the format in force (at least 2) just before the first line that
needs it, so books that never use it stay readable by versions before it:
`members_cumulative` (P3, B-13: a members close counted from its count's
first close, `"rounding": "cumulative_since"`).
**Enforced:** `ledger_store::format` (`JournalFormat::admit`, `declared`,
`gate`, `settle`, `declaration`), `journal::ENVELOPE_KEYS`,
`JournalLine::parse`, `journal::read_journal_and_format`,
+45 -1
View File
@@ -1,5 +1,5 @@
# Partnerships: shares of income and capital accounts
Governs `CloseTo::Members` and the members' equity statement. Partners' shares come from the partnership agreement (IRC §704(a)); Form 1065 reports each partner's capital account (Schedule K-1 item L) and the changes in them (Schedule M-2). No authority prescribes how to round a share to the cent: that rule is the owner's decision (largest remainder, 2026-10-13).
Governs `CloseTo::Members` and the members' equity statement. Partners' shares come from the partnership agreement (IRC §704(a)); a partner's capital account is one running balance of what they contributed, their allocations of income and loss, and what was distributed to them (Treas. Reg. §1.704-1(b)(2)(iv)(b)); when a partner's interest changes during the year, the distributive shares take the varying interests into account, period by period (IRC §706(d)(1), Treas. Reg. §1.706-4(a)); Form 1065 reports each partner's capital account on the tax basis (Schedule K-1 item L) and the changes in them (Schedule M-2). No authority prescribes how to round a share to the cent: that rule is the owner's decision (largest remainder, 2026-10-13), counted from the company's first close (D-10, 2026-10-16).
## 26 U.S.C. §704(a)-(b)
Source: <https://www.law.cornell.edu/uscode/text/26/704>
@@ -24,3 +24,47 @@ SHA-256 of the retrieved file (`i1065.txt`): `364ec05f6a688447def50169604ec0aa97
**Rounding on the return (not in the books)**
> The partnership may enter decimal points and cents when preparing its return. However, it should round off cents to whole dollars on its return, forms, and schedules to make completing its return easier. The partnership must either round off all amounts on the return to whole dollars, or use cents for all amounts. To round, drop amounts under 50 cents and increase amounts from 50 to 99 cents to the next dollar. For example, $8.40 rounds to $8 and $8.50 rounds to $9. 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.
## Treas. Reg. §1.704-1(b)(2)(iv)(b): what a capital account carries
Source: <https://www.ecfr.gov/api/renderer/v1/content/enhanced/current/title-26?part=1&section=1.704-1>
Retrieved: 2026-10-20
SHA-256 of the retrieved file (`ecfr17041.html`): `0eafa6f27d7df3a3cc93ebad7cbdc0c37a49b055c53a43d5e5e8efa29f80beeb`
> (b) Basic rules. Except as otherwise provided in this paragraph (b)(2)(iv), the partners' capital accounts will be considered to be determined and maintained in accordance with the rules of this paragraph (b)(2)(iv) if, and only if, each partner's capital account is increased by (1) the amount of money contributed by him to the partnership, (2) the fair market value of property contributed by him to the partnership (net of liabilities that the partnership is considered to assume or take subject to), and (3) allocations to him of partnership income and gain (or items thereof), including income and gain exempt from tax and income and gain described in paragraph (b)(2)(iv)(g) of this section, but excluding income and gain described in paragraph (b)(4)(i) of this section; and is decreased by (4) the amount of money distributed to him by the partnership, (5) the fair market value of property distributed to him by the partnership (net of liabilities that such partner is considered to assume or take subject to), (6) allocations to him of expenditures of the partnership described in section 705 (a)(2)(B), and (7) allocations of partnership loss and deduction (or item thereof), including loss and deduction described in paragraph (b)(2)(iv)(g) of this section, but excluding items described in (6) above and loss or deduction described in paragraphs (b)(4)(i) or (b)(4)(iii) of this section; and is otherwise adjusted in accordance with the additional rules set forth in this paragraph (b)(2)(iv). For purposes of this paragraph, a partner who has more than one interest in a partnership shall have a single capital account that reflects all such interests, regardless of the class of interests owned by such partner (e.g., general or limited) and regardless of the time or manner in which such interests were acquired.
## 26 U.S.C. §706(d)(1): a change in a partner's interest
Source: <https://www.law.cornell.edu/uscode/text/26/706>
Retrieved: 2026-10-20
SHA-256 of the retrieved file (`usc706.html`): `d716b72177fb0f8e7ae0f5b98faf63d6e40c444f7e9368443060042b761fd53c`
> (d) Determination of distributive share when partner’s interest changes (1) In general Except as provided in paragraphs (2) and (3), if during any taxable year of the partnership there is a change in any partner’s interest in the partnership, each partner’s distributive share of any item of income, gain, loss, deduction, or credit of the partnership for such taxable year shall be determined by the use of any method prescribed by the Secretary by regulations which takes into account the varying interests of the partners in the partnership during such taxable year.
## Treas. Reg. §1.706-4(a): varying interests, segment by segment
Source: <https://www.ecfr.gov/api/renderer/v1/content/enhanced/current/title-26?part=1&section=1.706-4>
Retrieved: 2026-10-20
SHA-256 of the retrieved file (`ecfr17064.html`): `e7b07463190db6a610dee37660efefdd73be81b21efd81517dc714dcd72c4277`
**§1.706-4(a)(1)**
> (1) Variations subject to this section. Except as provided in paragraph (a)(2) of this section, this section provides rules for determining the partners' distributive shares of partnership items when a partner's interest in a partnership varies during the taxable year as a result of the disposition of a partial or entire interest in a partnership as described in § 1.706-1(c)(2) and (3), or with respect to a partner whose interest in a partnership is reduced as described in § 1.706-1(c)(3), including by the entry of a new partner (collectively, a “variation”).
**§1.706-4(a)(2), its last sentence**
> In all cases, all partnership items for each taxable year must be allocated among the partners, and no partnership items may be duplicated, regardless of the particular provision of section 706 (or other Code section) which applies, and regardless of the method or convention adopted by the partnership.
**§1.706-4(a)(3)(vii), its first three sentences**
> (vii) Seventh, apportion the partnership's items for the year among its segments. The partnership shall determine the items of income, gain, loss, deduction, and credit of the partnership for each segment. In general, a partnership shall treat each segment as though the segment were a separate distributive share period.
## Partner's Instructions for Schedule K-1 (Form 1065) (2025)
Source: <https://www.irs.gov/instructions/i1065sk1>
Retrieved: 2026-10-20
SHA-256 of the retrieved file (`i1065sk1.html`): `d7d6298286601fd95ab8e445c10fd62499ea1e37f8e5c3531f8e1783862d8d03`
**Item L, its first paragraph** (the partner's capital account, on the tax basis; the K-1 worksheet carries the book capital account, which the CPA converts)
> The partnership must report your beginning capital account and ending capital account for the year using the tax-basis method, including the amount of capital you contributed to the partnership during the year, your share of the partnership’s current-year net income or loss as computed for tax purposes, any withdrawals and distributions made to you by the partnership, and any other increases or decreases to your capital account determined in a manner generally consistent with figuring the partner’s adjusted tax basis in its partnership interest (without regard to partnership liabilities), taking into account the rules and principles of sections 705, 722, 733, and 742. See the Instructions for Form 1065 for more details.
**Basis Limitations, its third paragraph of five** (where "based on the partnership's books and records" comes from: not Item L)
> Although the partnership provides an analysis of the partner’s capital account in item L of Schedule K-1, that information is based on the partnership’s books and records and can’t be used to figure the partner’s adjusted basis.
+107 -2
View File
@@ -595,6 +595,111 @@ scratch (`ledger-store` `backup.rs` and `root.rs`, `ledger-core`
`admin.rs`, `ledger-host` `commands.rs`, `ledger-cli`). The survivor lists
are kept with the run's output, outside the repository.
## P3: odd cents (2026-10-20)
Defect 17 (rules.md B-13; D-10, D-38), built without its mutation run
(D-33); what P12 must cover is listed at the end. Every expected figure is
worked by hand in the test's comment.
**The named regression.** `the_audit_odd_cent_never_piles_up_across_closes`
(`ledger-core` `tests/odd_cent.rs`): audit F52's −399.23 at 50/50,
anonymized, with a close to retained earnings between two members closes.
The run rule left A −399.24 / B −399.22; the rule since P3 gives −399.23
each (March: Dr 3000 199.61, Dr 3001 199.62). Its bookspec example is
`bookspec/examples/members-close.odd-cent-across-closes.toml`.
**Core** (`ledger-core` `tests/odd_cent.rs`): a true-up from inception
(`members_closes_true_up_from_inception`); a close that posts nothing
(`a_close_that_posts_nothing_carries_no_true_up`) and one that posts only a
sweep (`a_true_up_rides_on_a_distribution_sweep`, P3 review R-3); a reorder
(`reordering_members_keeps_counting_from_the_first_close`); a share change
and a return to the old shares (`a_share_change_starts_a_new_count`); a
member joining mid-year (`a_member_joining_mid_year_starts_a_new_count`,
R-8); a journaled start other than the derived one
(`a_journaled_start_other_than_the_derived_one_is_refused`); whole yen and
thousandths of a dinar (`a_yen_book_closes_to_members_in_whole_yen`,
`a_dinar_book_closes_to_members_in_thousandths`); the statement on a date
before and after its period closes
(`a_members_capital_on_a_date_does_not_change_when_its_period_closes`); a
pending true-up that shows only once posted
(`a_pending_true_up_shows_only_once_posted`, R-1); three members at 33.33%
/ 33.33% / 33.34% over twelve months, the statement checked against the
same entries never closed on every day of the year and the K-1 worksheet
at every month end (`three_members_at_a_third_each_over_twelve_months`).
Added by the P3 review: a gold book splits to the finest unit a line
carries, not cents (`a_gold_book_splits_to_the_finest_unit_a_line_carries`);
the K-1 worksheet shares contributed property in whole yen
(`a_yen_books_k1_shares_property_in_whole_yen`, and `share_property`'s unit
test in `k1.rs`); a reorder of the members in the settings moves no cent on
a day before the count's first close, nor in "other"
(`a_reorder_moves_no_cent_before_the_counts_first_close`). Each fails on
the code before the review's fix.
Properties (512 cases each): `members_capital_never_drifts_a_cent_from_their_share`
(a legacy prefix under the three earlier rules, then months closed to the
members in either order, to retained earnings or not at all; a close that
posts nothing changes no capital, R-3),
`each_members_capital_chains_and_holds_through_closes`,
`period_ends_rise_with_close_order` (R-10.6). In `statements.rs`:
`a_different_close_ends_a_legacy_run` (the run rule, still replayed) and
`a_different_close_no_longer_restarts_the_count`, the run rule's arm of
`a_run_of_closes_never_piles_up_rounding`, and February in
`a_partnership_closes_into_members_capital` (Bob 683.33 → 683.34: his share
of February's unclosed income is what its close will give, D-38). Unit
tests in `kernel.rs` (`apportion_cumulative_refuses_mismatched_or_out_of_range_inputs`,
`apportion_cumulative_trues_up_a_legacy_split`) and `periods.rs`
(`allocate_cumulative_*`: whole yen, a dinar, a loss, a 28-place share
refused, R-5). Bridges (`tests/kernel_bridge.rs`):
`apportion_cumulative_agrees`,
`allocate_cumulative_agrees_with_the_integer_model`.
**Store** (`ledger-store`): `members_closes_replay_under_the_rule_they_were_journaled_with`
(each rule; with a close to retained earnings between, the rule since P3
ends 0.01 / 0.01 and the run rule 0.02 / 0.00); `tests/member_closes.rs`:
`the_first_cumulative_close_declares_its_feature`,
`an_unknown_rounding_is_newer_not_corrupt` (R-9),
`a_count_that_starts_later_journals_its_start` (a share change: since_seq
2 journaled and replayed; added by the P3 review),
`a_close_journaled_with_another_start_refuses_to_open`,
`the_first_close_after_the_upgrade_trues_up_the_old_rules`, on the new
old-books fixture `x_member_closes` (written by main 00226a4c's Store API:
three untagged closes, three `"largest_remainder"`, `"cumulative"` broken by
a close to retained earnings; it opens to main's figures in
`books_an_earlier_version_wrote_open_to_its_figures`, R-2); `format.rs`
`an_older_reader_stops_at_the_members_cumulative_feature`.
**MCP and viewer**: `ledger-mcp` `close_period_shows_each_members_running_split`
(`tests/mcp_stdio.rs`: the preview's `members` is the confirmed one; after
a restart, `list_periods`' `close_rule` and the statement of members'
equity); `old_books_name_each_closes_rule_and_true_up_at_the_next_close`
(the old-books fixture `x_member_closes` through the server: every
`close_rule` an earlier rule gives, and December's true-up, the preview the
confirmed close); `ledger-gui` `a_members_page_shows_the_same_capital_before_and_after_a_close`
and `a_yen_members_page_splits_in_whole_yen` (what is owed to each member
and their property, in whole yen) (`tests/odd_cent.rs`).
**Proofs**: docs/verification.md (the three P3 laws).
**For P12** (nothing run here, D-33): `kernel::apportion_cumulative` (the
length and range guards; `checked_sub` for `checked_add`; the subtraction
skipped); `periods::allocate_cumulative` (the sign; `given` not negated for
a loss; `places` ignored; the exactness filter, zero products included);
`Ledger::members_regime` and `regime_where` (skip versus break at a close
to anything else; the order-free compare made ordered; the date filter
`<=` for `<`; `given` added to the wrong member; the count's order; the
next count's order as of a day before it begins);
`Regime::allocate` (the order); `Ledger::members_split` and
`close_period_to_steps` (the start check; the `posts` gate; the recorded
rule; the sweeps before the parts); `member_close_run` (it reads only
run-rule records); `member_close_split`; `reports::members_equity`
(`allocate(C)` replaced by `given`, R-1; `places`; "other"'s order);
`k1::share_property` and the viewer's `owed_to_member` (`places`); the store's
`close_to_from_journal` and `close_period_to` (the tag mapping; `since_seq`'s
parse; a tag that isn't text; the feature declaration);
`format::declared_knowing`; the MCP's `close_period` and `list_periods`
fields; `Currency::allocation_places`. P3's accept counts `ledger-core` and
`ledger-import` (R-6); the store and MCP mutants above are for the whole
pass.
## Benchmarks (baseline 2026-10-13)
No budget, by decision (the owner, 2026-10-13: the books do little work
@@ -638,12 +743,12 @@ accounting question), CHANGE (a source says otherwise; work pending).
| 4 | Closing a period with nothing to close still locks it | CONVENTION | A close is a lock (B-12); a dormant month must still lock and count for close-in-order |
| 5 | Draws and distributions are closed into capital at period end | CONFIRMED | OpenStax 5.1: dividends "are closed to the permanent account"; drawing accounts close to owner's capital. Monthly closes mean the year's K-1 Item L withdrawals sum across months |
| 6 | Reconciliation balances | CONFIRMED | Signed by the account's normal side (a card reads as owed), as statements show it; `reconciling_a_card_reads_its_normal_side_...` |
| 7 | A member's ending on one equity statement may differ by a cent from the next statement's beginning | CHANGED 2026-10-13 | A capital account is one running balance (Form 1065 K-1 instructions, Item L). Undistributed earnings now split cumulatively as of each date, so each member's capital chains exactly (`each_members_capital_chains_across_statements`; it fails on the old code); rules.md B-13 |
| 7 | A member's ending on one equity statement may differ by a cent from the next statement's beginning | CHANGED 2026-10-13; CHANGED P3 | A capital account is one running balance (Treas. Reg. §1.704-1(b)(2)(iv)(b); the Item L quote this row once gave was misread, P3 review R-4). Undistributed earnings now split cumulatively as of each date, so each member's capital chains exactly (`each_members_capital_chains_across_statements`; it fails on the old code); since P3 the split is what the count's next close would give, so a date's capital never moves when its month is closed (D-38; `each_members_capital_chains_and_holds_through_closes`); rules.md B-13 |
| 8 | Cash flow nets one line per account; investing comes only from registered assets | CHANGED 2026-10-13 | ASC 230-10-45-7: gross receipts and payments "more relevant" than net; 45-8 limits netting to quick-turnover items. Financing and investing are now gross; loans made and investments bought are investing (230-10-20, 45-13) via `[books] investing_accounts`. Owner-advanced expenses: PwC 6.9.3.1 constructive treatment via `[books] financing_accounts` (rules.md B-14) |
| 9 | An asset's life ends at purchase + life months, that day not held; day-count proration | CONVENTION | Exactly N months of service; day proration and the mid-month convention are both accepted book policies (ASC 360-10-35-4, read 2026-10-14: any systematic and rational allocation) |
| 10 | A payroll entry has one line per account total | CONFIRMED | The per-employee detail the IRS requires ("Amounts and dates of all wage ... payments", Employment Tax Recordkeeping) is kept in the run; a summary entry is standard |
| 11 | A period with exactly the dates of an existing one is allowed | DESIGN | Nothing governs duplicate periods; harmless (the second close locks only). Refusing exact duplicates is an option |
| 12 | Each close into members' capital rounds on its own (ties to the earlier member) | CHANGED 2026-10-13 | Twelve monthly 50/50 closes could leave one member several cents ahead. A run of members closes now splits cumulatively (rules.md B-13; Lean `runPart_telescopes`; property `each_member_stays_within_a_cent_of_their_share` fails on the per-close rule); journaled `"cumulative"`, older closes replay as booked |
| 12 | Each close into members' capital rounds on its own (ties to the earlier member) | CHANGED 2026-10-13; CHANGED P3 | Twelve monthly 50/50 closes could leave one member several cents ahead. A run of members closes split cumulatively from 2026-10-13 (Lean `runPart_telescopes`; property `each_member_stays_within_a_cent_of_their_share` fails on the per-close rule), but any other close restarted the run. Since P3 a count of members closes runs from the company's first close (D-10), skipping closes to anything else and ignoring the members' order, so no member is ever a cent from their share (`the_audit_odd_cent_never_piles_up_across_closes` fails on the run rule; Kani `cumulative_close_never_drifts_a_unit_at_*`, Lean `allocW_cumulative_drift`); journaled `"cumulative_since"`, older closes replay as booked (rules.md B-13) |
Questions are settled from the sources, not by any one company's CPA
(wanderbooks is general software). Where the standards leave a real
+74 -1
View File
@@ -142,10 +142,58 @@ sequence of commands, of any length**:
of cumulative closes is their allocation of everything the run closed
(so its rounding is one allocation's, never a sum of them), and each
close hands out exactly its net, for any allocation whose parts sum to
the whole (rules.md B-13).
the whole (rules.md B-13). Since P3 this is the run rule closes
journaled `"cumulative"` replay under (`CloseTo::MembersRun`).
- Lean `runPart_telescopes`, `closePart_total` · property
`each_member_stays_within_a_cent_of_their_share`
The P3 odd cent (defect 17; rules.md B-13; D-10, D-38) adds three laws;
their numbers are assigned when P3 lands:
- **A cumulative members close hands out exactly its net, and leaves each
member's running total one allocation of everything the count closed**,
whatever rule the count's earlier closes were made under, as long as
they handed out what they closed. The run rule is the special case where
they gave exactly the allocation. A refusal leaves the parts as they
were.
- Kani `apportion_cumulative_is_exact`,
`cumulative_closing_entry_balances` (the closing entry nets to zero
whatever the true-up)
- Lean `trueUp_total`, `trueUp_running`, `closePart_eq_trueUp`,
`Apportion.allocW_sum`, `Apportion.allocW_cumulative_total`
- properties `apportion_cumulative_agrees`,
`allocate_cumulative_agrees_with_the_integer_model`
- **Bounded drift: no member's running total is ever a minor unit from
their exact share.** With shares w_i / W (exact decimals) and every
amount a whole number of minor units (B-3), after every cumulative
close |(given_i + part_i)·W − T·w_i| < W, whatever earlier closes gave.
The precondition is the code's: `periods::allocate_cumulative` refuses a
product Decimal would round, so the remainders it ranks are exact, and
they sum to whole units (the Lean hypothesis of `rem_pos_of_rank_lt`).
- Kani `cumulative_close_never_drifts_a_unit_at_halves`, `_at_tenths`
and `_at_near_thirds` (two members at 50/50, three at 20/30/50%, three
at 33.33/33.33/33.34%; every running total, net and earlier split to
2^12 units)
- Lean `Apportion.rem_pos_of_rank_lt` (a member given a unit had a
positive remainder), `Apportion.apportion_drift` (one apportionment
is within a unit), `Apportion.allocW_drift` (the model of
`allocate_cumulative`, gain or loss), `cumulative_drift` (any
allocation within a unit stays so under the true-up),
`Apportion.allocW_cumulative_drift` (the two together): any number of
members, any amounts
- property `members_capital_never_drifts_a_cent_from_their_share`
- **A member's capital on a day never moves when its period is closed.**
The statement of members' equity gives each member, of the earnings not
yet closed, what the count's next close would give them; closing moves
those earnings into capital without changing the sum. When the count's
earlier closes gave something else (closes under an earlier rule), the
figure moves by exactly the true-up the close posts, never before.
- Lean `statement_after_close`, `statement_unmoved_by_close`
- property `each_members_capital_chains_and_holds_through_closes`;
`three_members_at_a_third_each_over_twelve_months` (every day of a
year, closed and never closed), the viewer's
`a_members_page_shows_the_same_capital_before_and_after_a_close`
## What the proofs found
The engine already had 190+ tests, property tests, mutation testing,
@@ -244,6 +292,31 @@ TINs kept out of the books (B-26), and the agent text lint. Out of scope
for proof today: the format gate and the walk are I/O, and the model in
`verify/lean` stops at the ledger.
### P3: the odd cent (2026-10-20)
The cumulative members close (rules.md B-13) adds a kernel function,
`kernel::apportion_cumulative`, which Kani proves exact and refusal-safe
for up to three parts and checks against the integer model of
`periods::allocate_cumulative` for bounded drift; Lean proves the drift
bound and the true-up laws for any number of members and any amounts. Not
proved: the regime query (`Ledger::members_regime`: which closes count),
the Decimal exactness check in `allocate_cumulative`, and the journal's
`since_seq`. They are checked by the tests in `ledger-core`
`tests/odd_cent.rs`, `ledger-store` `tests/member_closes.rs` and the
old-books fixture `x_member_closes` (docs/testing.md, "P3: odd cents").
Kani times, each harness alone on a busy laptop (Kani 0.68, 2026-10-20):
`apportion_cumulative_is_exact` 257 s, `cumulative_close_never_drifts_a_unit_at_halves`
17 s, `_at_tenths` 320 s, `_at_near_thirds` 449 s,
`cumulative_closing_entry_balances` 202 s (`apportion_is_exact`, for
scale: 121 s). Two shapes were too slow and were changed, not weakened in
what they prove: restating apportion's postconditions on the cumulative
parts directly ran past ten minutes, so `apportion_cumulative_is_exact`
proves the parts are apportion's less `given` on the same inputs (the
solver shares the two runs' formulas) and `apportion_is_exact` supplies
the rest; and the drift bound with symbolic weights ran past ten minutes,
so it is checked at three fixed sets of shares (the brief's fallback),
Lean carrying any weights.
## Running it
```sh