diff options
| -rw-r--r-- | lib/rites/rite_of/temporal_of.ml | 28 | ||||
| -rw-r--r-- | test/test_rite_of.ml | 14 | ||||
| -rw-r--r-- | test/test_temporal_of.ml | 93 |
3 files changed, 129 insertions, 6 deletions
diff --git a/lib/rites/rite_of/temporal_of.ml b/lib/rites/rite_of/temporal_of.ml index e375350..1da6cf9 100644 --- a/lib/rites/rite_of/temporal_of.ml +++ b/lib/rites/rite_of/temporal_of.ml @@ -532,8 +532,32 @@ let temporal d = let anchors y = let easter = Computus.gregorian_easter y in let off n = Date.add_days easter n in - [ ("of-nativity-vigil", mk y 12 24); - ("of-nativity", mk y 12 25); + (* CORRECTED (celebrant-rubrics-phase1 review, 2026-08-26): this used to + list ("of-nativity-vigil", mk y 12 24) UNCONDITIONALLY -- a regression + from the earlier R8 fix, which added the Sunday guard to [named] + above (Tabula I.2 outranks I.3: on a Sunday, 24 December is the + Fourth Sunday of Advent, not the Vigil) but never updated this + function to match, so in 2028/2034 this list claimed the Vigil where + [temporal] actually returns "of-advent-sunday-4". This module's own + doc comment on [anchors] says its whole purpose is to "catch an + accidental single-site drift... rather than leaving it invisible" -- + the irony being that it caught exactly this drift, just years later + than it should have, only once Validate.run was wired for the OF rite + at all (nothing before that compared [anchors] against [named]/ + [temporal], so the two were free to disagree unnoticed). The Fourth + Sunday of Advent is not added as a replacement entry on a Sunday year: + it is an ordinary generic week-numbered Sunday + ({!sunday_slug}'s own "of-advent-sunday-4", produced every year + regardless of where in the week it falls), never one of the NAMED + days this function exists to restate -- see the module doc comment + above [anchors] ("Independent restatement of {!named}'s... dates"). + [named]'s own guard is restated exactly, not re-derived: [named] + checks the WEEKDAY of the actual civil date, so this does too, rather + than assuming Advent 4 Sunday is always 24 December (it is any Sunday + 18-24 December; testing the weekday of 24 December itself is exact + regardless of which Sunday that turns out to be). *) + (if Date.weekday (mk y 12 24) = Date.Sun then [] else [ ("of-nativity-vigil", mk y 12 24) ]) + @ [ ("of-nativity", mk y 12 25); ("of-mary-mother-of-god", mk y 1 1); ("of-epiphany", mk y 1 6); ("of-holy-family", holy_family y); diff --git a/test/test_rite_of.ml b/test/test_rite_of.ml index 90cedc1..d16bcf3 100644 --- a/test/test_rite_of.ml +++ b/test/test_rite_of.ml @@ -204,7 +204,19 @@ let test_year_9999_does_not_raise () = THIS TASK'S SCOPE to fix -- lectionary_of.* and data/of/lectionary.sexp are excluded from this task's brief, and temporal_of.ml's own rubric logic is Task 1's deliverable -- reported as concerns in this task's own - report rather than fixed here or silently excluded without explanation. *) + report rather than fixed here or silently excluded without explanation. + + CORRECTED (fix-lectionary-anchors task, 2026-08-26): the of-nativity- + vigil/[anchors] disagreement described in the paragraph above is now + FIXED -- {!Temporal_of.anchors} carries the same Sunday guard [named] + already had. A dedicated exhaustive sweep + (test_temporal_of.ml's own [test_exhaustive_domain_sweep], every civil + year 1584-9997 under COLITUR_EXHAUSTIVE_SWEEP=1) confirmed this was + also the ONLY unexplained [anchors]/[temporal] drift anywhere in the + domain -- the other two items in this paragraph (the lectionary + weekday-cycle-letter asymmetry, and the Holy Family 30-December + fallback {!is_known_holy_family_fallback_gap} below already pins) are + unrelated and remain open, unchanged by this fix. *) let sample_years = let rec range a b = if a > b then [] else a :: range (a + 1) b in range 2005 2050 diff --git a/test/test_temporal_of.ml b/test/test_temporal_of.ml index 1ccd098..2522e44 100644 --- a/test/test_temporal_of.ml +++ b/test/test_temporal_of.ml @@ -252,6 +252,49 @@ let test_nativity_vigil_yields_to_advent_sunday () = Alcotest.(check string) "2026: named still claims the vigil" "of-nativity-vigil" (named_slug thu); Alcotest.(check string) "2026: temporal still resolves to the vigil" "of-nativity-vigil" (slug_of thu) +(* FIX (celebrant-rubrics-phase1 review, 2026-08-26): [anchors] used to + list ("of-nativity-vigil", 24 December) UNCONDITIONALLY -- a regression + from the R8 fix just above, which added [named]'s own Sunday guard but + never updated [anchors] to match, so in any year 24 December is itself + a Sunday (2028 and 2034 among them, the SAME two years + {!test_nativity_vigil_yields_to_advent_sunday} already exercises) + [anchors] claimed the Vigil where [temporal] actually resolves to + "of-advent-sunday-4". Checked the same way + Colitur_kernel.Validate.run's own "anchor" check does: for EVERY + (slug, date) pair [anchors] returns, not only the 24 December one -- + catching a drift anywhere else in the list, not merely confirming this + one entry. *) +let test_anchors_agree_with_temporal () = + let check_year y = + List.iter + (fun (expected_slug, date) -> + Alcotest.(check string) + (Printf.sprintf "%d: anchors' %S (%s) agrees with temporal" y expected_slug (iso date)) + expected_slug (slug_of date)) + (T.anchors y) + in + List.iter check_year [ 2026; 2027; 2028; 2029; 2034; 2035 ] + +(* The specific regression, restated directly (not merely implied by the + agreement check above passing): on a Sunday-24-December year [anchors] + must not claim the Vigil AT ALL, since it is a generic week-numbered + Sunday ("of-advent-sunday-4") [anchors]'s own doc comment says it never + restates ("Independent restatement of [named]'s... dates") -- adding a + replacement entry would be a second, equally wrong shape (a NAMED + restatement of a non-named day). On a non-Sunday year, unaffected: the + Vigil entry is unchanged. *) +let test_anchors_omit_vigil_on_advent_sunday_years () = + List.iter + (fun y -> + Alcotest.(check bool) (Printf.sprintf "%d: anchors omits of-nativity-vigil (24 Dec is Advent IV)" y) false + (List.mem_assoc "of-nativity-vigil" (T.anchors y))) + [ 2028; 2034 ]; + List.iter + (fun y -> + Alcotest.(check bool) (Printf.sprintf "%d: anchors still lists of-nativity-vigil" y) true + (List.mem_assoc "of-nativity-vigil" (T.anchors y))) + [ 2026; 2027; 2029; 2035 ] + let test_colours () = let colour_of dt = (T.temporal dt).Colitur_kernel.Temporal.office.Colitur_kernel.Celebration.colour in Alcotest.(check string) "Good Friday is red" "red" (Colitur_kernel.Colour.to_string (colour_of (d 2026 4 3))); @@ -366,7 +409,26 @@ let colitur_exhaustive_sweep_env = "COLITUR_EXHAUSTIVE_SWEEP" let test_exhaustive_domain_sweep () = if Sys.getenv_opt colitur_exhaustive_sweep_env = None then Alcotest.skip () - else + else begin + (* FIX (celebrant-rubrics-phase1 review, 2026-08-26): the audit half of + the anchors/temporal task brief -- does any OTHER {!T.anchors} entry + drift from {!T.temporal} the same way "of-nativity-vigil" used to + (fixed just above, {!test_anchors_agree_with_temporal})? Checked for + EVERY entry [anchors y] returns, across the WHOLE exhaustive domain, + not merely 24 December, folded into this loop rather than a separate + one so it shares its own env-var gate. Answer: no NEW drift -- the + only mismatch found is the ALREADY-KNOWN "of-holy-family" gap + (Normae n.35(a)'s own 30-December fallback, whenever 26-31 December + holds no Sunday): there, unlike the Vigil bug, {!T.anchors} (via + {!T.holy_family}) computes the RIGHT date and it is {!T.temporal} + that fails to reach it (its Holy-Family test lives inside + [sunday_slug]'s own Sunday-only dispatch arm) -- a different shape, + already found, pinned and explicitly deferred by test_rite_of.ml's + own [test_holy_family_fallback_1583_known_wrong_ferial]/ + [is_known_holy_family_fallback_gap] (see that file's own extensive + citation), collected below rather than failing the sweep on it a + second time in a second place. *) + let holy_family_fallback_years = ref [] in for y = 1584 to 9997 do let days = walk_year y in if compressed_seasons days <> expected_season_runs then @@ -375,8 +437,30 @@ let test_exhaustive_domain_sweep () = if not (prop_ordinary_time_week_in_range y) then Alcotest.failf "%d: an Ordinary Time week is out of 1..34" y; if not (prop_advent_four_sundays y) then Alcotest.failf "%d: Advent does not have exactly four Sundays" y; if not (prop_slug_uniqueness y) then Alcotest.failf "%d: a slug repeats within the liturgical year" y; - if not (prop_weekday_agrees y) then Alcotest.failf "%d: a weekday disagrees with Date.weekday" y - done + if not (prop_weekday_agrees y) then Alcotest.failf "%d: a weekday disagrees with Date.weekday" y; + List.iter + (fun (expected_slug, date) -> + let actual = slug_of date in + if actual <> expected_slug then + if expected_slug = "of-holy-family" then holy_family_fallback_years := y :: !holy_family_fallback_years + else + Alcotest.failf + "%d: anchors' %S (%s) disagrees with temporal (%S) -- an UNEXPECTED drift, not the known \ + Holy Family fallback shape" + y expected_slug (iso date) actual) + (T.anchors y) + done; + (* The known shape is real, not vacuous, and really is the Normae + n.35(a) trigger, for every year collected above, not merely + plausible-sounding. *) + Alcotest.(check bool) "the Holy Family fallback shape is real and not empty" true + (!holy_family_fallback_years <> []); + List.iter + (fun y -> + Alcotest.(check bool) (Printf.sprintf "%d is really a Christmas-Day-is-Sunday year" y) true + (D.weekday (d y 12 25) = D.Sun)) + !holy_family_fallback_years + end (* 1583 and 9999 themselves sit at the domain edges, where the [y-1]/[y+1] probes the properties above use would call [T.year_start] on an @@ -410,6 +494,9 @@ let suite = Alcotest.test_case "sunday slugs" `Quick test_sunday_slugs; Alcotest.test_case "ferial slugs" `Quick test_ferial_slugs; Alcotest.test_case "nativity vigil yields to Advent Sunday IV" `Quick test_nativity_vigil_yields_to_advent_sunday; + Alcotest.test_case "anchors agree with temporal" `Quick test_anchors_agree_with_temporal; + Alcotest.test_case "anchors omit the vigil on an Advent-IV-Sunday year" `Quick + test_anchors_omit_vigil_on_advent_sunday_years; Alcotest.test_case "colours" `Quick test_colours; Alcotest.test_case "domain edges do not raise" `Quick test_domain_edges_do_not_raise; Alcotest.test_case "exhaustive domain sweep" `Slow test_exhaustive_domain_sweep ] |
