(* RG 475-476, the Creed -- see lib/rites/rite_ef/rubrics_ef.ml for the rubric quoted in full and every branch's own citation. One end-to-end test per clause of 475, plus a 476 negative, resolved against REAL calendar dates through the shipped sanctoral data (the same pipeline `colitur day`/`colitur rubrics` use) -- every expected value below was derived from the rubric's own text and checked against `colitur day `'s real output (slug/rank/subject/weekday) BEFORE this module existed, never read off [Rubrics_ef.creed]'s own answer. Two synthetic unit tests isolate Trap One (RG 475(a) reads [temporal], never [observed]) directly, without depending on finding a real-calendar coincidence. One further test (celebrant-rubrics-phase1 review, finding 1) is different in kind from the rest: not an end-to-end [creed] check against a single real date, but a direct sweep of {!Rite_ef.Temporal_ef} output across a century, asserting the unstated invariant [creed]'s own RG 24/25 ferial branches rely on rather than leaving it asserted only in prose -- see its own header comment for the full account. *) module Cal = Colitur_kernel.Calendar module LD = Colitur_kernel.Liturgical_day module Date = Colitur_kernel.Date module Cel = Colitur_kernel.Celebration module Colour = Colitur_kernel.Colour module Subject = Colitur_kernel.Subject module Slug = Colitur_kernel.Slug module Temporal = Colitur_kernel.Temporal module Computus = Colitur_kernel.Computus module V = Rite_ef.Vocab_ef module RE = Rite_ef.Rubrics_ef module TE = Rite_ef.Temporal_ef let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e (* Loaded once, module-level: every test below is a lookup against the same shipped calendar, and {!Cal.day} recomputes its whole liturgical year on every call (calendar.mli's own documented cost), so at minimum the layer itself should not be reloaded and re-merged per test case. *) let layer = match Test_support.load_ef_layer () with Ok l -> l | Error e -> Alcotest.failf "%s" e let ctx = Test_support.ef_context () let creed_on y m d = (Cal.day ctx layer (mk y m d)).LD.creed let check name y m d expected = Alcotest.(check bool) name expected (creed_on y m d) (* ------------------------------------------------------------------------ *) (* Breviary 237-238 (Te Deum) and RG 431-432 (Gloria) -- Phase 2 of the *) (* celebrant-rubrics-phase1 design. See lib/rites/rite_ef/rubrics_ef.ml for *) (* both rubrics quoted in full and every branch's own citation. One *) (* end-to-end test per clause, resolved against REAL calendar dates through *) (* the shipped data -- every expected value below was read off *) (* `colitur rubrics `'s real output BEFORE being pinned here, the *) (* same discipline the Creed tests above already establish. *) let day_on y m d = Cal.day ctx layer (mk y m d) let gloria_on y m d = (day_on y m d).LD.gloria let te_deum_on y m d = RE.te_deum ~temporal:(day_on y m d).LD.temporal ~observed:(day_on y m d).LD.observed ~date:(mk y m d) let check_gloria name y m d expected = Alcotest.(check bool) name expected (gloria_on y m d) let check_te_deum name y m d expected = Alcotest.(check bool) name expected (te_deum_on y m d) (* ---- RG 475(a): "in qualibet dominica, etsi eius Officium alicui festo locum cedat" ---- *) let test_475a_ordinary_sunday () = (* 2026-01-25: an ordinary Time-after-Epiphany Sunday, Class2, green, "Dominica III post Epiphaniam" -- confirmed via `colitur day 2026`, no other clause of 475 could apply (Class2, subject Temporal, no octave, no apostle/vigil slug), so [true] here can only come from 475(a) itself. *) check "475(a): an ordinary Sunday" 2026 1 25 true (* Trap One, isolated directly: RG 475(a)'s own "ETSI EIUS OFFICIUM ALICUI FESTO LOCUM CEDAT" -- even when a feast has displaced the Sunday's own office, the Creed is still said. [observed] below is deliberately shaped so that NONE of 475(b)/(c)/(e) can produce [true] on their own (Class3, subject Saint, a slug on no list this module knows); the only way [creed] can return [true] is by reading [temporal]'s own [weekday], never [observed]. The synthetic [date] (an ordinary July day, itself a Wednesday in 2026) is chosen so nothing about the DATE itself suggests a Sunday either -- proving the function reads [temporal.weekday], not [Date.weekday date]. *) let impeded_observed = Cel.make ~slug:(Slug.of_string_exn "some-impeding-feast-of-the-lord") ~rank:V.Class3 ~status:Cel.Feast ~colour:Colour.Red ~subject:Subject.Saint ~layer:"synthetic" () let synthetic_temporal ~weekday : (V.season, V.rank) Temporal.t = { Temporal.season = V.Time_after_pentecost; week = Some 1; weekday; office = impeded_observed } let test_475a_reads_temporal_not_observed () = Alcotest.(check bool) "Sunday-shaped [temporal] overrides a non-Sunday-shaped [observed]" true (RE.creed ~temporal:(synthetic_temporal ~weekday:Date.Sun) ~observed:impeded_observed ~date:(mk 2026 7 1)); Alcotest.(check bool) "same [observed], non-Sunday [temporal]: false" false (RE.creed ~temporal:(synthetic_temporal ~weekday:Date.Wed) ~observed:impeded_observed ~date:(mk 2026 7 1)) (* ---- RG 475(b): "in festis I classis" ---- *) let test_475b_class1_feast () = (* 2026-08-15: the Assumption, Class1. *) check "475(b): a I-class feast" 2026 8 15 true (* ---- RG 475(c): "in festis II classis Domini et B. Mariae Virg." ---- *) let test_475c_lord () = (* 2026-09-14: Exaltation of the Holy Cross, Class2, subject Lord. *) check "475(c): a II-class feast of the Lord" 2026 9 14 true let test_475c_bvm_via_marian_slugs () = (* 2026-08-22: Immaculate Heart of Mary, Class2 -- ships [subject = Saint] in data/ef/sanctoral.sexp (confirmed by grep), so this can only come out [true] via {!Rite_ef.Precedence_ef.marian_slugs}, not via [subject = Bvm]. Also carries a real commemoration (+sts-timothy-hippolytus-and-symphorianus-martyrs), a live instance of 476(e): the commemoration plays no part in this answer. *) check "475(c): a II-class BVM feast (via marian_slugs, subject=Saint in the data)" 2026 8 22 true (* ---- RG 475(d): "per octavas Nativitatis Domini, Paschatis et Pentecostes, etiam in festis occurrentibus et in Missis votivis" ---- *) let test_475d_octave_even_occurring_feast () = (* 2026-12-26: St Stephen, Class2, "S. Stephani Protomartyris" -- a real saint's feast OCCURRING within the Octave of the Nativity (RG 67's own "Com. octavae Nativitatis", carried as +ef-nativity-octave-day-2 in colitur's own commemoration). RG 475(d)'s own "etiam in festis occurrentibus" is written for exactly this shape: the Creed is said regardless. (This is the one place this suite deliberately diverges from the task brief's own worked example, which expected [false] here -- the brief mis-cited 26 December as 476(b)'s "plain II-class feast" case, missing that RG 475(d) explicitly overrides 476(b) inside the Nativity octave; see the task report.) *) check "475(d): a saint's feast occurring within the Nativity octave" 2026 12 26 true let test_475d_octave_day_boundary () = (* 2026-01-01: the Octave Day of the Nativity itself (Circumcision), Class1 -- also [true] via 475(b) alone, kept as a boundary check that 1 January is correctly included in the 8-day window. *) check "475(d): 1 January, the Octave Day of the Nativity" 2026 1 1 true (* ---- RG 475(e): "in festis nataliciis Apostolorum et Evangelistarum, necnon in festis Cathedrae S. Petri et S. Barnabae Ap." ---- *) let test_475e_apostle_natalicium () = (* 2026-11-30: St Andrew, Class2, subject Saint -- not covered by 475(c) (not Domini/BVM), so [true] here can only come from 475(e)'s own natalicia list. Also carries a real commemoration (+ef-advent-1-monday), another live 476(e) instance. *) check "475(e): an Apostle's own natalicium (Andrew)" 2026 11 30 true let test_475e_barnabas_named_explicitly () = (* 2026-06-11: St Barnabas, Class3 -- named explicitly by the clause ("S. Barnabae Ap."); at Class3 it could not reach [true] via 475(b) or (c) regardless. *) check "475(e): St Barnabas, named explicitly" 2026 6 11 true let test_475e_chair_of_peter_named_explicitly () = (* 2027-02-22 (NOT 2026: Feb 22 2026 is impeded by Lent I Sunday, so the Chair is not observed that year -- checked via `colitur day 2026` before picking 2027 instead): the Chair of St Peter, Class2, subject Saint -- NOT a natalicium (Peter's own is 29 June, shared with Paul), so [true] here can only come from the clause's own explicit "Cathedrae S. Petri" naming, not from the natalicium reading in general. *) check "475(e): the Chair of St Peter, named explicitly (not a natalicium)" 2027 2 22 true let test_475e_excludes_conversion_of_paul () = (* Trap Two, directly: 2027-01-25, the Conversion of St Paul, Class3 -- names an Apostle but is NOT his natalicium (his own is 29 June, with Peter); Class3 rules out 475(b)/(c), and this slug is deliberately absent from [creed_apostle_slugs]. Picked 2027 for the same impeded- Sunday reason as the Chair of Peter above (25 January 2026 is itself a Sunday). *) check "475(e) does NOT cover the Conversion of St Paul (not a natalicium)" 2027 1 25 false (* ---- RG 23 (feriae) / RG 476(a): Ash Wednesday, Holy Week's own feriae, the Chrism/Lord's Supper Mass, the Easter Vigil Mass ---- *) let test_ash_wednesday_no_creed () = check "RG 23(a)/476: Ash Wednesday, a I-class FERIA, not a festum" 2026 2 18 false let test_holy_thursday_no_creed () = (* 2026-04-02: "Feria V in Cena Domini" -- RG 23(b)'s own "omnes feriae Hebdomadae sanctae"; also explicitly named by 476(a) ("sive... in Cena Domini"). *) check "RG 23(b)/476(a): Holy Thursday (Mass of the Lord's Supper)" 2026 4 2 false let test_holy_saturday_easter_vigil_no_creed () = (* 2026-04-04: "Sabbato sanctum" -- RG 23(b) again; also explicitly named by 476(a) ("in Missa Vigiliae paschalis"). *) check "RG 23(b)/476(a): Holy Saturday (the Easter Vigil Mass)" 2026 4 4 false (* ---- RG 476(f): "in Missis defunctorum" -- the Creed is never said at a Requiem Mass. Whole-branch review finding 1: colitur used to say [true] here on rank alone (475(b), Class1) -- the LMS Ordo layer's own former allow-list entry L1, now closed, was this exact day. See rubrics_ef.ml's own header for why {!Colour.Black} is a sound RG 117 proxy for "this Mass is a Requiem" on the shipped data. ---- *) let test_476f_all_souls_no_creed () = (* 2025-11-03: All Souls' Day (2 November, a Sunday that year, transferred to the next open day, RG's own Class1/black office unchanged by the transfer) -- `colitur day 2025` confirms class-1/black/ commemoration-of-all-souls on this date. Before this fix [creed] here was [true] via 475(b); the Ordo's own text ("No Cr") says otherwise. *) check "RG 476(f): All Souls' Day (transferred), a Requiem, says no Creed" 2025 11 3 false let test_476f_good_friday_no_creed () = (* 2026-04-03: Good Friday, [colour = Black] (temporal_ef.ml's own [days_between easter d = -2] branch) -- already [false] via the RG 23(b) feria-I-classis exclusion checked earlier in [creed], so this is a belt-and-braces witness that the 476(f) guard, were it ever reached first, would agree rather than a case that depends on it: Good Friday has no Mass in the 1955-restored Holy Week to begin with. *) check "RG 476(f)/RG 23(b): Good Friday, black, says no Creed" 2026 4 3 false (* Whole-branch review finding 1's own instruction: a test that FAILS if a third {!Colour.Black} celebration is ever introduced, so the RG 476(f) guard's colour proxy cannot silently rot into covering the wrong population. Two independent checks, matching the two places a black celebration could be added: DATA: every entry the shipped sanctoral layer (sanctoral.sexp, with adjustments.sexp merged on top -- the same [layer] every other test in this file resolves against) carries directly, fixed AND movable alike ({!Colitur_kernel.Layer.entries} holds the raw [Date_spec.t], not a resolved index, so this needs no date walk at all to cover every civil year). CODE: {!Rite_ef.Temporal_ef.temporal}'s own colour logic, which sanctoral.sexp cannot see (Good Friday's black is computed, not data) -- swept across a century the same span and for the same coverage reason {!test_ferial_origin_never_carries_lord_bvm_or_apostle_slug} above already uses, since a Colour.Black bug introduced into a rare branch (an Ember day, Rogation Wednesday...) would not show up in one year. *) let test_colour_black_population_is_exactly_two () = let black_data_entries = List.filter (fun (e : V.rank Colitur_kernel.Layer.entry) -> e.Colitur_kernel.Layer.cel.Cel.colour = Colour.Black) layer.Colitur_kernel.Layer.entries in Alcotest.(check int) "exactly one Colour.Black entry in the shipped data" 1 (List.length black_data_entries); (match black_data_entries with | [ e ] -> Alcotest.(check string) "the one Colour.Black data entry is All Souls" "commemoration-of-all-souls" (Slug.to_string e.Colitur_kernel.Layer.cel.Cel.slug) | _ -> ()); let code_black = ref [] in for y = 1583 to 1682 do let dec31 = mk y 12 31 in let d = ref (mk y 1 1) in while Date.compare !d dec31 <= 0 do let t = TE.temporal !d in if t.Temporal.office.Cel.colour = Colour.Black then code_black := (y, !d, t) :: !code_black; d := Date.add_days !d 1 done done; Alcotest.(check bool) "the century sweep found at least 100 Colour.Black temporal days" true (List.length !code_black >= 100); List.iter (fun (_, d, (t : (V.season, V.rank) Temporal.t)) -> let easter = Computus.gregorian_easter (Date.year d) in Alcotest.(check int) (Printf.sprintf "%s: the only code-side Colour.Black day is Good Friday (Easter-2)" (Date.to_iso8601 d)) (-2) (Date.to_rata d - Date.to_rata easter); Alcotest.(check string) (Printf.sprintf "%s: Good Friday's own slug" (Date.to_iso8601 d)) "ef-passiontide-2-friday" (Slug.to_string t.Temporal.office.Cel.slug)) !code_black (* ---- RG 476(b) negative: a plain II-class saint, not Domini/BVM, not an Apostle/Evangelist ---- *) let test_476b_plain_class2_saint () = (* 2026-08-10: St Lawrence, Class2, subject Saint -- a deacon and martyr, no Apostle/Evangelist connection, not on any list this module reads. *) check "476(b): a plain II-class saint (Lawrence) does not say the Creed" 2026 8 10 false (* ---- RG 28-34 (vigils): checked ahead of 475(c) so a Class2 vigil that is ALSO on marian_slugs is still excluded ---- *) let test_vigil_excluded_even_when_class2_and_marian () = (* 2026-08-14: Vigil of the Assumption, Class2 -- IS on {!Rite_ef.Precedence_ef.marian_slugs} (a real Marian entry), so without the vigil check ahead of 475(c) this would wrongly come out [true]. Also carries a real commemoration (+eusebius-confessor), a second live 476(e) instance. *) check "vigils are excluded even when Class2 and Marian (Vigil of the Assumption)" 2026 8 14 false (* ---- RG 476(d): a IV-class office (also exercises RG 78/309(a)'s votive Office of the BVM on Saturday, itself IV class) ---- *) let test_476d_bvm_saturday_office () = (* 2026-07-11: the unoccupied Saturday's Office of Our Lady, Class4 -- already a pinned example in test/cli.t (Task 4). *) check "476(d): the BVM Saturday Office, IV class" 2026 7 11 false (* One full civil year, walked day by day: RG 475(a)'s own invariant, "every Sunday says the Creed, no exceptions" -- the same sanity check the task asks for at the domain-measurement step, pinned here as a real assertion rather than left to a one-off shell scan. *) let test_every_sunday_in_2026_says_the_creed () = let days = Cal.year ctx layer 2026 in Array.iter (fun (d : (V.season, V.rank) LD.t) -> if d.LD.temporal.Temporal.weekday = Date.Sun then Alcotest.(check bool) (Printf.sprintf "%s is a Sunday: creed must be true" (Date.to_iso8601 d.LD.date)) true d.LD.creed) days (* ---- Review finding 1 (celebrant-rubrics-phase1 fix task): RG 24/25's feriae II and III classis (the Advent ferias 17-23 December, the Advent/Lent/September Ember sets, and the numbered Lenten/Passiontide ferias) are excluded from the Creed by NO check in rubrics_ef.ml -- see that file's own RG 23 comment (the [n = -46 ...] branch) for the full account. [creed]'s correct [false] answer for them rests entirely on an unstated property of {!Rite_ef.Temporal_ef}: no ferial-origin office it ever builds carries [subject = Lord] or [subject = Bvm] at [Class2] (RG 475(c)'s own rank guard), and none carries a slug on [RE.creed_apostle_slugs] (RG 475(e), which has no rank guard at all). This test asserts that property directly against real Temporal_ef output, rather than leaving it asserted only in prose, so a future Temporal_ef change that breaks it fails HERE loudly instead of silently changing the Creed. Two real, harmless, already-documented exceptions are excluded from the [subject] checks rather than papered over: the Sacred Triduum ([subject = Lord], [Class1] -- verified in temporal_ef.ml's own [triduum_names] branch) and the votive Office of the BVM on Saturday, RG 91 entry 27 ([subject = Bvm], [Class4] -- {!TE}'s own [is_bvm_saturday] citation). Neither can ever reach [creed]'s 475(c) branch: the Triduum is caught earlier by the explicit feria-I-classis check (its own [Class1] is not [Class2]), and the BVM Saturday Office is [Class4], not [Class2], so 475(c)'s own rank guard excludes it regardless of subject. Excluding them here is not a loophole in the test -- it is the precise boundary of what RG 475(c) actually reads, confirmed rather than assumed by restricting the [Lord]/[Bvm] checks to exactly [Class2] (RG 24's own II-class population) and [Class3] (RG 25's III-class population), the two ranks a real feria can carry that are NOT already handled by an explicit branch or a rank mismatch. The apostle-slug check has no such carve-out: it is asserted for every ferial-origin day of every rank, matching 475(e)'s own unguarded text. A THIRD exception -- not anticipated when this test was first written, found by actually running it, not by inspection -- had to be excluded from the POPULATION itself, not from the subject checks: RG 17(a)'s own "secus die 2 ianuarii" fallback (2 January, only in a civil year with no 2-5 January Sunday) is a genuine FEAST, [subject = Lord], [Class2], sitting outside {!TE.named}'s table for a purely architectural reason (temporal_ef.ml's own citation: that table's shape cannot express a fallback conditional on a per-year fact) and not always a Sunday either, so neither of [is_ferial_origin]'s two filters caught it on the first run -- it failed on 1584-01-02 before {!is_holy_name_fallback} below was added. Left in as evidence the sweep has real teeth rather than trimmed from the span: excluding a genuine mistaken positive by name, once found, is the correct fix; narrowing the span to dodge it would not have been. A day is "ferial-origin" here iff {!TE.named} returns [None] for it (nothing in the fixed/movable table of named feasts, vigils and octave days claims it), it is not a Sunday (RG 21's own definition: "Nomine feriae intelleguntur singuli dies hebdomadae, praeter dominicam" -- every day of the week EXCEPT Sunday), and it is not the RG 17(a) fallback date just above. *) let contains_substring ~needle haystack = let nl = String.length needle and hl = String.length haystack in let rec loop i = (i + nl <= hl) && (String.sub haystack i nl = needle || loop (i + 1)) in nl = 0 || loop 0 (* RG 17(a)'s own "secus die 2 ianuarii" fallback (temporal_ef.ml's own citation on [holy_name_fallback_date]): a genuine FEAST, [subject = Lord], [Class2] -- legitimately outside {!TE.named}'s table only for an architectural reason (that table's bare [Date.t -> ... option] shape cannot express a fallback conditional on a per-year fact, [holy_name_sunday y = None]), never a ferial one, and it is not always a Sunday, so the weekday filter below does not catch it either. Found by running this test before this exclusion existed: it failed on 1584-01-02, a real year with no 2-5 January Sunday. *) let is_holy_name_fallback (d : Date.t) = let y = Date.year d in Date.compare d (TE.holy_name_fallback_date y) = 0 && TE.holy_name_sunday y = None let is_ferial_origin (d : Date.t) = TE.named d = None && Date.weekday d <> Date.Sun && not (is_holy_name_fallback d) (* Span: civil years 1583-1682, the domain's own opening century -- every RG 24/25 population recurs every civil year (Advent 17-23, all three non-Whitsun Ember sets, and Lenten/Passiontide ferias are none of them rare or conditional), so one year already gives full COVERAGE; a century instead of one is for confidence the invariant is not an accident of a single Easter date, at a cost (day-by-day calls to the pure {!TE.temporal}, no Calendar/Precedence resolution) cheap enough to stay in the default, unsampled suite. 1583 is also the domain's own lower bound, and this span contains 1598, the earliest possible Easter (22 March) in [1583, 2500] per {!test_easter_extremes} above -- a real, not merely nominal, edge case. Coverage is VERIFIED below via counters, not assumed: a test that never actually walks an Ember day would prove nothing about Ember days. *) let test_ferial_origin_never_carries_lord_bvm_or_apostle_slug () = let total = ref 0 and advent_ember = ref 0 and lent_ember = ref 0 and september_ember = ref 0 and advent_17_23 = ref 0 and lenten_passiontide_class3 = ref 0 in for y = 1583 to 1682 do let dec31 = mk y 12 31 in let d = ref (mk y 1 1) in while Date.compare !d dec31 <= 0 do (if is_ferial_origin !d then begin let t = TE.temporal !d in incr total; let office = t.Temporal.office in let slug = Slug.to_string office.Cel.slug in let label = Printf.sprintf "%s (%s, rank=%s)" (Date.to_iso8601 !d) slug (match office.Cel.rank with | V.Class1 -> "1" | V.Class2 -> "2" | V.Class3 -> "3" | V.Class4 -> "4") in (* Coverage tallies -- checked against zero below. *) if contains_substring ~needle:"advent-ember" slug then incr advent_ember; if contains_substring ~needle:"lent-ember" slug then incr lent_ember; if contains_substring ~needle:"september-ember" slug then incr september_ember; if Date.month !d = 12 && Date.day !d >= 17 && Date.day !d <= 23 then incr advent_17_23; if office.Cel.rank = V.Class3 && (t.Temporal.season = V.Lent || t.Temporal.season = V.Passiontide) then incr lenten_passiontide_class3; (* The invariant itself. Apostle slugs: every rank, no carve-out. *) Alcotest.(check bool) (label ^ ": not on the apostle-natalicium slug list") false (List.mem slug RE.creed_apostle_slugs); (* Lord/Bvm subject: only at the two ranks a real RG 24/25 feria can carry -- see this test's own header for why Class1 (Triduum) and Class4 (BVM Saturday) are deliberately excluded. *) if office.Cel.rank = V.Class2 || office.Cel.rank = V.Class3 then begin Alcotest.(check bool) (label ^ ": not subject Lord") false (office.Cel.subject = Subject.Lord); Alcotest.(check bool) (label ^ ": not subject Bvm") false (office.Cel.subject = Subject.Bvm) end end); d := Date.add_days !d 1 done done; Alcotest.(check bool) "span examined a real number of ferial-origin days" true (!total > 10_000); Alcotest.(check bool) "span reached Advent Ember days" true (!advent_ember > 0); Alcotest.(check bool) "span reached Lent Ember days" true (!lent_ember > 0); Alcotest.(check bool) "span reached September Ember days" true (!september_ember > 0); Alcotest.(check bool) "span reached Advent 17-23 ferias" true (!advent_17_23 > 0); Alcotest.(check bool) "span reached Lenten/Passiontide III-class ferias" true (!lenten_passiontide_class3 > 0) (* ---- 237(a): the three named Paschaltide days ---- *) let test_237a_easter_sunday () = check_te_deum "237(a): Easter Sunday" 2026 4 5 true let test_237a_low_sunday () = check_te_deum "237(a): Low Sunday" 2026 4 12 true let test_237a_pentecost () = check_te_deum "237(a): Pentecost Sunday" 2026 5 24 true (* ---- 237(f): the vigils of Ascension and Pentecost, both otherwise reachable by 238(b) (the Ascension vigil is Class2) or moot to it (the Pentecost vigil is Class1) ---- *) let test_237f_ascension_vigil () = check_te_deum "237(f): Vigil of the Ascension" 2026 5 13 true let test_237f_pentecost_vigil () = check_te_deum "237(f): Vigil of Pentecost" 2026 5 23 true (* ---- 237(d): the three privileged octaves, reusing [creed]'s own windows -- a day within the Nativity octave whose OWN office is a real saint (RG 67's "Com. octavae Nativitatis"), and an Easter-week feria outside the narrower 237(a) list ---- *) let test_237d_nativity_octave_saint () = check_te_deum "237(d): St John within the Nativity octave" 2026 12 27 true let test_237d_easter_week_feria () = check_te_deum "237(d): Easter Tuesday" 2026 4 7 true (* ---- 238(b): the omissible (Class2/Class3) vigils -- St Lawrence's own, picked in a year where nothing displaces it, confirmed via `colitur day 2026` first the same way the Creed tests were derived ---- *) let test_238b_omissible_vigil () = (* 9 August 2026 is itself a Sunday (the vigil impeded, see 237(b)'s own test below, which reuses that exact date) -- 2027 is picked instead, confirmed via `colitur day 2027` first, the same discipline every other date in this file follows. *) check_te_deum "238(b): Vigil of St Lawrence" 2027 8 9 false (* ---- The Nativity Vigil (I class): NOT literally named by 238(b)'s own "II et III classis" text -- excluded here on the structural RG 21/35 taxonomy inference [te_deum]'s own comment states explicitly as an inference, not a citation. Flagged the same way in the task report; checked against the FIUV Ordo's own Te Deum marker for 24 December in test_fiuv_ordo.ml, which is this inference's real corroboration. ---- *) let test_nativity_vigil_excluded_by_inference () = check_te_deum "Nativity Vigil (I class): excluded, inference not literal 238(b)" 2026 12 24 false (* ---- 238(c)/RG23: Ash Wednesday and Good Friday (moot -- no Mass, but the RG23 feria-I-classis exclusion still answers [false] regardless of colour) ---- *) let test_238c_ash_wednesday () = check_te_deum "238(c)/RG23: Ash Wednesday" 2026 2 18 false let test_238c_good_friday () = check_te_deum "238(c)/RG23: Good Friday" 2026 4 3 false (* ---- 237(g): the votive Office of the BVM on Saturday -- reused directly from test/cli.t's own pinned example ---- *) let test_237g_bvm_saturday () = check_te_deum "237(g): BVM Saturday Office" 2026 7 11 true (* ---- 237(e): a plain, non-octave, non-Sunday feria of Christmastide (2-5 January) ---- *) let test_237e_christmastide_feria () = check_te_deum "237(e): 2 January, a Christmastide feria" 2026 1 2 true (* ---- 237(b): an ordinary II-class Sunday outside Septuagesima, and its own explicit exception (a Septuagesima/Sexagesima Sunday, [Class2], excepted by name) ---- *) let test_237b_ordinary_class2_sunday () = check_te_deum "237(b): an ordinary Time-after-Pentecost Sunday" 2026 8 9 true (* RE-VERIFIED against the CORRECTED FIUV extraction ([te_deum]'s own 237(a) comment has the full account of the extractor bug an earlier pass of this task found and fixed): 237(b)'s own literal "exceptis dominicis in Septuagesima, in Sexagesima et in Quinquagesima" holds after all -- these two Sundays do NOT say the Te Deum, confirmed against the corrected data, not merely the original transcription alone. *) let test_237b_septuagesima_exception () = check_te_deum "237(b)'s own exception: Septuagesima Sunday" 2027 1 24 false let test_237b_sexagesima_exception () = check_te_deum "237(b)'s own exception: Sexagesima Sunday" 2027 1 31 false (* ---- 237(c): a genuine sanctoral feast kept during a penitential season (St Paul of the Cross, Class3, outside any privileged window) and one in Ordinary Time (Lawrence, Class2) ---- *) let test_237c_sanctoral_feast () = check_te_deum "237(c): a sanctoral feast (Lawrence)" 2026 8 10 true (* Found alongside the FIUV extractor-bug fix ([te_deum]'s own 237(c) comment has the full account): Passion Sunday and Palm Sunday are BOTH entries in {!Temporal_ef.named} (named individually for RG 91 entry 6), which without the [temporal.weekday <> Sun] guard on 237(c)'s own [named<>None] disjunct would wrongly grant them [true] by ACCIDENT of table membership -- neither is a genuine "festum" (RG 35's own "dies dominica" is its own category), and both are [Class1] Sundays 237(b)'s own [Class2] guard already excludes. Confirmed [false] directly against the corrected FIUV extraction. *) let test_passion_sunday_not_a_festum () = check_te_deum "Passion Sunday is not a festum for 237(c)" 2026 3 22 false let test_palm_sunday_not_a_festum () = check_te_deum "Palm Sunday is not a festum for 237(c)" 2026 3 29 false (* ---- 238(d): the Requiem proxy, shared with [creed]'s own 476(f) and the SAME two-member {!Colour.Black} population {!test_colour_black_population_is_exactly_two} already asserts ---- *) let test_238d_all_souls () = check_te_deum "238(d): All Souls' Day (transferred)" 2025 11 3 false (* ---- RG 431(c): Holy Thursday and the Easter Vigil Mass say the Gloria even though neither day's own Matins says the Te Deum (both are feria-I-classis, [te_deum]'s own 238(c) branch) -- the one place [gloria] and [te_deum] genuinely disagree on real data. Holy Saturday also proves 431(c) outranks 432(b): its own colour is [Violet] (Passiontide's [season_colour]), yet the Gloria is still said. ---- *) let test_431c_holy_thursday () = check_gloria "431(c): Holy Thursday" 2026 4 2 true let test_431c_easter_vigil () = check_gloria "431(c): the Easter Vigil Mass (Holy Saturday)" 2026 4 4 true (* ---- RG 432(b): violet vestments -- independent of [te_deum], checked against an Advent Sunday (I class, so [te_deum] would ALSO answer [false] here via its own 237(b) rank guard: this is a same-answer witness, not proof of independent teeth -- see the task report for why no fully independent (te_deum=true, violet) witness exists anywhere in the shipped 1583-9999 domain: every real [Colour.Violet] [Feast] entry is one of the five sanctoral vigils, already excluded by [te_deum]'s own [is_vigil] branch either way). ---- *) let test_432b_violet_sunday () = check_gloria "432(b): Advent I Sunday, violet" 2026 11 29 false (* ---- RG 432(d): the Requiem proxy, same population as 238(d) above ---- *) let test_432d_all_souls () = check_gloria "432(d): All Souls' Day (transferred)" 2025 11 3 false (* ---- RG 431(a)/432(a): mirrors [te_deum] for everything not already decided above -- one true, one false, neither reachable via 431(c)/432(b)/ 432(d) ---- *) let test_431a_mirrors_te_deum_true () = check_gloria "431(a): mirrors [te_deum]=true (a sanctoral feast, Lawrence)" 2026 8 10 true let test_432a_mirrors_te_deum_false () = check_gloria "432(a): mirrors [te_deum]=false (an ordinary Advent feria)" 2025 12 1 false (* ---- Domain-wide sanity, task requirement 5: "every violet day must be false (RG 432 b), and every Requiem must be false (432 d)". Two invariants, checked over every day {!Cal.year} resolves -- [Cal.year], not [Cal.day] in a loop, for the same cost reason {!test_every_sunday_in_2026_says_the_creed} above already gives. FAST (default suite): a fixed 200-year span, 1583-1782 -- large enough to cross multiple Easter cycles and every season repeatedly, cheap enough to stay in `dune test`'s own budget. EXHAUSTIVE (gated the same way {!test_validate.test_exhaustive_domain_sweep} already is, via COLITUR_EXHAUSTIVE_SWEEP): the full 1583-9999 domain, also tallying and printing the true/false distribution so the measurement this task asks for is not merely "did the invariant hold" but has a number attached -- see the task report for the printed figures. *) (* NOT a blanket "every violet day is [gloria]=false" (the task brief's own phrasing, taken literally, is one exception too strong): RG 431(c) is a NAMED, lex-specialis override for the Easter Vigil Mass, and colitur's own per-day colour model gives Holy Saturday [Colour.Violet] ([gloria]'s own header has the full argument for why -- the historical violet-to-white vestment change happens AT the Gloria itself, a per-action nuance this whole day/colour model already cannot express). So the real invariant, checked here, is "every violet day is [gloria]=false EXCEPT the Easter Vigil (Easter offset -1), which is [gloria]=true BY DESIGN" -- and the exception is asserted to be EXACTLY that one shape, every year, nothing else: [n = -1] is checked directly rather than merely excluded, so a second, unexpected (violet, gloria=true) day anywhere in the domain still fails loudly. Pushed back on the task brief's own simplified phrasing rather than silently special-cased -- see the task report. *) let check_gloria_invariants_for_year y (counts : (int * int * int * int) ref) = let days = Cal.year ctx layer y in (* Per-date, NOT once per loop iteration off [y]: a liturgical year "opening in civil year y" ({!Rite.t.year_start}'s own doc comment) runs from that year's Advent into MOST of civil year y+1 -- so [Cal.year ctx layer y]'s own array holds dates whose civil year is y+1 for the whole Christmas-to-Pentecost span, governed by EASTER OF y+1, not y. A single [Computus.gregorian_easter y] computed once here wrongly used year y's own Easter for those dates -- found live: it misidentified 1584-03-31 (Holy Saturday, governed by 1584's Easter, surfaced while processing loop iteration y=1583) as an ordinary violet day instead of the Vigil's own 431(c) exception, because Easter 1583 (not 1584) was subtracted. Fixed by keying off [Date.year d.LD.date] instead, which is always safe here: Holy Saturday and Easter Sunday are never more than a few days apart and never cross a civil-year boundary. *) let violet, black, gloria_true, gloria_false = !counts in let violet = ref violet and black = ref black and gloria_true = ref gloria_true and gloria_false = ref gloria_false in Array.iter (fun (d : (V.season, V.rank) LD.t) -> (if d.LD.gloria then incr gloria_true else incr gloria_false); if d.LD.observed.Cel.colour = Colour.Violet then begin incr violet; let easter = Computus.gregorian_easter (Date.year d.LD.date) in let n = Date.to_rata d.LD.date - Date.to_rata easter in if n = -1 then Alcotest.(check bool) (Printf.sprintf "%s: the Easter Vigil's own 431(c) override, [gloria]=true despite violet" (Date.to_iso8601 d.LD.date)) true d.LD.gloria else Alcotest.(check bool) (Printf.sprintf "%s: violet -> [gloria]=false (RG 432(b))" (Date.to_iso8601 d.LD.date)) false d.LD.gloria end; if d.LD.observed.Cel.colour = Colour.Rose then (* Gaudete/Laetare -- [gloria] reads [false] here via [te_deum]'s own [Class2] guard on 237(b) (both Rose Sundays are [Class1] BY CONSTRUCTION -- {!Rite_ef.Rubrics_ef.gloria}'s own 432(b) citation has the full "checked, then found redundant, then removed" account of why 432(b) itself does NOT need its own Rose branch). Not folded into the [violet] counter above: keeping the two colours separately tallied is what let this invariant catch the Gaudete/Laetare gap live in the first place (found via the LMS Ordo, 2023-12-17 and 2024-03-10) rather than silently averaging it away inside one shared bucket. *) Alcotest.(check bool) (Printf.sprintf "%s: Rose (Gaudete/Laetare) -> [gloria]=false" (Date.to_iso8601 d.LD.date)) false d.LD.gloria; if d.LD.observed.Cel.colour = Colour.Black then begin incr black; Alcotest.(check bool) (Printf.sprintf "%s: Requiem (black) -> [gloria]=false (RG 432(d))" (Date.to_iso8601 d.LD.date)) false d.LD.gloria end) days; counts := (!violet, !black, !gloria_true, !gloria_false) let test_domain_violet_implies_no_gloria_sample () = let counts = ref (0, 0, 0, 0) in for y = 1583 to 1782 do check_gloria_invariants_for_year y counts done; let violet, _, _, _ = !counts in Alcotest.(check bool) "the 200-year sample reached a real number of violet days" true (violet > 1000) let test_domain_requiem_implies_no_gloria_sample () = (* Separate test name, same underlying sweep as the one immediately above -- {!Cal.year} is only computed once per year regardless (module-level [ctx]/[layer], no per-test reload), so this is not a second pass over the domain, only a second, independently-named assertion on the same tally, matching how {!test_colour_black_population_is_exactly_two} above separates its own DATA and CODE checks into one function while this pair keeps violet and black as two named outcomes. *) let counts = ref (0, 0, 0, 0) in for y = 1583 to 1782 do check_gloria_invariants_for_year y counts done; let _, black, _, _ = !counts in Alcotest.(check bool) "the 200-year sample reached at least one Requiem day" true (black > 0) let colitur_exhaustive_sweep_env = "COLITUR_EXHAUSTIVE_SWEEP" let test_exhaustive_gloria_domain_sweep () = if Sys.getenv_opt colitur_exhaustive_sweep_env = None then Alcotest.skip () else begin let counts = ref (0, 0, 0, 0) in for y = 1583 to 9999 do check_gloria_invariants_for_year y counts done; let violet, black, gloria_true, gloria_false = !counts in Printf.printf "gloria domain sweep 1583..9999: violet=%d black=%d gloria_true=%d gloria_false=%d total=%d\n%!" violet black gloria_true gloria_false (gloria_true + gloria_false); Alcotest.(check bool) "the full domain reached a real number of violet days" true (violet > 100_000); Alcotest.(check bool) "the full domain reached a real number of Requiem days" true (black > 1_000) end let suite = ( "Rubrics_ef", [ Alcotest.test_case "475(a): ordinary Sunday" `Quick test_475a_ordinary_sunday; Alcotest.test_case "475(a): reads [temporal], not [observed] (Trap One)" `Quick test_475a_reads_temporal_not_observed; Alcotest.test_case "475(b): I-class feast" `Quick test_475b_class1_feast; Alcotest.test_case "475(c): II-class feast of the Lord" `Quick test_475c_lord; Alcotest.test_case "475(c): II-class BVM feast via marian_slugs" `Quick test_475c_bvm_via_marian_slugs; Alcotest.test_case "475(d): octave overrides an occurring feast" `Quick test_475d_octave_even_occurring_feast; Alcotest.test_case "475(d): 1 January octave-day boundary" `Quick test_475d_octave_day_boundary; Alcotest.test_case "475(e): an Apostle's own natalicium" `Quick test_475e_apostle_natalicium; Alcotest.test_case "475(e): Barnabas, named explicitly" `Quick test_475e_barnabas_named_explicitly; Alcotest.test_case "475(e): Chair of Peter, named explicitly" `Quick test_475e_chair_of_peter_named_explicitly; Alcotest.test_case "475(e) excludes the Conversion of St Paul (Trap Two)" `Quick test_475e_excludes_conversion_of_paul; Alcotest.test_case "RG 23/476: Ash Wednesday" `Quick test_ash_wednesday_no_creed; Alcotest.test_case "RG 23/476(a): Holy Thursday" `Quick test_holy_thursday_no_creed; Alcotest.test_case "RG 23/476(a): Holy Saturday / Easter Vigil" `Quick test_holy_saturday_easter_vigil_no_creed; Alcotest.test_case "476(f): All Souls' Day (transferred), a Requiem, no Creed" `Quick test_476f_all_souls_no_creed; Alcotest.test_case "476(f)/RG 23(b): Good Friday, black, no Creed" `Quick test_476f_good_friday_no_creed; Alcotest.test_case "Colour.Black population is exactly two (data + code)" `Quick test_colour_black_population_is_exactly_two; Alcotest.test_case "476(b): plain II-class saint (negative)" `Quick test_476b_plain_class2_saint; Alcotest.test_case "vigils excluded even when Class2 and Marian" `Quick test_vigil_excluded_even_when_class2_and_marian; Alcotest.test_case "476(d): BVM Saturday Office, IV class" `Quick test_476d_bvm_saturday_office; Alcotest.test_case "every Sunday in 2026 says the Creed" `Quick test_every_sunday_in_2026_says_the_creed; Alcotest.test_case "RG 24/25: no ferial-origin office carries Lord/Bvm or an apostle slug" `Quick test_ferial_origin_never_carries_lord_bvm_or_apostle_slug; Alcotest.test_case "237(a): Easter Sunday" `Quick test_237a_easter_sunday; Alcotest.test_case "237(a): Low Sunday" `Quick test_237a_low_sunday; Alcotest.test_case "237(a): Pentecost Sunday" `Quick test_237a_pentecost; Alcotest.test_case "237(f): Vigil of the Ascension" `Quick test_237f_ascension_vigil; Alcotest.test_case "237(f): Vigil of Pentecost" `Quick test_237f_pentecost_vigil; Alcotest.test_case "237(d): St John within the Nativity octave" `Quick test_237d_nativity_octave_saint; Alcotest.test_case "237(d): Easter Tuesday" `Quick test_237d_easter_week_feria; Alcotest.test_case "238(b): Vigil of St Lawrence" `Quick test_238b_omissible_vigil; Alcotest.test_case "Nativity Vigil excluded by structural inference, not literal 238(b)" `Quick test_nativity_vigil_excluded_by_inference; Alcotest.test_case "238(c)/RG23: Ash Wednesday" `Quick test_238c_ash_wednesday; Alcotest.test_case "238(c)/RG23: Good Friday" `Quick test_238c_good_friday; Alcotest.test_case "237(g): BVM Saturday Office" `Quick test_237g_bvm_saturday; Alcotest.test_case "237(e): 2 January, a Christmastide feria" `Quick test_237e_christmastide_feria; Alcotest.test_case "237(b): an ordinary Class2 Sunday" `Quick test_237b_ordinary_class2_sunday; Alcotest.test_case "237(b)'s own exception: Septuagesima Sunday" `Quick test_237b_septuagesima_exception; Alcotest.test_case "237(b)'s own exception: Sexagesima Sunday" `Quick test_237b_sexagesima_exception; Alcotest.test_case "237(c): a sanctoral feast (Lawrence)" `Quick test_237c_sanctoral_feast; Alcotest.test_case "Passion Sunday is not a festum for 237(c)" `Quick test_passion_sunday_not_a_festum; Alcotest.test_case "Palm Sunday is not a festum for 237(c)" `Quick test_palm_sunday_not_a_festum; Alcotest.test_case "238(d): All Souls' Day (transferred)" `Quick test_238d_all_souls; Alcotest.test_case "431(c): Holy Thursday" `Quick test_431c_holy_thursday; Alcotest.test_case "431(c): the Easter Vigil Mass" `Quick test_431c_easter_vigil; Alcotest.test_case "432(b): Advent I Sunday, violet" `Quick test_432b_violet_sunday; Alcotest.test_case "432(d): All Souls' Day (transferred)" `Quick test_432d_all_souls; Alcotest.test_case "431(a): mirrors [te_deum]=true" `Quick test_431a_mirrors_te_deum_true; Alcotest.test_case "432(a): mirrors [te_deum]=false" `Quick test_432a_mirrors_te_deum_false; Alcotest.test_case "domain sanity: every violet day has [gloria]=false (sample)" `Quick test_domain_violet_implies_no_gloria_sample; Alcotest.test_case "domain sanity: every Requiem day has [gloria]=false (sample)" `Quick test_domain_requiem_implies_no_gloria_sample; Alcotest.test_case "domain sweep 1583..9999: violet/Requiem invariants, committed not sampled" `Slow test_exhaustive_gloria_domain_sweep ] )