Stephen asked that specs come from recognized documents, not from anyone's memory. There is no single US bookkeeping bible; authority is layered, and this records it: - docs/spec/sources/: verbatim excerpts (whitespace normalized), each with URL, retrieval date, and the SHA-256 of the fetched file, of public-domain texts: Pacioli's 1494 treatise in Geijsbeek's 1914 translation (double entry, the trial balance, one money, write only forward, correct by opposite entry, close into capital); IRS Pub. 583, 538, 946, 15; the 1099-MISC/NEC instructions (2025 and 2026); 26 U.S.C. §6041, §6041A, §704; Treas. Reg. §1.263(a)-1(f) and -4(f); Notice 2015-82; Rev. Proc. 98-25; Form 1065 instructions; 17 U.S.C. §105. The excerpts were extracted by matching each passage against the fetched file, so none can contain text the source doesn't. - docs/spec/rules.md: the rule catalog, B-1 to B-22. Each rule says AUTHORITY, CONVENTION, or DECISION; cites its sources (FASB's Codification and Concepts Statement 8 and OpenStax quoted briefly, never copied: copyright and CC BY-NC-SA); and names the code that enforces it and the tests and proofs that check it. - Spec blocks in the code now carry SOURCE: lines into the catalog; AGENTS.md makes sourcing rule 0 of the formal-methods bar. Corrections the sources turned up: the de minimis limit is $500 in the regulation and $2,500 by Notice 2015-82 (both now cited); 1099 thresholds after 2026 are inflation-indexed, so the engine's flat $2,000 for 2027+ is an open question (B-19). Unverified ASC paragraphs (360-10-35-4, 340-10, 230-10-45-10, 606-10-55-37B, 210) are listed in docs/spec/README.md until someone with a FASB basic-view login reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
193 lines
10 KiB
Markdown
193 lines
10 KiB
Markdown
# AGENTS.md — wanderbooks
|
|
|
|
Operating manual for agents working in this repo. Read this first, then the
|
|
docs it points to. The short version: this is an engine-first, clean-room
|
|
rebuild of canonical double-entry bookkeeping, done the storytold way —
|
|
agents verify against oracles, track parity automatically, and never ship
|
|
what they haven't demonstrated.
|
|
|
|
## What this is
|
|
|
|
`wanderbooks` is a from-scratch implementation of double-entry bookkeeping
|
|
semantics, built to become a full bookkeeping application (engine now, UI
|
|
later). The reference points:
|
|
|
|
- **Spec:** accounting principles, written down as proofs
|
|
(`docs/verification.md`). That is the only authority.
|
|
- **Cross-check oracles:** GnuCash (open source, open file formats), the
|
|
old Google Sheet, QBO/Xero exports. Useful to diff against, *never*
|
|
trusted to be right: a disagreement is adjudicated from principles, and
|
|
nothing may rest on another program's behavior alone. See
|
|
`docs/oracle.md`, "Who is right".
|
|
- **Workflow reference:** QuickBooks Online and Xero — for *what the UI must
|
|
do*, never for code. We observe behavior; we write our own implementation.
|
|
- **Method reference:** storytold/ArtCraft's app-cloning pipeline
|
|
(screenshot-in-the-loop, render oracles, automated parity floors). We adapt
|
|
it to accounting: semantic oracles instead of pixel oracles.
|
|
|
|
## The non-negotiables
|
|
|
|
1. **Money is `Money`.** Exact `Decimal` plus a currency code. Binary floats
|
|
(`f32`/`f64`) never appear in money paths — not for amounts, not for
|
|
rates, not for "temporary" calculations. If you need a ratio, it is a
|
|
`Decimal`.
|
|
2. **Every entry balances.** Debits equal credits exactly. No epsilon.
|
|
3. **Posted is forever.** Posted entries are immutable. Corrections are
|
|
reversing entries linked by `reversal_of`. There is no edit, no delete.
|
|
4. **Reports are pure functions of a ledger.** If a report needs state that
|
|
isn't in the ledger, the ledger model is wrong — fix the model.
|
|
5. **Clean room.** Build from public specs and observed behavior only:
|
|
GnuCash documentation and file formats, the IFRS conceptual framework,
|
|
IRS publications for tax-adjacent logic later. GnuCash itself is GPL —
|
|
read its docs, never copy its code. This repo is MIT OR Apache-2.0.
|
|
Never disassemble or copy from proprietary software.
|
|
6. **Accounting laws are proved, not just tested.** Arithmetic that money
|
|
flows through lives in `kernel.rs` and carries Kani proofs; the laws
|
|
it serves carry Lean proofs. `just prove` stays green. See
|
|
"Formal methods" below.
|
|
|
|
## Verification protocol (read this twice)
|
|
|
|
There is no UI yet, so "seeing what it's building" means **showing the
|
|
numbers**. The analog of storytold's screenshot-in-the-loop:
|
|
|
|
- A green `cargo test` is necessary but **not sufficient**. Every behavior
|
|
change must be demonstrated through the public API with human-readable
|
|
output: print the trial balance, the P&L, the balance sheet. Show the
|
|
numbers moving the way accounting says they should.
|
|
- New behavior needs new tests in `crates/ledger-core/tests/invariants.rs`
|
|
(or a new test file for a new crate). Tests encode accounting law, not
|
|
implementation details: prefer "the accounting equation holds" over
|
|
"this function returns that struct".
|
|
- **Golden files are sacred.** `tests/golden/*.json` hold hand-verified
|
|
expected reports for canonical fixtures. If a change intentionally alters
|
|
output, recompute the expected numbers *by hand from accounting principles*,
|
|
update the golden, and explain the recomputation in the commit message.
|
|
Never edit a golden to match output you haven't verified — that is how
|
|
oracles die.
|
|
- The randomized invariant test (`random_balanced_journals_preserve_invariants`)
|
|
is the always-on property oracle. Extend it when you add invariants.
|
|
|
|
## Formal methods: the bar (read this three times)
|
|
|
|
Tests check the cases someone thought of; proofs check all of them. The
|
|
first formal pass on this engine (2026-10, `docs/verification.md`) paid
|
|
for itself several times over, on code that already had 190+ tests,
|
|
property tests, mutation testing, and 11.5M fuzz executions:
|
|
|
|
- **Negative line amounts were accepted by the engine.** Only the MCP
|
|
layer refused them. Any other caller could post them.
|
|
- **A large enough amount crashed the engine** (a `Decimal` overflow
|
|
panic while summing lines) instead of returning an error.
|
|
- **Currency conversion could crash** the same way.
|
|
- **The verifier itself had a soundness bug.** Kani 0.68 mis-models
|
|
`copy_from_slice` of symbolic length (model-checking/kani#4904): it
|
|
reported a reversal that doesn't net to zero. We didn't wave it away.
|
|
Replaying the counterexample natively, then shrinking it to a 15-line
|
|
crate, proved it was the tool, not the books. Now a canary harness
|
|
watches for the fix, and covers prove no proof is vacuous.
|
|
- **Writing the spec simplified the code.** `credit_line` and
|
|
`closing_line` turned out to be the same function. Lean also proved
|
|
that no draft can exist in a closed period, the invariant that lets
|
|
the store journal a post before applying it.
|
|
|
|
So the bar, for every agent, every change:
|
|
|
|
0. **Specs come from authorities, not memory.** Every rule the engine
|
|
enforces is in `docs/spec/rules.md` with its source: Pacioli for the
|
|
mechanics of double entry, the IRS (vendored verbatim, public domain)
|
|
for records, periods, 1099s, capitalization, partnerships, and payroll,
|
|
FASB's Codification (cited, never copied) for GAAP. A rule no authority
|
|
states is marked CONVENTION or DECISION, with who decided and when.
|
|
Spec blocks in the code (```text GOAL/SOURCE/INVARIANTS/SCENARIOS)
|
|
carry a `SOURCE:` line. Never write a spec from what the code happens
|
|
to do, or from what you remember; find the document, or ask.
|
|
|
|
1. **Money arithmetic goes in `kernel.rs`, generic over `Amount`.** If
|
|
you write a sum, a sign flip, a split, or a rounding plug anywhere
|
|
else, move it into the kernel. The engine calls the kernel. It never
|
|
gets a second copy.
|
|
2. **Every kernel function gets a Kani harness** in `src/proofs.rs`, on
|
|
fixed arrays (no symbolic-length `Vec` growth, no `copy_from_slice`),
|
|
with a `kani::cover!` showing the interesting case is reached.
|
|
3. **Every new law gets a Lean theorem** in `verify/lean`. Laws about
|
|
the books over time are theorems about `Reachable` states. A new
|
|
`Ledger` command is a new `Step` constructor, with its preservation
|
|
proof. No `sorry`, ever; `Audit.lean` lists every headline theorem.
|
|
4. **Every kernel instantiation gets a bridge property** in
|
|
`tests/kernel_bridge.rs`, so what Kani proved on `i64` holds on
|
|
`Decimal`.
|
|
5. **A failing proof is a finding, not an obstacle.** Get the
|
|
counterexample, replay it natively, and decide on evidence: bug in
|
|
our code, bug in the spec, or bug in the tool. Never weaken an
|
|
assertion, raise a bound, or delete a harness to make red go green
|
|
without writing down why in `docs/verification.md`.
|
|
6. **More, not less.** When you touch an area the proofs don't cover
|
|
yet (A/R and A/P documents, payroll, depreciation, FX, persistence),
|
|
leave it with more formal coverage than you found it. Proptest,
|
|
mutation testing, fuzzing, Kani, and Lean all stack. Use all of them.
|
|
|
|
`just check` and `just prove` both pass before every commit that touches
|
|
`ledger-core`.
|
|
|
|
## Parity tracking
|
|
|
|
`docs/parity.md` is the parity catalog: every semantic feature we intend to
|
|
clone, with status. Rules:
|
|
|
|
- Every feature change updates `docs/parity.md` in the same commit.
|
|
- The **floor never decreases**: the count of `done` items is monotonic.
|
|
CI will enforce this once the catalog is machine-readable (roadmap phase 3);
|
|
until then, it is enforced by review — yours.
|
|
- Statuses are `done` (implemented + tested + golden-covered), `partial`,
|
|
`todo`. No other statuses. `done` requires all three.
|
|
|
|
## Engine-first architecture
|
|
|
|
- All mutations are commands on `Ledger` (`create_entry`, `add_line`,
|
|
`post_entry`, `reverse_entry`, `add_account`). Struct fields stay private;
|
|
no other mutation path may exist.
|
|
- `ledger-core` depends only on `rust_decimal`, `thiserror`, `chrono`,
|
|
`serde`, `serde_json`. New dependencies need justification in the commit
|
|
message. No I/O, no networking, no time-source in the core.
|
|
- Future crates (`ledger-store`, `ledger-import`, `ledger-oracle`, `books-app`)
|
|
depend inward toward the core, never the reverse. See `docs/architecture.md`.
|
|
|
|
## Working here
|
|
|
|
- `mise install` sets up every tool (pinned in `mise.toml`): Rust,
|
|
just, Kani, Lean, cargo-mutants, cargo-fuzz. If a recipe needs a tool,
|
|
it goes in `mise.toml`; nothing gets installed by hand.
|
|
- Rust stable, edition 2024. `just check` must pass: fmt, clippy with zero
|
|
warnings, every test. `just prove` must pass for core changes.
|
|
- Conventional commits: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`.
|
|
One logical change per commit. The commit message explains the *accounting*
|
|
reasoning, not just the code.
|
|
- You have a very large context window. Use it: when working on semantics,
|
|
load the whole `ledger-core` crate plus `docs/requirements.md` rather than
|
|
guessing from snippets.
|
|
- If a requirement in `docs/requirements.md` conflicts with code, the
|
|
requirement wins — then fix the code, and say so.
|
|
|
|
## Docs index
|
|
|
|
| Read this | When you need |
|
|
|---|---|
|
|
| `docs/requirements.md` | What the engine must do (R-1…), what is future work |
|
|
| `docs/architecture.md` | Crate layout, command pattern, dependency rules |
|
|
| `docs/oracle.md` | How we check correctness: golden files, property tests, fuzzing, cross-checks against other implementations (and who is right) |
|
|
| `docs/spec/` | The authorities every rule comes from (`rules.md`), with public-domain sources vendored verbatim |
|
|
| `docs/verification.md` | The formal proofs: every accounting law, its Kani harness and Lean theorem, what they found |
|
|
| `docs/parity.md` | Feature catalog and the non-regression floor |
|
|
| `docs/capability-parity.md` | Parity vs the GSheet+GDrive system + the cutover rule |
|
|
| `docs/mcp-interface.md` | The MCP tool surface: the primary interface spec |
|
|
| `docs/receipt-store.md` | Blob store + `ledger-store` SQLite persistence spec |
|
|
| `docs/glossary.md` | Debit/credit and friends, for agents new to accounting |
|
|
| `plan/STATUS.md` | Where the project stands right now |
|
|
| `plan/roadmap.md` | Phase plan: semantics → oracle harness → egui app → workflow parity |
|
|
|
|
## License
|
|
|
|
MIT OR Apache-2.0. New files carry no license headers; the license files at
|
|
the repo root cover the tree.
|