diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-13 10:40:40 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-13 10:40:40 +0200 |
| commit | b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6 (patch) | |
| tree | 3c49919b87fb33059506528441473806c316a18e /test/test_temporal_ef.ml | |
| parent | 59fbda87d565ad9b98c2493e9703390cd988609d (diff) | |
| download | colitur-b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6.tar.gz colitur-b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6.zip | |
temporal(ef): Holy Name of Jesus, RG 17(a) -- fallback + Lord subject
RG 17(a): "festum Ss.mi Nominis Iesu, celebrandum dominica quae occurrit
a die 2 ad 5 ianuarii (secus die 2 ianuarii)" -- both photographic scans,
confirmed on the calendarium's own January table and the Mass propers'
own heading. Two defects closed together, since they interact through
RG 91 entry 14/RG 16(a):
1. The Sunday shape (2-5 January) now tags subject = Lord, matching
RG 91 entry 14 ("Festa Domini II classis") -- previously subject =
Temporal, the same defect shape Holy Family had before it.
2. The fallback (RG 17(a)'s own "secus die 2 ianuarii", the calendarium's
"vel, ea deficiente, die 2 ianuarii") is now built: 2 January carries
the feast whenever no Sunday falls 2-5 January that year. Before this
fix colitur emitted no Holy Name office at all in such a year -- a
real II-class feast simply missing, on 3,619 of 8,417 years in the
domain (43%; 20 of the 46 years 2005-2050, independently re-derived
against `date -d`).
Both shapes band identically at RG 91 entry 14's MOVABLE half
(Precedence_ef.entry_14_movable_band) -- one feast, not two differently
classified ones, per the Mass propers' own single heading covering both
dates. No live collision exists in the current sanctoral data (nothing
fixed-date falls 2-5 January besides a Commemoration_only entry on 5
Jan), so the RG 16(a)/RG 112(a) interaction is proved with synthetic
precedence-level fixtures mirroring Holy Family's own precedent, not a
live-data regression. One asymmetry the synthetic tests found and kept
honest rather than forced: a losing Holy-Name-Sunday is privileged
(RG 109(a), "of a Sunday") and survives RG 111(a)'s cap; a losing
Holy-Name-fallback is not (2 January is not a Sunday, and no other
RG 109(a)-(f) category applies to it), so it gets no commemoration at
all if ever outranked -- both individually correct readings of RG 109/111's
closed lists.
Golden pins follow the brief's own vacuity warning: the Sunday shape
already emitted the right slug/rank/colour before this fix (an ordinary
Sunday and Holy Name coincide on those fields), so its pin asserts
subject explicitly rather than pinning the whole line uncritically; the
fallback pin (2029-01-02) is the one with teeth against the larger
defect, reddening on slug/rank/subject together if reverted.
Mutation-tested: each half of the fix reverted separately reddens
exactly its own tests (Sunday subject: the dedicated unit test, the
golden pin's subject field, one precedence end-to-end test; fallback:
the fallback unit/anchor/golden tests, the new C16 differential count,
and both shapes of the precedence end-to-end tests) and nothing else.
Adds data/ef/expected-divergences.sexp's C16 (lectio has no fallback for
this window at all) -- the lectio differential's own count, independently
re-derived against `date -d` for the fixture's 2005-2050 span, not
transcribed from the domain-wide figure. The missalemeum oracle window
(2026-2027) exercises only the Sunday shape and needed no new entry:
subject is not one of its compared fields, and both years' rows already
corroborate the Sunday-shape ranking (missalemeum's own 2026-01-04 and
2027-01-03 rows: rank 2, white, title "Holy Name of Jesus").
Diffstat (limited to 'test/test_temporal_ef.ml')
| -rw-r--r-- | test/test_temporal_ef.ml | 156 |
1 files changed, 156 insertions, 0 deletions
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 = |
