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

+33 -10
View File
@@ -260,7 +260,7 @@ impl Vault {
}
fn path(&self, party: u64, kind: Kind) -> PathBuf {
self.dir.join(format!("{}-{party}.bin", kind.name()))
record_path(&self.dir, party, kind)
}
fn aad(party: u64, kind: Kind) -> Vec<u8> {
@@ -361,18 +361,41 @@ impl Vault {
/// Delete everything the vault holds for a party (INVARIANT 6). Returns
/// whether anything was there.
pub fn purge(&self, party: u64) -> Result<bool, Error> {
let mut any = false;
for kind in [Kind::Tin, Kind::W9] {
match std::fs::remove_file(self.path(party, kind)) {
Ok(()) => any = true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(Error::Io(e)),
}
}
Ok(any)
purge_records(&self.dir, party)
}
}
fn record_path(dir: &Path, party: u64, kind: Kind) -> PathBuf {
dir.join(format!("{}-{party}.bin", kind.name()))
}
/// What the vault in `dir` holds for a party, `(TIN, W-9 file)`, without
/// the key: whether each record's file exists (nothing is decrypted).
pub fn records_held(dir: &Path, party: u64) -> Result<(bool, bool), Error> {
let exists = |kind| match std::fs::metadata(record_path(dir, party, kind)) {
Ok(_) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(Error::Io(e)),
};
Ok((exists(Kind::Tin)?, exists(Kind::W9)?))
}
/// Delete everything the vault in `dir` holds for a party (INVARIANT 6),
/// without the key: purging must work even where the vault can't be read
/// (the key lost, or books restored on another machine). Returns whether
/// anything was there.
pub fn purge_records(dir: &Path, party: u64) -> Result<bool, Error> {
let mut any = false;
for kind in [Kind::Tin, Kind::W9] {
match std::fs::remove_file(record_path(dir, party, kind)) {
Ok(()) => any = true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(Error::Io(e)),
}
}
Ok(any)
}
/// Write a file readable by its owner only (0600 on Unix), creating its
/// directory.
fn write_private(path: &Path, bytes: &[u8]) -> Result<(), Error> {