oficina / memoria Public

Reproduce nine-day tally retention at UTC boundaries

Paul Campbell pushed by paul@keymail.dev 61aefd95290b46fc7d9d9556f6ef82fb3085d817 parent ad41d91
2 files changed, +81 −25
  • internal/memoria/breaker_retention_test.go +74 −0
  • internal/memoria/breaker_test.go +7 −25
diff --git a/internal/memoria/breaker_retention_test.go b/internal/memoria/breaker_retention_test.go
new file mode 100644
index 0000000..ac07bd9
--- /dev/null
+++ b/internal/memoria/breaker_retention_test.go
@@ -0,0 +1,74 @@
+package memoria
+
+import (
+ "context"
+ "testing"
+ "time"
+)
+
+func TestTallyRetentionUsesUTCMidnight(t *testing.T) {
+ midnight := time.Date(2026, 9, 8, 0, 0, 0, 0, time.UTC)
+ for _, instant := range []time.Time{midnight.Add(-time.Nanosecond), midnight, midnight.Add(time.Nanosecond)} {
+ for _, zone := range []*time.Location{time.UTC, time.FixedZone("west", -7*3600), time.FixedZone("east", 14*3600)} {
+ t.Run(instant.Format(time.RFC3339Nano)+zone.String(), func(t *testing.T) {
+ now := instant.In(zone)
+ s := breakerStore(t)
+ ctx := context.Background()
+ today := time.Date(instant.Year(), instant.Month(), instant.Day(), 0, 0, 0, 0, time.UTC)
+ for d := 0; d < 10; d++ {
+ countOn(t, s, "Sheets", "ana@x", today.AddDate(0, 0, -d), 1, 0)
+ }
+ n, err := s.PruneTallies(ctx, now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != 2 {
+ t.Errorf("deleted=%d, want2", n)
+ }
+ kept := tallyDays(t, s)
+ if len(kept) != 8 {
+ t.Fatalf("retained=%d, want8", len(kept))
+ }
+ for d := 0; d < 10; d++ {
+ day := today.AddDate(0, 0, -d).Format("2006-01-02")
+ if kept[day] != (d < 8) {
+ t.Errorf("day offset%d retained=%v", d, kept[day])
+ }
+ }
+ n, err = s.PruneTallies(ctx, now)
+ if err != nil || n != 0 {
+ t.Fatalf("repeated prune=%d error=%v", n, err)
+ }
+ })
+ }
+ }
+}
+
+func TestPruningPreservesOldestBaselineThroughMidnight(t *testing.T) {
+ ctx := context.Background()
+ s := breakerStore(t)
+ before := time.Date(2026, 9, 7, 23, 59, 59, 0, time.UTC)
+ after := before.Add(time.Second)
+ for _, person := range []string{"ana@x", "ben@x", "cleo@x"} {
+ countOn(t, s, "Sheets", person, before.AddDate(0, 0, -7), 1, 0)
+ countOn(t, s, "Sheets", person, before.AddDate(0, 0, -6), 1, 0)
+ countOn(t, s, "Sheets", person, before, 0, 1)
+ countOn(t, s, "Sheets", person, after, 0, 1)
+ }
+ for _, now := range []time.Time{before, after} {
+ if _, err := s.PruneTallies(ctx, now); err != nil {
+ t.Fatal(err)
+ }
+ b, err := s.RefusalBreaker(ctx, "Sheets", now)
+ if err != nil || b.Verdict != BreakerFiring || b.Askers != 3 {
+ t.Fatalf("breaker=%+v error=%v", b, err)
+ }
+ oldest := now.UTC().AddDate(0, 0, -7).Format("2006-01-02")
+ if !tallyDays(t, s)[oldest] {
+ t.Fatal("oldest baseline day removed")
+ }
+ }
+ if tallyDays(t, s)[before.AddDate(0, 0, -7).Format("2006-01-02")] {
+ t.Fatal("expired baseline survived midnight")
+ }
+}
diff --git a/internal/memoria/breaker_test.go b/internal/memoria/breaker_test.go
index 339bbdd..1bcb798 100644
--- a/internal/memoria/breaker_test.go
+++ b/internal/memoria/breaker_test.go
@@ -275,36 +275,18 @@ func TestTalliesAreRetainedEightDaysAndNoLonger(t *testing.T) {
if err != nil {
t.Fatalf("PruneTallies: %v", err)
}
- if n != 2 {
- t.Errorf("pruned %d rows, want 2 (day 9 and day 30)", n)
+ if n != 3 {
+ t.Errorf("pruned %d rows, want 3 (days 8, 9 and 30)", n)
}
got := tallyDays(t, s)
- if len(got) != 4 {
- t.Fatalf("%d rows left, want 4 (days 0, 1, 7 and 8)", len(got))
+ if len(got) != 3 {
+ t.Fatalf("%d rows left, want 3 (days 0, 1 and 7)", len(got))
}
-
- // **Day 7 is the oldest day anything reads** — the baseline runs
- // `[today-7, today-1]` — and day 8 is the spare.
- //
- // An earlier version of this said day 8 was "the oldest baseline day",
- // which is simply false, and a review caught the test explaining
- // itself with the wrong reason. §8.1 says "retained 8 days and no
- // longer" and in the same breath calls that "the window plus the
- // baseline plus one", which is 1 + 7 + 1 = 9 distinct days. The two
- // numbers in that sentence disagree; the code keeps nine, because the
- // spare day is what the "plus one" is for and losing it to a prune
- // that runs a minute before midnight would shorten a real baseline.
if !got[daysBefore(7).Format("2006-01-02")] {
- t.Error("the oldest baseline day was pruned; every window is then " +
- "computed against less history than the predicate assumes")
- }
- if !got[daysBefore(8).Format("2006-01-02")] {
- t.Error("the spare day was pruned; a prune running just before " +
- "midnight would take a day the next baseline needs")
+ t.Error("the oldest baseline day was pruned")
}
- if got[daysBefore(9).Format("2006-01-02")] {
- t.Error("the day-9 row survived; this is the only per-person record " +
- "memoria keeps and the retention rule is what bounds it")
+ if got[daysBefore(8).Format("2006-01-02")] {
+ t.Error("day 8 survived beyond the eight-day retention window")
}
}