P2 slice 5: files in and out

Files reach the books through the server: uploads by hash and size,
then filed; exports (the CPA package, the 1099 worksheet, W-9s) handed
over as downloads from the server, never read off disk by the agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-20 11:14:21 -06:00
1 parent d1f3ad891a
commit b26c099a68
44 files changed
+5075 -2739

No files matched your search

+13 -8
View File
@@ -371,6 +371,11 @@ fn b64(bytes: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(bytes)
}
/// The SHA-256 (hex) a file given inline comes with.
fn sha(bytes: &[u8]) -> String {
ledger_store::journal::sha256_hex(bytes)
}
const CHART: &[(&str, &str, &str)] = &[
("1000", "Bank", "asset"),
("1300", "Prepaid Expenses", "asset"),
@@ -406,7 +411,7 @@ impl Mcp {
self.ok(
"books.file_document",
json!({"category": category, "file_name": format!("{category}.pdf"),
"mime_type": "application/pdf", "content_base64": b64(bytes),
"mime_type": "application/pdf", "content_base64": b64(bytes), "sha256": sha(bytes),
"link_to": link_to}),
)["attachment_id"]
.as_u64()
@@ -416,7 +421,7 @@ impl Mcp {
/// Attach a receipt (to `entry`, or unlinked); its id.
fn receipt(&mut self, entry: Option<u64>, bytes: &[u8]) -> u64 {
let mut args = json!({"file_name": "r.pdf", "mime_type": "application/pdf",
"content_base64": b64(bytes)});
"content_base64": b64(bytes), "sha256": sha(bytes)});
if let Some(e) = entry {
args["entry_id"] = json!(e);
}
@@ -787,25 +792,25 @@ fn filing_links_in_the_same_call_all_or_nothing() {
m.refuse(
"books.attach_receipt",
json!({"entry_id": e, "file_name": "r.pdf", "mime_type": "application/pdf",
"content_base64": b64(b"r"), "link_to": link_to}),
"content_base64": b64(b"r"), "sha256": sha(b"r"), "link_to": link_to}),
);
}
m.refuse(
"books.file_document",
json!({"category": "contract", "file_name": "c.pdf", "mime_type": "application/pdf",
"content_base64": b64(b"c"), "link_to": [{"kind": "asset", "id": "NOPE"}]}),
"content_base64": b64(b"c"), "sha256": sha(b"c"), "link_to": [{"kind": "asset", "id": "NOPE"}]}),
);
// An unknown entry_id is refused before anything is filed.
m.refuse(
"books.attach_receipt",
json!({"entry_id": 999, "file_name": "r.pdf", "mime_type": "application/pdf",
"content_base64": b64(b"r"), "link_to": [{"kind": "company"}]}),
"content_base64": b64(b"r"), "sha256": sha(b"r"), "link_to": [{"kind": "company"}]}),
);
assert_eq!(count(&mut m), n, "nothing filed");
let out = m.ok(
"books.attach_receipt",
json!({"entry_id": e, "file_name": "r.pdf", "mime_type": "application/pdf",
"content_base64": b64(b"r"),
"content_base64": b64(b"r"), "sha256": sha(b"r"),
"link_to": [{"kind": "entry", "id": other}, {"kind": "party", "id": "acme design"}]}),
);
assert_eq!(
@@ -819,7 +824,7 @@ fn filing_links_in_the_same_call_all_or_nothing() {
let doc = m.ok(
"books.file_document",
json!({"category": "formation", "file_name": "cert.pdf", "mime_type": "application/pdf",
"content_base64": b64(b"certificate"), "link_to": [{"kind": "company"}]}),
"content_base64": b64(b"certificate"), "sha256": sha(b"certificate"), "link_to": [{"kind": "company"}]}),
);
assert_eq!(kinds(&doc), ["company"]);
assert_eq!(m.count("document.filed"), 1);
@@ -849,7 +854,7 @@ fn documents_file_under_the_attachment_kinds() {
m.refuse(
"books.file_document",
json!({"category": "receipt-ish", "file_name": "x", "mime_type": "text/plain",
"content_base64": b64(b"x")}),
"content_base64": b64(b"x"), "sha256": sha(b"x")}),
);
}