aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 10:05:55 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 10:05:55 +0200
commitdde760c31c6e8cffa84446b0804ab6d6372ccb30 (patch)
treec311431f4e465af69073d76dd4124c32d1f860c3
parent3ad70f087f30075e6414d85195a5ca037e14f627 (diff)
downloadlectio-dde760c31c6e8cffa84446b0804ab6d6372ccb30.tar.gz
lectio-dde760c31c6e8cffa84446b0804ab6d6372ccb30.zip
caldata: the Lenten Ember days were serving Lent I Sunday's Mass
Three lectionary sections were keyed ef-lent-1-{wednesday,friday,saturday} while temporal_ef.go's efEmberSlug computes ef-lent-ember-{wed,fri,sat}. The lookup in caldata.Readings takes the observed slug and has no alias table, so it missed the entries entirely and fell through to the preceding-Sunday fallback. Every Lenten Ember day since the data was added has served Lent I Sunday's Mass -- three days a year, in a season that prints a proper Mass daily. The values were already right; only the keys were wrong, and they were stale rather than mistaken: they predate Lent being added to efEmberSlug, and scripts/genlect.go keys off day.Observed.Slug, so re-running it would already write the correct names. Advent and September were never affected -- both already use the -ember- form. Renamed, and verified the whole file: every one of the 119 section names is now a slug the calendar actually computes, checked by sweeping 2005-2050. Those three were the only dead keys. Added TestEFLectionaryKeysAreReachableSlugs to make the class detectable rather than just this instance. Mutation-tested: reverting the three keys fails it, naming all three. Found by differencing against colitur, the sibling OCaml engine, while giving it a lectionary of its own. Worth recording how nearly it escaped: colitur inherited the same wrong keys from this file during its bootstrap, so both engines produced the same wrong Mass and the differential between them was silent. It only surfaced when colitur's data was checked against the Missal itself. Two implementations agreeing is not evidence when one was seeded from the other.
-rw-r--r--internal/caldata/tridentine-lectionary.ini6
-rw-r--r--internal/calendar/oracle_ef_test.go62
2 files changed, 65 insertions, 3 deletions
diff --git a/internal/caldata/tridentine-lectionary.ini b/internal/caldata/tridentine-lectionary.ini
index 06ad9fd..1f8a9b9 100644
--- a/internal/caldata/tridentine-lectionary.ini
+++ b/internal/caldata/tridentine-lectionary.ini
@@ -153,7 +153,7 @@ gospel = John 16:23-30
first = 1 Pet 4:7-11.
gospel = John 15:26-27; 16:1-4.
-[ef-lent-1-friday]
+[ef-lent-ember-fri]
first = Ezech 18:20-28
gospel = John 5:1-15
@@ -161,7 +161,7 @@ gospel = John 5:1-15
first = Ezech 34:11-16
gospel = Matt 25:31-46
-[ef-lent-1-saturday]
+[ef-lent-ember-sat]
first = 1 Thess. 5:14-23
gospel = Matt 17:1-9
@@ -173,7 +173,7 @@ gospel = Matt 15:21-28
first = Isa 55:6-11
gospel = Matt 21:10-17
-[ef-lent-1-wednesday]
+[ef-lent-ember-wed]
first = 3 Kgs. 19:3-8
gospel = Matt 12:38-50
diff --git a/internal/calendar/oracle_ef_test.go b/internal/calendar/oracle_ef_test.go
index 6d63220..a2bb6a5 100644
--- a/internal/calendar/oracle_ef_test.go
+++ b/internal/calendar/oracle_ef_test.go
@@ -339,3 +339,65 @@ func TestOracleEF(t *testing.T) {
t.Fatalf("%d/%d EF colour mismatches vs missalemeum (not allow-listed)", colourMiss, total)
}
}
+
+// TestEFLectionaryKeysAreReachableSlugs asserts that every section name in the
+// EF temporal lectionary is a slug the calendar can actually compute. A key
+// nothing ever matches is dead data: the reading sits in the file, the lookup
+// misses it, and the day silently falls back to the preceding Sunday's Mass —
+// which looks like a plausible answer, so nothing downstream notices.
+//
+// This is not hypothetical. Three sections were filed as ef-lent-1-{wednesday,
+// friday,saturday} while temporal_ef.go's efEmberSlug computes
+// ef-lent-ember-{wed,fri,sat}, so the Lenten Ember days served Lent I Sunday's
+// Mass for as long as the data existed. The keys were stale: they predate Lent
+// being added to efEmberSlug, and scripts/genlect.go — which keys off
+// day.Observed.Slug — would already write the correct names if re-run.
+//
+// It survived every existing test because the wrong answer is a real Mass, and
+// it survived cross-checking against colitur (the sibling OCaml engine)
+// because colitur had bootstrapped the same wrong keys from this very file.
+// Two engines agreeing is not evidence when one was seeded from the other.
+func TestEFLectionaryKeysAreReachableSlugs(t *testing.T) {
+ raw, err := os.ReadFile("../caldata/tridentine-lectionary.ini")
+ if err != nil {
+ t.Fatalf("read lectionary: %v", err)
+ }
+
+ keys := map[string]bool{}
+ for _, line := range strings.Split(string(raw), "\n") {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+ keys[line[1:len(line)-1]] = true
+ }
+ }
+ if len(keys) == 0 {
+ t.Fatal("no sections parsed from the lectionary")
+ }
+
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ layers := []calendar.Layer{caldata.Tridentine()}
+
+ // Twelve years. Every key in the shipped file is reached within five (the
+ // movable ones recur annually; the Ember and vigil days are the only
+ // awkward cases and they occur every year too), so this is a bit over
+ // double the margin actually needed. Widening it costs test time linearly
+ // and buys nothing measurable — 46 years took 91s, this takes a quarter of
+ // that — but narrowing below five would start reporting live keys as dead.
+ seen := map[string]bool{}
+ for d := time.Date(2005, 1, 1, 0, 0, 0, 0, time.UTC); d.Year() <= 2016; d = d.AddDate(0, 0, 1) {
+ seen[calendar.Compute(d, sel, layers).Observed.Slug] = true
+ }
+
+ var dead []string
+ for k := range keys {
+ if !seen[k] {
+ dead = append(dead, k)
+ }
+ }
+ sort.Strings(dead)
+ if len(dead) > 0 {
+ t.Errorf("lectionary keys no computed slug ever matches (dead data, the day "+
+ "silently falls back to the preceding Sunday): %v", dead)
+ }
+}