(* 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. *) 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 V = Rite_ef.Vocab_ef module RE = Rite_ef.Rubrics_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) (* ---- 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(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 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(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 ] )