Attachments, statements, reconciliations, TIN vault, counterparty policy

MCP: books.link_attachment, unlink_attachment, attachments; link_to on
attach_receipt and file_document; record_statement, statements;
books.reconcile previews and, confirmed against a recorded statement,
records the reconciliation; void_reconciliation, reconciliations;
import_bank_csv links its source file; digest lists unreconciled
accounts.

TIN vault: books.set_w9 takes the TIN and the form into the encrypted
vault (last four in the books); books.vendor_1099 include_tins and
save_csv_to for the CPA's copy; books.get_w9; books.purge_tin. A leak
test scans every tool output, the journal, the database, and the blobs.

[books] parties = off | suggest | require (default suggest): every
preview names the entry's party or suggests likely ones; require refuses
to post until it names one or is marked as having none (entry.no_party).

Viewer: documents on every subject page; statements and reconciliation
history per account; reconciliation pages; reconcile and
no-counterparty attention items; party money by period (paid out,
received in, net, what for, by month); Needs attention gets its own
full-width section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-15 15:42:37 -06:00
1 parent a299077991
commit 08ce558ed3
41 files changed
+8463 -465

No files matched your search

+67 -3
View File
@@ -106,6 +106,13 @@ bill, payroll run, documents, and history. Members link to their capital
accounts. Global search finds parties. Needs-attention: a contractor
paid without W-9 facts; a 1099 party paid over the threshold.
Money with a party (the owner, 2026-10-15: "how much have I spent on Apple
versus how much did I get back"): paid out, received in, and net, for a
chosen period (this year, last year, any year, all time), from
`ledger_core::parties::activity`; what it was for (the non-money lines by
account); a monthly out-vs-in chart; the 1099 amount for the calendar
year. The directory shows the same columns, sortable.
## The TIN vault
Decision (the owner, 2026-10-15): wanderbooks holds full TINs, so the 1099
@@ -138,6 +145,21 @@ worksheet it hands the CPA is complete, but never in the journal.
contains a nine-digit run.
- **Books without a vault** (another machine, a restored backup) still
open and verify; the 1099 worksheet then says which TINs are missing.
- **Tools** (built; `docs/mcp-interface.md` "Tools — the TIN vault"):
`books.set_w9` takes `tin` + `tin_type` and `w9_file_path` (the form
goes to the vault, never the attachment store) and journals the last
four with `vault_record`; facts that disagree with the vault's TIN are
refused, and clearing facts with a vault record needs a purge first.
`books.get_w9 {party, save_to}` writes the form to a new file outside the
books dir and returns only the path. `books.purge_tin {party, reason,
confirmed?}` previews, then deletes the records (no key needed) and
journals the purge as `party.w9_set` with `vault_record` cleared and
`kept_at` saying when and why. `books.vendor_1099 {include_tins: true}`
is the one output with full TINs (with a warning; `save_csv_to` writes
the CSV outside the books dir). A key file inside the books dir is
refused. `tests/spec_vault.rs` scans every tool response, the server's
log, and every file in the books dir outside `vault/` for the TINs in
any formatting.
## Attachments
@@ -158,6 +180,14 @@ that entry). A receipt is an attachment linked to an entry; the receipt
policy counts links to the entry, as now. A file stored once can be
linked to many subjects (no more refiling).
Tools (built; `docs/mcp-interface.md` "Tools — attachments"):
`books.link_attachment`, `books.unlink_attachment` (refused for a posted
entry's last receipt under the receipt policy, and for a standing
reconciliation's statement), `books.attachments {subject}`, `link_to` on
`books.attach_receipt` and `books.file_document` (all or nothing), and
`links` on every listed attachment. `books.file_document` takes the kinds
below as its category (`receipt` is `books.attach_receipt`'s).
### Kinds
`receipt`, `statement`, `contract`, `formation`, `legal`, `tax`,
@@ -177,11 +207,23 @@ is never edited; a later one supersedes it; `reconciliation.voided
history with its statement; Needs-attention: an account with activity not
reconciled through last month's end.
Tools (built; `docs/mcp-interface.md` "Tools — bank reconciliation"):
`books.record_statement`, `books.statements`, `books.reconcile` (preview
by default; `statement_attachment` + `confirmed: true` records it),
`books.reconciliations`, `books.void_reconciliation`; `books.show
reconciliation`; `books.digest` `unreconciled_accounts` (the `[books]
cash_accounts` and liabilities with statements, not reconciled through
last month's end). The statement of a reconciliation not voided can't be
unlinked from it.
### Import sources
`books.import_bank_csv` files the CSV (kind `import_source`), links it to
every entry it created, and records the row each came from, so every
imported line traces to its source row.
Built: file the export (`books.file_document` category `import_source`)
and pass `source_attachment_id` to `books.import_bank_csv`; it is linked
to the bank account imported into. Not to the drafts it creates: a file
linked to an entry counts as its receipt, and a bank export is not one
(DECISION, 2026-10-15, building the MCP tools). Recording the row each
draft came from is not built yet.
### Projection
@@ -210,6 +252,28 @@ the digest, the viewer's Needs attention, and the session-start hook, and
enforcement where a rule is a rule (tax basis at registration). The
standard: do it now, completely.
### Counterparties
Decided (the owner, 2026-10-15): the books should be easy to make complete
without forcing one style. `[books] parties = "off" | "suggest" |
"require"`, default `"suggest"`:
- `off`: never mention counterparties.
- `suggest`: a draft or preview without a party names the likely one
(existing parties matched against the description, the receipt's text,
the vendor), or offers to create one; `next_steps` carries the exact
call. Nothing is refused.
- `require`: posting is refused until the entry names a party or is
marked as having none (transfers, depreciation, amortization, closes
are exempt by kind).
Also: memorized matches (journaled rules like description contains
"OPENROUTER" → OpenRouter, applied by bank-CSV import and new drafts,
reviewable and removable); a backfill punch list grouping untagged
entries by likely party for one confirmation per group; 1099 nudges (a
party given contractor or landlord, or a payment that looks like
services to an individual, asks for the role and the W-9).
## After that: updates (check and seamless upgrade)
Decided (the owner, 2026-10-15), after the completeness engine. New versions