You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while implementing the period engine (#1). Not fixed there: src/objects/duty.object.ts is outside that card's file surface, and the answer decides behaviour in two places at once.
The conflict
src/objects/duty.object.ts:
due_offset_days: Field.number({label: 'Offset (days)',defaultValue: 0,description: 'Days from the anchor. "5" with a monthly period anchored to period start = due on the 5th. Negative offsets count back from period end.',}),
The first sentence and the example disagree. A monthly period starts on the 1st, so five days from the anchor is the 6th, not the 5th. Two readings follow, and they differ by one day on every duty with a non-zero offset:
A — days from the anchor (offset 0 = the anchor day). period_start + 5 → the 6th. To land on the 5th you write 4.
B — the Nth day of the period, 1-based. period_start + 5 → the 5th, as the example says.
Evidence, both directions
For A:
The field's own first sentence: "Days from the anchor."
defaultValue: 0 has to mean something on both anchors. Under A it is the anchor day itself: period_start + 0 = the first day, period_end + 0 = the last day. Under B, 0 is neither 1 nor -1 and has no defined meaning, so every duty left on the default is undefined behaviour.
period_end + 0 = the last day of the period is exactly the worked example in the same file's header and in docs/product/data-model.md: "A quarterly duty 'due in Q3' is due on 30 September."
Symmetry with "Negative offsets count back from period end" — under A, period_end - 1 is the day before the last.
For B:
The "5" → the 5th example itself.
Weakly, Period engine — period keys, boundaries and due dates, timezone-correct #1's own phrasing: "due_offset_days: 30 on February resolves to the 28th (29th in a leap year), not 2 March." 2 March is the unclamped result of 1 Feb + 29 days in a non-leap February — the B arithmetic. Under A the unclamped non-leap result is 3 March, and 2 March is the leap-year result.
Where it lands
#1 shipped reading A, on the strength of the defaultValue: 0 argument, and pinned it in test/period.test.ts ('offset 0 is the anchor day itself, on either anchor' and 'a positive offset counts days forward from the period start'). Both readings produce the same answer for the clamped examples in #1's acceptance criteria, so the gates cannot distinguish them — this needs a person.
If A is confirmed, the fix is the one-line description: "4" with a monthly period anchored to period start = due on the 5th, or restate it as "0 is the anchor day itself". If B is confirmed, dueDateFor in src/functions/period.ts shifts by one for period_start (and needs a defined meaning for offset 0), and the two tests above flip.
Consumers that will bake in whichever answer is live: #2 (dispatcher) and #7 (seed data).
Found while implementing the period engine (#1). Not fixed there:
src/objects/duty.object.tsis outside that card's file surface, and the answer decides behaviour in two places at once.The conflict
src/objects/duty.object.ts:The first sentence and the example disagree. A monthly period starts on the 1st, so five days from the anchor is the 6th, not the 5th. Two readings follow, and they differ by one day on every duty with a non-zero offset:
period_start+ 5 → the 6th. To land on the 5th you write 4.period_start+ 5 → the 5th, as the example says.Evidence, both directions
For A:
defaultValue: 0has to mean something on both anchors. Under A it is the anchor day itself:period_start+ 0 = the first day,period_end+ 0 = the last day. Under B, 0 is neither 1 nor -1 and has no defined meaning, so every duty left on the default is undefined behaviour.period_end+ 0 = the last day of the period is exactly the worked example in the same file's header and indocs/product/data-model.md: "A quarterly duty 'due in Q3' is due on 30 September."period_end- 1 is the day before the last.For B:
"5" → the 5thexample itself.due_offset_days: 30on February resolves to the 28th (29th in a leap year), not 2 March." 2 March is the unclamped result of1 Feb + 29 daysin a non-leap February — the B arithmetic. Under A the unclamped non-leap result is 3 March, and 2 March is the leap-year result.Where it lands
#1 shipped reading A, on the strength of the
defaultValue: 0argument, and pinned it intest/period.test.ts('offset 0 is the anchor day itself, on either anchor'and'a positive offset counts days forward from the period start'). Both readings produce the same answer for the clamped examples in #1's acceptance criteria, so the gates cannot distinguish them — this needs a person.If A is confirmed, the fix is the one-line description:
"4" with a monthly period anchored to period start = due on the 5th, or restate it as "0 is the anchor day itself". If B is confirmed,dueDateForinsrc/functions/period.tsshifts by one forperiod_start(and needs a defined meaning for offset 0), and the two tests above flip.Consumers that will bake in whichever answer is live: #2 (dispatcher) and #7 (seed data).