Settlement balance guards, per-currency netting, Decimal rates #57

Open
padreug wants to merge 1 commit from fix/settlement-balance-and-decimal into fix/payment-idempotency
Owner

Third PR of the refactor series (stacked on #56#55; merge those first).

Problems (CODE-REVIEW-2026-06 #2, #8, #13 + libra-#38 + a new finding):

  • A partial Lightning payment cleared the user's FULL prior balance against a smaller payment — unbalanced postings shipped to the ledger.
  • get_unsettled_entries_bql hardcoded fiat_currency = "EUR" and used float() — USD (or SATS-only) entries were summed as if EUR when netting settlements.
  • fiat_rate/btc_rate cost-basis metadata was computed with float division, baking drift into every entry.
  • format_posting_at_average_cost emitted invalid SATS {} when no cost currency.
  • validate_metadata leaked raw decimal.InvalidOperation (libra-#38).

Changes:

  • format_net_settlement_entry enforces the same inline balance constraint as the fiat formatter (payment = receivable − payable + credit) and supports a credit-overflow leg. Unbalanced settlements now raise instead of reaching the ledger.
  • on_invoice_paid settles only what the payment covers; excess becomes user credit (same semantics as the fiat path's libra-#41 handling). Settlement links attach only on full-balance coverage, same-currency entries only.
  • get_unsettled_entries_bql returns real posting currencies and exact Decimal strings; /receivables/settle nets only same-currency entries.
  • New fiat_rate_metadata() helper computes rates in Decimal; all four call sites converted.
  • Underpay error payload now returns exact Decimal strings.
  • libra-#38 fixed; its strict xfail flipped to a passing test.

Tests: 10 new unit tests (balance guard, credit leg, cost braces, rate exactness); settlement test updated for the string payload contract. Full suite: 168 passed, 3 skipped, 7 xfailed.

🤖 Generated with Claude Code

Third PR of the refactor series (stacked on #56 → #55; merge those first). **Problems** (CODE-REVIEW-2026-06 #2, #8, #13 + libra-#38 + a new finding): - A partial Lightning payment cleared the user's FULL prior balance against a smaller payment — unbalanced postings shipped to the ledger. - `get_unsettled_entries_bql` hardcoded `fiat_currency = "EUR"` and used `float()` — USD (or SATS-only) entries were summed as if EUR when netting settlements. - `fiat_rate`/`btc_rate` cost-basis metadata was computed with float division, baking drift into every entry. - `format_posting_at_average_cost` emitted invalid `SATS {}` when no cost currency. - `validate_metadata` leaked raw `decimal.InvalidOperation` (libra-#38). **Changes:** - `format_net_settlement_entry` enforces the same inline balance constraint as the fiat formatter (payment = receivable − payable + credit) and supports a credit-overflow leg. Unbalanced settlements now raise instead of reaching the ledger. - `on_invoice_paid` settles only what the payment covers; excess becomes user credit (same semantics as the fiat path's libra-#41 handling). Settlement links attach only on full-balance coverage, same-currency entries only. - `get_unsettled_entries_bql` returns real posting currencies and exact Decimal strings; `/receivables/settle` nets only same-currency entries. - New `fiat_rate_metadata()` helper computes rates in Decimal; all four call sites converted. - Underpay error payload now returns exact Decimal strings. - libra-#38 fixed; its strict xfail flipped to a passing test. **Tests:** 10 new unit tests (balance guard, credit leg, cost braces, rate exactness); settlement test updated for the string payload contract. Full suite: 168 passed, 3 skipped, 7 xfailed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Settlement correctness cluster from CODE-REVIEW-2026-06 (#2, #8, #13)
plus libra-#38:

- format_net_settlement_entry now enforces the same inline balance
  constraint as the fiat formatter (payment = receivable - payable
  + credit) and grows an optional credit leg. An unbalanced
  settlement raises instead of reaching the ledger.
- on_invoice_paid settles only what the payment covers: a partial
  payment clears that much receivable; excess (or a payment with
  nothing owed) becomes user credit. Previously the full prior
  balance was cleared against a smaller payment, shipping unbalanced
  postings. Settlement links are attached only when the payment
  clears the full open balance, and only for same-currency entries.
- get_unsettled_entries_bql returns each entry's real posting
  currency (was hardcoded "EUR") and exact Decimal amount strings
  (was float). /receivables/settle nets only entries denominated in
  the settlement currency.
- fiat_rate/btc_rate metadata computed via Decimal (new
  fiat_rate_metadata helper) instead of float division — cost-basis
  records no longer carry float drift.
- format_posting_at_average_cost omits the cost braces when
  cost_currency is unset ("SATS {}" is invalid Beancount).
- Underpay error payload serializes amounts as exact Decimal strings.
- validate_metadata catches decimal.InvalidOperation so bad
  fiat_amount input becomes ValidationError (libra-#38); flipped the
  tracking xfail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/settlement-balance-and-decimal:fix/settlement-balance-and-decimal
git switch fix/settlement-balance-and-decimal

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch fix/payment-idempotency
git merge --no-ff fix/settlement-balance-and-decimal
git switch fix/settlement-balance-and-decimal
git rebase fix/payment-idempotency
git switch fix/payment-idempotency
git merge --ff-only fix/settlement-balance-and-decimal
git switch fix/settlement-balance-and-decimal
git rebase fix/payment-idempotency
git switch fix/payment-idempotency
git merge --no-ff fix/settlement-balance-and-decimal
git switch fix/payment-idempotency
git merge --squash fix/settlement-balance-and-decimal
git switch fix/payment-idempotency
git merge --ff-only fix/settlement-balance-and-decimal
git switch fix/payment-idempotency
git merge fix/settlement-balance-and-decimal
git push origin fix/payment-idempotency
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/libra!57
No description provided.