Commit Graph
7 Commits
Author SHA1 Message Date
swaitsandClaude Fable 5.1 fb83af5948 verify(lean): one docstring on Books after the S5 and S7 merges
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The S7 merge kept both branches' doc comments for the Books structure,
S5's (which names the sends) stranded above an unrelated theorem and
S7's on the structure; Lean refused the stray one. S5's text stays, on
the structure it describes. Kani's 52 harnesses verified in the same
run; only Lean's build stopped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 05:02:12 -06:00
swaitsandClaude Fable 5.1 335a3eaa7e merge: P4b S7, bad debts (branch S7, 4b4fecb9)
The fourth of the paused branches: Ledger::write_off and the bad_debts
control role, invoice.written_off with its evidence filed in the same
store call, books.write_off_invoice, the write-off on the timeline and
in Needs attention, Lean writeOff_within_bounds, rules.md B-41 and the
bookspec examples. Twenty-eight files conflicted with the S4.4, S5 and
S6 merges; resolved as plan 8.0.2 says, keeping both sides' appends with
their seams restored, and: the scope table at 125; S7's law numbered 37;
B-41 placed before B-42; the handbook's step 4 and recovery procedures
folded into the one invoices-and-bills section; S7's wire tests in their
own module with the confirm S4.4's contract needs; S7's copy of the
payment steps dropped (S6 replaced them with Ledger::settle) and its
write-off journaled through S6's journal_auto_lines; in the old-books
fixture S7's hand-written write-off moved to a second invoice, since
S6's hand-written payment settles invoice 1 in full.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 21:47:33 -06:00
swaitsandClaude Fable 5.1 be58059830 merge: P4b S6, paying on terms (branch S6, 6c4480cd)
The third of the paused branches: Ledger::settle with discounts and the
processor's fee (kernel::settle, Kani settle_is_exact, Lean
settle_posts_a_document_step), books.receive_payment and books.pay_bill
with applications, books.bills_due, the agings' detail and tie-out, the
aging exports and the CPA package's aging CSVs, the handlers in
tools/settlements.rs. Twelve files conflicted with the S4.4 and S5
merges, resolved as plan 8.0.2 says: both routers; both test appends
(the shared match arm in arap_standing.rs spliced into both tests; the
shared `.unwrap()` in store.rs); the scope table at 124; one
invoices-and-bills handbook (S5's send step 1 with S6's steps 2, 3, 5
and procedures) registered once; docs/mcp-interface.md with S4.4's
create docs and S6's payment docs; S5's law renumbered 35 and S6's 36
(S1's is 34); the parity rows and sections of both. S6's wire tests
create their bills as S4.4's contract now requires (confirmed, the
vendor's number, a line account, the vendor's invoice uploaded) and live
in their own module beside S4.4's tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 21:26:40 -06:00
swaits 47c0e4396e verify(lean): Step.markSent, a send moves no figure (P4b S5)
AGENTS.md's formal-methods bar makes every new Ledger command a Lean
Step with a preservation proof, so `Ledger::mark_sent` gets one even
though the unit expected no Lean change (the As-built block records
that). `Documents.lean`'s `Books` gains the sends recorded, and
`Step.markSent` adds one on a document the lines carry. A send touches
no line, so every control balance, every document's sum and what is
unattached are unchanged through any day
(`Step.markSent_changes_no_figure`, no axioms), and `Reachable.tagged`
covers the new step.

`Audit.lean` lists the theorem; rules.md B-38's model now covers the
record side of a document's life as well as its figures.
2026-10-27 13:24:10 -06:00
swaits 10ee0681c1 feat(arap): settle documents with discounts and a fee (P4b S6)
One payment had to name one document and could take no discount or
processor fee, so paying several invoices from one deposit meant several
entries and the fee had nowhere to go. This adds the kernel's exact
settlement arithmetic (applications total, net deposit), a Ledger::settle
that posts one entry for a customer's or vendor's documents, and the gross
method and gross fee A-8/A-4 chose.

- kernel::settle: sum(cash)+sum(discounts)=applications, sum(cash)-fee=
  deposit, None on overflow or a negative deposit. Kani settle_is_exact
  plus the Decimal bridge.
- Ledger::settle / settlement_lines: one party and one kind per
  settlement, one application per document, each cash+discount within
  payment_room; a discount within the terms (discount_max and
  discount_by) needs no reason, a larger or later one does; a bill with
  an asset line is refused with D-49's remedy; a fee is a customer
  payment's only and posts gross.
- Rows are payment lines in application order, then discount lines, the
  fee line and the cash line, so a journaled event's line indices are
  stable; replay tags exactly the line an event names and leaves an
  unmatched line unattached (never refuses).
- receive_payment_with and pay_bill_with become one-application calls.
- Roles: sales_allowances (4100), purchase_discounts (6995) and
  payment_processing_fees (6060), constants used on demand but not added
  to control_accounts() (existing charts may use those codes for other
  things).
- Lean: settle posts through Step.document, so settle_posts_a_document_step
  and settle_unattached, no new constructor; both in Audit.lean.
- Core tests: bill_on_terms_with_discount, late_discount_needs_a_reason,
  discount_on_an_asset_bill_is_refused_with_the_remedy,
  a_fee_posts_gross_to_receivables_and_net_to_the_bank,
  a_settlement_names_its_lines_and_refuses_a_document_twice, and
  equipment_bill_payment_is_investing_cash in the cash-flow suite.
- Bookspec B-42 is stated, with its id BS-arap-6; the catalogue and index
  now record it as CONVENTION.

just check and just prove green.
2026-10-27 12:59:05 -06:00
swaits 62f2fb058e feat(verify): prove a write-off never takes a document below zero (P4b S7)
Ledger::write_off is a new command, so it is a new Step: Step.writeOff,
whose two lines are a tagged control line (the receivable falls) and an
untagged expense line (the deduction). Being a document step in shape, it
moves nothing unattached, so control_agrees and
Reachable.control_is_documents still hold with no new proof:
Step.writeOff_unattached states its half of that.

writeOff_within_bounds is the law the command's refusal enforces: a
write-off of at most what the document is owed, read over the date-sum
docSum ... through D (B-38 fixed that sum, not standing), leaves what is
owed through the write-off's day at or above zero. through_append lets
the write-off's own two lines be read separately.

Both are listed in Audit.lean; just prove holds (standard axioms only, no
sorry).
2026-10-27 12:44:45 -06:00
swaitsandClaude Opus 5.5 d7fa16e974 feat(arap)!: documents read their figures from standing lines; control accounts
check / check (push) Canceled after 0s
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