summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 06:13:14 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 06:13:14 +0200
commitb8e1e2376de17feeb4d0b331f1febbc4ffe478e5 (patch)
treef8cebe3d8f3ed77c48a2fed4988e34e31bcf343c /test
parent9108aee65dc72e7793aeb604c2e877f2898111c8 (diff)
downloadcolitur-b8e1e2376de17feeb4d0b331f1febbc4ffe478e5.tar.gz
colitur-b8e1e2376de17feeb4d0b331f1febbc4ffe478e5.zip
test: golden pins for the known-tricky years
Validation layer 5: fourteen hand-verified pins in test/test_golden.ml, wired into the suite via test/test_colitur.ml. Each pinned day was computed and checked against its RG citation before being written down, never transcribed from `colitur day` output: - Easter extremes 1598 (earliest, 22 Mar), 1666 (latest, 25 Apr) and 2038 (late-modern instance of the same latest date) -- all three independently computed by hand via the Gauss/Meeus Gregorian Easter algorithm, not read off Computus.gregorian_easter. extreme_years in test_validate.ml already established 1598/1666 as the true 1583-2500 extremes (correcting a stale 1818/2038 comment); this file pins the resolved DAY there, not just the date. - Annunciation transfer, 25 March inside Holy Week (2016: 25 March is literally Good Friday) and the double transfer with St Joseph (2008: RG96's Attamen(a) claims Easter+8 for the Annunciation first, Joseph's own RG96 walk continues past it to the next day). - The RG96 Attamen(a) exception's own CONDITION pinned on both sides: 2057, 2007, 2012 (general walk suffices, lands before Easter, no exception) vs 2016/2024 (walk would cross Easter, Easter+8 fires). - 2011-07-04, the Precious Blood transfer (Sacred Heart outranks it outright on 1 July; the RG96 walk skips Visitation and a Sunday before landing on 4 July). - All Souls falling on a Sunday (2025, RG96 Attamen(b)) and Christmas falling on a Sunday (2022, RG91 entry 1 -- no contest, since 25 December is never an ordinary Sunday candidate in the EF temporal cycle). - Holy Thursday's white amid violet Passiontide (2026, RG128(b)/RG122) -- the case colitur and lectio previously agreed was violet, so the differential could never have caught it; only a golden pin or the missalemeum oracle can. - Advent/Lent Ember ferias commemorated when impeded (1900, 1902, RG24 + RG109(e)) contrasted with IV-class ferias never commemorated (2026, RG26) and RG111(b)'s Sunday rank floor (2009, 2026) -- all three assert the actual displaced/excluded candidate is present in `omitted`, not just that `commemorations` is empty, so a day with no losing candidate at all could not pass vacuously. 2038 is deliberately NOT pinned day-by-day: register item F4 confirms 2038-03-06/08/09 are wrong (a lectio bootstrap-generator defect, not fixable here). Only the Easter-week days, untouched by that bug, are pinned; the exclusion is stated in the test's own comment, not silent. Every weekday asserted was independently cross-checked against `date -d <iso> +%A` (glibc, wholly outside this codebase) before being written down. Precedence outcomes were traced against precedence_ef.ml's own band/ disposition/admit/transfer_target, not merely observed to look plausible; where the primary text alone doesn't fully settle an outcome (the 2008 Annunciation/Joseph tie-break, both landing at RG91 table entry 11), the test's own comment says so rather than overclaiming a citation. Perturbation-tested: temporarily broke Holy Thursday's white-colour special case in temporal_ef.ml, confirmed the golden test failed with a clear day-and-field diff, reverted.
Diffstat (limited to 'test')
-rw-r--r--test/test_colitur.ml2
-rw-r--r--test/test_golden.ml511
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
+ ] )