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>