d7fa16e97432722285d0a4be8b0f0fdf6df4b79d
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7fa16e974
|
feat(arap)!: documents read their figures from standing lines; control accounts
check / check (push) Canceled after 0s
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 |
||
|
|
321b94562a
|
Projects: classes as entities, and the sidebar fits short windows
check / check (push) Canceled after 0s
A project is a class with facts (description, status, kind, party, dates, links, notes) in one project.set event behind the format feature projects. Four tools (books.add_project, update_project, projects, project); documents and decisions link to projects; B-37: on-spec costs are expensed as incurred, repayment is revenue, and the unrecovered cost is never below zero (Kani). Viewer: a Projects list and a project page with its figures, by month and by account. The sidebar measures its footer instead of reserving a fixed 172 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
2f999c6050
|
P3: assets' posted and scheduled depreciation, their bases, and the K-1 on the tax basis (defect 13, D-41)
check / check (push) Canceled after 0s
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 |
||
|
|
b0a9fd8ad4
|
P3: depreciation straight-line by days through the final month, schedules by day (defects 22, 9, D-37)
The final month takes what the months before leave, spread over its days; a disposed asset stops on its disposal day. A skipped month is caught up in the month the run is made; a reversed run un-posts its month. New schedules spread by day; equal-months schedules must start on the 1st. Amounts round to the currency's own minor unit. Kani and Lean proofs; an exhaustive search over runs, reversals and disposals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
1304b9c0ad
|
P3: dated 1099 decisions, payment methods and the card exception (defects 23, 12, 18, D-40)
check / check (push) Canceled after 0s
A vendor's 1099 decision is its own dated fact (files NEC or MISC, not reportable with its reason, or undetermined), applied by each payment's date. Payment methods are recorded; card and network payments are left out and listed; a member's check through financing counts; a bank payment with no method counts (D-40); $2,000 for payments after 2025. One worksheet for the tool, the viewer and the CPA package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
9ab003d0d1
|
P3: rename accounts, one set of period defaults, tax categories warn or refuse (defects 15, 16, 8)
check / check (push) Canceled after 0s
books.rename_account; account names trimmed, bounded, refused when a sibling's or TIN-shaped; books.show refuses what its target ignores. A report asked for without dates covers this fiscal year so far, everywhere (one rule for tools, descriptions and the viewer; the IIF and CPA package keep their own dates). policy.require_tax_category warns or refuses; a Form 4797 gain is shown apart on every return. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
eb4329bb2a
|
P3: the cash flow statement by ASC 230 (defect 1, D-35, D-42)
check / check (push) Canceled after 0s
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 |
||
|
|
c587593d78 |
P1: the books keep how they are kept
Settings, conventions (posting templates, approval rules, policies, close steps, calendar, mappings, notes), decisions, open items and entry approvals are journal events (journal format 2). Books that keep their settings never read wanderbooks.toml or config.json; the server and the viewer take the vault key from --vault-key-file instead. books.import_settings moves a settings file in once, exactly as the server reads it. Approval rules, when enforced, gate every posting path. Replay refuses what it can't read as newer, never as corrupt. Lean: a record changes no figure. Mutation on the whole diff: every survivor killed by a test but two equivalent (docs/testing.md). The Company page says a late open item is overdue in words, not colour alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
8693d5a3f5 |
MCP: short connect text, a handbook, tool annotations, safe local imports
- The 13K-character instructions (clients cut them short) become a
connect text of at most 1,400 characters whose first 512 stand alone,
plus books.handbook and books://handbook/{section}: the old guidance by
section, with no helper commands, home paths, pull requests or
file-reading suggestions (tool_text_lint).
- Every tool has a title and read-only/destructive hints; no
requiresUserInteraction anywhere (owner decision 2026-10-16).
- books.import_local_file reads only regular files inside the import
roots (the downloads folder by default, or --import-root), never the
data dir, books dir, vault or the key's folder, no symlink escapes or
hard links, and the file opened must be the file checked.
- The store lock is a tokio mutex taken off the runtime threads.
- get_info refreshes settings first; a bare books://digest reads.
Review fixes (P0 review, 2026-10-16):
- Fail closed: a tokio mutex never poisons and spawn_blocking caught the
panic, so after a panic mid-mutation the server kept serving a Store
behind its journal. A panic (the task, or while holding the store)
now stops the shared store; every later request on every session is
refused with a plain message; a restart re-verifies the journal. The
join error goes through redact_refusal and never carries the panic's
text.
- books.set_w9 w9_file_path read any path (a link named w9.pdf to the
vault key was stored and get_w9 wrote it out): it now goes through the
import roots' lockdown, its type judged on the canonical path.
- Sealed even inside a root: the key file in force, where a linked one
leads, both folders, and the default key folder whatever the settings
say. Refusals check the roots first, so outside them every path gets
one message (no probing what exists); inside, other books-dir files say
to copy them out first.
- The open is O_NOFOLLOW | O_NONBLOCK (a pipe swapped in after the check
can't hang a thread) and must match the checked file's device and
inode. New dependency: libc (unix only), for those two open flags,
which std doesn't name.
- Default import roots are the downloads folder only (the plan); files
with full TINs (get_w9, vendor_1099 include_tins, the CPA package with
TINs) are never written into an import root, and the server refuses to
import any file it wrote with TINs (by SHA-256).
- set_tin and set_w9 overwrite vault records: destructive_hint = true;
annotations_complete pins the key tools' hints. The read-only hint is
checked in behaviour on every spec_mcp call (journal and data-dir files
unchanged).
- Text: the connect text names the settings file as the one local file
to edit, says every entry balances on real accounts and names its
party, says invoice/bill/payment/payroll tools post unchecked, rewords
the exemptions, and shows the company name as one plain line; the
handbook takes section "all", lists its ids in the schema and says to
read contract first; books.settings drops the config.json path and no
error names the data dir or the key file. tool_text_lint covers the
whole tool JSON and resources and bans git/commit/FL-0001; tool
descriptions over 700 characters are pinned to today's eight (P5).
- digest: an empty since= is bare; the default date is pinned on every
day of a month.
Round-2 review fixes (2026-10-16):
- TIN files (B-26): the in-memory, per-session set of TIN-file hashes
became one registry beside the vault (vault/tin-files.sha256, hashes
only), shared by every session and every later server. It holds the
W-9 set_w9 reads (recorded as read), get_w9 copies, 1099 worksheets
with TINs, CPA packages with TINs and each file in one that holds a
TIN; a file is recorded before it is written, and a failure refuses.
import_local_file, attach_receipt and file_document (base64 was never
checked) refuse a recorded file, and a text file (by type, name, or
UTF-8/UTF-16 decoding) holding a full TIN. Not caught: an altered
binary copy; the docs say so (P2/P7 remove the path tools).
- The text check is narrow (owner/engineer decision, 2026-10-16: bank
exports are filed for reconciliation, and has_full_tin's "nine digits
however separated" refused their routing numbers, long references and
dates run into amounts). Text is refused when it holds (a) a TIN the
vault holds for a party, anywhere, as nine digits, 3-2-4 joined by
hyphens, spaces or dots, or 2-7 joined by a hyphen or space, compared
on the server against the decrypted vault and never returned or
logged (a vault that can't be opened leaves (b)); or (b) a stand-alone
printed SSN (123-45-6789) or EIN (12-3456789) shape: no digit, and no
hyphen or dot followed by a digit, beside it. Full-width digits and
Unicode hyphens and spaces read as ASCII. has_full_tin still guards
refusals and results (redact_refusal). Known limit, for P7: a refusal
under (a) tells the agent a text it chose holds a vault TIN.
New dependency: zeroize in ledger-mcp (already in the workspace
through ledger-vault, same major version), so the vault's TINs decoded
for the scan are wiped from memory on drop as the vault's own are.
- Mutation testing of the final P0 diff (docs/testing.md): tests for
each law a survivor broke (handbook titles, a dropped default root, a
.jpg W-9, the waker, the walk's folder flags against a link and a
pipe, the 40-link limit, save paths refused as such, the package
marking only its TIN files, a registry that can't be read, text types,
the short receipt rule's counts and a 12-character code). Simpler
code where only a race could tell: inspect places an existing path by
canonicalize itself (no second comparison), read_checked's read limit
is its one size check, openat fails on -1 as specified. The 25 MiB
upload test removes its 50 MB of books however it ends.
- The GUI's stale-rule prompt no longer says to open a pull request: it
asks the agent to find the figure from the rule's official authority,
confirm it with the human, pass it explicitly, and tell the human so it
can be reported upstream (as the handbook says); a lib test pins it and
bans files, git, GitHub and pull requests from every attention prompt.
- Import roots (local_files.rs INVARIANT 0): XDG_DOWNLOAD_DIR="$HOME/"
made $HOME (or /) the root. No root may be /, the home folder or hold
it, or hold the books, data dir, vault or a key: a named one stops the
server at startup, a default one is dropped; the default falls back to
~/Downloads only if that is a real folder other than $HOME; with no
root, an import says so.
- The read is a component walk from / with openat(O_NOFOLLOW |
O_DIRECTORY) per folder and O_NOFOLLOW | O_NONBLOCK for the file, so a
folder swapped for a link after the check fails; no step may land on a
protected inode (one renamed into a root); dev/ino and nlink stay. No
/proc. Portable to macOS. (libc's openat, two small unsafe blocks.)
- One refusal for missing, dangling and outside paths: resolved()
follows every link, dangling ones too, so a link's target existing or
not no longer changes the answer.
- Sealing: the key files (in force, default) and their targets always;
their folders only when not $HOME, not a root, not holding a root; and
~/.config/wanderbooks whatever XDG_CONFIG_HOME says.
- Agent text (I-12): RuleNotKnown names the authority (IRS, with its
address) and says to tell the human, never a repo path, PR or GitHub;
the digest's stale_rules drop the repo catalog and path for it; vault,
outside-books, STOPPED and local-file refusals name no git, path or
flag. tool_text_lint bans docs/, .md, /home, data dir, --data-dir,
--import-root, vault.key, .jsonl, sqlite, .gitignore, github, pull
request, and also lints every error template (scanned from source),
built error texts, and the server's refusal constants.
- The receipt rule told (ReceiptPolicy::rule) is the one enforced: the
exact threshold in the base currency (no "$", no rounding), required
and exempt accounts, reversals exempt; the connect text fits 1,400
characters by dropping reasons, then codes, never the rule.
- The connect text follows the parties setting; "offer books.digest"
everywhere. shown_name drops Unicode format characters (bidi, zero
width).
- read_only_tools_change_nothing compares file contents, the database
by every row, the vault and the settings file (raw database bytes did
not change either; rows are the stable comparison).
- import_local_file takes the name and type from the canonical path;
the digest's journal repair says when the tail was set aside, not the
journal's file name; "version-history messages" back in the TIN rule;
openWorldHint false on every tool, pinned in annotations_complete.
- Outside the MCP crate, for the same texts: ledger-core's DatedRule
gains `authority` (the statute or publication with its address) and
RuleNotKnown carries it; ledger-store's "sqlite error" reads "database
error"; ledger-config's ReceiptPolicy::rule takes the base currency.
New dev-dependency: rusqlite in ledger-mcp (already in the workspace,
same version and features as ledger-store), to read the database's
rows in read_only_tools_change_nothing.
- The filing scan is no TIN oracle (tin_files.rs INVARIANT 4): text
holding more than two TIN-shaped numbers (any rendering rule (a)
reads) that end in a known TIN's last four (the books' TIN and W-9
facts, via known_tin_last4, before the vault is opened; then the
vault's own) is refused with TIN_PROBE, the same words whether or not
one is the vault's TIN; only one or two candidates are compared
against the vault. Tests: unit
text_probing_for_a_tin_is_refused_alike_whether_or_not_it_holds_it,
one_or_two_candidates_are_compared_against_the_vault; over HTTP
text_probing_for_a_tin_is_refused_alike_by_every_filing_tool.
Plan: plan/served.md P0, section 6 "Immediate hole (P0)", 4.1, 4.3, 4.9.
Review round 3. S1: a panic stops the server for every session, so no
input may reach one. Dates are four-digit years (new ledger_core::
calendar, 0001-01-01..9999-12-31): the MCP's parse_date is strict
YYYY-MM-DD, periods are bounded, last_day_of_month returns a refusal
(server and store); schedules, assets, recurring entries, periods,
documents, disposals and entries refuse dates outside the calendar, and
replay refuses such journal dates. Every amount a command takes is at
most a line's (draft lines by size, document lines, payouts, payroll,
statements, shares, tax basis, proceeds, recurring templates), on
replay too, so sums are exact; the 1099 vendor part and the K-1
property share use checked products (TooLargeToCompute); a recurring
run makes at most 1,000 drafts. tests/hostile_args.rs drives every tool
with schema-derived hostile arguments and every resource with generated
URIs, asserting the server never stops; the audit's panics are pinned.
S2: a `.` or `..` path name is refused as NO_SUCH_FILE up front. S3:
NUL-interleaved UTF-16 without a byte-order mark is scanned both ways.
S4: resource-read refusals are TIN-redacted. S5: set_w9 records its W-9
only once the call passes every check, just before the vault write.
S8: idempotentHint only on verify, run_depreciation, run_schedules,
run_recurring, set_party_roles (a repeat leaves journal and vault
alone; spec_vault checks every candidate twice); every tool's hints
pinned in one table. C1: books.settings lists receipt_required_accounts
and receipt_exempt_accounts uncut. C2: the default company name reads
"a new company". C3: the parties rule names no-party reasons. C4/C5:
the handbook names the settings file only as books.settings' books_toml,
and restores "check books.digest and offer it". C6: an import root
inside the books, data dir or vault is refused at startup. The lint
catches `just` however it ends and where the books are;
digest_resource_bare_reads reads the server's month.
Review round 4 (2026-10-17): round 3's bounds ran in replay too, so
journals main wrote and reopened (an entry dated 0000-06-01 or
+10000-01-01, a draft line of 2e15, an asset ending past 9999, a basis
or share past the line limit, sub-cent document and payroll lines) no
longer opened. Posted is forever: the bounds are the commands' only.
- Replay applies main's rule exactly: parse_date takes any date chrono
reads; replay_add_line(_fx) has no size bound (add_line/add_line_fx
do); replay_register_asset/correct_asset (chrono overflow only),
replay_set_asset_tax_basis (check_journaled), replay_set_entry_parties
(check_shares_journaled), replay_register_recurring
(validate_recurring_journaled), replay_register_schedule (months 1 to
600), replay_create_period (order only), replay_dispose_asset, and no
line, date, payment or payroll range checks in replay_invoice/bill,
their payments and replay_payroll_run.
- Derived figures over such books refuse (TooLargeToCompute,
DateOutOfRange), never panic: amortization plans, depreciation into
chrono's last month (and the true-up for lives past 9999), intervals,
document totals and aging, net pay and payroll plans, money flows and
share resolution, the asset register, receipt-policy debits; the MCP
tools and the viewer show what can't be computed.
- tests/fixtures/old-books: the review's 13 journals, its control and 15
more edges, written by main's Store API with main's figures
(expected.json); every one opens in the writer and the viewer to those
figures, every derived figure computes or refuses, every MCP tool and
GUI page answers; hostile_args restarts the server after each
property; each bound has a command/replay test pair.
Final review: a schedule's plan refuses a total of 10^26 or more
(TooLargeToCompute) instead of letting Decimal round its cents silently,
and a null `remaining` in the schedule output is always explained by
plan_error. Replay is unaffected: no journaled schedule month can belong
to a total that large (the line limit bounds every posted month).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
|
||
|
|
a299077991 |
Parties, attachment links, statements, reconciliations (store), TIN vault crate
Parties: one directory for vendors, contractors, customers, employees, members, platforms, agencies, banks, landlords (ledger-core parties.rs). W-9/W-4 facts without the number (last four only; any nine-digit run in a party field is refused, Treas. Reg. 301.6109-4). Vendor and customer records link to parties on replay; core tags every bill, invoice, payment, and reversal with its counterparty. Journal events party.*, entry.party_set; payroll lines name the employee party. MCP: books.add_party, update_party, set_party_roles, set_w9, set_w4, parties, party, set_entry_party; draft_entry party; run_payroll employee; show party. Viewer: Parties directory and party pages, search, journal text, W-9 attention, demo data. One definition of a party's money (ledger_core::parties::activity) for MCP and viewer. 1099 amounts count payments from cash and financing accounts (a member paying for the company counts; a card doesn't; a prepayment counts when made): vendor_1099_report_with, B-19. Store: attachment links to any subject, statements, reconciliations (schema v11), with replay and projection. ledger-vault: encrypted TIN and W-9 records (ChaCha20-Poly1305, key file outside the books). Spec-blind tests for parties (core, store, MCP, viewer) and attachments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
ba7373bdb8 |
Fixed assets carry federal tax basis beside book value; tests sweep stale temp dirs
- core: TaxBasisRule (cost IRC 1012, carryover IRC 723, personal-use conversion Treas. Reg. 1.168(i)-4(b)), TaxBasis with the contributor's adjusted basis, Asset::tax_basis and ::built_in_gain (IRC 704(c)); the register gains tax rule, contributor basis, tax basis, built-in gain, total tax basis, missing count (rules.md B-25; sources vendored in docs/spec/sources/irs-partnership-basis.md) - store: asset.tax_basis_set journaled, replayed, projected (schema v10) - mcp: books.set_asset_tax_basis; register_asset takes tax_basis_rule and contributor_basis, checked before anything registers (found by the spec-blind suite); list_assets and the register export (json, csv) carry tax basis; instructions say to record it for every asset - import: the register's XLSX and PDF gain the federal tax basis table with citations - viewer: tax basis on the Assets page, the asset page (a Tax basis card), the report, the journal, and Needs attention for assets without one - just: check and the mutation recipes sweep this user's test book dirs older than an hour (killed runs had left 23 GB in RAM-backed /tmp) - docs: the parties, attachments, TIN vault, completeness engine, and updates designs (docs/parties-and-attachments.md) - tests: spec-blind tax basis suites in core, store, MCP; register PDF/XLSX and viewer tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
cd085108c1 |
Fixed assets, fully featured: tags, serials, acquisition links, label sheets, the fixed asset register, and an Assets section in the viewer; fix a flaky verify
- core: asset tags (generated PREFIX-0001 or the human's own; unique ignoring case; never reused by another asset) and serial numbers (rules.md B-24); a journaled link from an asset to the entry that acquired it (posted, unreversed, debiting the cost account); fixed_asset_register(as_of), reconciled to the GL (unposted depreciation, unregistered cost) - store: asset.tagged, asset.serial_set, asset.acquisition_linked journaled, replayed, projected (schema v9); differs_from now also compares control roles and tag history - config: [books] asset_tag_prefix, default the company's initials without legal-form words - import: Avery 5160/8160 label sheets with QR codes (geometry vendored from Avery, docs/spec/sources/avery-5160.md); the register as XLSX and PDF - mcp: register_asset tags (and links) automatically; books.label_asset, books.asset_labels; asset tools take a tag; books.show asset / assets page / register report; export fixed_asset_register (json, csv, xlsx, pdf) and in the CPA package - viewer: Assets page (register, reconciliation), asset page (identity, schedule posted vs not, entries, documents), Fixed asset register report, global search by tag/serial/description, journal text, needs-attention items - fix: verify compared snapshot amounts as strings; a zero total summed in hash-map order could print "0" or "0.00", so agreeing books failed verify at random. Now by value (regression test) - tests: spec-blind suites for tags, register, prefix, store, MCP; label and register PDF/XLSX tests; viewer tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
762d47268a |
Control roles in the journal (books.assign_role, books.roles); the MCP reloads wanderbooks.toml on change and reports settings problems (books.settings, digest); new writes a full commented wanderbooks.toml
- core: ControlAccount keys, Ledger::assign_role/check_assign_role/role_account (rules.md B-23): one account per role, of the role's type, balance sheet roles move only when empty; invoices, bills, payroll, remittance, and closes post to the assigned account; an assigned role frees its default code for renumbering - store: role.assigned journaled and replayed (strict account_id) - mcp: settings.rs reloads by content at the start of each request, keeps the last good settings on a broken edit; books.settings with chart problems; digest settings_problems; instructions say when to edit the toml - viewer: journal text for role.assigned - cli: full example wanderbooks.toml, tested to load with every example uncommented - tests: spec_roles.rs (25, spec-blind), store roles.rs (6), MCP reload/problems/roles, settings unit tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
d99589e6c1 |
import: grounded in Intuit's specs; atomic chart and bank imports
New vendored sources (docs/spec/sources/): Intuit's bank CSV upload and
IIF formats, ECMA-376 / Excel number storage, and the IRS form lines the
tax categories map to.
IIF export (rewritten to Intuit's IIF Import Kit):
- declares every account first (!ACCNT NAME, ACCNTTYPE, ACCNUM): an
undeclared account is created in QuickBooks "as a new bank account";
the MCP export types them (cash accounts BANK, the control accounts AR
and AP, registered fixed assets FIXASSET, the rest by type);
- subaccounts by colon path, parents first; colons in a name replaced;
- ASCII only (IIF is ASCII text), dashes and quotes transliterated;
- the kit's 2007+ journal layout and M/D/YYYY dates; an entry using an
undeclared account is refused.
Bank CSV:
- the 4-column layout is money out then money in by position, which
matches Intuit's "Credit, Debit" and banks' "Debit, Credit" both (the
doc's "Expense, Income" labels were wrong, the parsing was not);
- a slash date's order comes from the file (a part over 12 settles it)
or the caller's new date_order, never a guess: Intuit recommends
dd/mm/yyyy, US banks write mm/dd/yyyy;
- fractions of a cent and double negatives ("(-5)") are refused; the
header is recognized on the first non-blank row.
Chart CSV: a blank code or name is refused with its row (found by the
new fuzz_import_csv target), and the core now refuses a blank account
code or name too (replay keeps older books).
MCP:
- books.import_chart_csv and books.import_bank_csv rehearse the whole
file on a copy of the ledger first: a bad row anywhere refuses the
file (before, a duplicate within the file, a parent after its child,
or a date in a closed period left earlier rows behind);
- bank rules: the longest matching pattern wins (they arrive in a map,
so "first match" changed from run to run).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
|
||
|
|
e2c421be35 |
core: no entry posts a fraction of a cent; verify compares whole ledgers
CI's run of the store refusal property found a payroll run that posted $65.27794: the employer tax was passed in unrounded and nothing refused it, though rules.md B-3 already says a ledger in one money holds no fractions of its smallest unit. Sums of it then differed in scale between journal replay and the SQLite read-back. - Ledger::post_entry refuses any line finer than its currency's ISO 4217 minor unit (FinerThanMinorUnit), whatever made the line: payroll, closes, disposals. add_line refuses it up front. Older journals replay as booked through replay_post_entry. Spec tests for the kernel's ten-place limit now run on the replay path. - The property's body is now run_ops, so failing sequences become named regression tests (regression_payroll_with_a_fraction_of_a_cent). The generator rounds payroll tax to the cent, as callers must. - Ledger derives PartialEq and gains differs_from; Store::verify compares the live ledger with the journal replay in full (drafts, documents, schedules, periods, settings), not just balances, and names the parts that differ. The property checks the ledger its commands built against that replay and against the reopened books. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
f9bd507765 |
fix(core): payments never predate their documents; aging as of a date; refusals leave no draft
Three spec decisions from the owner (2026-10-13), raised by the spec-blind test writers, each test-first: - A payment dated before its invoice or bill is refused (PaymentBeforeDocument); a real prepayment is a deposit. Test: a_payment_never_predates_its_document. Replay is unaffected (it goes through replay_invoice_payment / replay_bill_payment). - Aging as of a date shows only documents issued by then, less only the payments made by then. The ledger now records each document's payment entries (live and on replay), and aging counts those dated by as_of. Test: aging_as_of_a_date_sees_only_what_existed_then; the A/R and A/P conservation properties' aging oracles updated to the confirmed rule (they had assumed aging ignored dates). - A schedule through a mid-month date prorates that month by days; the code was right and its doc wrong. The spec-blind test that disagreed is rewritten to the confirmed spec and no longer ignored. Also found while wiring payments: a payment to an unknown cash account created the payment's draft and then failed, leaving it behind; the cash account is now checked before anything is created. The unreviewed golden van_schedule.json is gone: every row was first checked against an independent hand computation (all 61 matched), then replaced by van_schedule_is_the_hand_computed_one, which asserts that computation directly (548.39, 59 x 1,000.00, 451.61; total 60,000.00). Tests: 452. No spec-disagreements left ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
bac81593e9 |
test(core): spec-blind suites and a live GnuCash oracle; four fixes they found
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 |
||
|
|
4647611f96 |
fix(core,store): child accounts share their parent's type; periods nest; lines checked when added
Three spec decisions from the owner (2026-10-13), each test-first:
- A child account has its parent's type (ParentTypeMismatch): a child
subdivides its parent. Test: a_child_account_has_its_parents_type.
- Periods nest (a month in a quarter in a year) but never partially
overlap (PeriodsOverlap; a refusal uses no id). Test:
periods_nest_but_never_partially_overlap.
- A line's account must exist and take postings when the line is added,
not only at post (UnknownAccount / PlaceholderAccount from add_line
and add_line_fx); posting still re-checks. Two older tests that pinned
the post-time refusal now check both (the post-time check is reached
through the unchecked replay path, as old journals can).
Compatibility: replay reads history through new Ledger::replay_*
commands (add_account, add_line, add_line_fx, create_period) that skip
checks added later, so books written before these rules still open.
The contract test books_written_under_older_rules_still_open writes such
a journal directly and found, on its first run, that the SQLite
cross-check rebuild used the checked commands and would have failed
verification ("sqlite projection can't be read back"); it now replays
too. Documented in docs/spec/README.md; decisions recorded in rules.md
(B-1, B-4, B-12).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
|
||
|
|
4eaac20311 |
feat(core,mcp,gui): never apply a legal rule past what wanderbooks knows
The owner (2026-10-13): when what wanderbooks knows is stale, check the date, research it or ask the owner or CPA, and update the rule for everyone with a pull request (best) or an issue (worst). - ledger_core::rules: a registry of rules set by law that change over time. Each says the years it's known for, its rules.md entry, its vendored official source, and a review date. stale_rules(today) is pure; the engine has no clock. - The 1099 threshold is the first: $600 through 2025, $2,000 in 2026, then indexed for inflation (26 U.S.C. §6041(h)). default_threshold and vendor_1099_report now return Result; for 2027+ they refuse with LedgerError::RuleNotKnown, whose message says what to do: get the figure from the official source or the owner/CPA, pass it explicitly, then open a PR (or issue) at github.com/swaits/wanderbooks. Before, 2027+ silently used $2,000. - MCP: books.vendor_1099 relays that refusal; books.digest gains stale_rules; the server instructions tell the agent the procedure (and how to handle control-account conflicts: explain, never renumber). The CPA package writes a NOT-INCLUDED note instead of a guessed 1099 worksheet. - Viewer: "Needs attention" shows each stale rule with a ready prompt for the agent; the 1099 page explains instead of guessing. Review date for the 1099 rule: 2026-11-01, when the IRS usually publishes next year's inflation adjustments (CONVENTION, marked as such in the registry). Tests: an_unknown_year_threshold_is_never_guessed, dated_rules_go_stale_on_their_review_date (dates passed in, so no clock in the test), MCP stdio: 2027 refused with guidance, explicit threshold accepted, digest carries stale_rules (asserted date-independently). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
8559bba6bc |
fix(core,store): refuse control accounts of the wrong type, with a reason
Invoices, bills, payments, payroll, and closes post to accounts the engine finds by code (1200, 2000, 4000, 6999, 3999, 6100-2150). If an account with that code existed it was used whatever its type: a 1200 "Customer Deposits" liability would have taken receivables. Now (the owner, 2026-10-13: never force an imported chart into our numbering) the engine refuses with ControlAccountMismatch, naming the code, the account, its type, the role, and the type needed, so the agent can work it out with the human. A placeholder is refused too. Every command checks all its control accounts before changing anything (Ledger::check_controls, spec + SOURCE in its doc comment), so a refusal never leaves a half-made account behind. One table of control accounts (ledger_core::RECEIVABLES ... PAYROLL) replaces the codes duplicated across the engine and the store. Tests (spec-derived, all assert the books are untouched after the refusal): an_invoice_refuses_a_1200_that_is_not_receivables, an_invoice_refuses_a_placeholder_1200, an_invoice_creates_nothing_when_its_second_account_is_wrong, a_bill_refuses_a_2000_that_is_not_payables, a_close_refuses_a_3999_that_is_not_equity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX |
||
|
|
847dd4388d |
fix(store,core): a refused command never reaches the journal; reversal rules
Max-testing pass, step 2 (rank 1 regressions + a rank 2 law). Two ways
to make the books unopenable, both found by test, both fixed:
1. Reversing into a closed period: the store journaled `entry.reversed`,
then the engine refused it at post. Replay could never apply that
line, so Store::open failed ("period 1 is closed"). Regression:
reversal_into_a_closed_period_is_refused_before_journaling.
2. An oversized invoice (or any command whose hand-written store
pre-checks lagged the engine, here the new line range) journaled,
then failed in the engine. Found by the new property
refused_commands_never_reach_the_journal within 4s.
The fix is structural, not another hand-written check: every
journal-first store command now rehearses on a scratch clone of the
ledger (`Store::rehearse`), so whatever the engine refuses is refused
before the journal, now and after any future engine change. The
create_entry rehearsal uses the checked constructor: no draft is ever
dated in a closed period (the Lean invariant Inv.draftsOpen, which the
store had bypassed).
The property (1,000 random command sequences, heavy on refusals)
asserts, after every command: a refusal leaves the journal
byte-identical; every posted entry validates; the trial balance
balances; no draft sits in a closed period; and the books reopen and
verify at the end. Shown to have teeth: removing the create_entry
rehearsal makes it fail and shrink to open-period, close, draft.
Engine (spec confirmed by the owner, 2026-10-13), Ledger::check_reversal,
a pure pre-check that reverse_entry and the store share:
- an entry is reversed at most once (EntryAlreadyReversed); reversing
the reversal re-instates it;
- a closing entry is never reversed (ClosingEntryIrreversible);
- a refused reversal leaves no trace (it used to leave a draft and
burn an id).
Specs go in doc comments as a ```text GOAL/INVARIANTS/SCENARIOS block.
New dev dependency: proptest for ledger-store (already the workspace's
property library).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
|
||
|
|
2d36fb05a4 |
feat(core): prove the accounting laws (Kani + Lean); fix three engine gaps
The money arithmetic every invariant rests on now lives in one kernel,
ledger-core/src/kernel.rs: signing a line, totals, net, per-account
movements, reversal, normal balances, trial-balance columns, closing
lines, and the rounding plug for member splits. It is generic over a
checked `Amount` trait. The engine runs it on Decimal (validate,
movements, reverse_entry, account_balance, close_period_to, allocate,
trial_balance): there is one copy of the arithmetic, and it is the one
under proof.
Three layers, docs/verification.md maps every law to its proofs:
- Kani (src/proofs.rs): 17 harnesses prove the kernel at i64 for every
input up to 4 lines over 3 accounts. An entry is accepted exactly
when it is two or more positive, in-range lines whose debits equal
credits; accepted entries net to zero; grouping by account loses
nothing; a reversal mirrors and cancels its original; trial-balance
columns balance iff the books net to zero; closing lines zero their
accounts and a close balances however the net is split; the plug
makes parts sum to the whole. No panics, no overflow. kani::cover!
shows the proofs are not vacuous.
- Lean 4 (verify/lean): 24 headline theorems for books of any size. The
ledger is a state machine (add account, draft, post, reverse, open,
close), and in every reachable state the books net to zero, the trial
balance balances (as of any date), and the accounting equation holds.
Posting only appends ("posted is forever"), closed periods are
frozen, a close zeroes revenue and expenses and credits exactly the net
income, and period statements add up. Audit.lean shows standard axioms
only, no sorry.
- Bridge (tests/kernel_bridge.rs): the Decimal and i64 kernels agree.
What the proofs found (each one missed by every test, fuzzer, and
mutant):
- The engine accepted negative line amounts; only MCP refused them. Now
NegativeAmountLine: direction is the side's job.
- Summing lines near Decimal::MAX panicked. Lines are now capped at one
quadrillion and ten decimal places (AmountOutOfRange), which keeps
every realistic sum exact, and the kernel's sums are checked.
- FX conversion (foreign_amount * fx_rate) could panic the same way; it
is now a checked multiply.
- Kani itself: 0.68 mis-models symbolic-length copy_from_slice
(model-checking/kani#4904) and reported a reversal that doesn't net to
zero. The counterexample, replayed natively, passes, and a 15-line
standalone crate reproduces the bug. Harnesses avoid bulk copies, and
a should_panic canary will flag the upstream fix.
`just prove` runs it all (Kani memory- and time-capped, fails on an
unsatisfiable cover or a non-standard Lean axiom). AGENTS.md now makes
formal coverage part of every core change.
Also, docs: GnuCash is *an* oracle, never *the* oracle (the owner). The
spec is accounting principles, proved. Golden files are recomputed from
principles. External implementations (GnuCash, the old Sheet, QBO/Xero)
are cross-checks whose disagreements get adjudicated, never obeyed.
docs/oracle.md gains "Who is right"; AGENTS, README, architecture,
roadmap, parity, and STATUS are reworded to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
|
||
|
|
d519f8a0d8 |
feat: payout gross-up (books.record_payout)
A platform deposit becomes a draft entry with the deposit, fees, adjustments, and gross revenue (by class), refused unless gross - fees +/- adjustments equals the deposit to the cent. The payout statement is attached as its receipt before posting. 60 tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
75623afd46 |
feat: prepaid/deferred amortization schedules and recurring entries
Amortization schedules spread a prepaid expense or deferred revenue over the months it covers, to the cent, posting on month ends; runs preview first, are idempotent, honor closed periods, and are exempt from the receipt policy like depreciation. Recurring entries generate drafts only, never posts. Six new tools (59 total); four new journal events, replayed and checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
e6c49d6650 |
feat: tax categories and the tax summary report
wanderbooks is a general MCP-first accounting application: every business needs its accounts mapped to where they land on its income tax return, and its CPA needs book-to-tax differences surfaced, not hidden. - ledger_core::tax: a catalog of stable categories (gross_receipts, commissions_fees, meals, guaranteed_payments, officer_compensation, nondeductible, ...) with kind (income, cost of goods, deduction), the returns each appears on (Schedule C, 1065, 1120-S, 1120), a default deductible percent (meals 50, nondeductible 0), and guidance. Keys, not line numbers: form lines move from year to year. - Accounts carry tax_category and an optional deductible_percent override, validated against the account type (and the return, at the MCP layer), journaled as account.tax_set, replayed, projected (schema v6). - tax_summary(start, end, form): the P&L grouped by category, book and deductible amounts (half away from zero to the cent), contra income (refunds) reducing receipts, and every account still uncategorized. - MCP: books.tax_categories (filtered to the configured return), books.set_tax_category, a tax_summary report in every format and in the CPA package, chart output with categories, and a fifth tax_category column in chart CSV imports, validated before anything is created. [company] tax_form, or derived from tax_classification. Scope stays book basis: no tax depreciation, basis, or filing. Tests: a hand-computed quarter (50% meals 333.33 -> 166.67, penalties at 0%, refunds as negative receipts, guaranteed payments, one uncategorized account; book net 5,276.67 vs tax 5,483.33); overrides; category/account /return validation; form parsing and derivation; CSV parsing; stdio: filtered catalog, mapping, refusal, summary, all-or-nothing import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
754060ccf0 |
fix: period close sweeps only through the period end; partnerships close into members' capital
The close zeroed every revenue and expense account's all-time movement, so closing a period after later entries were posted swept those too: close January in March and February's and March's income landed in January's closing entry. The preview had the same bug and never showed where the net went. Now: - close_period_to(id, to) zeroes balances as of the period end; entries dated later stay open for their own period. Periods must close in order (LedgerError::CloseOutOfOrder): a later close has already swept the earlier period. - CloseTo::Members: for a partnership (multi-member LLC) the net goes to each member's capital account by share, to the cent, the last member absorbing rounding, which is what K-1 capital accounts track. CloseTo::RetainedEarnings (3999) stays the default; close_period is shorthand for it. books.close_period picks Members when wanderbooks.toml lists members. - The journal records the close target; replay re-derives the closing entry and now compares every line with the journal, not just the id, so a change to closing math can never silently rewrite history. - The tool's preview is the real close run on a clone of the ledger (Ledger now derives Clone), so preview and post can't disagree. - Members' equity: closing entries into a capital account are income, not contributions. Balance sheet for a partnership: capital accounts (with allocated earnings), then only unallocated earnings — "Undistributed earnings, prior years" if any, and this year's unclosed net income (loss). periods::allocate is shared by the close and the statement. Tests: January closed after February is posted sweeps exactly Dr 4000 2,500 / Cr 6100 800 / Cr 3999 1,700 (mutation-checked: the old code gives 3,500/120/800/2,580); out-of-order close refused; a partnership close splits 666.67 into 333.34/333.33 with no 3999, and the members' equity statement reads correctly before and after; store replay of a members close (reopen, rebuild, verify); partnership and corporate balance sheet layouts; stdio close preview equals the posted lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
353bef1d5d |
feat: general ledger by account, trial balance as of, members' equity
Three reports a CPA expects that the engine could not produce: - general_ledger(start, end): the canonical general ledger, organized by account in code order. Each account has its beginning balance, every posting in the period (date, entry, description, debit, credit, running balance on the normal side) and its ending balance, which ties to the trial balance. Each posting names its split: the one account on the opposite side of its entry, or "-Split-" when that side has several, so a compound entry (one debit, several credits) reads correctly from every account it touches. The entry-ordered listing the exports called a "general ledger" is really the general journal. - trial_balance_as_of(as_of): a trial balance is always as of a date. - members_equity(start, end, members): the statement of changes in members' equity for a multi-member LLC taxed as a partnership. Each member's capital roll-forward: beginning (capital plus their share of undistributed earnings), contributions, allocated net income (loss), distributions, ending. Allocation is by ownership share to the cent, the last member absorbing rounding, so totals are exact and the ending total equals balance-sheet equity; any equity change the roll-forward can't explain shows as "other" instead of disappearing. Shares must be positive and sum to exactly 1; capital accounts must be distinct equity accounts (LedgerError::InvalidMembers). - CashFlowLine.account: the account a line is about, so renderers can name members instead of account codes. Tests (every expectation computed by hand): trial balance as of Jan 31; February general ledger with beginning balances, running balances, a card that nets to zero, an inactive account still listed, and every ending balance tied to the trial balance; a compound entry's splits from all three sides; a two-member partnership where 666.67 and 100.01 split unevenly (333.34/333.33, 50.01/50.00), with and without closing January, tying to the balance sheet each month; bad member configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
c38d843633 | Payroll accounting, class tracking, CPA handoff package (48 MCP tools) | ||
|
|
fb4dd2055d | Period close, bank rec, AR/AP, multi-currency (41 MCP tools) | ||
|
|
470e663f00 | Next steps 5-10: resources, cash flow, QB interop, backfill, HTTP, parallel-run plan | ||
|
|
3b39cd2135 | asset slice + testing initiative: fixed assets, property/mutation/fuzz testing, docs | ||
|
|
a614600a87 | Initial commit: wanderbooks accounting semantics engine |