summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/test_differential.ml21
-rw-r--r--test/test_golden.ml53
-rw-r--r--test/test_precedence_ef.ml105
-rw-r--r--test/test_temporal_ef.ml156
4 files changed, 335 insertions, 0 deletions
diff --git a/test/test_differential.ml b/test/test_differential.ml
index 94adb2c..bea1bf4 100644
--- a/test/test_differential.ml
+++ b/test/test_differential.ml
@@ -500,6 +500,22 @@ let joseph_annunciation_collision_dates_2005_2050 = [ "2008-04-01"; "2035-04-03"
let holy_family_baptism_collision_dates_2005_2050 =
[ "2008-01-13"; "2013-01-13"; "2019-01-13"; "2030-01-13"; "2036-01-13"; "2041-01-13"; "2047-01-13" ]
+(* C16 -- ef-holyname-rg110 task: RG 17(a)'s own fallback (2 January, "secus
+ die 2 ianuarii"), for every civil year 2005-2050 with no Sunday 2-5
+ January -- independently re-derived against `date -d <year>-01-0{2..5}
+ +%u`, not transcribed from the register's domain-wide figure (see
+ data/ef/expected-divergences.sexp's own C16 note for the full citation
+ and the derivation). lectio has no equivalent fallback at all (checked
+ directly against tridentine-calendar.ini, which carries no 01-02 entry),
+ so it shows the plain Christmastide ferial slug on every one of these
+ dates, class-4 -- colitur's own `ef-holy-name`, class-2, is a genuine
+ [Slug_f; Rank] divergence, not a naming synonym Layer A could absorb. *)
+let holy_name_fallback_dates_2005_2050 =
+ [ "2006-01-02"; "2007-01-02"; "2008-01-02"; "2012-01-02"; "2013-01-02"; "2017-01-02";
+ "2018-01-02"; "2019-01-02"; "2023-01-02"; "2024-01-02"; "2029-01-02"; "2030-01-02";
+ "2034-01-02"; "2035-01-02"; "2036-01-02"; "2040-01-02"; "2041-01-02"; "2045-01-02";
+ "2046-01-02"; "2047-01-02" ]
+
(* [layer_c_reason l c diffs] returns the [data/ef/expected-divergences.sexp]
[id] this row-pair's remaining (post Layer A/B) diff set belongs to, or
[None] if nothing here explains it (a genuine, uncovered failure). *)
@@ -550,6 +566,11 @@ let layer_c_reason (l : row) (c : row) diffs =
&& subset diffs [ Season; Slug_f ]
&& String.equal c.slug "ef-time-after-epiphany-sunday-1"
then Some "C15"
+ else if
+ List.mem l.date holy_name_fallback_dates_2005_2050
+ && subset diffs [ Slug_f; Rank ]
+ && String.equal c.slug "ef-holy-name"
+ then Some "C16"
else None
(* ---------------------------------------------------------------------- *)
diff --git a/test/test_golden.ml b/test/test_golden.ml
index 1724375..c5aa65a 100644
--- a/test/test_golden.ml
+++ b/test/test_golden.ml
@@ -634,6 +634,53 @@ let test_holy_family_earliest_possible_date_2024 () =
"2024-01-07 sunday season=christmastide week=- slug=ef-time-after-epiphany-sunday-1 rank=class-2 \
colour=white subject=lord comms=[] in=- out=[]"
+(* Most Holy Name of Jesus (RG 17(a), scan-verified, docs/research/rules-
+ register.md §6.2/§4): "festum Ss.mi Nominis Iesu, celebrandum dominica
+ quae occurrit a die 2 ad 5 ianuarii (secus die 2 ianuarii)" -- unlike Holy
+ Family (RG 17(b), immediately above), this window CAN be, and routinely
+ is, empty of a Sunday (3,619 of 8,417 years domain-wide) -- the
+ calendarium's own fallback, "vel, ea deficiente, die 2 ianuarii", is what
+ the SECOND pin below exercises.
+
+ *** THIS FILE'S OWN VACUITY WARNING, APPLIED DIRECTLY ***
+
+ [test_holy_name_sunday_shape_2026] pins a year where the Sunday shape
+ fires -- slug/rank/colour/season/comms here are ALL already what
+ [T.sunday_slug]'s Christmastide branch computed before this task's fix
+ (the identical accident Holy Family's own [describe] note above already
+ explains for its shape): an unnamed Sunday and Holy Name of Jesus share
+ every one of those fields on this date. Reverting the fix leaves this
+ pin GREEN except for [subject] -- the ONE field it actually tests, same
+ discipline as Holy Family's own "coordinator finding 4" fix. This pin
+ earns its place only by asserting [subject] explicitly, not by pinning
+ the whole line uncritically.
+
+ [test_holy_name_fallback_2029] is the pin that actually has teeth against
+ the LARGER of the two defects this task closed: before the fix, colitur
+ emitted NO Holy Name office at all in a fallback year -- not merely an
+ unnamed generic Sunday (Holy Family's own pre-fix shape), but the
+ ordinary Christmastide ferial slug (`ef-christmas-1-tuesday`, class-4)
+ the task brief's own example names. Reverting the fallback half of the
+ fix reddens this pin on EVERY field (slug, rank, subject), not only one --
+ the shape this file's header calls "the whole line moved", the opposite
+ of the Sunday pin's own narrow, single-field teeth. *)
+let test_holy_name_sunday_shape_2026 () =
+ check ~msg:"2026-01-04: Holy Name of Jesus, Sunday shape -- oracle-corroborated (missalemeum's own \
+ 2026-01-04 row: rank 2, white, title \"Holy Name of Jesus\")"
+ 2026 1 4
+ "2026-01-04 sunday season=christmastide week=- slug=ef-holy-name-sunday rank=class-2 colour=white \
+ subject=lord comms=[] in=- out=[]"
+
+let test_holy_name_fallback_2029 () =
+ check ~msg:"2029-01-02: Holy Name of Jesus, FALLBACK shape (RG 17(a)'s own \"secus die 2 ianuarii\", the \
+ calendarium's \"vel, ea deficiente, die 2 ianuarii\") -- no Sunday falls 2-5 January 2029 \
+ (`date -d 2029-01-0{2,3,4,5} +%u` = 2,3,4,5, independently confirmed), so 2 January itself \
+ carries the feast; before this task's fix colitur emitted `ef-christmas-1-tuesday class-4` \
+ here instead -- a real, II-class office simply missing, not merely misnamed"
+ 2029 1 2
+ "2029-01-02 tuesday season=christmastide week=- slug=ef-holy-name rank=class-2 colour=white \
+ subject=lord comms=[] in=- out=[]"
+
(* 2025-02-02, the other real instance of the SAME pattern -- not a
contrasting second direction, per this task's fix round 1 (CRITICAL
finding, item 1, reverted; see register §6.0 for the full account).
@@ -793,6 +840,12 @@ let suite =
`Quick test_holy_family_excludes_baptism_2030;
Alcotest.test_case "Holy Family, earliest possible date (2024-01-07)" `Quick
test_holy_family_earliest_possible_date_2024;
+ Alcotest.test_case "Holy Name of Jesus (RG17(a)), Sunday shape, subject asserted (2026-01-04)" `Quick
+ test_holy_name_sunday_shape_2026;
+ Alcotest.test_case
+ "Holy Name of Jesus, fallback shape (RG17(a) \"secus die 2 ianuarii\", 2029-01-02 -- no Sunday in \
+ the window)"
+ `Quick test_holy_name_fallback_2029;
Alcotest.test_case
"ef-rebootstrap: RG91 entry24 (III-class feast) outranks entry28 (class-4 feria) -- the six dates \
deferred since 2038's own Easter-extreme pin (2008-04-02/04/05, 2038-03-06/08/09)"
diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml
index 4ee2fd8..d873e34 100644
--- a/test/test_precedence_ef.ml
+++ b/test/test_precedence_ef.ml
@@ -1426,6 +1426,103 @@ let test_class1_feast_inside_holy_familys_window_end_to_end () =
Alcotest.failf "expected exactly one Privileged commemoration (Holy Family), got %d"
(List.length other))
+(* Most Holy Name of Jesus (RG 17(a)) -- the SAME two proofs immediately
+ above, restated for Holy Name instead of Holy Family. UNLIKE Holy Family,
+ there is no LIVE collision to exercise here (this file's own [cases]/
+ [disposition_cases] rows never construct one, because
+ data/ef/sanctoral.sexp has nothing fixed-date 2-5 January besides
+ [telesphorus-pope-and-martyr] (5 Jan, Commemoration_only, so it can never
+ contest [observed] at all -- {!P.resolve}'s own partition holds it out of
+ the band contest entirely) -- register §6.2's own account: "no live
+ collision exists for it in this codebase's current data". These two
+ tests are the SYNTHETIC proof the task brief asked for instead: that
+ {!band}'s [entry_14_movable_band]/RG 112(a) mechanism, ALREADY proved
+ above for Holy Family, governs Holy Name identically, since neither
+ {!band} nor {!disposition} key on anything Holy-Family-specific (only on
+ [subject]/[rank]/[origin], which [temporal_ef.ml]'s fix now sets
+ identically for both feasts) -- not a live-data regression test, a
+ mechanism-generalises-correctly one, the same distinction this file's own
+ header draws between a table row and an end-to-end proof.
+
+ Run against BOTH of RG 17(a)'s own shapes -- the Sunday (2026-01-04) and
+ the 2 January fallback (2029-01-02, {!T.holy_name_fallback_date}) --
+ because [temporal_ef.ml]'s own fallback-branch comment claims both band
+ identically at {!PE.entry_14_movable_band} (ONE feast, not two
+ differently-classified ones); a table proving the SAME two outcomes hold
+ for both dates is direct evidence for that specific claim, not merely
+ for the Sunday shape colitur already had a name for before this task. *)
+let holy_name_probe_dates =
+ [ ("Sunday shape (2026-01-04)", mk 2026 1 4); ("fallback shape (2029-01-02)", T.holy_name_fallback_date 2029) ]
+
+let test_holy_name_excludes_synthetic_lord_end_to_end () =
+ List.iter
+ (fun (label, date) ->
+ let holy_name = of_temporal date in
+ Alcotest.(check bool) (label ^ ": sanity -- temporal_ef really did tag this Lord") true
+ (holy_name.P.cel.Cel.subject = Sub.Lord);
+ let day_ctx = ctx date in
+ let synthetic_lord =
+ cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
+ "ef-synthetic-fixed-lord-probe"
+ in
+ let resolution = P.resolve real_rules day_ctx ~temporal:holy_name ~sanctoral:[ synthetic_lord ] in
+ Alcotest.(check string) (label ^ ": Holy Name is observed (RG91 e14: movable first)")
+ (S.to_string holy_name.P.cel.Cel.slug) (S.to_string resolution.P.observed.P.cel.Cel.slug);
+ Alcotest.(check int) (label ^ ": no commemorations at all -- RG112(a)'s own exclusion") 0
+ (List.length resolution.P.commemorations);
+ Alcotest.(check (list string)) (label ^ ": the synthetic Lord candidate is OMITTED, not commemorated")
+ [ "ef-synthetic-fixed-lord-probe" ]
+ (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.omitted))
+ holy_name_probe_dates
+
+(* A REAL asymmetry this test found between the two shapes, kept explicit
+ rather than papered over by forcing one uniform expectation on both --
+ RG 109(a)'s privilege is textually "of a SUNDAY" (docs/research/rules-
+ register.md §4), read here via {!PE.is_sunday_slug}'s own substring test:
+ the Sunday shape's slug ("ef-holy-name-sunday") carries the marker and
+ the fallback shape's ("ef-holy-name") genuinely does not, because 2
+ January genuinely is not a Sunday. So a losing Holy-Name-Sunday is
+ PRIVILEGED (RG109(a)) and survives RG111(a)'s "I class: none save one
+ privileged" cap when it loses to a higher day; a losing Holy-Name-
+ fallback has no RG109(a)-(f) category to claim (not a Sunday, not I
+ class, none of (c)-(f) either) and so is only ORDINARY -- which
+ RG111(a)'s own cap admits NONE of on a I-class day. Both outcomes are
+ individually correct readings of RG 109/111's own closed lists, not a
+ defect in either shape -- first observed as a test failure while writing
+ this pair (the original version of this test asserted one Privileged
+ commemoration for BOTH shapes uniformly and reddened here), traced to
+ this citation rather than forced to pass. *)
+let test_class1_feast_inside_holy_name_window_end_to_end () =
+ List.iter
+ (fun (label, date, expect_privileged_commemoration) ->
+ let holy_name = of_temporal date in
+ let day_ctx = ctx date in
+ let synthetic_class1 =
+ cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:PE.universal_layer "ef-synthetic-class1-probe-hn"
+ in
+ let resolution = P.resolve real_rules day_ctx ~temporal:holy_name ~sanctoral:[ synthetic_class1 ] in
+ Alcotest.(check string) (label ^ ": the I-class candidate is observed, NOT Holy Name (RG91 e11 beats e14-movable)")
+ "ef-synthetic-class1-probe-hn" (S.to_string resolution.P.observed.P.cel.Cel.slug);
+ Alcotest.(check (list string)) (label ^ ": nothing transfers") []
+ (List.map (fun c -> S.to_string c.P.cel.Cel.slug) resolution.P.deferred);
+ if expect_privileged_commemoration then
+ match resolution.P.commemorations with
+ | [ (c, P.Privileged) ] ->
+ Alcotest.(check string)
+ (label ^ ": Holy Name itself is privileged-commemorated (RG109(a)), neither omitted nor transferred")
+ (S.to_string holy_name.P.cel.Cel.slug) (S.to_string c.P.cel.Cel.slug)
+ | other ->
+ Alcotest.failf "%s: expected exactly one Privileged commemoration (Holy Name), got %d" label
+ (List.length other)
+ else
+ Alcotest.(check int)
+ (label ^ ": RG111(a) admits none -- Holy Name's own commemoration here is Ordinary \
+ (no RG109(a)-(f) category applies to a non-Sunday loser), and a I-class day admits \
+ only a Privileged one")
+ 0 (List.length resolution.P.commemorations))
+ [ ("Sunday shape (2026-01-04)", mk 2026 1 4, true);
+ ("fallback shape (2029-01-02)", T.holy_name_fallback_date 2029, false) ]
+
(* Completes Task 7's carried fix (RG 91 entry 8, §4): on a real Sunday
landing on 2 November, All Souls does not merely lose (that was Task 7's
[band] fix, proved by [test_all_souls_yields_to_sunday] above) -- it must
@@ -1681,6 +1778,14 @@ let suite =
"fix round 1, finding 1: a synthetic I-class candidate inside Holy Family's own window is \
observed and does not transfer (2026-01-11)"
`Quick test_class1_feast_inside_holy_familys_window_end_to_end;
+ Alcotest.test_case
+ "RG112(a) + RG91 e14 \"primum mobilia\": Holy Name of Jesus excludes a synthetic fixed Lord \
+ candidate entirely, both shapes (Sunday + fallback)"
+ `Quick test_holy_name_excludes_synthetic_lord_end_to_end;
+ Alcotest.test_case
+ "a synthetic I-class candidate inside Holy Name's own window is observed and does not \
+ transfer, both shapes (Sunday + fallback)"
+ `Quick test_class1_feast_inside_holy_name_window_end_to_end;
Alcotest.test_case "transfer_target: general RG96 search walks past more than one blocked day"
`Quick test_transfer_target_general_multi_step_search;
Alcotest.test_case "transfer_target: Annunciation exception starts at Monday after Low Sunday"
diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml
index 23a817d..f341533 100644
--- a/test/test_temporal_ef.ml
+++ b/test/test_temporal_ef.ml
@@ -272,6 +272,105 @@ let test_holy_family_no_gap_year () =
Alcotest.(check string) "2025 (6 Jan a Monday): 12 Jan" "2025-01-12"
(D.to_iso8601 (T.holy_family_sunday 2025))
+(* RG 17(a) (Caput III, "De Dominicis"; full text on [T.holy_name_sunday]'s
+ own citation), Sunday shape: "festum Ss.mi Nominis Iesu, celebrandum
+ dominica quae occurrit a die 2 ad 5 ianuarii". Deliberately NOT part of
+ [T.named], same reason as Holy Family (that test's own comment): the
+ slug/rank/colour this branch produces for a plain Sunday 2-5 January
+ already match what [T.sunday_slug]'s Christmastide branch always
+ computed; only [subject]/[names] change. *)
+let test_holy_name_sunday_shape () =
+ (* 2026: 4 January is the Sunday (independently cross-checked against
+ `date -d 2026-01-04 +%u` = 7). Oracle-corroborated: missalemeum's own
+ 2026-01-04 row is rank 2, white, title "Holy Name of Jesus" (the task
+ brief's own example). *)
+ Alcotest.(check string) "2026: slug is the ordinary Holy-Name-window Sunday key" "ef-holy-name-sunday"
+ (slug_of (d 2026 1 4));
+ Alcotest.(check string) "2026: II class" "class-2" (rank_of (d 2026 1 4));
+ Alcotest.(check string) "2026: white" "white" (colour_of (d 2026 1 4));
+ Alcotest.(check bool) "2026: subject is Lord (RG 91 entry 14, \"Festa Domini II classis\")" true
+ (subject_of (d 2026 1 4) = Sub.Lord);
+ Alcotest.(check (option string)) "2026: Latin name from the calendarium/propers, verbatim"
+ (Some "Sanctissimi Nominis Iesu") (name_la_of (d 2026 1 4));
+ Alcotest.(check (option string)) "2026: no English name (deliberate -- would read the oracle it is \
+ compared against)"
+ None (Colitur_kernel.Names.find (office (d 2026 1 4)).Cel.names en);
+ (* Negative controls: every OTHER day in the 2-5 January window that year
+ stays [subject Temporal] -- the override is scoped to exactly the one
+ Sunday [T.holy_name_sunday] names, not the whole window. *)
+ List.iter
+ (fun dd ->
+ Alcotest.(check bool) (Printf.sprintf "2026-01-%02d: NOT Lord (not the Sunday)" dd) true
+ (subject_of (d 2026 1 dd) = Sub.Temporal))
+ [ 2; 3; 5 ];
+ (* 2027: 3 January is the Sunday (`date -d 2027-01-03 +%u` = 7) -- the
+ oracle window's own second instance of the Sunday shape. missalemeum's
+ 2027-01-03 row independently corroborates: rank 2, white, title "Holy
+ Name of Jesus". *)
+ Alcotest.(check bool) "2027: 3 January is a Sunday" true (D.weekday (d 2027 1 3) = D.Sun);
+ Alcotest.(check string) "2027: same slug" "ef-holy-name-sunday" (slug_of (d 2027 1 3));
+ Alcotest.(check bool) "2027: still subject Lord" true (subject_of (d 2027 1 3) = Sub.Lord);
+ (* Cross-checked against [T.holy_name_sunday] itself, independent of the
+ literal dates above. *)
+ Alcotest.(check (option string)) "2026: T.holy_name_sunday agrees" (Some "2026-01-04")
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2026));
+ Alcotest.(check (option string)) "2027: T.holy_name_sunday agrees" (Some "2027-01-03")
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2027))
+
+(* RG 17(a)'s own fallback clause, "(secus die 2 ianuarii)" -- the calendarium
+ confirms it in its own words: "vel, ea deficiente, die 2 ianuarii:
+ Sanctissimi Nominis Iesu, II classis". Unlike Holy Family's 7-13 January
+ window, this narrower 2-5 January window CAN be empty of a Sunday --
+ 3,619 of the 8,417 years in [1583, 9999] -- and colitur emitted NO Holy
+ Name office at all on any of them before this fix (register §6.2's own
+ account of the gap this closes). *)
+let test_holy_name_fallback_shape () =
+ (* 2029: no Sunday falls 2-5 January (independently cross-checked: `date -d
+ 2029-01-0{2,3,4,5} +%u` are 2,3,4,5 -- Tuesday through Friday, none a
+ 7). The task brief's own reported example: "2029-01-02
+ ef-christmas-1-tuesday class-4 white (no Sunday in window: feast
+ MISSING)" -- that is the pre-fix behaviour this test pins the fix
+ against. *)
+ Alcotest.(check (option string)) "2029: no Sunday in the window" None
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2029));
+ Alcotest.(check string) "2029: slug is the fallback key" "ef-holy-name" (slug_of (d 2029 1 2));
+ Alcotest.(check string) "2029: II class" "class-2" (rank_of (d 2029 1 2));
+ Alcotest.(check string) "2029: white" "white" (colour_of (d 2029 1 2));
+ Alcotest.(check bool) "2029: subject is Lord, same as the Sunday shape" true
+ (subject_of (d 2029 1 2) = Sub.Lord);
+ Alcotest.(check (option string)) "2029: SAME Latin name as the Sunday shape (one feast, two dates)"
+ (Some "Sanctissimi Nominis Iesu") (name_la_of (d 2029 1 2));
+ (* Negative controls: 3, 4 and 5 January stay ordinary Christmastide
+ ferias in a fallback year -- the fallback names 2 January specifically,
+ not "some day near the start of the window". *)
+ List.iter
+ (fun dd ->
+ Alcotest.(check bool) (Printf.sprintf "2029-01-%02d: NOT the feast (ordinary feria)" dd) true
+ (subject_of (d 2029 1 dd) = Sub.Temporal
+ && Sl.to_string (office (d 2029 1 dd)).Cel.slug <> "ef-holy-name"))
+ [ 3; 4; 5 ];
+ (* A second, independently cross-checked fallback year, at the OTHER end
+ of the week (2008: `date -d 2008-01-02 +%A` = Wednesday), so this is
+ not a single-year accident. *)
+ Alcotest.(check (option string)) "2008: no Sunday in the window either" None
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2008));
+ Alcotest.(check string) "2008: same fallback slug" "ef-holy-name" (slug_of (d 2008 1 2))
+
+(* [T.holy_name_sunday]'s own [Some]/[None] split, cross-checked against
+ `date -d` independently for a sample spanning both shapes -- not merely
+ the two years the shape tests above already pin literally, so the
+ boundary itself (not only two hand-picked instances) is exercised. *)
+let test_holy_name_sunday_or_none () =
+ List.iter
+ (fun (y, expected) ->
+ Alcotest.(check (option string))
+ (Printf.sprintf "%d: holy_name_sunday" y)
+ expected
+ (Option.map D.to_iso8601 (T.holy_name_sunday y)))
+ [ (2026, Some "2026-01-04"); (2027, Some "2027-01-03"); (2009, Some "2009-01-04");
+ (2010, Some "2010-01-03"); (2020, Some "2020-01-05"); (2025, Some "2025-01-05");
+ (2006, None); (2007, None); (2008, None); (2018, None); (2029, None) ]
+
(* Regression for a Task 14 Validate finding, reworked (register finding 4):
[named] no longer carries its own week at all -- [temporal] computes it
uniformly via [week] for every day, named or not -- so "a named day inside
@@ -583,6 +682,56 @@ let test_holy_family_anchor_present_and_erosion_is_caught () =
Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
expected_slug (slug_of expected_date)
+(* Holy Name of Jesus (RG 17(a)), SUNDAY shape -- same structure as Holy
+ Family's own test immediately above, same reasoning (also not one of
+ [T.named]'s outputs). 2026 is a Sunday-shape year (4 January). *)
+let test_holy_name_sunday_anchor_present_and_erosion_is_caught () =
+ let y = 2026 in
+ let expected_date =
+ match T.holy_name_sunday y with
+ | Some d -> d
+ | None -> Alcotest.fail "sanity: 2026 must have a Sunday in the Holy Name window"
+ in
+ let expected_slug =
+ match T.sunday_slug expected_date with
+ | Some s -> s
+ | None -> Alcotest.fail "sanity: holy_name_sunday must itself be a Sunday"
+ in
+ Alcotest.(check (list string))
+ "the anchor entry exists: T.sunday_slug's own independent computation is not missing from T.anchors" []
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:(anchor_slugs y));
+ let eroded_anchors = List.filter (fun s -> s <> expected_slug) (anchor_slugs y) in
+ Alcotest.(check (list string)) "the erosion is caught: missing_from_anchors reports it, and only it"
+ [ expected_slug ]
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:eroded_anchors);
+ Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
+ expected_slug (slug_of expected_date)
+
+(* Holy Name of Jesus, FALLBACK shape -- the SAME two properties (presence,
+ erosion caught), for a year with no Sunday in the window. Unlike the
+ Sunday shape above, there is no SECOND independent function (the way
+ [T.sunday_slug] is independent of [T.anchors]) that names "ef-holy-name"
+ for a non-Sunday date -- [expected_slug] is the literal this task chose,
+ cross-checked instead against [T.temporal]'s own output (the function
+ under test) via the final sanity line, same as every other branch of
+ this cross-check, honestly not a second independent source. 2029 has no
+ Sunday 2-5 January (test_holy_name_fallback_shape's own citation). *)
+let test_holy_name_fallback_anchor_present_and_erosion_is_caught () =
+ let y = 2029 in
+ (match T.holy_name_sunday y with
+ | None -> ()
+ | Some _ -> Alcotest.fail "sanity: 2029 must have no Sunday in the Holy Name window");
+ let expected_date = T.holy_name_fallback_date y in
+ let expected_slug = "ef-holy-name" in
+ Alcotest.(check (list string)) "the anchor entry exists for the fallback shape" []
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:(anchor_slugs y));
+ let eroded_anchors = List.filter (fun s -> s <> expected_slug) (anchor_slugs y) in
+ Alcotest.(check (list string)) "the erosion is caught: missing_from_anchors reports it, and only it"
+ [ expected_slug ]
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:eroded_anchors);
+ Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
+ expected_slug (slug_of expected_date)
+
let test_totality () =
(* Every day of 2026 yields an office without raising. Not a slug
re-validation -- Slug.t is a private string validated on construction,
@@ -606,6 +755,9 @@ let suite_extra =
Alcotest.test_case "sunday slugs" `Quick test_sunday_slugs;
Alcotest.test_case "holy family (RG 17(b))" `Quick test_holy_family;
Alcotest.test_case "holy family: no gap year (RG 17(b))" `Quick test_holy_family_no_gap_year;
+ Alcotest.test_case "holy name: Sunday shape (RG 17(a))" `Quick test_holy_name_sunday_shape;
+ Alcotest.test_case "holy name: fallback shape (RG 17(a))" `Quick test_holy_name_fallback_shape;
+ Alcotest.test_case "holy name: Sunday-or-none cross-check" `Quick test_holy_name_sunday_or_none;
Alcotest.test_case "week/sunday_slug agree" `Quick test_week_sunday_slug_agree;
Alcotest.test_case "resumed sundays" `Quick test_resumed_sundays;
Alcotest.test_case "ferial slugs and ranks" `Quick test_ferial_slugs_and_ranks;
@@ -620,6 +772,10 @@ let suite_extra =
test_anchors_cover_all_named_days;
Alcotest.test_case "holy family anchor present, erosion caught" `Quick
test_holy_family_anchor_present_and_erosion_is_caught;
+ Alcotest.test_case "holy name (Sunday shape) anchor present, erosion caught" `Quick
+ test_holy_name_sunday_anchor_present_and_erosion_is_caught;
+ Alcotest.test_case "holy name (fallback shape) anchor present, erosion caught" `Quick
+ test_holy_name_fallback_anchor_present_and_erosion_is_caught;
Alcotest.test_case "anchors erosion is caught" `Quick test_anchors_erosion_is_caught ]
let suite =