refuse hand entries (P4b S1, part A)
Plan P4b (D-45), slice S1, the engine half: rules.md B-38.
- ledger-core: every receivables or payables line carries its document
(Ledger.line_docs: DocRef, Movement, DocTag), written by the document
commands in the step that posts it, derived on replay from the existing
document events (the role's account as it stood then; payments by the
ordinal rule), copied onto a reversal's mirror. Ledger::stands_on;
doc_state / invoice_state / bill_state (total, paid, outstanding,
status Open/Partial/Paid/WrittenOff/Void, overdue days) as of any day;
ar_aging / ap_aging read them, so reversed payments and voids are right.
The control guard (add_line, add_line_fx, post_entry,
register_recurring) refuses untagged lines on receivables/payables,
exempting a reversal's mirror of a legacy line; replay never refuses.
Bounds (kernel::running_bounds, bounds_kept) on payments and reversals;
a document reversal never dated before its entry; deposit/payer accounts
must be cash-like and hold no role. New API: receive_payment_with /
pay_bill_with (PaymentDetails: instrument, reference), void_document /
check_void_document, control_check, payments_recorded,
standing_payments, replay_reverse_entry. Invoice.amount_paid/status
remain only as derived caches for the MCP layer until part B.
cashflow bill_splits reads payments from the tags.
- ledger-store: journal feature document_standing (D-39 gated) for a
payment accepted only because an earlier one was reversed;
receive_payment_with / pay_bill_with (instrument journaled as
entry.instrument_set, reference in the description), void_document;
replay uses replay_reverse_entry; schema 15 drops the projection's
amount_paid/status. Fixture p4-arap-before + golden figures written by
the version before S1, adjudicated in arap_standing.rs.
- ledger-gui: party card, cash "coming up" and company owed totals read
doc_state as of today (late from the derived state).
- Lean Documents.lean (control_agrees and friends); Kani
bounds_check_matches_definition,
bounds_kept_is_the_definition_on_books_in_bounds;
rules B-38, B-18/B-23 amended; bookspec `older = true` for legacy hand
lines; tests that hand-post to 1200/2000 post as replay or renumbered.
Review fixes (both reviews of part A):
- control_role_of guards only an account the role is assigned to or one
holding its documents' lines (a chart's own 1200 Equipment or card at
2000 is free until a document posts there); payment accounts check
other roles by holds_role (type-checked). The MCP tests that failed on
the guard (oracle_mcp, spec_projects) pass unchanged.
- standing_payments leaves out reversals (sorted, deduped); replayed
tags match the movement's side; payments_recorded counts every payment
event (tagged or not, Ledger.payment_events) and refuses overflow.
- The record caches settled (paid + discounted + written off) and owed
(Invoice/Bill.owed, never serialized): a void never reads as paid.
- assign_role: receivables/payables refuse moving onto an account with
untagged lines (RoleAccountHasHandLines) and, on their control account
holding a balance, say DocumentRoleHoldsBalance (no hand entry named);
B-23 reworded. The reversal-mirror exemption only takes a hand line
off (even chain depth); putting it back is refused.
- DocStatus Overpaid and Unattached; aging leaves out credit balances;
ControlCheck.attention lists out-of-bounds and unattached documents.
- stands_on counts a chain in order; indexes (doc_index, doc_accounts,
order_pos, reversed_by) keep the derived figures linear.
- Viewer reads a post-dated document as of its own date; one state per
row. bounds_kept's side-crossing documented as intended.
- Tests: ten new spec_documents scenarios, stronger proptests, store
proptest arap_props (live = replay = snapshot at every date, closed
months), fuzz ops void/payments with details, a GUI post-dated test;
Lean Step.assign / Step.release with unattached_zero kept; model
limits stated in docs/testing.md and B-38.
Review fixes (CPA and engineering lenses): only an assigned role's
account or one holding document lines is a control account (a chart's
own 1200 asset stays free); a role can't move onto untagged lines or off
document lines (Lean Step.assign); a void's cache never reads as paid;
no Decimal sum panics; per-document indexes keep figures linear; a
replayed tag matches its side; the feature gate counts payment events;
a payment's reversal is never listed as standing; an overpaid document
has its own status; the viewer keeps post-dated documents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
A distribution declared to a payable and then paid is financing when paid,
the declaration disclosed (ASC 230-10-45-15(a)); a disposal's depreciation
lines count as depreciation in the cash flow and the tax-basis K-1; an
as-of asset list says the status of that day. Seam tests tie the members'
statement, K-1, party totals, cash flow, 1099s, depreciation and the CPA
package together on three dates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Book value is cost less what the books posted; the schedule's figure is
apart. An asset's acquired date comes from its entry. Contributed
property keeps its section 723 carryover basis apart from its depreciable
basis (the lower of value and adjusted basis), with its 704(c) built-in
gain and method. The K-1 worksheet reports tax-basis capital (Form 1065
Item L) beside the book figures, leaving open what only the CPA knows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
Direct method with the operating reconciliation and noncash schedules; it
foots by construction (Lean and Kani). Every non-cash account belongs to
one section; reversals net; a member or lender paying for the company is
cash borrowed and spent (D-42, constructive receipt and disbursement);
property a member contributes stays noncash. The bookspec seed: one loader
checks every example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
ledger-config: parse gets a sourced spec. A fiscal year starts on the
first of a month (IRS Pub. 538); tax_classification is read by whole
words (Treas. Reg. 301.7701-3, Schedule C instructions; "business corp"
no longer reads as an S corp, "corporation or S corporation" is refused);
duplicate or blank members and shared capital accounts are refused; a
negative or non-grammatical receipt threshold is refused; codes trimmed.
tests/spec_config.rs (65 spec-blind tests, 14 properties), fuzz target
fuzz_config_parse (weekly). Mutation: 55 of 55 viable caught (from 25/38).
The receipt policy moves to ledger-config, with one loader
(effective_receipt_policy) and one decimal grammar, so the server and
the viewer enforce and show the same rule (the viewer had its own: only
expense-like debits, a silent $25 default, and no config.json).
ledger-mcp: a module spec (MCP 2025-06-18 tools, JSON-RPC 2.0, the
operating contract). Amounts in are exactly -?digits[.digits]; a number
that is one account's code and another's id (vendors, classes alike) is
refused, not guessed; the threshold is validated at startup. Fixed:
add_line with a bad class journaled the line before refusing;
profit_and_loss_by_class ignored `class`; aging buckets said "0". A wire
in front of rmcp answers malformed JSON with -32700 and invalid requests
with -32600 and their id (rmcp dropped the first, omitted the id).
tests/spec_mcp.rs (26 spec-blind tests: every tool's result against its
own outputSchema, a random-call state machine checking the journal is
untouched by every refusal, amount grammar, previews, the receipt policy).
ledger-gui: money, accounting, and dollars never round (they rounded to
cents, banker's style); export file names avoid Windows device names,
trailing dots and spaces, empty names, and 255-byte overflow; specs on
attention, history, fuzzy search, and axis ticks.
ledger-core: the lifetime profit_and_loss is renamed unclosed_earnings
(docs/testing.md #3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
New concurrency test: one thread writes entries, drafts and receipts as
fast as it can while a Snapshot (the viewer) refreshes in a loop. At
every refresh the snapshot's books must balance, never go backward, and
never see the writer's database as differing from the journal; at the
end it must equal the writer in full. It failed 30 runs in 50:
check_disk read the applied position and the tables in separate
statements, so a write landing between them made a consistent database
look like it "differs", which the viewer shows as a problem in Needs
attention.
- check_disk reads in one read transaction (one consistent view).
- A database that has applied lines the viewer hasn't read yet is now
DiskCheck::Ahead ("viewer catching up"), not Behind.
- Without the transaction the test fails 14 runs in 20; with it, 50/50.
Also: members' equity spec 3b states the exact bound (a member's period
income is within two cents of their share, one cent of rounding at each
end; the running balance chains exactly), and the spec-blind property
checks that bound. docs/testing.md gains the concurrency layer and item
12: each close into members' capital still rounds on its own (pending).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
A capital account is one running balance (Form 1065 Schedule K-1
instructions, Item L: beginning and ending capital "based on the
partnership's books and records"), but the statement split prior
earnings and the period's income separately, so a member's ending on one
statement could differ by a cent from their beginning on the next.
Now each member's share of undistributed earnings is allocated as of a
date (largest remainder, as before): beginning as of the day before the
period, ending as of its last day. A member's period income is the
roll-forward's remainder (their share within a rounding cent); incomes
still sum to net income. New property
each_members_capital_chains_across_statements (random books, odd-cent
shares, closes to members or not, any split date) fails on the old code
and passes now. The hand-computed test is recomputed: the old split gave
two cents off an even 50/50 to date.
Also: rules.md B-13 quotes Item L; docs/testing.md #7 and #8 marked
changed; the CPA-question list is replaced by the rule that wanderbooks
settles questions from the sources and makes real choices per-company
settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
From ASC 230 (via Deloitte DART and PwC Viewpoint), replacing two
formerly assumed behaviors:
- Financing and investing are gross (230-10-45-7): per account, cash in
and cash out are separate lines (contributions apart from
distributions, borrowing apart from repayment), netted only within
one entry. Lines come in code order, in before out.
- Investing includes making and collecting loans and buying investments
(230-10-20, 45-13), not only registered fixed assets:
CashFlowParams gains investing_accounts.
- The roles are per-company settings: [books] financing_accounts and
investing_accounts in wanderbooks.toml (beside cash_accounts). The MCP
tools (cash_flow, export, CPA package) default every list to them and
share one path; the viewer reads them too, falling back to its name
guess for debt. A due-to-owners liability listed as financing gives
PwC 6.9.3.1's constructive treatment of expenses an owner paid.
rules.md B-14 quotes the sources; new tests cite them; docs/testing.md
#8 is resolved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Each formerly ASSUMED behavior is now CONFIRMED, CONVENTION, DESIGN, or
CHANGE (pending), with its source (Reg. S-X, ASC 230 via Deloitte DART,
IRS K-1 instructions, OpenStax). Two need engine work: members' equity
should allocate cumulatively so each member's beginning equals the prior
ending (#7), and the cash flow should show financing gross and classify
investing by account role (#8). Two questions for the CPA are listed.
The test doc comments that relied on them now cite the verdicts.
Core mutation run: 1,011 caught + 13 timeouts of 1,024 viable, 0 missed.
No benchmark budget, by decision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
Max-testing pass, steps 2-4.
Spec-blind tests (rank 1-2): four agents, each given only the specs and
the public interface (doc comments and signatures, every body removed,
never src/ or the existing tests), wrote tests/spec_reports.rs (62),
spec_ledger.rs (72), spec_arap.rs (42), spec_assets_payroll.rs (50):
hand-computed examples per scenario and a proptest law per invariant
(conservation, partition, invariance, scaling, reversal cancels,
"a refusal leaves the books unchanged" over random invalid commands).
They found:
1. A refused payroll run changed the books: an all-zero run passed
validation, then failed at post after creating seven payroll
accounts and using up an entry id. validate_payroll_lines now
refuses a run with no gross pay.
2. A refused add_account used up an account id (empty code, duplicate,
unknown parent). The id is now taken only when the account is added.
3. Stale prose: close_period_to and members_equity still described the
old last-member rounding.
4. Open (left #[ignore]d pending the owner): depreciation_schedule's doc
says "through the month containing" the date; the code prorates that
month by days.
GnuCash oracle (rank 3, an oracle never the spec): `just oracle` builds
GnuCash 5.10 in a container pinned by base digest and package versions
(oracle/gnucash/) and runs tests/oracle_gnucash.rs: 200 seeded random
books built through wanderbooks (entries, foreign lines, reversals,
monthly closes), every account's balance at month ends and random dates
and each month's P&L, compared exactly: 15,800 comparisons. First run:
4,724 differences, all from foreign lines; without them, zero.
Adjudicated from principles (rules.md B-3): wanderbooks booked
foreign * rate unrounded ($108.34567); a ledger in one money holds no
fractions of its smallest unit, and its cash must match a bank. The owner
decided: book in the base currency's minor unit, half away from zero.
add_line_fx now rounds; minor units per ISO 4217 list one (2026-09-17,
SIX), generated into Currency::minor_units and recorded in
docs/spec/sources/iso-4217.md. Replay now takes the journaled base
amount (replay_add_line_fx), so lines booked before replay exactly as
booked (contract: books_written_under_older_rules_still_open). Now:
zero differences, foreign lines included.
Tests: 449, from 223.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX