diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-26 23:45:16 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-26 23:45:16 +0200 |
| commit | 9ebb06983b7a26db5564302253f2551dfbcf834e (patch) | |
| tree | f73de5c193a13239258d062ca5f4caf64ba64dbe /test/test_litcal_of.ml | |
| parent | 2f6d25aff88b4c76c61e36889f1c017b3cc8faee (diff) | |
| download | colitur-9ebb06983b7a26db5564302253f2551dfbcf834e.tar.gz colitur-9ebb06983b7a26db5564302253f2551dfbcf834e.zip | |
fix(of): dispatch Holy Family's Normae n.35(a) fallback (W2)
temporal_of.ml's temporal only ever reached holy_family's own correct
26-31 December / 30 December fallback logic through sunday_slug, which
returns None immediately for a non-Sunday date. In a year 25 December
is itself a Sunday (26-31 December then holding no Sunday of its own),
the feast vanished entirely and fell through to an ordinary Christmas
ferial slug -- confirmed on 2022, 2033, 2039, 2044, 2050 (and roughly
1583..9999 domain-wide, one year in seven).
temporal's dispatch now consults holy_family directly, independent of
weekday, between the sunday_slug and christmas_feria_slug branches,
carrying the identical identity the Sunday case already builds
(subject Lord, holy_family_names, rank Festum). holy_family and
anchors were already correct and needed no change.
Added a direct example-based test for the fallback across five
affected years plus a control for the unaffected Sunday case, and a
domain-wide property (Holy Family is observed in every liturgical
year 1583-9999) folded into both the 200-sample QCheck run and the
exhaustive sweep -- the property that would have caught this directly.
Un-pinned the now-fixed behaviour everywhere it was recorded as
known-wrong: test_rite_of.ml's own 1583 pin (rewritten to assert the
fix), test_validate_of.ml's own independent exhaustive-sweep counter
and landmark-year filter (found via make check, not the initial
survey), and data/of/expected-divergences-litcal.sexp's L2/L3
allow-list entries against the litcal oracle (closed and removed, with
a dedicated regression test replacing the allow-list's own silence).
Diffstat (limited to 'test/test_litcal_of.ml')
| -rw-r--r-- | test/test_litcal_of.ml | 64 |
1 files changed, 48 insertions, 16 deletions
diff --git a/test/test_litcal_of.ml b/test/test_litcal_of.ml index 0c53899..6a638a7 100644 --- a/test/test_litcal_of.ml +++ b/test/test_litcal_of.ml @@ -532,16 +532,22 @@ let test_unwitnessed_ordinary_time_is_counted () = none is a loose pattern that could silently absorb an unrelated future mismatch. *) -(* L2/L3 -- Normae n. 35(a), the Holy Family fallback: KNOWN WRONG, pinned - (not fixed) by test_rite_of.ml's own - [test_holy_family_fallback_1583_known_wrong_ferial]/ - [is_known_holy_family_fallback_gap]. 30 December 2033 is this fixture's - own live witness (2033: 25 December is a Sunday, so 26-31 December's own - window has no Sunday of its own and {!Rite_of.Temporal_of.temporal} - never reaches [holy_family]'s own fixed 30-December fallback branch). - litcal correctly names ["HolyFamily"]; colitur observes an ordinary - Nativity-octave feria (Tabula II.9, band 90) instead. *) -let is_l2_l3_holy_family_2033 (l : litcal_row) = String.equal l.event_key "HolyFamily" && String.equal l.date "2033-12-30" +(* L2/L3 -- CLOSED, REMOVED (W2 fix, CLAUDE.md, temporal_of.ml). This + paragraph used to describe a KNOWN WRONG, pinned-not-fixed divergence: + Normae n. 35(a)'s "vel, ea deficiente, die 30 decembris" Holy Family + fallback was reached by {!Rite_of.Temporal_of.holy_family} but never by + {!Rite_of.Temporal_of.temporal} itself (its Holy-Family test lived only + inside [sunday_slug]'s own Sunday-only dispatch arm), so a year 25 + December is itself a Sunday (26-31 December then holding no Sunday of + its own) left the fallback unreached. 30 December 2033 was this + fixture's own live witness. [Temporal_of.temporal] now consults + [Temporal_of.holy_family] directly, independent of weekday, so litcal + and colitur now agree on this date on both axes -- both the L2 (grade) + and L3 (identity) allow-list entries in + data/of/expected-divergences-litcal.sexp are removed (their own + `expected_rows` would now be 0, not 1), and [test_no_holy_family_2033_ + divergence] below asserts this directly rather than merely by omission. *) +let is_holy_family_2033 (l : litcal_row) = String.equal l.event_key "HolyFamily" && String.equal l.date "2033-12-30" (* L4 -- litcal's own [grade_lcl] "celebration with precedence over solemnities" text covers Trinity Sunday and Corpus Christi too (24 @@ -653,7 +659,6 @@ let test_grade_matches_or_is_explained () = let day = resolved_of l.date in let band = band_of day in if List.mem band bands then () - else if is_l2_l3_holy_family_2033 l then bump "L2" else if is_l4_trinity_corpus_christi l then bump "L4" else if is_l5_mary_magdalene l then bump "L5" else if is_l6_2033_tie_grade l then bump "L6" @@ -674,7 +679,7 @@ let test_grade_matches_or_is_explained () = match List.assoc_opt id by_id with | None -> Alcotest.failf "allow-list entry %s is used by the comparator but not declared in %s" id allow_list_path | Some e -> Alcotest.(check int) (Printf.sprintf "%s expected_rows (grade)" id) e.expected_rows actual) - [ "L2"; "L4"; "L5"; "L6"; "L8" ] + [ "L4"; "L5"; "L6"; "L8" ] (* The complement, mirroring [test_unwitnessed_ordinary_time_is_counted]: every row [expected_bands] returns [None] for, classified by which of @@ -717,7 +722,6 @@ let test_identity_matches_or_is_explained () = let day = resolved_of l.date in let actual_slug = Slug.to_string day.LD.observed.Cel.slug in if String.equal actual_slug expected_slug then () - else if is_l2_l3_holy_family_2033 l then bump "L3" else if is_l7_2033_tie_identity l then bump "L7" else if is_l8_l9_joseph_2035 l then bump "L9" else @@ -735,7 +739,7 @@ let test_identity_matches_or_is_explained () = match List.assoc_opt id by_id with | None -> Alcotest.failf "allow-list entry %s is used by the comparator but not declared in %s" id allow_list_path | Some e -> Alcotest.(check int) (Printf.sprintf "%s expected_rows (identity)" id) e.expected_rows actual) - [ "L3"; "L7"; "L9" ] + [ "L7"; "L9" ] (* The complement: every row [identity_map] has no entry for, whether because litcal names an event_key outside the closed FIXED/NAMED set @@ -758,15 +762,41 @@ let test_identity_unresolved_is_counted () = Alcotest.(check int) "unresolved + the 508 checked = 4346 non-Triduum rows" 4346 (List.length unresolved + 508) +(* W2 fix, direct regression coverage: L2/L3's own former witness (2033-12-30, + HolyFamily) now matches on BOTH axes, checked here explicitly rather than + only by the allow-list's own [expected_rows] falling to 0 (see + [is_holy_family_2033]'s own updated header for the full history). Also + confirms the predicate is not vacuous: the fixture really does carry this + row. *) +let test_no_holy_family_2033_divergence () = + let litcal = litcal_rows () in + let rows = List.filter is_holy_family_2033 litcal in + Alcotest.(check int) "the fixture really does carry a HolyFamily row on 2033-12-30" 1 (List.length rows); + let row = List.hd rows in + let day = resolved_of row.date in + let band = band_of day in + (match expected_bands row.grade_lcl with + | None -> Alcotest.fail "2033-12-30: HolyFamily's own grade_lcl is no longer a comparable band" + | Some bands -> + Alcotest.(check bool) "2033-12-30: colitur's band matches litcal's expected grade" true (List.mem band bands)); + let actual_slug = Slug.to_string day.LD.observed.Cel.slug in + Alcotest.(check string) "2033-12-30: colitur observes of-holy-family" "of-holy-family" actual_slug; + match Hashtbl.find_opt identity_tbl row.event_key with + | None -> Alcotest.fail "HolyFamily is no longer in identity_tbl" + | Some expected_slug -> + Alcotest.(check string) "2033-12-30: identity_tbl's own expected slug agrees" expected_slug actual_slug + (* ---------------------------------------------------------------------- *) (* The whole allow-list file, taken as a whole: every id it declares is *) -(* recognised by exactly one comparator above (L1 season, L2 grade, L3 *) +(* recognised by exactly one comparator above (L1 season, L4 grade, L7 *) (* identity) -- no orphan entry a comparator no longer references, and no *) (* comparator silently reading an id this file does not declare (each *) (* comparator's own [List.assoc_opt] already fails loudly for that half). *) +(* L2/L3 (Holy Family) are gone -- CLOSED, REMOVED, W2 fix -- see *) +(* [is_holy_family_2033]'s own header. *) (* ---------------------------------------------------------------------- *) -let recognized_allow_ids = [ "L1"; "L2"; "L3"; "L4"; "L5"; "L6"; "L7"; "L8"; "L9" ] +let recognized_allow_ids = [ "L1"; "L4"; "L5"; "L6"; "L7"; "L8"; "L9" ] let test_allow_list_has_no_orphan_entries () = let allow_list = load_allow_list () in @@ -785,5 +815,7 @@ let suite = Alcotest.test_case "grade-unresolved rows are counted, not skipped" `Quick test_grade_unresolved_is_counted; Alcotest.test_case "identity matches or is explained" `Quick test_identity_matches_or_is_explained; Alcotest.test_case "identity-unresolved rows are counted, not skipped" `Quick test_identity_unresolved_is_counted; + Alcotest.test_case "W2 fix: no more Holy Family divergence on 2033-12-30" `Quick + test_no_holy_family_2033_divergence; Alcotest.test_case "allow-list has no orphan entries" `Quick test_allow_list_has_no_orphan_entries ] ) |
