Commit Graph
4 Commits
Author SHA1 Message Date
swaitsandClaude Opus 5.5 b292a505b1 P1: the books keep how they are kept
check / check (push) Canceled after 0s
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
2026-10-17 19:27:48 -06:00
swaitsandClaude Opus 5.5 c9772b70c1 docs: P0 review: server instructions, handbook, local files, fail closed
docs/mcp-interface.md: the connect text and its budgets, the handbook
(tool and resources), annotations, failing closed, import_local_file's
roots and refusals, the set_w9 path rule and where TIN-bearing files may
go. docs/testing.md: the P0 laws and the tests that check them.
docs/architecture.md: the journal format gate and the server's
fail-closed store lock.

Round-2 review (2026-10-16): mcp-interface.md lists all 26 destructive
tools (unlink_receipt, unlink_attachment and correct_asset were missing)
and openWorldHint false on every tool; the connect text states the
parties rule and the exact receipt rule, and how it fits its budget; the
lint covers every refusal; the import roots refuse /, the home folder and
folders holding the books or a key, the open is a component walk, every
missing or outside path gets one refusal, and the TIN-file registry and
text scan (and what they can't catch) are described. architecture.md:
the frozen envelope, the lenient next-line prev, no repair of newer
books. testing.md: the new laws and tests. verification.md and
parity.md: P0 notes (no new law to prove, no status moved).
The TIN text check, narrowed (2026-10-16; rules.md B-26,
mcp-interface.md): text is refused for a TIN the vault holds, in its
common renderings, or a stand-alone printed SSN or EIN shape, so bank
exports go in; the oracle a vault comparison gives is recorded as a
known limit for P7. testing.md: the P0 mutation run of the final diff,
per crate, and how each survivor was resolved (killed by a named test,
removed by simpler code, or argued equivalent).
The probe bound (rules.md B-26, mcp-interface.md): rule (a) answers
for at most two candidates a filing, so finding a TIN from its last four
takes on the order of 50,000 refused filings; the open limit now names
that path, closed by P2's access log and P7's human-only TIN channel.
testing.md: the law and its tests; tin_files.rs mutated again, 74 of
74 caught.

Review round 3: rules.md B-28 (four-digit years, amounts a line can
carry, checked products, bounded recurring runs) and B-26/B-27 updates
(BOM-less UTF-16, when set_w9 records); mcp-interface.md matches the
code (25 destructive tools, the five idempotent ones, no reverse_entry
receipt checklist, strictly above the threshold, no tool text names
wanderbooks.toml, dot names and roots inside the books refused, the new
settings lists); testing.md lists the new laws and this round's
mutation run.

Review round 4: rules.md B-28 says the bounds are for new commands
only, replay accepts every line an earlier version accepted, and derived
figures refuse rather than panic; mcp-interface.md documents plan_error
and the nullable totals; testing.md lists the old-books contracts and this round's
mutation run (core, store, MCP, config, GUI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-17 09:25:00 -06:00
swaitsandClaude Opus 5.5 fb3b4ab476 closes: a run of members closes splits cumulatively
check / check (push) Canceled after 0s
Each close into members' capital rounded on its own, ties to the
earlier member, so twelve monthly 50/50 closes of odd-cent income could
leave one member several cents ahead of half the year (docs/testing.md
#12, found with the members' equity fix).

- CloseTo::Members now splits cumulatively over a run (closes back to
  back with the same members and shares): each close gives a member
  allocate(run total so far) - allocate(run total before). Periods
  record their close order and a cumulative close's members and net
  (Period::close_seq, member_close) so the next close continues the run
  and replay recomputes it exactly. A close to anything else, or other
  shares, ends the run.
- Journaled "rounding": "cumulative". The per-close rule is kept as
  CloseTo::MembersPerClose for closes journaled "largest_remainder";
  older closes still replay with the last member absorbing it.
- Lean: runPart_telescopes (a member's total from a run is their
  allocation of the run's total) and closePart_total (each close hands
  out exactly its net), audited to the standard axioms.
- Tests: hand-computed three one-cent months (per close A 0.03 / B 0;
  cumulative A 0.02 / B 0.01), a run ending, a property that each member
  stays within a cent of their share (fails on the per-close rule), and
  a store contract that each rule journals and replays as itself.

Wanderland's books have no closes yet, so all of theirs use the new rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
2026-10-13 18:58:21 -06:00
swaitsandClaude Opus 5.5 3609bf065e 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 (Stephen). 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
2026-10-13 11:12:13 -06:00