From f8d694d0cc19b71598e1ab64254efb069969f0a0 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Sat, 15 Aug 2026 00:36:43 +0200 Subject: kernel+ef: fix round 1 -- lectionary caller-supplied, not eager Critical (coordinator review): a clean `dune build` produced a `colitur` that died at startup on EVERY subcommand, including ones touching no lectionary data at all. Root cause was two-fold: data/ef/lectionary.sexp was never added to the root default-build alias (only materialised as a side effect of the test suite's own deps, which is why every check in the prior report passed), and Rite_ef.context loaded it as a module-init side effect via failwith, undoing Lectionary.load's own "never raises" promise at a point no caller could catch. Fixed structurally: Rite_ef.context is now a function taking ~lectionary, Lectionary_ef.readings takes ~lectionary, and neither touches the filesystem any more -- the same caller-supplied discipline the sanctoral layer already had, restoring rite_ef.mli's own pre-existing claim about it and leaving a seam for a future diocesan lectionary overlay. bin/main.ml grows load_ef_lectionary, a sibling of load_ef_layer, routed through the same colitur: %s / exit 2 path. data/ef/lectionary.sexp added to the root default alias. Every caller of Rite_ef.context updated to supply it. Also: two new tests that genuinely distinguish chain step 1 from step 2 (19 March 2026, Joseph's own proper over a competing temporal entry; 13 January 2030, Holy Family reached only through the temporal slug, the Baptism entirely absent) -- the prior two tests both survived swapping the chain order. Both new pins verified directly against the real data. The chain's own comment now states plainly that its warrant is lectio's observed behaviour, not a confirmed Missal citation, per the rules register's own open item. --- test/test_lectionary_ef.ml | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) (limited to 'test/test_lectionary_ef.ml') diff --git a/test/test_lectionary_ef.ml b/test/test_lectionary_ef.ml index a1a324f..b1c9e7d 100644 --- a/test/test_lectionary_ef.ml +++ b/test/test_lectionary_ef.ml @@ -10,6 +10,7 @@ open Rite_ef declares both as deps. *) let sanctoral_path = "../data/ef/sanctoral.sexp" let adjustments_path = "../data/ef/adjustments.sexp" +let lectionary_path = "../data/ef/lectionary.sexp" let real_layer () = let layer = @@ -27,6 +28,18 @@ let real_layer () = (List.map Overlay.diagnostic_to_string diagnostics); layer +(* [Rite_ef.context] takes [~lectionary] (fix round 1, coordinator review): + the module used to load data/ef/lectionary.sexp itself, as a side effect + of being linked, which meant a bare `dune build` produced a `colitur` + that died at startup on EVERY subcommand -- including ones (`easter`) + that touch no lectionary data at all -- the moment that file was absent + from the default build target. Caller-supplied now, same as the + sanctoral layer above. *) +let real_lectionary () = + match Lectionary.load lectionary_path with + | Ok l -> l + | Error e -> Alcotest.failf "%s: failed to load: %s" lectionary_path e + (* [Calendar.day] (not [year]): the liturgical year "opening in civil year y" is Advent-anchored (RG 61), so [Calendar.year _ _ 2030] covers Advent 2030 through November 2031 -- it would never contain 13 January 2030, which @@ -35,7 +48,7 @@ let real_layer () = let day y m d = let date = match Date.make ~year:y ~month:m ~day:d with | Ok x -> x | Error e -> Alcotest.fail e in - Calendar.day Rite_ef.context (real_layer ()) date + Calendar.day (Rite_ef.context ~lectionary:(real_lectionary ())) (real_layer ()) date let refs (ld : _ Liturgical_day.t) = List.map (fun c -> c.Citation.reference) ld.citations @@ -63,6 +76,48 @@ let test_step2_lenten_feria_has_its_own () = [ "Ezech 34:11-16"; "Matt 25:31-46" ] (refs (day 2026 2 23)) +(* Fix round 1 (coordinator review, Important finding 2): neither test above + actually distinguishes step 1 from step 2 -- both survive swapping the + chain order. 13 January 2026's temporal slug (ef-time-after-epiphany-1- + tuesday) has no lectionary entry at all, so a swapped chain falls through + to the same []-then-sanctoral answer; 23 February 2026's OBSERVED + celebration IS the temporal office (ef-lent-1-monday carries no sanctoral + entry of its own), so [observed.citations] and the temporal-slug lookup + are the same lookup wearing two names -- order is a no-op either way. + + 19 March 2026 (St Joseph) genuinely needs step 1 to run FIRST: the + observed celebration (Joseph, Class1, a real sanctoral entry with its own + citations) and the day's own temporal slug (ef-lent-4-thursday, ALSO a + real lectionary entry, with different citations) disagree. Verified + directly against the real data (not transcribed): both value pairs below + were read off the actual resolved day and the actual + data/ef/lectionary.sexp entry, not assumed. *) +let test_step1_wins_over_a_competing_step2_entry () = + Alcotest.(check (list string)) + "19 March 2026: Joseph's own proper wins over Lent 4 Thursday's, which the temporal slug also has" + [ "Ecclus 45:1-6"; "Matt 1:18-21" ] + (refs (day 2026 3 19)) + +(* Fix round 1 (coordinator review): the mirror-image pin for step 2 -- a day + whose OBSERVED celebration carries no citations of its own (Holy Family, + synthesised by [Temporal_ef] itself, not sourced from + data/ef/sanctoral.sexp, so [Celebration.citations] is empty) falls + through to the temporal slug, and the temporal slug's own lectionary + entry is genuinely Holy Family's Mass, not the Baptism's -- RG 112(a) + (see test_golden.ml's own [test_holy_family_excludes_baptism_2030]) + excludes the Baptism from this day entirely, so there is no sanctoral + citation anywhere to fall back to even in principle. Verified directly + against the real data. *) +let test_step2_holy_family_reached_through_temporal_slug () = + Alcotest.(check (list string)) + "13 January 2030: Holy Family reached via the temporal slug, the Baptism entirely absent" + [ "Col 3:12-17"; "Luke 2:42-52" ] + (refs (day 2030 1 13)) + let suite = [ ("step 1: sanctoral proper", `Quick, test_step1_sanctoral_proper); - ("step 2: own temporal proper", `Quick, test_step2_lenten_feria_has_its_own) ] + ("step 2: own temporal proper", `Quick, test_step2_lenten_feria_has_its_own); + ("step 1 wins over a competing step 2 entry", `Quick, + test_step1_wins_over_a_competing_step2_entry); + ("step 2: Holy Family reached through the temporal slug, Baptism absent", `Quick, + test_step2_holy_family_reached_through_temporal_slug) ] -- cgit v1.3