Files
budget2.0/src
Lucas Winther 3649a37cfa feat(domain): anchored costs have no slots
A lastCharge cost's next due date is one cycle after the latest actual charge.
So it can be charged any number of times per cycle, each charge restarts the
clock, and the reported bug is gone: two payments in September leave the next
due in October, not November.

calendar is untouched and keeps its grid. Paying September AND October's rent
really does mean nothing is due until November — the bug was only ever a bug
for the anchored case.

occurrencesOf's second parameter changes from a confirmations Map to the raw
purchase list: the anchored walk needs chargesFor's view (every Purchase.date
for the cost, latest wins), the grid walk needs confirmationsFor's view
(purchases keyed by the slot they settle), and only occurrencesOf itself knows
which to build. occurrencesByMonth now passes dataset.purchases straight
through instead of pre-building a map.

stepFrom's `anchoring === "lastCharge" && date !== monthOf(date)` guard (Task
2 scaffolding, for the old month-only-cursor walk) is deleted. The anchored
walk's cursor is always day-granular now (startDate or a real charge date), so
the condition was dead; if a month-only date ever reaches that branch,
addMonthsToDate throwing is correct, not a bug to paper over.

confirmationsFor's `if (!purchase.source.occurrenceDate) continue` scaffolding
guard (also Task 2) is now a thrown error instead: the function is calendar-
only, a lastCharge charge should never reach it, and continuing silently would
hide a programming error as a slot that merely looks unpaid.

DELETED: rebasedFrom, wouldAdvancePast, requireConfirmationAdvances, and the
tests that existed only to exercise them (6 in occurrences.test.ts describing
the old Map-keyed lastCharge slot-rebasing and its termination throw, 3 in
ExpectedBand.test.ts exercising the band's X1 "would this pairing advance the
series" suppression, 5 in actions.test.ts exercising
requireConfirmationAdvances's refusal and its pass-throughs — see each file's
CONTROLLER RULING comment for the line-by-line reasoning). All three existed
to stop a confirmation landing behind the slot it claims; with no slots on the
anchored path and no rebasing on the grid path, that situation cannot arise
any more. The anchored walk's own termination throw stays, as a defensive
assert against n < 1 — never softened into a break.

ExpectedBand.tsx and actions.ts also had their wouldAdvancePast-dependent code
removed (the X1 condition and requireConfirmationAdvances respectively) so
this task's suite would compile and run; Task 6/7 own reintroducing whatever
anchored-model-correct guard, if any, replaces them.

This removes more code than it adds.
2026-09-05 01:22:26 +02:00
..