Description
When invoice amounts are divided among recipients using non-integer ratios, floating-point rounding can produce per-recipient amounts that do not sum exactly to the invoice total. Even a one-stroop discrepancy causes settlement failures or unintentional overpayment, and the error is currently silent. A rounding auditor would verify that computed per-recipient amounts sum exactly to the total, apply a deterministic largest-remainder correction when needed, and record each adjustment for downstream audit logging.
Technical Context
New module src/invoice/rounding.ts exposing auditSplitRounding(total: bigint, splits: SplitLine[]): AuditedSplitResult. All amounts represented as bigint stroops throughout to avoid floating-point drift. SplitLine, AuditedSplitResult, and RoundingOverflowError types in src/types.ts. Integrates with src/invoice/calculator.ts where per-recipient amounts are first derived. Edge cases include ratios that do not sum to exactly 1.0 and invoice amounts as small as 1 stroop.
Acceptance Criteria
Description
When invoice amounts are divided among recipients using non-integer ratios, floating-point rounding can produce per-recipient amounts that do not sum exactly to the invoice total. Even a one-stroop discrepancy causes settlement failures or unintentional overpayment, and the error is currently silent. A rounding auditor would verify that computed per-recipient amounts sum exactly to the total, apply a deterministic largest-remainder correction when needed, and record each adjustment for downstream audit logging.
Technical Context
New module
src/invoice/rounding.tsexposingauditSplitRounding(total: bigint, splits: SplitLine[]): AuditedSplitResult. All amounts represented asbigintstroops throughout to avoid floating-point drift.SplitLine,AuditedSplitResult, andRoundingOverflowErrortypes insrc/types.ts. Integrates withsrc/invoice/calculator.tswhere per-recipient amounts are first derived. Edge cases include ratios that do not sum to exactly 1.0 and invoice amounts as small as 1 stroop.Acceptance Criteria
auditSplitRoundingreturns adjusted amounts that sum exactly tototalas abigintBigInt(Math.ceil(splits.length / 2))stroops throw aRoundingOverflowErrorto surface unexpectedly large deviationsAuditedSplitResult.adjustmentsas{ recipientId: string; delta: bigint }for use by the per-split audit log