summaryrefslogtreecommitdiff
path: root/test/test_temporal_of.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 10:35:33 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 10:35:33 +0200
commite199ad6f599a263cad1f9886b512da8b3a46f465 (patch)
tree21f34e527b43a5c4b87a6a79b77e48cec8f913b9 /test/test_temporal_of.ml
parentb6d0eba04d1da51515dad70127112898b500c1bc (diff)
downloadcolitur-e199ad6f599a263cad1f9886b512da8b3a46f465.tar.gz
colitur-e199ad6f599a263cad1f9886b512da8b3a46f465.zip
fix(of-temporal): make anchors agree with temporal on the Nativity Vigil
Temporal_of.named correctly guards the Nativity Vigil: when 24 December falls on a Sunday it is the Fourth Sunday of Advent (Tabula I.2 outranks I.3), not the Vigil, and named returns None so temporal's dispatch falls through to the ordinary Sunday slug. anchors, however, still listed ("of-nativity-vigil", 24 December) unconditionally -- a regression from the earlier R8 fix, which added the guard to named but never updated anchors to match. In 2028 and 2034, anchors claimed the Vigil where temporal actually returns "of-advent-sunday-4". anchors exists precisely as an independent restatement whose stated purpose is to catch an accidental single-site drift rather than leaving it invisible -- it caught exactly this drift, just later than it should have, only once Validate.run was wired for the OF rite for the first time (nothing before that compared anchors against named/temporal, so the two were free to disagree unnoticed). Fixed by restating named's own guard exactly: the Vigil entry is omitted on a Sunday year rather than replaced, since the Fourth Sunday of Advent is an ordinary generic week-numbered Sunday, not one of the named days anchors exists to restate. Audited every other anchors entry the same way, across the full 1584-9997 domain (test_temporal_of.ml's own exhaustive sweep, run under COLITUR_EXHAUSTIVE_SWEEP=1): no further drift found. The sweep does surface one already-known, already-pinned mismatch -- the "of-holy-family" entry, in any year 26-31 December holds no Sunday (Normae n.35(a)'s own 30-December fallback) -- but it is a different shape, not a second instance of this bug: there anchors computes the right date and it is temporal that fails to reach it, which test_rite_of.ml already found, pinned and deferred as out of scope. Corrected that file's own stale comment, which still described the now-fixed Vigil disagreement as an open gap. New tests in test_temporal_of.ml cover both a Sunday-24-December year (2028, 2034) and a non-Sunday one, checking every anchors entry against temporal directly, plus a positive assertion that the Vigil entry is correctly present or absent depending on the year.
Diffstat (limited to 'test/test_temporal_of.ml')
-rw-r--r--test/test_temporal_of.ml93
1 files changed, 90 insertions, 3 deletions
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 ]