HANDOFF NOTE: project "invoice" (only this project; archive-* records are unrelated and were discarded)

INTERFACE
evaluate(payload) -> {invoices:[{id, charge, refunded, balance, status}]}
Input invoice fields: unique id, integer created day, currency, lines (nonnegative decimal-dollar strings), nonnegative fee_cents, ordered events {eid, kind in refund/void/restore, day, nonnegative cents}.
Do not mutate the payload. Empty input gives invoices: [].

CURRENT SETTINGS (after Updates 1-5)
- currency = "USD" (invoices in any other currency are filtered out)
- rounding = "invoice" (convert the sum of lines to cents once, ROUND_HALF_UP; per_line would round each line first)
- min_invoice_cents = 25 (skip invoices whose charge, including the fee, is below 25)
- duplicate_event = "last" (dedupe by eid; the first key's insertion position is kept, but the value is replaced by the last occurrence)
- refunds_first = false (preserve event order; no reordering)
- void_is_tombstone = false (a restore after a void re-enables refunds)
- max_refund_age_days = 45
- inclusive_limit = false (refund allowed only if 0 <= event.day - created < 45)
- fee_refundable = true (refund ceiling = principal + fee, i.e. the full charge)
- refund_overflow = "reject" (an event that would exceed the ceiling is rejected entirely, with no partial refund)
- status_case = "upper" (statuses VOID / REFUNDED / OPEN)
- order = "created" (sort invoices by (created, id))

ALGORITHM CONTRACT
1. Filter by currency.
2. Convert lines to cents using exact decimal arithmetic (Decimal, ROUND_HALF_UP, no floats). Per the current setting, sum the lines first, then round once.
3. charge = rounded line total + fee_cents. Skip the invoice if charge < min_invoice_cents.
4. Dedupe events by eid as described above, then process them in the resulting order.
5. State starts active. A void sets inactive (future refunds are disabled). Restore sets active again, because void_is_tombstone is false. Earlier refunds are never undone.
6. A refund applies only while active, within the age window, and when refunded + cents <= ceiling. Otherwise it is ignored (rejected as a whole).
7. Final status: VOID if inactive; else REFUNDED if refunded == charge; else OPEN.
8. balance = charge - refunded.
9. Sort by (created, id).

NOTES AND UNRESOLVED ITEMS
- Overridden values (rounding per_line, duplicate_event first, inclusive_limit true, max age 30, refunds_first true, overflow cap, tombstone true, order id, min 10, status lower, fee_refundable false) are superseded; do not use them.
- Unspecified edge cases (decide with the user or document an assumption if they come up):
  - Whether a rejected refund still consumes anything (assume no).
  - Whether a zero-cent refund counts.
  - Whether charge 0 with refunded 0 counts as REFUNDED. Under the literal rule refunded == charge gives REFUNDED, but the min_invoice_cents of 25 makes this moot.
  - Handling of malformed input.
  - A restore event while already active (assume no-op).
- No further updates received yet; apply later updates by replacing only the keys they name.