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 | |
| 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')
| -rw-r--r-- | test/test_differential.ml | 21 | ||||
| -rw-r--r-- | test/test_golden.ml | 53 | ||||
| -rw-r--r-- | test/test_precedence_ef.ml | 105 | ||||
| -rw-r--r-- | test/test_temporal_ef.ml | 156 |
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 = |
