0e5aebda9931397fc33041e7a19777f0d1352c8f
271
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0e5aebda99 |
feat(store): journal the document fields and file the issued PDF (P4b S4.3)
S4.1 gave documents a number, terms, line accounts and snapshots, and S4.2
gave parties an email and default terms, but the store journaled none of
them: a document carrying one was refused by a temporary guard, because a
public write that replay loses would break I-3. This slice makes the
journal carry them.
- `invoice.created` and `bill.created` write a number, terms, bill-to and
seller snapshots, references, memo and a duplicate's reason, and each
line its account, quantity and unit price, every key only when set. A
document naming none is journaled byte for byte as every earlier version
wrote it (`no_new_fields_journals_byte_for_byte`), so the owner's live
books keep working.
- All of it is behind `documents_v2` (B-27): a write that names one waits
for the owner's upgrade, refused with `format_upgrade_pending` and
journaling nothing; a write that names none never waits
(`documents_v2_waits_for_the_upgrade`).
- `invoicing.first_number` is settable only while no numbered invoice
exists (A-2, B-40): the series has begun, and a number is never reused or
renumbered. Refused at `set_setting`, before anything is journaled.
- The issued copy is filed in the issuing call (A-7, B-40 item 3):
`create_invoice_issued` journals the entry, the invoice, then
`receipt.attached` on that entry (`INV-0007.pdf`, `application/pdf`,
"Issued invoice") and `attachment.linked` to the invoice. There is no
`issued_pdf` field: the attachment id is allocated after the entry exists
(CD-A6), and the link is how the copy is found and how its SHA-256 is
checked. Every refusal happens before the first journal line; the PDF's
bytes are stored before it, so a refused issue leaves an unreferenced,
deduplicated blob at worst.
- A preview runs the issue on a clone and journals nothing: the posting,
the total, the number it would take and the due date (`preview_invoice`,
`preview_bill`). Books that haven't upgraded keep the legacy shape: the
copy is filed as "Invoice {id}.pdf" and no number is taken (migration
item 5).
- Schema 16 adds the document columns; a line's new fields ride
`lines_json` as its project does. `differs_from` already compares whole
invoices, bills and parties, so it sees every new field.
8.0.6: (1) `books_written_under_older_rules_still_open` hand-writes both
new document shapes through JournalWriter and still opens; (2) a new
fixture `old-books/p4_s43_documents` was written by main
|
||
|
|
c8541ea9fc |
fix(deploy): the tunnel's door binds loopback only (P9a)
Review finding: `--http` parsed whatever address it was given and bound it. The door is the plaintext MCP service with a static bearer token; plan 3.9 and the P9a model put a TLS proxy or tunnel in front of a loopback listener, and no decision authorises exposing the books unencrypted. `--http 0.0.0.0:7411` would have done exactly that. `local::tunnel_address` now accepts only a loopback address (127.0.0.0/8 or ::1; `127.0.0.1:7411` stays the default) and refuses anything else with a message naming loopback and why. `server` refuses while parsing, before it takes the lock or binds a socket; `serve` refuses before it starts a host, so nothing is left half-open. The bind-failure behaviour for a loopback address is unchanged (said, not fatal, 3.9), as is the unit's hardening. The test `the_tunnel_door_binds_loopback_only` refuses 0.0.0.0, a public sample address and [::] through `server`, checks no socket or host was left behind, refuses through `serve`, then starts a loopback listener and reaches `books.start` with the bearer. No accounting code, report or figure changes. |
||
|
|
1441dabcca |
feat(deploy): self-host recipe, the enforced isolation flag, and the tunnel's door (P9a)
The books can live on a machine of the owner's own (plan 3.8, 8.2 P9a, D-51). Three things make that honest instead of nominal: - `--isolation same_user|enforced` sets `server::Isolation`, carried through `Served` and `BooksServer` to `books.start`. Locally the books stay `same_user (advisory)`; under the self-host unit the server is told `enforced` and reports `service_user (enforced)`. The server never guesses, and the literal it used to report is gone. - `--http [ADDR]` binds the same MCP router on loopback (default 127.0.0.1:7411) beside the Unix sockets, before the ready line, for the TLS proxy or tunnel in front of the server. It is the same bearer door and the same graceful close; a failed bind is reported and is not fatal (3.9). `serve` carries both flags through the host, so the system unit can ask for them once. `deploy/wanderbooks-server.service` is the unit: a dedicated account, a 0700 data root, `ProtectSystem=strict`, no seccomp or Landlock (D-32), and loopback-only networking. `deploy/README.md` is the recipe: install, setup, move the books by `books adopt` with its head check (D-31: a copied book is the transport), mint a bearer token, put Caddy or Tailscale Funnel in front of the loopback door, and point Claude Code and Codex at it. The two-account CI job (`.github/workflows/two-user.yml`) makes the deployment and runs `crates/ledger-cli/tests/two_user.rs` as another account: `agent_uid_cannot_read_tenant` proves the tenant root and its journal are refused, then reads, writes, uploads, files, exports and verifies over HTTP with the bearer (in a second book, so the moved books' head is not advanced); `self_host_adopt_same_head` compares the moved books' head and event count with the source clone's. `isolation_label_is_enforced_under_the_unit` runs everywhere, including the unit-file read that keeps the recipe from drifting. Accounting reason for the shape: nothing here changes an entry, a report or a figure. It changes who can reach them: the move is lossless and verifiable at the head, and the boundary is the operating system's, not a promise in our text. No journal code, kernel function, Kani harness or Lean step is touched, so `just prove` is not required. No go-ahead needed: no new journal feature or event; the live books are unaffected. |
||
|
|
26d3a9941b |
fix(package): books.start runs the real integrity check (U0b)
books.start passed Integrity::default() to the attention engine, so it asserted "database matches, no file failures". On a book whose database differs from the journal, or whose stored file is missing or corrupt, the viewer showed the database/stored-files items and the server did not: the two lists diverged exactly where they matter most. Call find, which computes the check: every distinct stored blob is read and hash-checked once (a damaged blob is still reported per attachment filed from it), and Store::disk_check now makes the viewer's comparison through a snapshot of its data directory instead of reporting a match. The parity test now damages its own fixture (removes the receipt's blob, then changes a projected account row) and asserts both lists still agree item for item and both name the damage. On the demo books (138 attachments, 138 distinct blobs, debug build) books.start takes about 0.26 s warm, 0.29 s cold, so the real check costs no decision. |
||
|
|
0cf2f8f5c5 |
feat(package): one attention engine over PackageBooks (U0b)
Needs attention was computed by the viewer alone, while books.start hand-picked a handful of next steps. The human's list and the agent's could disagree, and a new item had to be written twice (or, as books.start showed, not at all). Move the engine to ledger-package over PackageBooks: one function, find/find_with(books, today, settings, integrity), reads the ledger, the journal's event types (an entry's origin), the attachments and their links, the statements, the reconciliations, and the database check. The viewer's attention module now only maps Target to Route; books.start turns the same items into its next steps (why = the item's title, top 12), so the list the human sees and the steps the agent gets cannot drift. PackageBooks gains settings, journal, statements, reconciliations and disk_check, implemented for Store and Snapshot, so both views answer alike. The W-9, reconciliation and 1099 predicates move beside the items to keep one copy. ledger-package gains ledger-config and serde_json (the receipt policy and the tool arguments the items carry). Tests: view_model.rs and spec_kept.rs pass unchanged; the new attention_parity.rs compares the viewer and books.start item for item on the demo books and on a book written entry by entry. just check green. |
||
|
|
3cab75c494 |
refactor(mcp): split server.rs into a file per tool group (U0a)
server.rs had grown to 12,566 lines: every tool handler, every helper and the request machinery in one file, so every unit that adds a tool touched it and parallel worktrees conflicted on every merge (plan/served.md 8.2 U0a, 8.0.3). The 98 handlers still in it now live one file per group under tools/, following the pattern tools/kept.rs and tools/projects.rs already use (`#[path]` declaration, `use super::*`, one `#[tool_router(vis = "pub(super)")] impl` per file): documents (invoices, bills, payments, voids, books.documents, books.document, the control check, the agings), entries (draft, line, post, reverse, get, search, the bank CSV import), reports (the statements, books.show, verify, digest, export, the CPA package, the P&L by class, with the export builders), assets, schedules, periods, reconciliation, parties (parties, vendors, customers, W-9s, the TIN vault, 1099s, entry parties), attachments (receipts, filed documents, links), settings (the handbook, books.settings, FX rates), chart, payroll, payouts, tax; add_class and list_classes join the existing projects file, since a project is a class with its facts. The helpers only one group uses moved into that group's file; the ones several groups share moved to tools/helpers.rs, re-exported by server.rs so every group reaches them through `use super::*` and no group file reaches into another's (resolve_instrument and the attachment/statement shapers moved there too, since the payment, entries and attachment tools all use them). server.rs keeps BooksServer, the store and its lock, the request machinery, call_tool/get_info, tool_router (which now composes the group routers; core_router is gone), and the shared helpers the plan names (as_of, hand_over, resolve_vendor, parse_doc_ref, preview_lines_for). This is a move, not a change: no accounting behaviour, tool name, description, annotation or schema changed. A dump of tool_router().list_all() — every tool's name, title, description, input and output schema and annotations, in order — is identical to main's (122 tools; the plan's "118" is stale). server.rs is 1,909 lines and has no #[tool] handler. The server's cross-group tests moved to server_tests.rs beside it (a #[path] module; fixtures in server::test_support) and each group's own tests into its group file, so server.rs stays under the 3,000-line bar. cargo test -p ledger-mcp: 99 passed; cargo test -p ledger-cli: 25 suites, 0 failed. |
||
|
|
bf91c5a7c2 |
test(settings): S4.1's Terms canonicalizes a short custom date (P4b S4.2)
The landed Terms::parse reads a date chrono reads and prints it padded, so "Due 2026-1-5" is one value with "Due 2026-01-05", not a refusal. The settings key still keeps one spelling; the test now asserts that canonicalization instead of refusing the short form. |
||
|
|
98320b3018 |
test(store): S4.2's shapes open old books and wait for consent (P4b S4.2)
The 8.0.6 checklist for this slice, which adds fields on party.updated
(an email and two default terms) and the invoicing settings it guards:
1. books_written_under_older_rules_still_open now hand-writes a party's
email and terms and an invoicing setting through JournalWriter, and the
books still open, verify and replay them.
2. A new old-books fixture, p4_s42_settings_parties, was written by the
version before this slice (
|
||
|
|
962b52c991 |
refactor(settings): the slice's contracts and notes (P4b S4.2)
invoicing.first_number's refusal (LedgerError::FirstNumberLocked) lands as the contract the store's check raises once invoices carry numbers (S4.3): a series that has begun is never renumbered (B-40, A-2). Also: the settings invariants name the number prefix's bound, update_party's doc says which facts wait for documents_v2, and the email check no longer reads as a single-arm match. Terms is S4.1's as it landed; this slice no longer carries a copy. |
||
|
|
02cb7f0254 |
feat(parties): an email and default terms per side (P4b S4.2)
A customer's email is copied onto an invoice's bill-to snapshot at issue, and a party's default terms (one as our customer, one as our vendor) are the fallback a document's due date takes when the document itself names none (A-13, rules.md B-39). Both are journaled as party.updated facts and guarded by documents_v2: in books that have not declared it the write is refused with the upgrade remedy and journaling nothing, and replay gives the same directory back. An email must be one plain address: one @, no spaces, no full TIN (the party text rule already refuses nine digits). Terms are typed and already canonical, so there is no second spelling to check. A party.updated naming a field this version does not know is now NewerJournal, never corrupt (B-27): an older version must say update, not damage; a known field with a malformed value is still corrupt. |
||
|
|
1fa77cff02 |
feat(settings): invoicing, bills and contact settings (P4b S4.2)
Eleven settings the invoice and bill workflow reads: the number prefix and first number, default terms, payment methods with their bank details and QR links, a footer, the unsent and reminder day counts, the bill reminder and discount-warning day counts, and the company's email and website that the seller snapshot prints. Three new kinds keep them exact: a count is a positive whole number, terms are canonicalized through Terms so "net 30" is stored once as "Net 30" (B-39), and a payment method list admits at most eight methods whose lines are at most 120 characters. A routing number is nine digits, so an ach or wire method's bank-detail lines are the one text exempt from the full-TIN check (A-3); every other text in a method is scanned. The keys need the documents_v2 feature (D-52): an older version doesn't know them, and the settings replay refuses an unknown key as newer. The constant, the known-features entry and the gate test land with this slice (its 8.0.6 work); S4.3 adds the document write call sites and must not redeclare them. A settings file now exports the [invoicing] and [bills] tables, a payment method as an inline table (never a [[payment_methods]] section that would land outside its table), and imports them back to the same values. The unknown-key test uses fax instead of website, which is now a setting. |
||
|
|
13282cf80e |
feat(arap): terms, numbers and line accounts in the core (P4b S4.1)
Documents could name only one revenue or general-expenses account per project, had no number of their own, and took a due date the caller had to work out. This slice gives the core the pieces the rest of S4 needs, and journals nothing: an old-shape document still writes byte for byte, so the owner's books need no upgrade for it. - Payment terms (B-39): Net n, Net n EOM, due on receipt and a custom date, with the early-payment discount and its exact bound (kernel::discount_max, Kani + bridge). A due date past the calendar's end is refused, never a panic, and a discount is refused with terms that have no net days. - Invoice numbers (B-40, A-2): first_number plus the count of numbered invoices, zero-padded to four digits and wider past 9,999, stored at issue, kept by a void; legacy invoices are uncounted. - Line accounts (A-10): a document line may name its account, and the entry posts one line per (account, project) part. An invoice line takes revenue or a liability holding no role (deferred revenue); a bill line an expense or a non-cash asset holding no role. Roles, cash accounts and headers are refused. - A line's amount is exactly its quantity times its unit price (kernel::line_amount, Kani + bridge): both or neither, never rounded. - A vendor's invoice number names one standing bill per vendor, with case, spaces and hyphens ignored, unless a duplicate reason says why (B-40). - The record fields S4.3 will journal: number, terms, bill-to and seller snapshots, customer reference, memo, the empty sends list S5 fills, and the bill's received_on, all optional and defaulted. The store refuses a line carrying an account, quantity or unit price before it rehearses or journals anything: those fields are journaled from S4.3's documents_v2 on, and I-3 will not let a public write replay without them. `document_line_fields_wait_for_documents_v2` proves the refusal, the unchanged journal and allocators, and that an ordinary line journals exactly as before. S4.3 removes this guard in the same change that journals the fields and adds its byte-for-byte test. The document commands keep their signatures; create_invoice_with and create_bill_with take the optional fields for S4.3 and for tests. They are the existing issue commands with more fields, not a new state transition, so no Lean Step is added. Kani: line_amount_is_exact runs in `just prove`; discount_max_is_bounded relates a product to a quotient and runs in `prove-slow` (163 s), as the proration charge harnesses do. just check green; just prove green. |
||
|
|
b7be5a5472 |
feat(bookspec): the checker crate, the Engine trait and the runner (P13)
bookspec is a specification first: every rule normative and sourced,
every rule demonstrated by concrete examples, and any accounting engine
checked against them through one interface (D-34). This moves the
examples' schema out of the test that happened to parse them and makes
the interface explicit.
- `bookspec/checker` (its own workspace, inside this repository until
the harness is stable): `Example`, `Setup`, `Event`, `Expect`, `Query`,
`Refusal` and the TOML parser, every structure `deny_unknown_fields`,
with `Example::from_toml` checking the cross-field shape (closed lists,
exact decimal amounts, dates, one event kind, `reverses` counting from
1, the shares summing to 1, commands accepted or refused and not both).
- `Engine` (`load`, `apply`, `query`, `claims`) and `Refusal { rule,
reason }`; the refusal's rule is the bookspec identifier, so a refusal
names the rule it belongs to. `Claims` also carries the option an
engine picks for a CHOICE rule.
- `bookspec/runner` drives any `Engine` through the examples and reports
per rule: pass, fail, not claimed. It compares the answer itself; an
engine is never asked whether it agrees with itself.
- `crates/ledger-core/tests/bookspec.rs` is the wanderbooks adapter over
`Store`: the loader parses through the checker, its own checks are
unchanged, and the new `the_runner_reports_every_claimed_rule_green`
runs all 56 examples through the trait. The IIF appendix is answered
through `ledger_package::export_iif`, the code its own test checks.
- `just check` runs the bookspec workspace's fmt, clippy, rustdoc and
tests too, so an example cannot rot (D-34, Z-17).
The plan's trait has four methods and no path for an example's
`[[changes]]` (a rename, the policy, an asset correction), which post no
entry: `change` is the fifth, required so an engine cannot skip those
examples silently. This is reported to the EM as the one departure.
One money rule holds here too: an answer's figures are exact decimal
strings, never JSON numbers, so no engine can round a cent through a
float.
|
||
|
|
9e67c8819d |
fix(tin): mask and refuse a comma-separated TIN (P7)
The scanner now finds a comma-grouped TIN, so the two places that hand raw text out or let it in must follow: the access log's digest and its 64-character client texts masked hyphens but not commas, and a filed file's printed-shape check refused 123-45-6789 but let 123,45,6789 in. The access log uses the shared redactor, so it inherits the fix; the filing scan keeps its own deliberately narrow shapes (a bank export's routing numbers and amounts must still be filed, B-26) and gains the comma renderings of an SSN's 3-2-4 and an EIN's 2-7. A comma-grouped amount is still no TIN and still files. No book, journal line or figure changes. |
||
|
|
c6527280e7 |
fix(parties): read every separator a TIN can be written with (P7)
A taxpayer identification number can be written 123,45,6789 or (123) 45-6789 as easily as 123-45-6789, and the scanner only knew the second: a comma-grouped number slipped through every party field, every refusal that quotes its input and the access log's masking, which is how a full TIN could reach a result the books promise never shows one (rules.md B-26, I-7). The scanner now reads a comma or a parenthesis between a TIN's 3-2-4 or 2-7 groups, and any run of spaces counts once however long it is, so 12 3456789 is an EIN. Commas and parentheses join only those groups: an amount's thousands separators (1,234,567.89 is 1-3-3-2) still read as an amount, and a nine-digit D-U-N-S number stays its own setting's kind (company.duns). The old run reading is unchanged for everything it found, which the property asserts; replay still reads a journaled line as P2 did (B-28). The truth table and the ASCII-equality property pinned the old separator set, so they were updated with it: the property is now has_full_tin_never_forgets_a_tin (what was found before is still found). No journal, entry or figure changes: this is a text scanner only. |
||
|
|
c1f9001cfe
|
feat: the invoice PDF, with stamps and each payment (P4b S3)
check / check (push) Canceled after 0s
ledger-import invoice_pdf (templates/invoice.typ): seller, bill-to, number, dates, terms, lines, total, each settled line and the balance due; money formatted in Rust (the template does no arithmetic), text as data, deterministic bytes. Stamps as a rubber stamp would mark it, in red: PAID (with the day it was paid in full: the invoice is then the customer's receipt), PARTIALLY PAID, PAST DUE, VOID; DRAFT in gray for S4's preview. ledger-package invoice_document builds it from the books as of a day: the derived figures (B-38), each payment that stands on its own line with its date, payment method and reference; re-created copies say so (the issued copy is kept from S4 on and never changes). books.document pdf hands it over as a download; the viewer's invoice page saves or opens it. The owner asked for the paid stamp with the payment's date and details (2026-10-24). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
b7b7f17163
|
feat(viewer): Invoices & Bills, a document's page, overview tiles (P4b S2)
check / check (push) Canceled after 0s
One sidebar item, Invoices & Bills (the owner's choice, 2026-10-24), with Money in (invoices) and Money out (bills) tabs: what is owed and overdue, and the list tied to its receivables or payables account (Agrees, or what to look at); filters Open, Overdue, Paid, Void, All; rows by due date, an overdue one saying how many days late, each named in full for assistive tech. A document's page: total, received or paid, still owed, status; its lines with their projects; its timeline, a payment later reversed marked so, each row opening its entry. The overview adds Owed to you and You owe on bills once the books have documents. Every figure is the document's derived state (rules.md B-38). The sidebar's rows (and the rail's) give way to 22 points, not 24, so 13 links still fit the owner's 1479x519 window unscrolled; the sidebar's tests compute heights from the link count; the 1440x920 golden is re-blessed for the new link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
6f0fd97db0
|
test: the burst-close test closes once every client has posted
check / check (push) Canceled after 0s
closing_during_a_burst_of_posts_loses_nothing slept a fixed 1.5 s before
closing and asserted every client had posted; under load one hadn't (a
false failure of just check on
|
||
|
|
7d2bf0b75f
|
feat(mcp): books.documents and books.document (P4b S2, read tools)
check / check (push) Canceled after 0s
books.documents lists invoices or bills, due date first, with what each still owes and its status, all derived from its ledger lines that stand (B-38): status open (default), overdue, paid, void, written_off, overpaid or all; party and due_by narrow it (what to pay this week). books.document shows one (ref invoice:N or bill:N) as of a day: lines with projects, total, paid, discounted, written off, outstanding, status, days overdue, and its timeline of every tagged ledger line, each saying whether its entry still stands. The over-the-wire test documents_list_and_timeline_over_the_wire covers a reversed payment; the happy-path journey covers both output schemas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
07f213c9e5
|
feat(mcp): void, control check, payment method and reference (P4b S1, part B)
check / check (push) Canceled after 0s
books.void_document voids an invoice or bill that should never have existed by reversing its own entry with the reason (refused while a payment stands on it, naming that payment). books.control_check ties receivables and payables to their documents (B-23, B-38 INVARIANT 1): balance, documents' total, lines that are no document's, the documents needing attention; books.verify checks the identity and books.digest lists its problems (control_problems). books.receive_payment and books.pay_bill take instrument (tagged in the same call) and reference (in the description), and answer the document's derived remaining and status. The books record payments; they never collect or make them (D-45). Docs, handbook and STATUS; the over-the-wire test documents_void_and_tie_out_over_the_wire; the happy-path journey covers both tools' output schemas. The processor fee waits for S6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
ebb5fcb46f
|
refactor!: files only by handle: uploads in, downloads out (D-46)
check / check (push) Canceled after 0s
The owner (2026-10-23, via the question tool): "Uploads only"; and before it: "What we would DEFINITELY want to avoid is passing a bunch of BLOBs through AI context windows. That's just a pure waste." No file passes through an agent's context, in either direction. In: the inline arguments are gone with the 256 KiB limit (INLINE_MAX, take_file's inline branch): content_base64 and its sha256 on books.attach_receipt and books.file_document, w9_content_base64 and w9_sha256 on books.set_w9, csv_text on the two CSV imports. A file comes only by books.begin_upload and `wanderbooks upload`, named by its upload_id; existing_attachment_id still names bytes stored already. The five filing tools deny unknown fields, and any inline argument to any tool is refused before it runs as an unknown argument pointing to books.begin_upload (access log: refused inline_file). Out: books.export (every format), books.asset_labels and books.get_receipt view bytes hand the file over (HandedFile: download command and expiry, D-29), as the CPA package and the W-9 do. Resource reads stay text. A token keeps up to 64 handed files (was 16). The access log's digest no longer exempts file bytes. Docs: handbook files-in-out and parties-and-tins, mcp-interface.md, rules.md, receipt-store.md, parties-and-attachments.md, plan/served.md (D-46; the inline decisions marked superseded), STATUS.md, testing.md (D-46: tests migrated to uploads and downloads, rewritten, removed, added). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
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 |
||
|
|
7741e96eb3
|
refactor!: one way to serve books: the stdio server is gone (D-44)
check / check (push) Canceled after 0s
The owner (2026-10-23): "We should probably just delete the stdio path altogether. THere's no point in that. And it sitting there unused will just lead to bitrot. Nuke it?" Books are served only by the books server (`wanderbooks server`), reached by agents through the bridge (`wanderbooks mcp --book ID`), which stays. Removed: `wanderbooks dev-stdio`, the `ledger-mcp` binary, and `ledger_mcp::run` (stdio and `--http` over a bare data dir), with all that only that mode had: - files the books make coming back inline: `HandedFile.content_base64` is gone, and `download` and `expires_at` are always there; - books with no access log or backups: `books.access_log`, `books.backup` and `books.back_up_now` are always offered, by scope; - the settings file read and reloaded (`legacy_files`), the vault key named by a file or `--vault-key-file`, and the books dir's key-inside-the-books and git checks (with `paths.rs`); - `wire.rs` (the bridge frames an agent's lines itself); - `books.settings`' file fields and `books.start`'s `settings_source`. `BooksServer` is built one way, from `Served` (a book as the books server serves it), in production and in the unit tests. An unknown tool is now a protocol error (mcp-tools.md). Inline file input is unchanged. Then the owner's three decisions (2026-10-23, the question tool): (a) `books.import_settings` is removed entirely (118 tools): old books come in whole by `wanderbooks books adopt`, which reads their settings file once; ledger-config keeps its settings-file parsing only as far as adoption uses it (`load` and the path-reading `effective_receipt_policy` are gone), and `books.chart_template` gives its settings as `books.set_setting` changes. (b) The viewer's loose-books-folder mode is removed (`--data-dir`, `--books-toml`, `--vault-key-file`, the settings file and config.json followed): it opens only books of the data root, reading settings from the journal and the vault from the book's own `vault/` and `keys/`. (c) `wanderbooks dev-stdio` has no stub: it is an unknown command like any other word. Also: an upload's expiry past the calendar is an error, never an empty string. Tests: every suite that drove the stdio server moved to crates/ledger-cli/tests/ and drives a real `wanderbooks server` on a data root of its own (tests/common), handed files by their download; mcp_stdio.rs is mcp_journeys.rs. Tests of what is gone are deleted and listed in docs/testing.md. Docs: plan D-44 (D-18 superseded), STATUS, testing, rules, mcp-interface, the handbook, README; justfile recipes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
a9de57d6cd
|
feat(viewer): thumbnails drawn at load, kept on disk by hash; no picture crashes it
check / check (push) Canceled after 0s
Tiles draw a thumbnail, not the file: the top of the first page, 560 pixels wide (never scaled up) and at most 420 tall, rendered by two workers of their own so opening a file never waits behind them. Every file's thumbnail starts when the books load, before the documents page is opened, and the live viewer keeps each as <sha>.png in $XDG_CACHE_HOME (else ~/.cache)/wanderbooks/thumbs/v1-560 (private to the user, written whole and renamed into place), so the next launch reads small PNGs, not PDFs. A file's name is its content's hash, so a thumbnail always matches the file the books hold; the blob is still read through the verifying store each time, so a corrupt or missing file is an error, never a kept picture. From review: a picture or page past the device's largest texture is scaled to fit (egui panicked on it, in the full view too); a renderer panic is that file's error, not the end of its worker; a thumbnail asked for before the cache moved is dropped, never shown over a newer one; text thumbnails keep 2 KiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
7cc08a0039
|
feat(viewer): Markdown and plain-text documents are drawn as text
check / check (push) Canceled after 0s
A text/markdown file (or text/x-markdown) is drawn as formatted text by egui_commonmark, with no image loading, so a document never makes the viewer read a file or the network; any other text/* type as its selectable text. UTF-8 only (a leading byte-order mark dropped), at most 2 MiB; past that or not UTF-8, the viewer says why. The documents page's tile shows the text's first lines. The SHA-256 row is cut to its column (it ran off the panel and widened the page under it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
d64e0e9cdf
|
fix(k1 tax basis): odd cents of property basis run cumulatively (B-36, D-38)
check / check (push) Canceled after 0s
The tax-basis K-1 shared each entry's built-in gain, property without a basis, prepaid at book and property distributed among the members entry by entry, every tied odd cent to the first member: three equal odd-cent properties contributed 50/50 left the members three cents apart. Apply D-38's rule (the closes' cumulative largest remainder, through kernel::apportion_cumulative): tax_capital::RunningSplit keeps each member's exact running share, rounds the running shares afresh to the cent and hands out the change, so no member's running share is ever a cent from exact and tied cents alternate. A negative running whole is rounded as its magnitude, a zero one by the sign of the whole just split, so a run of losses mirrors its gains part for part. Exact running shares are taken to ten places before rounding: a sum of Decimal thirds can sit a hair under its cent (the law found 399.93 as 399.9299...). tax_capital::entry_parts runs the splits over every posted entry from the books' first, in date order, whatever span is asked for, with one count per set of members with a part in the entry: a member never takes a cent of an entry they had no part in (Item L's capital contributed is the basis of the property that member contributed), and the count never restarts, since its weights are each entry's own amounts, not shares. An entry's reversal takes its original's split back out of the count (RunningSplit::unsplit), so an entry's cents are the same in every span and next year's beginning is this year's ending. The K-1's per-entry lines come from k1::entry_lines, which k1_detail now uses too. The book property each member contributed in an entry stays split entry by entry (each member's property must stay within their own credit); B-36 says so. Tests: tied_odd_cents_of_property_basis_run_cumulatively (fails on the per-entry split: 665.86 / 665.83); a_member_outside_an_entry_never_ takes_its_cents (three members; fails with one count for all members), an_entrys_odd_cent_is_the_same_in_every_span (a reversal across the span's start; fails with the count carried span by span or the reversal not taken out), the_split_runs_in_date_order (fails in posting order); the law a_running_split_never_drifts_a_cent (5,000 cases: the largest-remainder rounding of exact integer shares, ties to the first, and the negated run mirrored; fails with ties to the later member or a zero running whole rounded as a gain); unit tests for one split, alternating ties, a member outside a split, the mirror at zero, unsplit and the thirds case; bookspec example partnership-k1-tax-basis-odd-cents. rules.md B-36, the handbook (tracing figures), STATUS, testing.md and verification.md updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
535874e0f3
|
fix(cash flow): a payment to a government is other operating, by its payee
The P3 cutover review's open point 2: a state's LLC filing fee, booked to an organization-costs account (tax category organization_startup), read as "Cash paid to suppliers and employees". B-14 rule 5 sent a payment to class (g) only when every operating line's account was taxes_licenses, so the account, not the payee, decided. ASC 230-10-45-17, read in EY's guide that reproduces it (Statement of cash flows, July 2026, p. 43; the PDF re-fetched and its SHA-256 recorded in docs/spec/sources/fasb-asc.md), lists apart "(b) Cash payments to other suppliers and employees for other goods or services" and "(c) Cash payments to governments for taxes, duties, fines, and other fees or penalties". 45-25's classes name only income taxes (f) among the latter, so the rest are (g), other operating cash payments. A formation filing fee is rightly an organization cost (its account and tax line), and still a payment to a government: the payee is what 45-17(c) is about. So rule 5 now sends a payment's government part to (g): of an operating payment the structure classes (d), what went to a party with the government_agency or tax_agency role (the chain root's parties, so a reversal keeps its original's class) is (g), on any account; and, as before, a payment whose every operating line is on a taxes_licenses account is (g). Every party a government: the whole payment. A government and a supplier named together: the payment is split by their resolved shares (B-35), the government's part (g), the rest (d), since ASC 230-10-45-22 has "each separately identifiable source or each separately identifiable use" classed "on the basis of the nature of the underlying cash flows" (EY p. 44; quoted in fasb-asc.md; it names the three activities, read for 45-25's classes too as a CONVENTION). The government's part is the payment x its share / the whole, rounded half to even to the base currency's places, the rest exact, so the line still sums to the payment and a reversal mirrors it. Shares that don't resolve leave the whole payment (d). One naming no party is decided by its accounts. Receipts are untouched (a refund from a government stays (c)). No total changes, only which operating row a payment is in. Two limits, from the review of this change: a government selling goods or services (postage) is 45-17(b)'s supplier "for other goods or services", so the rule says, and the handbook tells the agent, to add such a payee as a vendor, never with the government_agency role (the books can't tell what a payment bought; the role says it). And payroll keeps its structure (CONVENTION): an entry with a line on the salary expense or a payroll payable control account, or on a pay tax category (salaries_wages, officer_compensation, employee_benefits, pension_profit_sharing), stays (d) whoever it names: withheld taxes remitted are the employees' pay, and the payables mix them with the employer's taxes, which a remittance can't tell apart. So a remittance to the IRS is (d) as it was before this change; the employer's payroll tax paid on its own expense account is a tax, (g). The first company's books have this case: their state filing fee moves from "Cash paid to suppliers and employees" to "Other operating cash payments" at every date once its entry names the state's agency with the government_agency role and its own entry is the one whose cash the statement lists (paid from the bank, or on a card that is a lender's credit, D-42, or by a member through the members' account while it is a financing account, B-14 rule 11, the way that fee was paid); charged to an operating-payable card, its cash leaves in the card payment, whose payee is the bank, and it stays with suppliers. STATUS says how to check both, and that the cutover goldens' cash-flow snapshots will differ on the two class lines only (totals the same), so the next cutover diff isn't read as a regression. Docs: rules.md B-14 rule 5 (the rule, its source quoted, the date and what it was before); fasb-asc.md (45-17(b) and (c) quoted exactly, with the guide's SHA-256); the handbook's entries section (name the agency as the party on a tax, fee, fine or penalty paid to a government; a shared payment named with both amounts; a government selling services added as a vendor; payroll remittances stay with pay; a fee on a card follows the card payment); fasb-asc.md also quotes 45-22 and 45-22A; bookspec example cash-flow.government-fees.toml (a filing fee and a lawyer on one organization-costs account, a license on taxes and licenses with no party, a penalty to a tax agency on the wrong account, paper from a supplier, an annual report paid through a registered agent, 37.00 the state's and 100.00 the agent's; every figure by hand) with a new `class.<a..g>` key in the cash_flow query (bookspec README; the runner checks every class the statement has is given); testing.md; STATUS. Tests: ledger-core tests/spec_cash_flow.rs `a_payment_to_a_government_is_other_operating` (the filing fee and a penalty to a tax agency (g); the same account paid to a lawyer, or with no payee, (d); its reversal next month keeps (g); a refund (c)), `a_government_payee_moves_a_fee_on_any_account` (a taxes_licenses license with no party (g); a payment shared half and half between a government and a printer split 50.00 (g) and 50.00 (d), the government alone all (g)), `a_shared_government_payment_splits_to_the_cent` (a third by percent of 100.01: 33.34 (g), 66.67 (d); amounts split as given; the reversal mirrors to the cent), `a_payroll_remittance_to_a_tax_agency_stays_pay` (withheld taxes remitted from a payable holding its payroll role, and pay on a salaries_wages account, stay (d) with the IRS named; the employer's tax on its own expense account is (g)), `a_government_selling_services_is_a_supplier` (postage to a vendor (d); given government_agency, (g): the limit the handbook guards); src cashflow.rs `tests` `a_government_part_rounds_half_to_even` and the proptest `a_government_part_sums_and_mirrors`; the bookspec example; from the P4 review, `a_reversal_takes_its_originals_payee` (a fee and its reversal next month land in one class, the chain's payee deciding: the books keep a chain's parties one, so reading the reversal's own parties is an equivalent mutant) and `a_shared_government_payment_a_member_paid_splits_both_ways` (a shared payment a member paid: each part with its own on_behalf; fails with the on-behalf part unsplit). docs/mcp-interface.md: books.cash_flow and the Roles paragraph say a government or tax agency payee makes a payment other operating, and a shared one two operating lines. Broken to confirm: without the payee check the five spec tests and the example fail; without the split for mixed payees the two shared-payment tests and the example (class.d -549.50) fail; without the payroll exclusion the payroll test fails; rounding toward zero fails both unit tests; without the tax-category route `operating_classes_follow_asc_230_10_45_25`, the second test and the example fail. The viewer's cash flow page, rendered on made-up books before and after: the fee moved from "Cash paid to suppliers and employees" to "Other operating cash payments", every total the same; and on made-up books with an annual report shared between the state and an agent: each part on its own class line, every total the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
6c587086a0
|
feat(iif): carry each line's project in CLASS, the classes declared first
The P3 cutover review's open point 1: entries carry projects (classes, B-37), but the QuickBooks Desktop file (B-34) left the CLASS field empty on every line, and neither B-34 nor the README said so, though B-34 rule 6 says nothing changes silently. Intuit's IIF Import Kit (vendored in docs/spec/sources/intuit-iif.md, the kit's SHA-256 unchanged): the `!CLASS` header help says "Only the NAME column is required" and a subclass's NAME is its parents' names first, joined by colons; the kit's "Import First - List Declarations" example declares `!CLASS` rows before any transaction, and its "2004 Deposit (Income)" example names the class line by line in CLASS. So the file now declares every class in a `!CLASS` list after the accounts and before `!TRNS`, and writes each line's class in CLASS (empty where the line has none). Books with no class write no list, byte for byte as before. A line whose class wasn't declared is refused (IifError::UndeclaredClass), never written classless. A class's NAME follows B-34's rules for an account's own name: plain ASCII (rule 3), a colon written '-' (it would start a subclass), trimmed, '?' if nothing is left, cut to 31 characters: Intuit's qbXML Onscreen Reference gives `ClassAdd`'s Name "maxDT" : 31 (and 31 in the Canadian, UK and Australian desktop editions; 100 only in Online), the same as its `AccountAdd` Name (both JSON files vendored by SHA-256); Conductor's QuickBooks Desktop API reference agrees and says the name is case-insensitive. Classes whose NAMEs would match ignoring case get " (id)", round after round, as accounts get " (code)", except that among names that match, those already written differently from the books (made safe, or tagged) yield first, a second " (id)" if need be: a class's own name is kept unless another class's own name is the same ignoring case (classes 1 "Café", 2 "Cafe", 3 "Cafe (1)" are written "Cafe (1) (1)", "Cafe", "Cafe (1)"). Each class is tagged twice at most and ids differ, so none is left alike (a hand-built list is refused). Each class name written differently is an IifChange (IifField::ClassName), said one line each in the warnings and in the package README, whose "written differently" paragraph now names class names' limits; its IIF line says CLASS is each line's project when there are classes. The MCP and viewer notes count "account or class name(s)". API: `iif_classes(ledger)` builds the list; `journal_to_iif` takes it (`&[]` for none). Docs: rules.md B-34 rule 8 with its sources; intuit-iif.md (the `!CLASS` help, the list declarations and deposit examples, Intuit's SDK department page, the qbXML Onscreen Reference's ClassAdd and AccountAdd and Conductor's class reference, each with SHA-256); mcp-interface.md (books.export iif, the package note); the bookspec example iif-quickbooks-journal-file.toml (two projects: one carried and renamed, one only declared; every line derived by hand); testing.md; STATUS (open point 1 done; point 3 waits for the close, the owner said). Tests: ledger-import `iif_writes_each_lines_class_and_declares_the_classes_first`, `iif_class_names_fit_quickbooks_and_every_change_is_listed`, `iif_class_names_written_as_the_books_have_them_are_kept`, `iif_refuses_an_undeclared_or_unfit_class`, property `iif_class_names_never_clash`; ledger-package `the_package_iif_carries_each_lines_project` and the bookspec example. Broken to confirm: CLASS written empty fails the two exact-file tests, the package test and the bookspec example; the " (id)" tag dropped fails the names test and the property; tagging every name that meets (no yielding) fails the kept-names test and the property (its shrunk case, "A-B" beside "A\uFF1AB", kept in the regressions file). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
528c070605
|
feat: invoices and bills take a project, line by line (B-37 item 8)
A client project's invoiced revenue and a contractor's billed cost could never count toward the project: books.create_invoice and books.create_bill posted at once with untagged lines, and only a draft's line takes a class. Job costing (B-37 item 3) collects a job's billings and the supplier invoices for its direct costs on its cost sheet, so each document line now takes an optional project. The owner decided it on 2026-10-22 (plan D-43, rules.md B-37 item 8): each line takes one project or none; a document spanning N projects is N lines, each with its own amount; no percentage split, so every figure is exact; a cost truly shared among projects names none on the document and reaches projects only as entries of their own under the company's monthly allocation convention, when books.conventions has one (otherwise it stays untagged and the agent asks the human). The reasoning, stated in B-37: job costing charges a job the costs traceable to it, whole, and reaches shared costs only by allocation on a stated basis. ASC 340-40-25-7 counts as directly related both the costs traceable to a contract (25-7(a), (b), (d), (e)) and allocations of costs that relate directly to it (25-7(c)); 25-8 expenses general and administrative cost. The books carry the first whole on the document's line and the second as the allocation convention's own entries; neither is a percentage on the document (sources/fasb-asc.md, 25-7 and 25-8 added). The handbook's projects rule and the line's class parameter say the same, and no handbook sentence lets a shared cost be split across a document's lines. The accounting: the document's entry posts one revenue (an invoice's) or expense (a bill's) line per project its lines name, in the order each is first named, each the sum of that project's lines, and the untagged lines' sum as one more line (arap::document_parts, conservation checked by a proptest). A document naming no project therefore posts exactly the one line every document posted before. The receivable or payable line is never tagged: it is a balance-sheet line, which item 3 counts nothing; the payments (receive_payment, pay_bill) move cash against it and take no project either. Nothing but the projects moves: the P&L, the 1099 worksheet (payments made, B-19) and party totals (what moved the money, B-35) read the same with or without the tags (twin-books test); the projects with the untagged lines still partition the P&L. - ledger-core: DocumentLine.class_id (serde-omitted when none, so old documents serialize as before); create_invoice / create_bill split the revenue or expense side by project and tag it; an unknown project is refused whole (atomically). - ledger-store: a document line's project is journaled as class_id on invoice.created / bill.created, and the entry's line as entry.line_class_set right after its entry.line_added (the entry.posted lines carry class_id, as a hand-built entry's do). Behind a NEW journal feature, document_line_projects (KNOWN_FEATURES, declared by new books, asked for before the first journaled line, not even the control accounts written on refusal); not `projects`, which the live books already declared: the owner consents again at the next upgrade. A malformed class_id is corrupt; null or absent is none. Untagged documents journal byte for byte as before; old journals replay unchanged. - ledger-mcp: each line takes class (id or name, resolved like add_line's, every one before anything is written); the result gains notes for a finished or abandoned project, and on an invoice for an on-spec project (is there now an agreement? on spec a hoped-for payment is no receivable, B-37 item 4), tagged anyway; books lacking the feature refuse with format_upgrade_pending. books.get_entry shows a line's class_id and project. The handbook's "an invoice or bill takes no project yet" caveat now depends on the books: gone where the feature is declared, an "until their format upgrade" note where it isn't. - ledger-gui: an invoice's or bill's page (its entry's) lists the document's lines, each project a link that opens it; the demo's client invoices are tagged to "Client work", whose snapshot is now its revenue less its contractors. Looking at the page in a narrow window found a tagged line's tag pushing the amounts past the pane (any tagged entry): the name is now cut to leave the tag room, the tag itself at most half the cell. - bookspec: an `invoice` event, and `lines` with `project` on a bill or an invoice; example projects-invoice-and-bill-lines-count-by-project, worked by hand, both projects client work (Zeta 3,500.00 - 1,200.00 = 2,300.00; Harbor 200.00; untagged -1,750.00; together the P&L's 750.00; Dana's 1099 payments 1,250.00). - fuzz: fuzz_store_replay can declare journal features. - Tests: core, store, MCP, viewer, the P2-served cutover fixture and a viewer/server parity test (its month lists the same length, not empty); a tagged document's reversal nets its projects to zero, live and replayed; books.get_entry's class_id; the handbook served as a resource both ways; each fix broken to confirm its test fails (docs/testing.md). Docs: rules.md B-27 and B-37 items 3 and 8, sources/fasb-asc.md, plan/served.md D-43, mcp-interface.md, testing.md, parity.md, plan/STATUS.md. No proof changed (no kernel arithmetic: document_parts sums with the existing document_total). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
e19df28bf2
|
Test: the closing door is measured from the close, after a books route
check / check (push) Canceled after 0s
while_closing_the_door_stays_open_refusing_as_requests_come raced the server: its only request before the close was a route for no books, which LINGER doesn't count, so when the server reached the linger before the first ping it found none in the last 500 ms and rightly shut at once (about 3 runs in 8 on a loaded machine). It now sends a books route just before the close, measures from the close, and holds the 3 s only when no request waited LINGER or near it. Checked: 20 of 20 under load; with LINGER_MAX 1 s or LINGER 0 it fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
6e7fe50e53
|
Test: the tall sidebar's pixels are compared only where they were blessed
check / check (push) Canceled after 0s
CI's software renderer antialiases the same shapes differently (4635 pixels at 1440x920), so a_tall_sidebar_is_unchanged failed there. The shape hash stays the check everywhere; the pixels are a local check with SIDENAV_PIXELS=1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
641184d3a3
|
viewer: the sidebar's links fit short windows
check / check (push) Canceled after 0s
The owner's viewer at 1479x519 (scale 1) showed Overview through half of
Accounts above the status card; Assets, Parties, Projects, Documents,
Company and Journal sat in a scroll area with no sign that it scrolled,
so the links seemed to go behind the status box.
The sidebar now gives way in steps, each only when the one before is not
enough, so a window where everything fit looks exactly as it did
(pixel-checked against main at 1440x920, tests/goldens):
1. The status card folds to its one summary line, "● Live · event 559 ·
✓ verified". Hovering it shows the three checks; right-click keeps the
card's copy menu. The line is the fullest wording that fits the card
(summaries), never cut off: a failed check is named ("⊘ Database
differs", "⊘ 2 files damaged") before the event and the word "Live"
are kept, and only then shortened to a count. Whatever the line
leaves out, its tooltip says.
2. The rows tighten: the footer's menus, the gap under the header, the
spacing between links, then the rows' height, down to 24 points, in
the same font. The rail tightens the same way, also to 24 points.
3. Only then do the links scroll, with a soft fade and a chevron at each
clipped edge (the rail's chevron sits centred on its own pill, clear
of the icons), and the current page's link is scrolled into view
whenever the page changes.
Links that overrun their room by under half a pixel count as fitting, so
the card folds where main's links began to lose a visible sliver, not a
point sooner at fractional scales.
At 1479x519 and scale 1 all twelve links show unscrolled in the sidebar,
and at 900x519 in the rail, in every footer state (Live, Snapshot, each
database state, damaged files, no Books menu).
Tests (tests/sidenav.rs, fixture books with a footer like the live one):
every height from 300 to 920 px at scales 1, 1.25, 1.5 and 2, sidebar and
rail, for each footer state: no link under the footer, links cut off only
when they scroll, and nothing scrolls from 519 px up at scale 1; the
owner's window in sidebar and rail; the folded line fits its card and
names a failed check, its tooltip the three checks; the scroll cue and
scrolling to the current link; the tall sidebar unchanged. Unit tests for
fit, its order of steps, the footer's memo and the folded line's
wordings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
|
||
|
|
3eda95eb3c
|
Projects: the snapshot of a project's figures
check / check (push) Canceled after 0s
The project page opens on a snapshot: revenue, direct costs, the contribution before overhead and its margin of revenue (a segment margin: revenue less every cost traced to the project, fixed and variable; B-37 item 7), in the black or red, and for on-spec work the cost unrecovered if repaid. To date or this fiscal year. By month, revenue and costs with the net to date in its own strip; costs by category; the list colors each net by its sign. books.project carries the same figures, and a parity test runs the viewer and the MCP on the same books to the cent. Tests honor TMPDIR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
d24653558c
|
Tokens: one keyring item per token, read back; the bridge says a locked keyring; token check
check / check (push) Canceled after 0s
On the live books a reconnected bridge got no token from a keyring that couldn't be read, was told to "make a token", and after `token create --replace` kept on with the new token in memory while `download` read a revoked one from the keyring. - tokens.rs INVARIANT 4: keeping a token removes every item for its account in every collection, makes one and reads it back; reading finds every item and, finding several, refuses with how to keep one again; a locked or unreachable keyring is KeyringError::Unavailable, never "no token". The logic is shared by the OS keyring and the test files (now a folder per collection a test can lock, make unreachable, or plant a second item in). - make_token reads the token back before saying anything, or revokes it. - The bridge tells the agent the keyring is locked or unreachable and the token is fine (or that it holds several items), never to make a token; it reads the keyring again before every new session, even with a token held. - `wanderbooks token check --book ID [--name N]`: whether the token kept here is the books' live one, asked by its SHA-256 (admin op token_check), never its text. 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 |
||
|
|
0495726001
|
P3 review: the surface, docs and proofs
check / check (push) Canceled after 0s
Split kernel::entry_flows into entry_movements and flows_of so the reversal-mirror proof fits CI; prove runs the fast harnesses, prove-slow the 8 slow ones (own job in scheduled CI), prove-all both. Laws in verification.md renumbered 1-31 with a table of all 55 harnesses. MCP docs checked against the 115 tools (about 60 fixes) with a test that every tool is documented; rules.md cross-references fixed; bookspec statuses and loader tightened; viewer notes wrap and accounts sort by code; STATUS.md says P3 is built, cutover pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
2c5b5a7171
|
P3 review: P2 can read the books until the owner consents
check / check (push) Canceled after 0s
A schedule-convention setting, the two new tax categories, a disposal before the in-service date and a depreciation catch-up each need their feature now (tax_categories_gain_exempt, disposal_before_service, depreciation_catch_up), so no write before consent leaves books P2 refuses or misreads; replayed names are read with P2's TIN detector (B-28). The cutover runs end to end through the real P2 and P3 binaries in a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
6288ead747
|
P3 review: asset bases and the tax-basis K-1
Schema 13 is as shipped and the asset columns are schema 14 (shipped migrations pinned by hash); the tax-basis K-1 leaves net income open (only the return has it) and takes a contributed prepaid plan at its book amount as its section 723 basis; a disposal of contributed property names its 704(c) gain or loss; aliases compare by meaning; fixture amounts made up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
2ae93fb2d6
|
P3 review: the same figures agree across the pieces
A distribution declared to a payable and then paid is financing when paid, the declaration disclosed (ASC 230-10-45-15(a)); a disposal's depreciation lines count as depreciation in the cash flow and the tax-basis K-1; an as-of asset list says the status of that day. Seam tests tie the members' statement, K-1, party totals, cash flow, 1099s, depreciation and the CPA package together on three dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
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 |
||
|
|
3a9aa92315
|
P3: the books' format moves only with the owner's go-ahead (D-39)
Books an update could write a newer format for open on their old format and are asked at every open; anything needing a new feature is refused until the owner agrees with the passphrase, after a verified backup that includes the keys. New books start on the current format. Each projection schema has its own database file, so an older version still opens the books until the owner agrees. 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 |
||
|
|
c2ceabbae9
|
P3: party totals by what moved the money, once (defects 14, 11, 24)
Each party's part of an entry is signed as its own account would show it, with a direction and its cash and in-kind amounts; a reversal is its original negated. Totals on a cash or all basis: for a member, all equals the members' equity statement and cash the K-1's cash. Money is cash, cards and the financing accounts, and a liability credit never pays its own creditor (creditor_of), so accruals and declared distributions count once. tin_last4 from the TIN or W-9 facts. 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 |
||
|
|
8255ac2103
|
Tests and docs: no figures from the first company's books
check / check (push) Canceled after 0s
The odd-cent regression's net loss and its splits, the website's example Apple charge and the GUI plan's example receipt now use made-up amounts. 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 |
||
|
|
2c29d48ab9
|
P3: the IIF in plain ASCII with every change listed, and the CPA package as of one day (defects 7, 21)
The CPA package's general-journal.iif now holds every posted entry through as_of (closing entries left out, D-36), written as plain ASCII with CR LF, DOCNUM the entry id, QuickBooks Desktop's field limits and subaccount types honoured, and every character written differently listed in the export's warnings and the README (grouped by substitution, TINs masked). The 1099 worksheet stops at as_of too. PDFs print CJK, Arabic, Hebrew, Thai and Devanagari (Noto fallbacks, OFL), and so does the viewer. has_full_tin reads Unicode separators. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
1e8f9275b9
|
Plan and a doc comment: no figures from the first company's books
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |
||
|
|
a7f3cc09a9
|
P3: odd cents split the same running way everywhere (defect 17, D-38)
check / check (push) Canceled after 0s
Members' shares of earnings are split cumulatively by largest remainder in the closes, the members' equity statement, the K-1 worksheet and the viewer, so a member's capital on a given day never moves by a cent when its month is later closed. Old closes replay as they were; the first P3 close carries any true-up. Feature members_cumulative. Kani and Lean proofs of the bounded drift; bookspec examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj |