From 03d3c00ccdde318207c8b4fdc456e5e64d6b4abf Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 26 Aug 2026 13:57:14 +0200 Subject: feat(of-lectionary): hand-author the Christmas Day Mass, close the gap colitur readings --rite of printed "- | -" on 25 December every year -- lectio's own 988 keys never included the Christmas DAY Mass at all, only the Vigil (already correctly noted, but left unfilled, in tools/bootstrap_lectionary_of.ml's own named_overrides comment). Sourced, not guessed: docs/research/of/olm-1981-ocr.txt:4513-4520, "16 Ad Missam in die", the same primary authority this module already cites for the cycle rules. First reading and Gospel only, matching this file's own scope everywhere else (First/Gospel is the kernel's own boundary, I7, a separate deferred task) -- Isaiah 52:7-10 / John 1:1-18 (OLM's own LONGER form; the file's LINEAGE section already discloses that short/long-form choices are not modelled anywhere in this data, so this is an application of an existing limitation, not a new one). Injected via a new hand_authored table in the generator, merged into entries before assert_reachable/Lectionary.of_entries -- there is no ini section to derive this from. One new lookup_any call (readings' own existing temporal-slug fallback, unmodified) now resolves BOTH the formulary and the citations for 25 December from the same entry, so both halves of the "citations-unresolved"/"formulary" gap close together, not just the citation half. Verified across the WHOLE domain, not assumed: test_validate_of.ml's exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1, all 8 416 years) now asserts the Nativity gap fires on NONE of them, down from all 8 416 before this fix -- and is kept as a live, dedicated "must never recur" counter rather than deleted. test_rite_of.ml's landmark-year check and test_lectionary_of.ml's 2026 coverage pins are updated the same way (the unresolved-day set that used to be pinned as {25 December} is now pinned as empty, not merely a count going to zero). A concrete golden pin (test_golden_of.ml) locks the exact citations through the full resolved pipeline. data/of/lectionary.sexp regenerated (771 entries, was 770); its own SHA-256 re-pinned. make check (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, unpiped, foreground): exit 0, 895 tests, ~536s. --- test/test_lectionary_of.ml | 50 +++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'test/test_lectionary_of.ml') diff --git a/test/test_lectionary_of.ml b/test/test_lectionary_of.ml index a9fcdb1..03aaea5 100644 --- a/test/test_lectionary_of.ml +++ b/test/test_lectionary_of.ml @@ -178,15 +178,14 @@ let sha256_of_file path = don't transcribe it from generator stdout" discipline test_calendar_of_data.ml's own [test_sha256] already follows. *) let test_sha256_pinned () = - (* Fix wave I2 (final-review.md, 2026-08-25-colitur-of-phases-3-5): the - hash changed because the header grew a new "Citation-siglum - conversion" census -- the payload (parse this test's siblings assert - against) is byte-identical; only the disclosure comment above it is - new. Re-derived with `sha256sum data/of/lectionary.sexp`, not - transcribed from generator stdout, the same discipline this test's own - header states. *) + (* Fix wave I8 (final-review.md, 2026-08-25-colitur-of-phases-3-5): the + hash changed again -- ONE genuine new entry this time ("of-nativity", + the Christmas Day Mass hand-authored from OLM 1981; see + test_coverage_2026's own citation), not header-only as the I2 change + just above was. Re-derived with `sha256sum data/of/lectionary.sexp`, + same discipline. *) Alcotest.(check string) "data/of/lectionary.sexp SHA-256" - "aba7817ace8add740a5722f5fdb401a119869545eda9082e41e203df4ac27ee5" (sha256_of_file lectionary_path) + "e08207614472955974b504bfe6984ff44e882c4d8fadc778c4fa1ddce1adecf8" (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 @@ -252,7 +251,12 @@ let test_missing_source_fails_loudly () = let test_load () = let l = real_lectionary () in - Alcotest.(check int) "770 entries" 770 (List.length (Lectionary.entries l)) + (* 771, not 770: fix wave I8 (final-review.md) added ONE hand-authored + entry, "of-nativity" (the Christmas Day Mass, absent from lectio's own + source entirely -- see this file's own [test_coverage_2026] and + tools/bootstrap_lectionary_of.ml's own [hand_authored] table for the + citation and full argument). *) + Alcotest.(check int) "771 entries" 771 (List.length (Lectionary.entries l)) (* ---- coverage: a full sample civil year, both directions, PINNED ------ *) @@ -285,14 +289,18 @@ let test_coverage_2026 () = all_days_of_2026 in Alcotest.(check int) "365 days in 2026" 365 (List.length all_days_of_2026); - (* The ONE genuine gap this task's own bootstrap found and named: lectio's - 988 keys never include a Christmas DAY Mass (only the Vigil) -- see - data/of/lectionary.sexp's own provenance header and - tools/bootstrap_lectionary_of.ml's own [named_overrides] comment on - "christmas". Pinned as a NAMED set, not a bare count: a silent zero - here is exactly the failure mode the task brief warns against. *) - Alcotest.(check (list string)) "the unresolved-day set is exactly {25 December}" [ "2026-12-25" ] unresolved; - Alcotest.(check int) "364 of 365 days resolve readings" 364 (365 - List.length unresolved) + (* CLOSED, fix wave I8 (final-review.md, 2026-08-25-colitur-of-phases-3-5): + lectio's 988 keys never included a Christmas DAY Mass (only the + Vigil) -- tools/bootstrap_lectionary_of.ml's own [hand_authored] table + now injects it directly (Isa 52:7-10/John 1:1-18, OLM 1981's own "16 + Ad Missam in die", docs/research/of/olm-1981-ocr.txt:4513-4520), so + the set that used to be pinned as {25 December} is now pinned as + EMPTY -- still a NAMED set, not a bare count collapsing to zero by + coincidence: a future regression reopening this (or any other) gap + still fails loudly by naming the date, not merely by a count + mismatch. *) + Alcotest.(check (list string)) "the unresolved-day set is empty" [] unresolved; + Alcotest.(check int) "365 of 365 days resolve readings" 365 (365 - List.length unresolved) (* Same measurement, restated as "how many days resolve" rather than "how many don't" -- redundant with the count above by construction, kept @@ -308,7 +316,7 @@ let test_pinned_resolved_count () = (fun date -> match readings_on lectionary index date with Some _, _ :: _ -> true | _ -> false) all_days_of_2026) in - Alcotest.(check int) "364 days resolve readings in 2026" 364 resolved + Alcotest.(check int) "365 days resolve readings in 2026 (fix wave I8: was 364)" 365 resolved (* ---- resolution chain: unit-level spot checks -------------------------- *) @@ -471,9 +479,9 @@ 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, 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 "loads, 771 entries" `Quick test_load; + Alcotest.test_case "2026 coverage: nothing unresolved (fix wave I8)" `Quick test_coverage_2026; + Alcotest.test_case "2026 coverage: 365 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 "date_keyed_slug boundaries (OLM n. 69.3)" `Quick test_date_keyed_slug_boundaries; -- cgit v1.3