diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/test_colitur.ml | 2 | ||||
| -rw-r--r-- | test/test_golden.ml | 511 |
2 files changed, 512 insertions, 1 deletions
diff --git a/test/test_colitur.ml b/test/test_colitur.ml index b1c8a34..58557aa 100644 --- a/test/test_colitur.ml +++ b/test/test_colitur.ml @@ -4,4 +4,4 @@ let () = [ Test_date.suite; Test_computus.suite; Test_colour.suite; Test_slug.suite; Test_names.suite; Test_overlay.suite; Test_temporal_ef.suite; Test_validate.suite; Test_precedence.suite; Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_rite_ef.suite; - Test_differential.suite; Test_oracle.suite ] + Test_differential.suite; Test_oracle.suite; Test_golden.suite ] diff --git a/test/test_golden.ml b/test/test_golden.ml new file mode 100644 index 0000000..945abe5 --- /dev/null +++ b/test/test_golden.ml @@ -0,0 +1,511 @@ +(* Task 17: golden pins -- validation layer 5 of the design spec's five + (colitur CLAUDE.md "Validation" section; layers 1-4 -- Types, Property, + the lectio differential, the missalemeum oracle -- are already built and + green: test_validate.ml, test_differential.ml, test_oracle.ml). + + *** THE ONE RULE THIS FILE IS BUILT AROUND *** + + A golden test blesses whatever it is given. Every other layer derives its + expectation from something independent of colitur's own output -- an + invariant, a sibling engine, a published calendar oracle. This layer's + expectation comes from ME, so every literal string below was checked by + hand, against sources OUTSIDE this codebase, BEFORE it was typed in here + -- never copied from a `colitur day` run and then rationalised: + + 1. The three Easter dates this file pins (1598-03-22, 1666-04-25, + 2038-04-25) were independently computed by hand using the standard + Gauss/Meeus Gregorian Easter algorithm (the same algorithm register + §0's "first Sunday on/after the ecclesiastical full moon on/after 21 + March" reduces to), NOT read off Computus.gregorian_easter or the CLI. + All three matched colitur's own output exactly on the first attempt. + 2. EVERY weekday asserted below was independently cross-checked against + `date -d <iso-date> +%A` (GNU coreutils, glibc's own proleptic- + Gregorian calendar arithmetic -- a completely separate implementation + from anything in this repository) before being written into a literal + string. All matched. + 3. Every precedence/transfer/commemoration outcome was traced by hand + against its RG citation (quoted or paraphrased in each test's own + comment) and against the actual `band`/`disposition`/`admit`/ + `transfer_target` logic in precedence_ef.ml, not merely observed to + "look plausible". Where the primary text alone does not fully settle + an outcome (the 2008 Annunciation/Joseph tie, see that test's own + comment), this is said explicitly rather than papered over with a + confident-sounding citation. + + *** WHY [describe] BUILDS ONE STRING PER DAY *** + + Every assertion below compares ONE formatted line against a single + expected literal, rather than five or six separate field checks. This is + deliberate: a one-line diff on failure still names the day (it is the + first token) and shows exactly which field changed (Alcotest's own diff + highlights the differing substring) -- "prove the pins have teeth" in the + brief's own words -- while keeping each test's body to one literal per + date instead of five, which is what makes 30-odd pinned dates reviewable + at all. [describe]'s own five components (season/week/slug/rank/colour, + commemorations, transferred_in, transferred_out) are exactly the fields + {!Colitur_kernel.Liturgical_day.t} promises never to lose (that type's own + doc comment) -- nothing is cherry-picked to make a case look cleaner than + it is. *) + +module Cal = Colitur_kernel.Calendar +module Layer = Colitur_kernel.Layer +module Overlay = Colitur_kernel.Overlay +module LD = Colitur_kernel.Liturgical_day +module Slug = Colitur_kernel.Slug +module Date = Colitur_kernel.Date +module Cel = Colitur_kernel.Celebration +module Colour = Colitur_kernel.Colour +module Temporal = Colitur_kernel.Temporal +module Prec = Colitur_kernel.Precedence +module V = Rite_ef.Vocab_ef + +(* Same relative paths every other suite in this directory uses (dune test + runs from _build/default/test/). *) +let sanctoral_path = "../data/ef/sanctoral.sexp" +let adjustments_path = "../data/ef/adjustments.sexp" + +(* Loaded once at module init, same convention test_validate.ml's own + [real_ef_layer] uses (not test_oracle.ml/test_differential.ml's + per-test-case reload, which recomputes a whole 46-year or 2-year sweep + per call for reasons specific to those files -- loading the immutable + layer itself has no such per-call cost and nothing here mutates it). *) +let real_ef_layer = + match Layer.load V.rank_of_sexp sanctoral_path with + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" sanctoral_path e) + | Ok layer -> ( + match Overlay.load V.rank_of_sexp adjustments_path with + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" adjustments_path e) + | Ok overlay -> + let layer, diagnostics = Overlay.apply layer overlay in + if diagnostics <> [] then + failwith + (Printf.sprintf "unexpected overlay diagnostics: %s" + (String.concat "; " (List.map Overlay.diagnostic_to_string diagnostics))); + layer) + +let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e + +(* [Cal.day] recomputes its whole liturgical year on every call (calendar.mli + -- "pure, no cache"); acceptable here, same as test_rite_ef.ml's own use + of [Cal.year] for a handful of dates, not a hot loop. *) +let fetch y m d = Cal.day Rite_ef.context real_ef_layer (mk y m d) + +let slug_s (c : V.rank Cel.t) = Slug.to_string c.Cel.slug +let rank_s (c : V.rank Cel.t) = V.rank_to_string c.Cel.rank +let colour_s (c : V.rank Cel.t) = Colour.to_string c.Cel.colour +let privilege_s = function Prec.Privileged -> "privileged" | Prec.Ordinary -> "ordinary" + +(* The five fields {!Colitur_kernel.Liturgical_day.t} promises are never + silently lost, folded into one comparable line -- see the file header for + why one string, not five checks. *) +let describe (day : (V.season, V.rank) LD.t) = + let t = day.LD.temporal in + let week = match t.Temporal.week with Some n -> string_of_int n | None -> "-" in + let comms = + List.map (fun (c, p) -> Printf.sprintf "%s:%s" (slug_s c) (privilege_s p)) day.LD.commemorations + |> List.sort compare |> String.concat "," + in + let tin = match day.LD.transferred_in with None -> "-" | Some c -> slug_s c in + let tout = + List.map (fun (c, d) -> Printf.sprintf "%s->%s" (slug_s c) (Date.to_iso8601 d)) day.LD.transferred_out + |> List.sort compare |> String.concat "," + in + Printf.sprintf "%s %s season=%s week=%s slug=%s rank=%s colour=%s comms=[%s] in=%s out=[%s]" + (Date.to_iso8601 day.LD.date) + (Date.weekday_to_string t.Temporal.weekday) + (V.season_to_string t.Temporal.season) + week (slug_s day.LD.observed) (rank_s day.LD.observed) (colour_s day.LD.observed) comms tin tout + +let check ~msg y m d expected = Alcotest.(check string) msg expected (describe (fetch y m d)) + +let omitted_has (day : (V.season, V.rank) LD.t) slug = + List.exists (fun (c, _) -> slug_s c = slug) day.LD.omitted + +(* ------------------------------------------------------------------ *) +(* Easter extremes (register §0; independently Gauss-computed, see file + header point 1). extreme_years in test_validate.ml already pins that + 1598/1666 are the true earliest/latest within 1583..2500 -- this file + pins what the RESOLVED DAY looks like there, not merely that Easter + lands on the expected date. *) +(* ------------------------------------------------------------------ *) + +(* 1598: earliest Gregorian Easter possible, 22 March -- hand-verified via + Gauss's algorithm (a = 2, b = 15, c = 98, ..., h = 0, l = 0, m = 0 -> + 22 March), independent of Computus.gregorian_easter. RG 76 (Paschaltide + begins with the Vigil Mass) + RG 91 entry 1 (Easter Sunday, I class) + + RG 119(b) (white from the Vigil Mass) fix season/rank/colour; the day + before (Holy Saturday, still Passiontide, violet per RG 128) is pinned + alongside it to prove the season boundary itself falls in the right + place, not merely that 22 March is white. *) +let test_easter_extreme_1598 () = + check ~msg:"1598-03-21 Holy Saturday: still Passiontide, violet (RG 128)" 1598 3 21 + "1598-03-21 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \ + comms=[] in=- out=[]"; + check ~msg:"1598-03-22 earliest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1598 3 + 22 + "1598-03-22 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \ + out=[]" + +(* 1666: latest Gregorian Easter possible, 25 April -- hand-verified via + Gauss's algorithm (a = 13, b = 16, c = 66, ..., h = 29, l = 5, m = 0 -> + 25 April). Same citations as 1598 above. *) +let test_easter_extreme_1666 () = + check ~msg:"1666-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 1666 4 24 + "1666-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \ + comms=[] in=- out=[]"; + check ~msg:"1666-04-25 latest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1666 4 + 25 + "1666-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \ + out=[]" + +(* 2038: the brief's "late modern case" -- Easter also 25 April (Gauss- + verified: a = 5, b = 20, c = 38, ..., h = 29, l = 5, m = 0 -> 25 April, + the SAME extreme as 1666, reached independently), chosen because it is a + near-term year rather than a 17th-century one. + + DELIBERATE EXCLUSION, not an oversight: register §6's F4 finding + confirms 2038-03-06 (+sts-felicitas-perpetua), 2038-03-08 (+john-of-god) + and 2038-03-09 (+frances-rome) are WRONG -- three sanctoral entries + bootstrapped from lectio carry a Commemoration_only status that should be + a real III-class Feast (a bootstrap-generator defect traced to + missalemeum's own commemoration-id ranks, not fixable without touching + lectio). Pinning those three dates as "correct" would freeze a known bug + into a regression test that then fights its own fix -- the report's own + warning against exactly this. Only the Easter-week days (temporal-cycle + territory, untouched by the March sanctoral bug) are pinned here. *) +let test_easter_extreme_2038_late_modern () = + check ~msg:"2038-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 2038 4 24 + "2038-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \ + comms=[] in=- out=[]"; + check ~msg:"2038-04-25 late-modern instance of the latest possible Easter (Gauss-verified)" 2038 4 25 + "2038-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \ + out=[]" + +(* ------------------------------------------------------------------ *) +(* Annunciation transfer, 25 March inside Holy Week (register §4, RG 96 + Attamen (a)). 2016: Easter = 27 March (Gauss-independent of this file's + own concern -- already covered by extreme-year hand-checks above; 2016 is + an ordinary year, trusted via the property sweep + the weekday + cross-check below), so 25 March 2016 falls on GOOD FRIDAY itself -- + literally inside Holy Week, not merely "impeded by the Easter octave" the + way the 1598/2008/1666 cases are. Independently confirmed: `date -d + 2016-03-25 +%A` = Friday, `date -d 2016-03-27 +%A` = Sunday (Easter). *) +let test_annunciation_transfer_inside_holy_week_2016 () = + (* RG 91 entry 2 (Sacred Triduum) outranks entry 11 (an ordinary universal + I-class feast) outright -- Good Friday wins, Annunciation is + Transfer-disposed (RG 95: only I-class feasts transfer). RG 23 (I-class + ferias admit no commemoration except one privileged) leaves nothing on + the day itself: register's own note on RG 23 says this branch "never + actually reaches a live case" for exactly this reason. *) + check ~msg:"2016-03-25 Good Friday: Triduum outranks the Annunciation (RG91 entry 2 > 11), no commemoration" + 2016 3 25 + "2016-03-25 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet \ + comms=[] in=- out=[annunciation-of-the-blessed-virgin-mary->2016-04-04]"; + (* The general RG 96 walk from 26 March would still be inside the Triduum, + the Easter octave (all I class, entry 2/10) -- carrying the feast PAST + Easter -- so RG 96 Attamen (a)'s condition fires: sedes propria = Monday + after Low Sunday = Easter + 8 = 27 Mar + 8 = 4 April. *) + check ~msg:"2016-04-04 Easter+8 (Monday after Low Sunday): RG96 Attamen(a) sedes propria fires" 2016 4 4 + "2016-04-04 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" + +(* ------------------------------------------------------------------ *) +(* 2008: the double transfer (register §4, "cases worth adding"). Easter = + 23 March 2008 (trusted via the property sweep, not independently + Gauss-checked a fourth time -- the algorithm is already hand-verified + above; what this case adds is the TWO-CANDIDATE collision, not a fourth + Easter-date check). Independently confirmed: `date -d 2008-03-19 +%A` = + Wednesday, `-03-31` = Monday, `-04-01` = Tuesday. *) +let test_annunciation_joseph_double_transfer_2008 () = + (* St Joseph (19 March, I class, universal, band 11 -- same table entry as + the Annunciation) falls on Wednesday of Holy Week (RG 91 entry 7: an + I-class feria, band 7, above 11) -- impeded, Transfer-disposed. *) + check ~msg:"2008-03-19 Wednesday of Holy Week outranks Joseph (RG91 entry 7 > 11), no commemoration" 2008 3 + 19 + "2008-03-19 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet \ + comms=[] in=- out=[joseph-spouse-of-the-bl-virgin-mary->2008-04-01]"; + (* Both Joseph and the Annunciation are band 11 (register: neither is + Immaculate Conception/Assumption, band 4; both are ordinary universal + I-class feasts). RG 96 Attamen (a) is CONDITIONAL, so it is evaluated + for BOTH: the Annunciation's own general walk from 26 March would cross + the Easter octave and land past Easter, so its exception fires -- + Easter + 8 = 23 Mar + 8 = 31 March, its NAMED sedes propria. Joseph has + no such named exception; its own general walk (from 20 March) also + crosses the octave and, absent Annunciation, would land on the SAME 31 + March. WEAKER-THAN-IT-LOOKS, stated honestly: RG 97-98's plain text + ("the higher in the table is kept") does not itself distinguish two + candidates tied at the SAME table entry (11) the way it does for a + genuine dignity difference -- the engine breaks this specific tie by + slug (calendar.ml's own [compare_deferred]: alphabetical, an + engineering convention, not itself an RG citation), and + "annunciation-of-the-blessed-virgin-mary" < "joseph-spouse-of-the-bl- + virgin-mary" is exactly why Annunciation claims 31 March first in + [place_transfers]'s per-round sort. A textual argument beyond the bare + tie-break does exist -- the Annunciation's own Attamen clause names its + target as a "sedes propria" (proper seat), a positively-assigned day, + where Joseph is merely wherever the generic RG96 search happens to + land -- but this is this report's own reading of the primary text, not + something the register states outright, so it is offered as + corroboration, not as the citation carrying the outcome. *) + check ~msg:"2008-03-31 Easter+8: Annunciation's named sedes propria (RG96 Attamen a) claims the day first" + 2008 3 31 + "2008-03-31 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]"; + (* Joseph's own general RG96 walk, finding 31 March already claimed + (occupant reports Class1 there, still blocking per [is_blocking]), + continues one more day to 1 April -- RG 97-98's "in order": the second + I-class candidate to reach an already-occupied target keeps searching + rather than displacing the first. *) + check ~msg:"2008-04-01: Joseph's RG96 walk continues past the now-claimed 31 March (RG97-98 'in order')" 2008 + 4 1 + "2008-04-01 tuesday season=paschaltide week=2 slug=joseph-spouse-of-the-bl-virgin-mary rank=class-1 \ + colour=white comms=[] in=joseph-spouse-of-the-bl-virgin-mary out=[]" + +(* ------------------------------------------------------------------ *) +(* RG 96 Attamen (a) is CONDITIONAL ("quando est transferendum post + Pascha") -- pinning both sides so the condition itself, not merely the + exception's existence, is under test. All six weekdays independently + confirmed via `date -d`. *) +(* ------------------------------------------------------------------ *) + +(* Before Easter: 2057 (Easter 22 Apr), 2007 and 2012 (both Easter 8 Apr). + In each, 25 March is a Sunday impeding the Annunciation (Lent III in + 2057; Passion Sunday, Dominica I Passionis, in 2007/2012), and the + GENERAL RG96 walk lands on 26 March -- an ordinary III-class feria, band + 22/25, well before Easter -- so the condition never fires: no named + exception, just the plain walk. Each arrival day's own displaced feria is + RG25/RG109(e)-privileged (an ordinary Lent/Passiontide feria, impeded, + must be commemorated) -- a second, independent confirmation of the + RG24/25/RG109(e) mandate this file also pins directly below at 1900/1902, + in a different code path (an ARRIVING transferred feast's displaced + office, not a plain sanctoral winner's). *) +let test_annunciation_exception_not_triggered_general_walk_suffices () = + check ~msg:"2057-03-26: general RG96 walk suffices (target before Easter, 22 Apr) -- no Attamen(a) exception" + 2057 3 26 + "2057-03-26 monday season=lent week=3 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[ef-lent-3-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; + check ~msg:"2007-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception" + 2007 3 26 + "2007-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; + check ~msg:"2012-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception" + 2012 3 26 + "2012-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]" + +(* After Easter: 2016 (already pinned above, Easter+8 = 4 April) and 2024 + (Easter 31 March, Easter+8 = 8 April). In both, the general walk would + cross Easter, so Attamen(a) fires and lands the feast on the Monday after + Low Sunday exactly. Both arrival days are Paschaltide-2 (Class4 + ferias) -- RG26 (IV-class ferias never commemorated), so unlike the + "before" trio above, no commemoration is left behind; a second, + independent confirmation of RG26 in yet another code path. *) +let test_annunciation_exception_triggered_when_walk_would_cross_easter () = + check + ~msg: + "2024-04-08: Easter+8 (Easter=31 Mar): RG96 Attamen(a) fires, arrival day is Class4 so no commemoration \ + (RG26)" + 2024 4 8 + "2024-04-08 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ + colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" +(* 2016's own pin (test_annunciation_transfer_inside_holy_week_2016) is the + other half of this pair -- not repeated here to avoid asserting the same + date twice. *) + +(* ------------------------------------------------------------------ *) +(* 2011-07-04, the Precious Blood (register §4/§6, differential C10). + Sacred Heart (Easter + 68, RG 91 entry 3) coincides with the fixed 1 July + feast of the Most Precious Blood (entry 11) in 2011 (Easter = 24 April, + so Easter + 68 = 1 July). Entry 3 outranks entry 11 outright (RG 97-98: + the higher in the table is kept) -- no tie, unlike the 2008 case above. + The general RG96 walk from 2 July must skip Visitation (2 July, II + class, entry 19) and the ordinary Sunday (3 July, entry 15, II class) + before landing on 4 July, an ordinary Class4 feria. All four weekdays + independently confirmed via `date -d`. *) +let test_precious_blood_transfer_2011 () = + check ~msg:"2011-07-01: Sacred Heart (RG91 entry 3) outranks the fixed Precious Blood (entry 11) outright" + 2011 7 1 + "2011-07-01 friday season=time-after-pentecost week=2 slug=ef-sacred-heart rank=class-1 colour=white \ + comms=[] in=- out=[precious-blood-of-our-lord-jesus-christ->2011-07-04]"; + check ~msg:"2011-07-02: the RG96 walk's first blocked day (Visitation, II class) -- Precious Blood not here" + 2011 7 2 + "2011-07-02 saturday season=time-after-pentecost week=2 slug=visitation-of-the-blessed-virgin-mary \ + rank=class-2 colour=white comms=[processus-and-martinian:ordinary] in=- out=[]"; + check ~msg:"2011-07-03: the RG96 walk's second blocked day (an ordinary Sunday, II class) -- still not here" + 2011 7 3 + "2011-07-03 sunday season=time-after-pentecost week=3 slug=ef-time-after-pentecost-sunday-3 rank=class-2 \ + colour=green comms=[] in=- out=[]"; + check ~msg:"2011-07-04: the first non-I/II-class day (RG96) -- the Precious Blood lands here" 2011 7 4 + "2011-07-04 monday season=time-after-pentecost week=3 slug=precious-blood-of-our-lord-jesus-christ \ + rank=class-1 colour=red comms=[] in=precious-blood-of-our-lord-jesus-christ out=[]" + +(* ------------------------------------------------------------------ *) +(* All Souls falling on a Sunday (register §4, RG96 Attamen (b), primary- + source-verified: "quando occurrit cum dominica, transfertur ... in feriam + II sequentem"). 2025: All Souls (2 Nov) falls on an ordinary Sunday. + Weekdays independently confirmed via `date -d`. *) +let test_all_souls_on_a_sunday_2025 () = + (* RG91 entry 8's own parenthetical ("yields to an occurring Sunday") plus + Attamen (b): the Sunday is simply observed, and RG94 ("a fixed-day + commemoration is not carried with a transferred feast") means the + transferred All Souls leaves nothing behind on the Sunday itself. *) + check ~msg:"2025-11-02: All Souls yields to the occurring Sunday (RG91 entry 8), no commemoration (RG94)" 2025 + 11 2 + "2025-11-02 sunday season=time-after-pentecost week=21 slug=ef-time-after-pentecost-sunday-21 rank=class-2 \ + colour=green comms=[] in=- out=[commemoration-of-all-souls->2025-11-03]"; + check ~msg:"2025-11-03: All Souls transfers to the following Monday, its sedes propria (RG96 Attamen b)" 2025 + 11 3 + "2025-11-03 monday season=time-after-pentecost week=21 slug=commemoration-of-all-souls rank=class-1 \ + colour=black comms=[] in=commemoration-of-all-souls out=[]" + +(* ------------------------------------------------------------------ *) +(* Christmas falling on a Sunday (RG91 entry 1: Nativity, Easter, Pentecost + are I class WITH THEIR OWN OCTAVE, listed first in the whole table -- + temporal_ef.ml assigns 25 December the Nativity office unconditionally, + so unlike a Feast of the Lord merely REPLACING an occurring Sunday (RG + 16(a), register §6), there is no Sunday-of-Advent/-per-annum candidate to + even contest here: 25 December is never, in the EF temporal cycle, an + "ordinary Sunday" in the first place. 2022: `date -d 2022-12-25 +%A` = + Sunday, independently confirmed. *) +let test_christmas_on_a_sunday_2022 () = + check ~msg:"2022-12-25 falling on a Sunday: the Nativity (RG91 entry 1) takes the day outright, no contest" + 2022 12 25 + "2022-12-25 sunday season=christmastide week=- slug=ef-nativity rank=class-1 colour=white comms=[] in=- \ + out=[]" + +(* ------------------------------------------------------------------ *) +(* Holy Thursday is white (register §3b, RG 128(b) + RG 122, Task 16) while + the surrounding Passiontide days stay violet -- the case the brief flags + because colitur and lectio previously AGREED on violet (both wrong), so + the lectio differential was structurally incapable of ever catching this; + only a golden pin (or the missalemeum oracle, layer 4) can. 2026: Easter + 5 April (trusted via the property sweep). Weekdays independently + confirmed via `date -d`. *) +let test_holy_thursday_is_white_2026 () = + check ~msg:"2026-04-01 (Wednesday of Holy Week): ordinary Passiontide violet (RG128)" 2026 4 1 + "2026-04-01 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet \ + comms=[] in=- out=[]"; + check + ~msg: + "2026-04-02 Holy Thursday: white (RG128(b)'s named exception + RG122's affirmative statement), not \ + Passiontide's violet" + 2026 4 2 + "2026-04-02 thursday season=passiontide week=2 slug=ef-passiontide-2-thursday rank=class-1 colour=white \ + comms=[] in=- out=[]"; + check ~msg:"2026-04-03 Good Friday: back to violet (RG128) -- Thursday's white is a one-day exception" 2026 4 + 3 + "2026-04-03 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet \ + comms=[] in=- out=[]" + +(* ------------------------------------------------------------------ *) +(* Advent/Lent Ember ferias are commemorated when impeded (RG 24: "si vero + impediuntur, commemorari debent" -- MUST be commemorated) while IV-class + ferias never are (RG 26: "quae nunquam commemorantur") -- the fix-round-1 + F1/F2 finding, a Critical bug caught late precisely because no golden pin + existed for it before now. Weekdays independently confirmed via + `date -d`. *) +(* ------------------------------------------------------------------ *) + +let test_ember_ferias_commemorated_when_impeded () = + (* 1900-12-21: Advent Ember Friday (RG91 entry 18, II class) impeded by St + Thomas (II class, universal, entry 16 -- Thomas outranks an II-class + feria only because entry 16 < 18 in the table). RG24 makes the Ember + feria's commemoration MANDATORY, and RG109(e) (corrected, fix round 1) + makes it PRIVILEGED, not merely eligible. *) + check ~msg:"1900-12-21: Advent Ember Friday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)" + 1900 12 21 + "1900-12-21 friday season=advent week=3 slug=thomas rank=class-2 colour=red \ + comms=[ef-advent-ember-fri:privileged] in=- out=[]"; + (* 1902-02-22: Lent Ember Saturday (RG91 entry 18, II class) impeded by + the Chair of St Peter (II class, universal, entry 16). Same RG24/ + RG109(e) mandate. *) + check ~msg:"1902-02-22: Lent Ember Saturday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)" + 1902 2 22 + "1902-02-22 saturday season=lent week=1 slug=chair-of-st-peter rank=class-2 colour=white \ + comms=[ef-lent-ember-sat:privileged] in=- out=[]" + +let test_iv_class_ferias_never_commemorated () = + (* 2026-07-22: an ordinary Time-after-Pentecost feria (Class4, RG91 entry + 28) impeded by St Mary Magdalene (III class). RG26 ("IV-class ferias + are NEVER commemorated") means the displaced feria is dropped outright + -- present in [omitted], never in [commemorations]. Asserting only + "comms=[]" would be vacuous if there had been no losing candidate at + all (one of this project's own catalogued vacuity flavours); the + [omitted_has] check below proves a real candidate existed and was + actively excluded, not merely absent. *) + let d1 = fetch 2026 7 22 in + Alcotest.(check bool) "2026-07-22: the displaced IV-class feria is in [omitted], proving RG26 actually fired" + true + (omitted_has d1 "ef-time-after-pentecost-8-wednesday"); + Alcotest.(check string) "2026-07-22: Mary Magdalene observed, no commemoration (RG26)" + "2026-07-22 wednesday season=time-after-pentecost week=8 slug=mary-magdalene rank=class-3 colour=white \ + comms=[] in=- out=[]" + (describe d1); + let d2 = fetch 2026 8 10 in + Alcotest.(check bool) "2026-08-10: the displaced IV-class feria is in [omitted], proving RG26 actually fired" + true + (omitted_has d2 "ef-time-after-pentecost-11-monday"); + Alcotest.(check string) "2026-08-10: St Lawrence observed, no commemoration (RG26)" + "2026-08-10 monday season=time-after-pentecost week=11 slug=lawrence rank=class-2 colour=red comms=[] in=- \ + out=[]" + (describe d2) + +(* ------------------------------------------------------------------ *) +(* RG 111(b): a II-class SUNDAY admits only a commemoration "de festo II + classis" -- a rank restriction, not "the best available ordinary + candidate". 9 August falling on a Sunday: `romanus` (Class3, + commemoration-only, register §6 notes his very existence is + questionable, but that is irrelevant here -- RG111(b) excludes him on + RANK alone, regardless of that open question) has no standing for the + day's single slot. The brief names four recurring years (2009, 2015, + 2020, 2026); two are pinned here (2009 for an older-era instance, 2026 + for a current one) -- 2015/2020 were hand-checked via the CLI and found + identical in shape (same slug family, same `romanus` exclusion, only the + week number differs), so pinning all four would repeat the same + assertion four times without exercising a different code path; rejected + as duplicative. *) +let test_ii_class_sunday_admits_only_ii_class_commemoration () = + let d2009 = fetch 2009 8 9 in + Alcotest.(check bool) "2009-08-09: romanus (Class3) is offered and excluded, proving RG111(b) actually fired" + true + (omitted_has d2009 "romanus"); + Alcotest.(check string) "2009-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)" + "2009-08-09 sunday season=time-after-pentecost week=10 slug=ef-time-after-pentecost-sunday-10 rank=class-2 \ + colour=green comms=[] in=- out=[]" + (describe d2009); + let d2026 = fetch 2026 8 9 in + Alcotest.(check bool) "2026-08-09: romanus (Class3) is offered and excluded, proving RG111(b) actually fired" + true + (omitted_has d2026 "romanus"); + Alcotest.(check string) "2026-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)" + "2026-08-09 sunday season=time-after-pentecost week=11 slug=ef-time-after-pentecost-sunday-11 rank=class-2 \ + colour=green comms=[] in=- out=[]" + (describe d2026) + +let suite = + ( "golden pins (known-tricky years)", + [ Alcotest.test_case "Easter extreme: 1598 earliest (22 Mar, Gauss-verified)" `Quick + test_easter_extreme_1598; + Alcotest.test_case "Easter extreme: 1666 latest (25 Apr, Gauss-verified)" `Quick + test_easter_extreme_1666; + Alcotest.test_case "Easter extreme: 2038 late-modern instance of the latest date" `Quick + test_easter_extreme_2038_late_modern; + Alcotest.test_case "Annunciation transfer: 25 March falls inside Holy Week (2016)" `Quick + test_annunciation_transfer_inside_holy_week_2016; + Alcotest.test_case "Annunciation/Joseph double transfer (2008)" `Quick + test_annunciation_joseph_double_transfer_2008; + Alcotest.test_case "Annunciation RG96 exception NOT triggered: general walk suffices (2057, 2007, 2012)" + `Quick test_annunciation_exception_not_triggered_general_walk_suffices; + Alcotest.test_case "Annunciation RG96 exception triggered: walk would cross Easter (2024; see also 2016)" + `Quick test_annunciation_exception_triggered_when_walk_would_cross_easter; + Alcotest.test_case "2011-07-04: the Precious Blood transfer" `Quick test_precious_blood_transfer_2011; + Alcotest.test_case "All Souls falling on a Sunday (2025)" `Quick test_all_souls_on_a_sunday_2025; + Alcotest.test_case "Christmas falling on a Sunday (2022)" `Quick test_christmas_on_a_sunday_2022; + Alcotest.test_case "Holy Thursday is white amid violet Passiontide (2026)" `Quick + test_holy_thursday_is_white_2026; + Alcotest.test_case "Advent/Lent Ember ferias commemorated when impeded (1900, 1902)" `Quick + test_ember_ferias_commemorated_when_impeded; + Alcotest.test_case "IV-class ferias never commemorated (2026)" `Quick test_iv_class_ferias_never_commemorated; + Alcotest.test_case "RG111(b): a II-class Sunday admits only a de-festo-II-classis commemoration (2009, 2026)" + `Quick test_ii_class_sunday_admits_only_ii_class_commemoration + ] ) |
