config, mcp, gui: specs from sources, spec-blind suites, and the bugs they found
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
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
This commit is contained in:
1 parent
5b8910a1cd
commit
3f20df9700
42 files changed
+4636
-363
No files matched your search
@@ -16,7 +16,7 @@ private, and constructors are `pub(crate)` where it matters. This is what
|
||||
makes the ledger auditable and what will make the future oracle harness
|
||||
trustworthy — the harness only needs to drive the command surface.
|
||||
|
||||
Reports (`trial_balance`, `balance_sheet`, `profit_and_loss`, `snapshot`)
|
||||
Reports (`trial_balance`, `balance_sheet`, `unclosed_earnings`, `snapshot`)
|
||||
are free functions: pure, total, and deterministic. Same ledger in, same
|
||||
report out, every time, on every platform. That determinism is the entire
|
||||
basis of `docs/oracle.md`.
|
||||
@@ -31,7 +31,7 @@ crates/ledger-core/
|
||||
account.rs # AccountType, Side, Account, ChartOfAccounts
|
||||
journal.rs # JournalLine, JournalEntry, validate()
|
||||
ledger.rs # Ledger: the command surface, append-only log
|
||||
reports.rs # trial_balance, profit_and_loss, balance_sheet, snapshot
|
||||
reports.rs # trial_balance, unclosed_earnings, balance_sheet, snapshot
|
||||
tests/
|
||||
invariants.rs # accounting-law tests + randomized property test
|
||||
golden/ # hand-verified expected report snapshots
|
||||
|
||||
+10
-10
@@ -85,14 +85,13 @@ below.
|
||||
- `books.draft_entry { date: "2026-10-10", description: "..." }`
|
||||
→ `{ entry_id, status: "draft" }`.
|
||||
- `books.add_line { entry_id, account: "1000" | <id>, side: "debit" | "credit",
|
||||
amount: "123.45" }`
|
||||
→ `{ lines: [...], totals: { debits, credits, balanced } }`. Running totals
|
||||
after every line: the agent watches the entry balance in real time.
|
||||
- `books.post_entry { entry_id, confirmed }` → without confirmation:
|
||||
`{ preview: { lines, resulting_balances, receipts: { attached: [...],
|
||||
required_but_missing: bool } } }`; with confirmation: validates (including
|
||||
the receipt policy), posts, returns `{ entry, balances_changed: [{ code,
|
||||
before, after }] }`.
|
||||
amount: "123.45" }` → `{ ok: true }`. A refused line (unknown account or
|
||||
class, bad amount) adds nothing.
|
||||
- `books.post_entry { entry_id, confirmed }` → `{ status, entry_id, date,
|
||||
description, total, lines (with resulting balances), receipts,
|
||||
receipt_required, receipt_missing, split_detected }`. Without
|
||||
`confirmed: true`, `status` is `"preview"` and nothing changes; with it,
|
||||
the receipt policy is enforced and `status` is `"posted"`.
|
||||
- `books.reverse_entry { entry_id, date, description, confirmed }` → same
|
||||
preview/confirm pattern as post.
|
||||
- `books.get_entry { entry_id }` → full entry with lines.
|
||||
@@ -492,8 +491,9 @@ entries.
|
||||
- `books.add_line` gains optional `class` (name or id): tags the new line.
|
||||
Shared costs are split explicitly across lines at entry time.
|
||||
- `books.profit_and_loss` gains optional `class` filter.
|
||||
- `books.profit_and_loss_by_class { start, end }` → per-class revenue,
|
||||
expenses, net income, plus an unclassified row. Reversals carry their
|
||||
- `books.profit_and_loss_by_class { start, end, class? }` → per-class revenue,
|
||||
expenses, net income, plus an unclassified row (only that class's row
|
||||
when `class` is given; an unknown or ambiguous class is refused). Reversals carry their
|
||||
class so divisional P&L nets correctly.
|
||||
|
||||
## Tools — CPA handoff
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# File names the viewer writes
|
||||
|
||||
Governs `ledger_gui::export::file_name`. Fetched 2026-10-14.
|
||||
|
||||
## Microsoft, "Naming Files, Paths, and Namespaces" (learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
|
||||
|
||||
> Use any character in the current code page for a name, including
|
||||
> Unicode characters and characters in the extended character set
|
||||
> (128–255), except for the following: The following reserved characters:
|
||||
> < (less than) > (greater than) : (colon) " (double quote) / (forward
|
||||
> slash) \ (backslash) | (vertical bar or pipe) ? (question mark) *
|
||||
> (asterisk) Integer value zero, sometimes referred to as the ASCII NUL
|
||||
> character. Characters whose integer representations are in the range
|
||||
> from 1 through 31, except for alternate data streams where these
|
||||
> characters are allowed.
|
||||
|
||||
> Do not use the following reserved names for the name of a file: CON,
|
||||
> PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9,
|
||||
> COM¹, COM², COM³, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9,
|
||||
> LPT¹, LPT², and LPT³. Also avoid these names followed immediately by an
|
||||
> extension; for example, NUL.txt and NUL.tar.gz are both equivalent to
|
||||
> NUL.
|
||||
|
||||
> Do not end a file or directory name with a space or a period. Although
|
||||
> the underlying file system may support such names, the Windows shell and
|
||||
> user interface does not. However, it is acceptable to specify a period
|
||||
> as the first character of a name.
|
||||
|
||||
## POSIX (IEEE Std 1003.1-2017, `<limits.h>`)
|
||||
|
||||
`{NAME_MAX}`: "Maximum number of bytes in a filename (not including the
|
||||
terminating null of a filename string)"; Linux's common file systems set
|
||||
it to 255, and NTFS limits a name to 255 UTF-16 units. wanderbooks keeps a
|
||||
name within 255 bytes of UTF-8, which fits both.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Entity classification: which return a business files
|
||||
|
||||
Governs `ledger_config`'s reading of `[company] tax_classification`.
|
||||
Fetched 2026-10-14.
|
||||
|
||||
## Treas. Reg. §301.7701-3 (eCFR, via law.cornell.edu/cfr/text/26/301.7701-3)
|
||||
|
||||
**(a) In general**
|
||||
|
||||
> A business entity that is not classified as a corporation under
|
||||
> § 301.7701-2(b) (1), (3), (4), (5), (6), (7), or (8) (an eligible
|
||||
> entity) can elect its classification for federal tax purposes as
|
||||
> provided in this section. An eligible entity with at least two members
|
||||
> can elect to be classified as either an association (and thus a
|
||||
> corporation under § 301.7701-2(b)(2)) or a partnership, and an eligible
|
||||
> entity with a single owner can elect to be classified as an association
|
||||
> or to be disregarded as an entity separate from its owner.
|
||||
|
||||
**(b)(1) Domestic eligible entities**
|
||||
|
||||
> Except as provided in paragraph (b)(3) of this section, unless the
|
||||
> entity elects otherwise, a domestic eligible entity is— (i) A
|
||||
> partnership if it has two or more members; or (ii) Disregarded as an
|
||||
> entity separate from its owner if it has a single owner.
|
||||
|
||||
## Instructions for Schedule C (Form 1040) (2025), "Single-member limited liability company (LLC)"
|
||||
|
||||
> Generally, a single-member domestic LLC is not treated as a separate
|
||||
> entity for federal income tax purposes. If you are the sole member of a
|
||||
> domestic LLC, file Schedule C (or Schedule E or F, if applicable) unless
|
||||
> you have elected to treat the domestic LLC as a corporation.
|
||||
|
||||
## The returns (titles, from irs-tax-lines.md)
|
||||
|
||||
- Form 1065, U.S. Return of Partnership Income: a partnership.
|
||||
- Form 1120-S, U.S. Income Tax Return for an S Corporation: an S
|
||||
corporation.
|
||||
- Form 1120, U.S. Corporation Income Tax Return: a corporation (an
|
||||
association included) that is not an S corporation.
|
||||
|
||||
## Derived (wanderbooks)
|
||||
|
||||
| classification says | return |
|
||||
|---|---|
|
||||
| partnership | Form 1065 |
|
||||
| S corporation (S corp, S-corp) | Form 1120-S |
|
||||
| C corporation; corporation, corp, or association alone | Form 1120 |
|
||||
| disregarded entity, sole proprietor(ship), single-member | Schedule C |
|
||||
|
||||
A phrase naming two returns ("corporation or S corporation") and anything
|
||||
else names no return wanderbooks can be sure of, so it is
|
||||
refused rather than guessed.
|
||||
@@ -0,0 +1,36 @@
|
||||
# JSON-RPC 2.0 Specification (jsonrpc.org/specification)
|
||||
|
||||
Governs `ledger-mcp`'s wire protocol (MCP is JSON-RPC 2.0). Fetched
|
||||
2026-10-14.
|
||||
|
||||
**5 Response object**
|
||||
|
||||
> When a rpc call is made, the Server MUST reply with a Response, except
|
||||
> for in the case of Notifications. [...]
|
||||
>
|
||||
> result: This member is REQUIRED on success. This member MUST NOT exist
|
||||
> if there was an error invoking the method. [...]
|
||||
>
|
||||
> error: This member is REQUIRED on error. This member MUST NOT exist if
|
||||
> there was no error triggered during invocation. [...]
|
||||
>
|
||||
> id: This member is REQUIRED. It MUST be the same as the value of the id
|
||||
> member in the Request Object. If there was an error in detecting the id
|
||||
> in the Request object (e.g. Parse error/Invalid Request), it MUST be
|
||||
> Null.
|
||||
>
|
||||
> Either the result member or error member MUST be included, but both
|
||||
> members MUST NOT be included.
|
||||
|
||||
**5.1 Error object**
|
||||
|
||||
> code: A Number that indicates the error type that occurred. This MUST be
|
||||
> an integer. [...]
|
||||
>
|
||||
> | code | message | meaning |
|
||||
> |---|---|---|
|
||||
> | -32700 | Parse error | Invalid JSON was received by the server. |
|
||||
> | -32600 | Invalid Request | The JSON sent is not a valid Request object. |
|
||||
> | -32601 | Method not found | The method does not exist / is not available. |
|
||||
> | -32602 | Invalid params | Invalid method parameter(s). |
|
||||
> | -32603 | Internal error | Internal JSON-RPC error. |
|
||||
@@ -0,0 +1,47 @@
|
||||
# Model Context Protocol: tools (specification 2025-06-18)
|
||||
|
||||
Governs `ledger-mcp`'s tool results and errors. Source:
|
||||
https://github.com/modelcontextprotocol/modelcontextprotocol,
|
||||
docs/specification/2025-06-18/server/tools.mdx (MIT). Fetched 2026-10-14.
|
||||
|
||||
**Structured Content**
|
||||
|
||||
> **Structured** content is returned as a JSON object in the
|
||||
> `structuredContent` field of a result.
|
||||
>
|
||||
> For backwards compatibility, a tool that returns structured content
|
||||
> SHOULD also return the serialized JSON in a TextContent block.
|
||||
|
||||
**Output Schema**
|
||||
|
||||
> Tools may also provide an output schema for validation of structured
|
||||
> results. If an output schema is provided:
|
||||
>
|
||||
> - Servers **MUST** provide structured results that conform to this schema.
|
||||
> - Clients **SHOULD** validate structured results against this schema.
|
||||
|
||||
**Error Handling**
|
||||
|
||||
> Tools use two error reporting mechanisms:
|
||||
>
|
||||
> 1. **Protocol Errors**: Standard JSON-RPC errors for issues like:
|
||||
> - Unknown tools
|
||||
> - Invalid arguments
|
||||
> - Server errors
|
||||
>
|
||||
> 2. **Tool Execution Errors**: Reported in tool results with `isError: true`:
|
||||
> - API failures
|
||||
> - Invalid input data
|
||||
> - Business logic errors
|
||||
|
||||
**Security Considerations**
|
||||
|
||||
> 1. Servers **MUST**:
|
||||
> - Validate all tool inputs
|
||||
> - Implement proper access controls
|
||||
> - Rate limit tool invocations
|
||||
> - Sanitize tool outputs
|
||||
|
||||
(A local stdio server for one bookkeeper: access control is the file
|
||||
system's, and rate limiting has no one to limit; wanderbooks takes
|
||||
"validate all tool inputs" and "sanitize tool outputs" as binding.)
|
||||
+1
-1
@@ -83,7 +83,7 @@ accounting question), CHANGE (a source says otherwise; work pending).
|
||||
|---|---|---|---|
|
||||
| 1 | A P&L whose start is after its end is refused | DESIGN | No standard governs a reversed range; refusing beats silently swapping a typo |
|
||||
| 2 | A balance sheet omits zero-balance accounts | CONFIRMED | Reg. S-X 210.4-03(a): "No caption should be shown ... as to which the items and conditions are not present." A comparative statement must still show a zero beside a non-zero prior column |
|
||||
| 3 | The lifetime `profit_and_loss` counts closing entries | CONFIRMED | Used as earnings not yet closed on the balance sheet; closed income is already in capital (OpenStax Principles of Financial Accounting 5.1). Its name misleads: rename to unclosed earnings (pending) |
|
||||
| 3 | The lifetime `profit_and_loss` counts closing entries | CONFIRMED | Used as earnings not yet closed on the balance sheet; closed income is already in capital (OpenStax Principles of Financial Accounting 5.1). Renamed `unclosed_earnings` (2026-10-14) |
|
||||
| 4 | Closing a period with nothing to close still locks it | CONVENTION | A close is a lock (B-12); a dormant month must still lock and count for close-in-order |
|
||||
| 5 | Draws and distributions are closed into capital at period end | CONFIRMED | OpenStax 5.1: dividends "are closed to the permanent account"; drawing accounts close to owner's capital. Monthly closes mean the year's K-1 Item L withdrawals sum across months |
|
||||
| 6 | Reconciliation balances | CONFIRMED | Signed by the account's normal side (a card reads as owed), as statements show it; `reconciling_a_card_reads_its_normal_side_...` |
|
||||
|
||||
Reference in new issue
Block a user