Files
wanderbooks/bookspec/examples/p3-party-signs-and-reversals.toml
T
swaitsandClaude Opus 5.5 d7fa16e974
check / check (push) Canceled after 0s
feat(arap)!: documents read their figures from standing lines; control accounts
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
2026-10-24 09:17:42 -06:00

293 lines
8.3 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P3 defect 14 (wanderbooks plan/served.md P3; audit findings F36, F37):
# a party's part of an entry is signed as a line on the party's own
# account, with its direction and what of it was money or property; a
# reversal is the exact opposite of what it reverses; party totals take a
# basis. Regression test: ledger-core tests/spec_party_totals.rs
# defect14_shares_take_the_partys_side_and_reversals_flip. The shape:
# bookspec/README.md.
id = "p3-party-signs-and-reversals"
title = "A party's part of an entry is signed as its own account would show it, and a reversal is the exact opposite"
rule = """
A party's part of an entry is debit-positive, as a line on the party's own
account would be: positive when the company paid it or gave it value,
negative when the company received from it. Each part has a direction
(paid, received, neither) and a cash and an in-kind amount. An entry
naming only members that moves equity counts what moved their capital,
the property in it in kind. A reversal's part is exactly its original's
negated. A bill or invoice moves nothing with the party yet (neither).
Party totals on basis cash count the cash parts, on basis all the cash
and in-kind parts, each on its root original's side.
"""
status = "CHOICE"
sources = [
"Pacioli, Summa (1494), Geijsbeek tr., ch. 36: debit is what owes, credit what is owed (wanderbooks docs/spec/sources/pacioli-1494.md)",
"Treas. Reg. §1.704-1(b)(2)(iv)(b)(1)-(2), (4)-(5): a partner's capital account rises by money and property contributed, falls by money and property distributed (docs/spec/sources/irs-capital-accounts.md)",
"Helvering v. Price, 309 U.S. 409, 412-413 (1940): a promise to pay is not payment (docs/spec/sources/helvering-v-price-1940.md)",
"DECISION (owner, 2026-10-20): a member's entry counts what moved their capital (wanderbooks rules.md B-35)",
"DECISION D-42 (owner, 2026-10-20): what a member pays for the company through the members' financing is money: cash borrowed and spent (wanderbooks plan/served.md; rules.md B-14 rule 11, B-35 items 5 and 8)",
]
tags = ["parties", "signs", "reversals", "members", "in-kind", "basis"]
[setup]
currency = "USD"
fiscal_year_start = "01-01"
basis = "accrual"
rounding = "half-even"
cash_accounts = ["1000"]
financing_accounts = ["2100"]
[[setup.accounts]]
code = "1000"
name = "Checking"
type = "asset"
[[setup.accounts]]
code = "1200"
name = "Accounts Receivable"
type = "asset"
[[setup.accounts]]
code = "1500"
name = "Equipment"
type = "asset"
[[setup.accounts]]
code = "2000"
name = "Accounts Payable"
type = "liability"
[[setup.accounts]]
code = "2100"
name = "Due to Members"
type = "liability"
[[setup.accounts]]
code = "3000"
name = "Capital — Tobin Harlow"
type = "equity"
[[setup.accounts]]
code = "3001"
name = "Capital — Morgan Ellis"
type = "equity"
[[setup.accounts]]
code = "3999"
name = "Retained Earnings"
type = "equity"
[[setup.accounts]]
code = "4000"
name = "Revenue"
type = "revenue"
[[setup.accounts]]
code = "6000"
name = "Organizational Costs"
type = "expense"
[[setup.parties]]
name = "Tobin Harlow"
roles = ["member"]
[[setup.parties]]
name = "Morgan Ellis"
roles = ["member"]
[[setup.parties]]
name = "Northwind Filings"
roles = ["vendor"]
[[setup.parties]]
name = "Riley Chen"
roles = ["customer"]
[[setup.members]]
name = "Tobin Harlow"
share = "0.5"
capital_account = "3000"
[[setup.members]]
name = "Morgan Ellis"
share = "0.5"
capital_account = "3001"
# 1. Tobin pays the filing fee for the company (through the members'
# financing account), tagged with the vendor: paid to the vendor, money.
[[events]]
date = "2026-10-04"
description = "Filing fee, paid by Tobin"
parties = [{ party = "Northwind Filings" }]
lines = [
{ account = "6000", side = "debit", amount = "64.00" },
{ account = "2100", side = "credit", amount = "64.00" },
]
# 2. Tobin's cash contribution: received, money.
[[events]]
date = "2026-10-05"
description = "Tobin's contribution"
parties = [{ party = "Tobin Harlow" }]
lines = [
{ account = "1000", side = "debit", amount = "400.00" },
{ account = "3000", side = "credit", amount = "400.00" },
]
# 3. Reimbursing Tobin for the fee: paid, money (cash paying down what
# the company owed him).
[[events]]
date = "2026-10-08"
description = "Reimbursement"
parties = [{ party = "Tobin Harlow" }]
lines = [
{ account = "2100", side = "debit", amount = "64.00" },
{ account = "1000", side = "credit", amount = "64.00" },
]
# 4. A laptop Tobin contributes: received, in kind.
[[events]]
date = "2026-10-12"
description = "Laptop contributed"
parties = [{ party = "Tobin Harlow" }]
lines = [
{ account = "1500", side = "debit", amount = "1480.00" },
{ account = "3000", side = "credit", amount = "1480.00" },
]
# 5. Its reversal: the exact opposite, paid, in kind.
[[events]]
date = "2026-10-13"
description = "Laptop contribution reversed"
reverses = 4
# 6. The laptop again, half each, by amount.
[[events]]
date = "2026-10-14"
description = "Laptop contributed jointly"
parties = [{ party = "Tobin Harlow", amount = "740.00" }, { party = "Morgan Ellis", amount = "740.00" }]
lines = [
{ account = "1500", side = "debit", amount = "1480.00" },
{ account = "3000", side = "credit", amount = "740.00" },
{ account = "3001", side = "credit", amount = "740.00" },
]
# 7. A bill from the vendor (credit to payables, its own account): neither.
[[events]]
date = "2026-10-20"
description = "Bill"
older = true
parties = [{ party = "Northwind Filings" }]
lines = [
{ account = "6000", side = "debit", amount = "80.00" },
{ account = "2000", side = "credit", amount = "80.00" },
]
# 8. The bill reversed: the exact opposite, still neither.
[[events]]
date = "2026-10-21"
description = "Bill reversed"
reverses = 7
# 9. An invoice to the customer (a debit to receivables): neither.
[[events]]
date = "2026-10-20"
description = "Invoice"
older = true
parties = [{ party = "Riley Chen" }]
lines = [
{ account = "1200", side = "debit", amount = "120.00" },
{ account = "4000", side = "credit", amount = "120.00" },
]
[[expect]]
query = "party_part"
entry = 1
party = "Northwind Filings"
values = { resolved = "64.00", direction = "paid", cash = "64.00", in_kind = "0.00" }
[[expect]]
query = "party_part"
entry = 2
party = "Tobin Harlow"
values = { resolved = "-400.00", direction = "received", cash = "-400.00", in_kind = "0.00" }
[[expect]]
query = "party_part"
entry = 3
party = "Tobin Harlow"
values = { resolved = "64.00", direction = "paid", cash = "64.00", in_kind = "0.00" }
[[expect]]
query = "party_part"
entry = 4
party = "Tobin Harlow"
values = { resolved = "-1480.00", direction = "received", cash = "0.00", in_kind = "-1480.00" }
[[expect]]
query = "party_part"
entry = 5
party = "Tobin Harlow"
values = { resolved = "1480.00", direction = "paid", cash = "0.00", in_kind = "1480.00" }
[[expect]]
query = "party_part"
entry = 6
party = "Morgan Ellis"
values = { resolved = "-740.00", direction = "received", cash = "0.00", in_kind = "-740.00" }
[[expect]]
query = "party_part"
entry = 7
party = "Northwind Filings"
values = { resolved = "-80.00", direction = "neither", cash = "0.00", in_kind = "0.00" }
[[expect]]
query = "party_part"
entry = 8
party = "Northwind Filings"
values = { resolved = "80.00", direction = "neither", cash = "0.00", in_kind = "0.00" }
[[expect]]
query = "party_part"
entry = 9
party = "Riley Chen"
values = { resolved = "120.00", direction = "neither", cash = "0.00", in_kind = "0.00" }
# Tobin: cash in 400.00 (event 2), paid back 64.00 (event 3); in kind
# 1,480.00 − 1,480.00 (events 4 and 5 net) + 740.00 (event 6).
[[expect]]
query = "party_totals"
party = "Tobin Harlow"
basis = "cash"
from = "2026-01-01"
to = "2026-12-31"
values = { paid = "64.00", received = "400.00" }
[[expect]]
query = "party_totals"
party = "Tobin Harlow"
basis = "all"
from = "2026-01-01"
to = "2026-12-31"
values = { paid = "64.00", received = "1140.00" }
# The vendor: the fee a member paid for the company; the bill and its
# reversal count nothing.
[[expect]]
query = "party_totals"
party = "Northwind Filings"
basis = "all"
from = "2026-01-01"
to = "2026-12-31"
values = { paid = "64.00", received = "0.00" }
# Basis all over Tobin's equity entries is the statement of members'
# equity: contributions 400.00 + 740.00 (events 4 and 5 cancel).
[[expect]]
query = "members_equity"
from = "2026-01-01"
to = "2026-12-31"
values = { "Tobin Harlow.contributions" = "1140.00", "Tobin Harlow.distributions" = "0.00" }