Shared parties with exact shares, member capital statements, TINs for any party, K-1 worksheet

Entries may name several parties, each with an exact amount, a percent,
or a share derived from the members' capital lines or [[company.members]]
shares (entry.parties_set); shares always sum exactly. The 1099
worksheet counts a vendor's share. A member's party page is their
partner capital statement, from the same ledger_core::k1 source as the
CPA package.

books.set_tin stores any party's TIN (members' SSNs for K-1s) in the
vault (party.tin_set, last four in the books). The CPA package carries a
K-1 member worksheet (k1-members.csv, with a members' total that ties to
Schedule L) and the 1099 worksheet with full TINs, saved outside the
books only. files_jointly_with notes spouses' two K-1s land on one 1040.

Adversarial review fixes: reversed contributions no longer inflate K-1
contributions and distributions; a K-1 row never takes another party's
TIN; TIN detection covers dot/slash/dash separators; refusals redact
TINs; vendor tagging can't silently replace shared parties; reversals
follow re-apportioning.

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 18:20:18 -06:00
1 parent ef00ccc242
commit e1b2509c22
32 files changed
+7232 -375

No files matched your search

+7 -4
View File
@@ -147,7 +147,7 @@ fn mcp_end_to_end_over_stdio() {
// Tool listing: the narrow tools; chart is read-only.
let list = mcp.request("tools/list", json!({}));
let tools = list["result"]["tools"].as_array().unwrap();
assert_eq!(tools.len(), 91, "expected 91 tools, got {}", tools.len());
assert_eq!(tools.len(), 92, "expected 92 tools, got {}", tools.len());
let chart = tools.iter().find(|t| t["name"] == "books.chart").unwrap();
assert_eq!(chart["annotations"]["readOnlyHint"], true);
assert!(
@@ -875,11 +875,14 @@ fn mcp_statement_pdfs_and_cpa_package_over_stdio() {
assert!(err.contains("not pdf"), "{err}");
// The CPA package carries all three statements.
let err = mcp.call_err("books.export_cpa_package", json!({"as_of": "2026-10-31"}));
let err = mcp.call_err(
"books.export_cpa_package",
json!({"as_of": "2026-10-31", "include_tins": false}),
);
assert!(err.contains("cash_accounts"), "{err}");
let pkg = mcp.call(
"books.export_cpa_package",
json!({"as_of": "2026-10-31", "cash_accounts": ["1000"]}),
json!({"as_of": "2026-10-31", "cash_accounts": ["1000"], "include_tins": false}),
);
let zip_bytes = b64.decode(pkg["content_base64"].as_str().unwrap()).unwrap();
let mut zip = zip::ZipArchive::new(std::io::Cursor::new(zip_bytes)).unwrap();
@@ -931,7 +934,7 @@ fn mcp_statement_pdfs_and_cpa_package_over_stdio() {
// Without receipts, a lighter package.
let light = mcp.call(
"books.export_cpa_package",
json!({"as_of": "2026-10-31", "cash_accounts": ["1000"], "include_receipts": false}),
json!({"as_of": "2026-10-31", "cash_accounts": ["1000"], "include_receipts": false, "include_tins": false}),
);
let light = zip::ZipArchive::new(std::io::Cursor::new(
b64.decode(light["content_base64"].as_str().unwrap())