summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 10:11:47 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 10:11:47 +0200
commitb6d0eba04d1da51515dad70127112898b500c1bc (patch)
treea82263c3d436d94b6208b14c61295b0e550fbe93 /test
parent8fa9db25f55f4ae92ad6a28115484dd981593a3c (diff)
downloadcolitur-b6d0eba04d1da51515dad70127112898b500c1bc.tar.gz
colitur-b6d0eba04d1da51515dad70127112898b500c1bc.zip
fix(of-lectionary): stop serving drifting readings for O-Antiphon and Christmas-season dates
The bootstrap excluded 19 lectio bases as "date-keyed duplicates", but 17 of them were not duplicates at all: the 8 O-Antiphon days (17-24 December) and 8 further Christmas-season dates (29-31 December, 2-5 and 7 January) each carry unique per-date content found nowhere else among the emitted entries, verified against lectio's own ini directly. 17 December is Gen 49:2,8-10 / Matthew 1:1-17, but colitur was falling through to that year's unrelated weekday-keyed "advent-3-mon" content instead. OLM n. 69.3 explains why: those ferias are fixed by civil date, unlike every other Advent/Christmastide/Paschaltide feria, which is merely non-alternating within a weekday slot. Temporal_of's own ferial slugs for these dates are weekday-keyed, so looking one of those up served whichever OTHER date happened to share that year's weekday alignment -- a citation that silently drifted year to year on ~16 real civil dates (6 January stays excluded: Temporal_of fixes Epiphany there unconditionally, so that date can never reach the ferial path at all; two more, easter-6-thu and advent-4-sat, stay excluded for their own, different structural reasons). Fixed with a date-keyed lookup route (Lectionary_of.date_keyed_slug), tried before the weekday-keyed one in readings' own step 3. Temporal_of's slugs are unchanged -- only which lectionary key resolves a day's citations. tools/bootstrap_lectionary_of.ml now maps the 17 bases to synthetic date-keyed slugs instead of excluding them, and its own reachability sweep calls date_keyed_slug directly so the two can never drift apart. Re-emitted data/of/lectionary.sexp (754 -> 770 entries) and corrected its provenance header, which previously described these dates as excluded duplicates. Also states, for the first time, a Minor finding from the same review: 190 of 222 shipped sanctoral slugs have no dedicated lectionary entry and correctly fall through to the ferial per OLM norms -- expected behaviour, not a gap, but never said in the coverage report before. New tests pin 17 December (Gen 49:2,8-10 / Matthew 1:1-17) and 2 January (1 John 2:22-28 / John 1:19-28) in two years with different weekday alignments, proving neither reading drifts, plus direct boundary coverage of date_keyed_slug itself (both windows, the 6 January exclusion, and the Sunday guard).
Diffstat (limited to 'test')
-rw-r--r--test/test_lectionary_of.ml119
1 files changed, 112 insertions, 7 deletions
diff --git a/test/test_lectionary_of.ml b/test/test_lectionary_of.ml
index 6fb48a0..03838be 100644
--- a/test/test_lectionary_of.ml
+++ b/test/test_lectionary_of.ml
@@ -172,13 +172,14 @@ let sha256_of_file path =
| hash :: _ -> hash
| [] -> Alcotest.fail "sha256sum produced no output"
-(* Pinned 2026-08-26 against the shipped file itself, independently
- re-derived (`sha256sum data/of/lectionary.sexp`), the same "derive the
- new value, don't transcribe it from generator stdout" discipline
+(* Pinned 2026-08-26 (FIX 1, O-Antiphon/Christmas-season date-keyed review)
+ against the shipped file itself, independently re-derived
+ (`sha256sum data/of/lectionary.sexp`), the same "derive the new value,
+ don't transcribe it from generator stdout" discipline
test_calendar_of_data.ml's own [test_sha256] already follows. *)
let test_sha256_pinned () =
Alcotest.(check string) "data/of/lectionary.sexp SHA-256"
- "e7fd109bc30baf8a5531a614995e986959214424c80f4d444fa2b772538decde" (sha256_of_file lectionary_path)
+ "2277e3dc302e853d78b72608884f59eb67671bfdcca4f5e4c99358ee12811f89" (sha256_of_file lectionary_path)
(* lectio's OWN of-lectionary.ini SHA-256, pinned inside data/of/
lectionary.sexp's own provenance header (tools/bootstrap_lectionary_of
@@ -244,7 +245,7 @@ let test_missing_source_fails_loudly () =
let test_load () =
let l = real_lectionary () in
- Alcotest.(check int) "754 entries" 754 (List.length (Lectionary.entries l))
+ Alcotest.(check int) "770 entries" 770 (List.length (Lectionary.entries l))
(* ---- coverage: a full sample civil year, both directions, PINNED ------ *)
@@ -357,6 +358,105 @@ let test_step2_sanctoral_wins () =
(cs <> Option.get (Lectionary.find lectionary (Slug.of_string_exn "of-advent-1-monday")))
| _ -> Alcotest.fail "30 November 2026 should resolve readings via step 2"
+(* ---- date-keyed windows (OLM n. 69.3) -- FIX 1, 2026-08-26 review ----- *)
+
+(* {!Lect_of.date_keyed_slug} itself, direct unit coverage of its own
+ boundaries: the O-Antiphon window (17-24 December), the Christmas-season
+ window (29-31 December, then 2-5 and 7 January), 6 January's own
+ deliberate exclusion (Temporal_of's own [named] fixes Epiphany there
+ unconditionally, so that date can never reach this function's caller as
+ a ferial), and the Sunday guard (a Sunday inside either window is a
+ NAMED office -- Advent 4, Holy Family, the Second Sunday after
+ Christmas -- never one of these ferial dates). *)
+let test_date_keyed_slug_boundaries () =
+ let some_str dt = Option.map Slug.to_string (Lect_of.date_keyed_slug dt) in
+ Alcotest.(check (option string)) "16 December: outside the window" None (some_str (d 2026 12 16));
+ Alcotest.(check (option string)) "17 December: in the window" (Some "of-advent-dec-17") (some_str (d 2026 12 17));
+ Alcotest.(check (option string)) "24 December: in the window" (Some "of-advent-dec-24") (some_str (d 2029 12 24));
+ Alcotest.(check (option string)) "25 December: outside the window" None (some_str (d 2026 12 25));
+ Alcotest.(check (option string)) "28 December: outside the window" None (some_str (d 2026 12 28));
+ Alcotest.(check (option string)) "29 December: in the window" (Some "of-christmas-dec-29") (some_str (d 2026 12 29));
+ Alcotest.(check (option string)) "31 December: in the window" (Some "of-christmas-dec-31") (some_str (d 2029 12 31));
+ Alcotest.(check (option string)) "1 January: outside the window" None (some_str (d 2026 1 1));
+ Alcotest.(check (option string)) "2 January: in the window" (Some "of-christmas-jan-2") (some_str (d 2026 1 2));
+ Alcotest.(check (option string)) "5 January: in the window" (Some "of-christmas-jan-5") (some_str (d 2026 1 5));
+ Alcotest.(check (option string))
+ "6 January: deliberately excluded, Epiphany is fixed there unconditionally" None (some_str (d 2026 1 6));
+ Alcotest.(check (option string)) "7 January: in the window" (Some "of-christmas-jan-7") (some_str (d 2026 1 7));
+ Alcotest.(check (option string)) "8 January: outside the window" None (some_str (d 2026 1 8));
+ Alcotest.(check bool) "2028-12-24 is a Sunday, sanity check" true (D.weekday (d 2028 12 24) = D.Sun);
+ Alcotest.(check (option string)) "24 December on a Sunday (2028, Advent 4): guarded off" None
+ (some_str (d 2028 12 24));
+ Alcotest.(check bool) "2025-01-05 is a Sunday, sanity check" true (D.weekday (d 2025 1 5) = D.Sun);
+ Alcotest.(check (option string)) "5 January on a Sunday (2025): guarded off" None (some_str (d 2025 1 5))
+
+(* The fix's own headline claim, through the REAL resolution chain, not
+ just the slug function in isolation: the same civil date resolves the
+ SAME citation across two years with a different weekday alignment,
+ where it used to drift. Verified by hand against the pre-fix binary
+ before this fix landed: 2024-12-17 served "Zephaniah 3:1-2,9-13/
+ Matth 21:28-32" (that year's own "of-advent-3-tuesday" content) while
+ 2029-12-17 served "Numbers 24:2-7,15-17a/Matth 21:23-27" ("of-advent-3-
+ monday") -- two DIFFERENT citations for the identical calendar date.
+ 2024 (Tuesday) and 2029 (Monday) are two different, non-Sunday
+ weekdays, so neither the sanctoral layer nor the Sunday guard is in
+ play -- both years reach {!Lect_of.readings}'s own step 3 through the
+ plain temporal-ferial branch, exactly like {!test_step3_temporal_ferial}
+ above. *)
+let test_o_antiphon_no_drift () =
+ let check_year y expected_weekday_slug =
+ let lectionary = real_lectionary () in
+ let layer = real_sanctoral_layer () in
+ let index = L.index layer ~easter:Computus.gregorian_easter ~years:[ y - 1; y ] in
+ let date = d y 12 17 in
+ let expected_slug = (T.temporal date).Colitur_kernel.Temporal.office.Cel.slug in
+ Alcotest.(check string) (Printf.sprintf "%d-12-17: expected temporal slug" y) expected_weekday_slug
+ (Slug.to_string expected_slug);
+ match readings_on lectionary index date with
+ | Some fm, cs ->
+ Alcotest.(check bool) (Printf.sprintf "%d: tagged Own_slug" y) true
+ (fm.Mass_formulary.via = Mass_formulary.Own_slug);
+ Alcotest.(check (option string)) (Printf.sprintf "%d: said the temporal slug" y)
+ (Some (Slug.to_string expected_slug)) (Option.map Slug.to_string fm.Mass_formulary.said);
+ Alcotest.(check (list string)) (Printf.sprintf "%d: 17 December is Gen 49:2,8-10 / Matthew 1:1-17" y)
+ [ "Genesis 49:2,8-10"; "Matthew 1:1-17" ]
+ (List.map (fun (c : Colitur_kernel.Citation.t) -> c.reference) cs)
+ | None, _ -> Alcotest.failf "%d-12-17 should resolve readings" y
+ in
+ check_year 2024 "of-advent-3-tuesday";
+ check_year 2029 "of-advent-3-monday"
+
+(* Same claim, the Christmas-season window, through a REALISTIC step 2 ->
+ step 3 path: 2 January carries the fixed memorial of Saints Basil and
+ Gregory Nazianzen (calendar-2002.sexp), which has no dedicated
+ lectionary entry of its own, so both years fall through to the day's
+ own ferial -- exactly the shape 2024-01-02's own CLI-verified pre-fix
+ drift took (that year served "1 John 3:22-4:6/Matt 4:12-17,23-25",
+ 2029's weekday-keyed content, before this fix). 2024 (Tuesday) and 2025
+ (Thursday) are again two different non-Sunday weekdays. *)
+let test_christmas_date_keyed_no_drift () =
+ let check_year y =
+ let lectionary = real_lectionary () in
+ let layer = real_sanctoral_layer () in
+ let index = L.index layer ~easter:Computus.gregorian_easter ~years:[ y - 1; y ] in
+ let date = d y 1 2 in
+ (match L.on_date index date with
+ | [ e ] ->
+ Alcotest.(check string) (Printf.sprintf "%d-01-02: Basil/Gregory's own entry" y)
+ "saints-basil-the-great-and-gregory-nazianzen-bishops-and-doctors" (Slug.to_string e.L.cel.Cel.slug)
+ | es -> Alcotest.failf "%d-01-02: expected exactly one sanctoral entry, got %d" y (List.length es));
+ match readings_on lectionary index date with
+ | Some fm, cs ->
+ Alcotest.(check bool) (Printf.sprintf "%d: tagged Own_slug (fell through to the ferial)" y) true
+ (fm.Mass_formulary.via = Mass_formulary.Own_slug);
+ Alcotest.(check (list string)) (Printf.sprintf "%d: 2 January is 1 John 2:22-28 / John 1:19-28" y)
+ [ "1 John 2:22-28"; "John 1:19-28" ]
+ (List.map (fun (c : Colitur_kernel.Citation.t) -> c.reference) cs)
+ | None, _ -> Alcotest.failf "%d-01-02 should resolve readings" y
+ in
+ check_year 2024;
+ check_year 2025
+
let suite =
[ Alcotest.test_case "Sunday cycle table (OLM n.66, straddles Advent I)" `Quick test_sunday_cycle_table;
Alcotest.test_case "weekday cycle table (OLM n.69.4, straddles Advent I)" `Quick test_weekday_cycle_table;
@@ -364,9 +464,14 @@ let suite =
Alcotest.test_case "data/of/lectionary.sexp SHA-256 pinned" `Quick test_sha256_pinned;
Alcotest.test_case "lectio source SHA-256 pinned in the header" `Quick test_source_sha256_pinned_in_header;
Alcotest.test_case "a missing lectio source fails loudly, not silently" `Quick test_missing_source_fails_loudly;
- Alcotest.test_case "loads, 754 entries" `Quick test_load;
+ Alcotest.test_case "loads, 770 entries" `Quick test_load;
Alcotest.test_case "2026 coverage: exactly {25 December} unresolved" `Quick test_coverage_2026;
Alcotest.test_case "2026 coverage: 364 days resolve" `Quick test_pinned_resolved_count;
Alcotest.test_case "step 3: temporal ferial fallback" `Quick test_step3_temporal_ferial;
- Alcotest.test_case "step 2: sanctoral proper wins over the ferial" `Quick test_step2_sanctoral_wins
+ Alcotest.test_case "step 2: sanctoral proper wins over the ferial" `Quick test_step2_sanctoral_wins;
+ Alcotest.test_case "date_keyed_slug boundaries (OLM n. 69.3)" `Quick test_date_keyed_slug_boundaries;
+ Alcotest.test_case "O-Antiphon date-keyed reading does not drift (17 December)" `Quick
+ test_o_antiphon_no_drift;
+ Alcotest.test_case "Christmas-season date-keyed reading does not drift (2 January)" `Quick
+ test_christmas_date_keyed_no_drift
]