summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/rites/rite_of/rubrics_of.ml290
-rw-r--r--lib/rites/rite_of/rubrics_of.mli78
-rw-r--r--test/test_colitur.ml1
-rw-r--r--test/test_rubrics_of.ml307
4 files changed, 676 insertions, 0 deletions
diff --git a/lib/rites/rite_of/rubrics_of.ml b/lib/rites/rite_of/rubrics_of.ml
new file mode 100644
index 0000000..3fa59e9
--- /dev/null
+++ b/lib/rites/rite_of/rubrics_of.ml
@@ -0,0 +1,290 @@
+(* Institutio Generalis Missalis Romani, editio typica tertia (2002)
+ (docs/research/of/igmr-2002-latin.pdf; extracted with `pdftotext
+ docs/research/of/igmr-2002-latin.pdf`, line numbers below are that
+ extraction's own line numbers) -- CITATION DISCIPLINE, restated from
+ vocab_of.ml/temporal_of.ml: the Missale Romanum PDF (docs/research/of/
+ missale-romanum-2002.pdf) carries BOTH the Normae universales and the
+ IGMR, each independently numbered from 1. Every citation below names
+ "IGMR n. N" for that reason, never a bare "n. N". Gloria and Credo are
+ IGMR questions (Ordo Missae structure); Normae has nothing to say about
+ either.
+
+ GLORIA (IGMR n. 53, igmr.txt:456-460), quoted in full:
+
+ "53. Gloria est antiquissimus et venerabilis hymnus, quo Ecclesia, in
+ Spiritu Sancto congregata, Deum Patrem atque Agnum glorificat eique
+ supplicat. Huius hymni textus cum alio commutari nequit. [...]
+ Cantatur autem vel dicitur diebus dominicis extra tempus Adventus et
+ Quadragesimae, necnon in sollemnitatibus et festis, et in peculiaribus
+ celebrationibus sollemnioribus."
+
+ So: every Sunday OUTSIDE Advent and Lent; every Sollemnitas; every
+ Festum; and, permissively ("in peculiaribus celebrationibus
+ sollemnioribus" -- "in particular more solemn celebrations", "dicitur"
+ not "dicendum est" elsewhere in this paragraph's own register), certain
+ occasions this engine has no dimension to model at all -- the same
+ "votive Mass class" scope limit {!Rite_ef.Rubrics_ef}'s own header
+ states for RG 431(d)/(e)/432(c) -- genuinely N/A, not silently skipped.
+
+ CREDO / SYMBOLUM (IGMR n. 67-68, igmr.txt:568-577), quoted in full:
+
+ "67. Symbolum seu professio fidei eo tendit ut universus populus
+ congregatus verbo Dei in lectionibus e sacra Scriptura nuntiato et per
+ homiliam exposito respondeat [...]
+ 68. Symbolum cantandum vel dicendum est a sacerdote cum populo diebus
+ dominicis et in sollemnitatibus; dici potest etiam in peculiaribus
+ celebrationibus sollemnioribus."
+
+ NARROWER than n. 53, on purpose, not by oversight: n. 68 names only
+ "dominicis et... sollemnitatibus" -- NO "et festis" clause at all, unlike
+ n. 53's explicit third member. An ordinary [Festum] (the Presentation,
+ the Transfiguration, the Exaltation of the Holy Cross...) therefore does
+ NOT get the Credo, exactly matching real practice (only Sundays and
+ solemnities carry it) and confirmed by the literal absence of the word
+ "festis" from n. 68 where n. 53 has it -- checked directly, not assumed.
+ The trailing "dici potest... peculiaribus... sollemnioribus" is the same
+ permissive, unmodelled N/A clause as n. 53's.
+
+ Neither n. 53 nor n. 68 carries an Easter-relative WINDOW the way RG
+ 475(d)'s three EF octaves do (Nativity/Easter/Pentecost) -- checked by
+ reading both paragraphs in full above, not merely by their absence from
+ this file's own branches: nothing in IGMR nn. 52-69 (the whole "Actus
+ paenitentialis" through "Oratio universalis" span, igmr.txt:440-585)
+ mentions an octave at all. [credo]'s own [date] parameter is therefore
+ genuinely unread, kept only for {!Colitur_kernel.Rite.t}'s shared shape
+ -- an honest [None]-shaped fact about this rubric, not a gap.
+
+ THE REQUIEM QUESTION -- IGMR IS GENUINELY SILENT, FLAGGED AS SUCH. RG
+ 476(f)/432(d) name "in Missis defunctorum" explicitly for EF; IGMR nn.
+ 379-385 ("II. DE MISSIS DEFUNCTORUM", igmr.txt:2327-2349), read in full,
+ say nothing at all about the Gloria or the Symbolum -- confirmed by
+ reading every line of that section, not by a failed grep. The Missale's
+ own printed propers corroborate the OMISSION reading only indirectly:
+ every OTHER Mass text in the extracted Proper of Time/Sanctoral carries
+ an explicit "Dicitur Gloria in excelsis."/"Non dicitur..." marker (over
+ 150 occurrences, docs/research/of/missale-romanum-2002.pdf via
+ `pdftotext -layout`), but the two "IN COMMEMORATIONE OMNIUM FIDELIUM
+ DEFUNCTORUM" (2 November) Mass-text blocks carry NEITHER marker --
+ consistent with (not proof of) universal, uncontested funeral-liturgy
+ practice that a Requiem Mass never has either. Picked as the reading
+ with the better textual support, per this task's own brief, and applied
+ as narrowly as the evidence allows: {!requiem_slugs} is a CLOSED,
+ directly-checked list of the ONE slug in data/of/calendar-2002.sexp this
+ can ever apply to (grepped for "defunct"/"requiem"/"exsequial": zero
+ other hits), the same "checked, not assumed, closed population"
+ discipline {!Rite_ef.Rubrics_ef}'s own [Colour.Black] proxy uses for the
+ identical EF question. *)
+
+open Colitur_kernel
+
+let requiem_slugs = [ "all-souls" ]
+
+let is_requiem (observed : Vocab_of.rank Celebration.t) =
+ List.mem (Slug.to_string observed.Celebration.slug) requiem_slugs
+
+let gloria ~(temporal : (Vocab_of.season, Vocab_of.rank) Temporal.t)
+ ~(observed : Vocab_of.rank Celebration.t) ~(date : Date.t) : bool =
+ let easter = Computus.gregorian_easter (Date.year date) in
+ let n = Date.to_rata date - Date.to_rata easter in
+ if is_requiem observed then false
+ else if
+ (* Missale Romanum, "FERIA V IN CENA DOMINI", Ad Missam vespertinam, n.
+ 7 (docs/research/of/missale-romanum-2002.pdf, pdftotext -layout,
+ line ~8534): "Dicitur Gloria in excelsis. Dum cantatur hymnus,
+ pulsantur campanae, eoque expleto, silent usque ad Gloria in
+ excelsis Vigiliae paschalis..." -- the Missal's own PROPER-OF-TIME
+ rubric for the Mass of the Lord's Supper (Easter-3) explicitly
+ requires the Gloria despite the day sitting inside Lent (Tabula I.1
+ territory once the evening Mass begins; Precedence_of.band's own
+ comment on WHY the civil day itself stays a Holy Week feria, not the
+ Triduum, is unaffected -- this rubric is keyed to the Mass, not the
+ day's own band), and the same sentence extends the silence "usque
+ ad" (UP TO) the Easter Vigil's own Gloria (Easter-1), confirming
+ that Mass also has one. Neither day is ever a Sunday (both are fixed
+ Easter-offsets within Holy Week), so this must be checked ahead of,
+ and independently of, the season/weekday branches below -- the
+ identical lex-specialis placement {!Rite_ef.Rubrics_ef.gloria}'s own
+ RG 431(c) branch uses for the same two days. *)
+ n = -3 || n = -1
+ then true
+ else if
+ (* IGMR n. 53: "extra tempus Adventus et Quadragesimae" -- read off
+ [temporal], never [observed]: Temporal_of.temporal tags every Sunday
+ of Advent, Lent AND Easter [Sollemnitas] (temporal_of.ml, [build]'s
+ own [match s with Advent | Lent | Easter -> Sollemnitas | ...]), so
+ an [observed.rank] test alone would wrongly grant an Advent or Lent
+ Sunday the Gloria this clause explicitly withholds -- confirmed by
+ Palm Sunday specifically ([Vocab_of.Lent], [Sollemnitas],
+ [named]'s own entry): real, uncontested practice omits the Gloria
+ there, and only a temporal-read guard gets that right. *)
+ temporal.Temporal.weekday = Date.Sun
+ && (temporal.Temporal.season = Vocab_of.Advent || temporal.Temporal.season = Vocab_of.Lent)
+ then false
+ else if
+ (* IGMR n. 53: every remaining Sunday (Christmas time, Easter, Ordinary
+ Time -- outside Advent/Lent by the branch just above). Read off
+ [temporal.weekday], not [observed.rank]: an ordinary Sunday per
+ annum is [Festum]-ranked (temporal_of.ml, the same [build] match
+ above), not [Sollemnitas], and would otherwise only reach a [true]
+ answer via the [Festum] arm below -- redundant for it, but this
+ branch is what makes an Easter- or Christmas-season Sunday's [true]
+ answer trace to n. 53's OWN "diebus dominicis" clause rather than
+ accidentally to its "et festis" clause. *)
+ temporal.Temporal.weekday = Date.Sun
+ then true
+ else
+ (* IGMR n. 53: "in sollemnitatibus et festis" -- every remaining
+ Sollemnitas or Festum, whatever day of the week it falls on (a
+ solemnity transferred onto an ordinary weekday, or one that was
+ never a Sunday to begin with -- St Joseph 19 March, the Annunciation
+ 25 March, both always inside Lent by construction: Gregorian
+ Easter's earliest possible date is 22 March, so Ash Wednesday can
+ never fall later than 10 March and Easter itself can never fall
+ before 19 March, meaning neither date can ever fall outside Lent).
+ [Memoria_obligatoria], [Memoria_ad_libitum] and [Feria] are not
+ named by n. 53 at all and correctly fall through to [false]. *)
+ observed.Celebration.rank = Vocab_of.Sollemnitas || observed.Celebration.rank = Vocab_of.Festum
+
+let credo ~(temporal : (Vocab_of.season, Vocab_of.rank) Temporal.t)
+ ~(observed : Vocab_of.rank Celebration.t) ~(date : Date.t) : bool =
+ ignore date;
+ (* IGMR n. 67-68's own [date]-independence: see this file's own header. *)
+ if is_requiem observed then false
+ else if
+ (* IGMR n. 68: "diebus dominicis" -- EVERY Sunday, Advent and Lent
+ included (unlike [gloria]'s own n. 53 carve-out: n. 68 has no such
+ exception in its text, checked directly). Read off [temporal], not
+ [observed], for the same reason [gloria]'s own Sunday branch is:
+ an ordinary (non-privileged-season) Sunday per annum is [Festum]-
+ ranked, and n. 68's own [Sollemnitas] branch below would silently
+ miss it if this branch were dropped. *)
+ temporal.Temporal.weekday = Date.Sun
+ then true
+ else
+ (* IGMR n. 68: "in sollemnitatibus". NO "et festis" here -- the one
+ place this function's shape most visibly narrows on EF's RG
+ 475(b)/(c): a bare [Festum] (the Presentation, the Transfiguration,
+ a I-class-shaped feast of the Lord with no Sunday privilege) does
+ NOT get the Credo. [Memoria_obligatoria]/[Memoria_ad_libitum]/
+ [Feria] fall through to [false], matching real practice (an
+ obligatory memorial on a weekday never carries the Creed). *)
+ observed.Celebration.rank = Vocab_of.Sollemnitas
+
+(* PREFACE CHOICE (IGMR n. 364-365, igmr.txt:2244-2263) -- quoted in full:
+
+ "364. Plurimae praefationes, quibus Missale Romanum ditatur, eo
+ spectant ut argumenta gratiarum actionis in Prece eucharistica plenius
+ eniteant, et variae rationes mysterii salutis pleniore luce
+ proponantur.
+ 365. Electio inter Preces eucharisticas, quae in Ordine Missae
+ inveniuntur, his normis opportune regitur: [...]"
+
+ n. 364-365 govern choosing among the FOUR EUCHARISTIC PRAYERS (I-IV),
+ NOT which preface accompanies whichever Mass is said -- a different
+ question this engine has no need to answer (colitur resolves a Mass's
+ readings/rubrics, never which of the interchangeable Canons a celebrant
+ picks). Genuinely N/A, cited here only because n. 364-365 is the
+ nearest thing to a "how is the Eucharistic Prayer/preface chosen"
+ numbered rubric in the whole IGMR, and this file should say plainly
+ that it is not the rule this function needs, rather than leave a reader
+ to wonder why it was not used.
+
+ UNLIKE RG 482-499, the IGMR carries NO numbered rubric governing preface
+ ASSIGNMENT at all -- confirmed by reading nn. 1-386 in full, not merely
+ by a failed grep for "praefatio" (which itself found zero hits: the
+ word never appears with that spelling anywhere in
+ docs/research/of/igmr-2002-latin.pdf's extracted text; every occurrence
+ in the Missale PDF instead uses the ligature "praefationem"/"praefatio"
+ spelt with <ae> or the printed <æ>, confirming this is a genuine textual
+ absence, not an extraction artifact). Preface assignment in the 2002
+ editio typica tertia is done PER MASS, inline throughout the Proper of
+ Time, Proper of Saints and Commons (each Mass's own rubric names its
+ preface directly, e.g. "Praefatio de Nativitate, pp. 520-522.") -- the
+ collection itself is indexed at the Missal's own back matter under
+ "INDEX PRAEFATIONUM" (missale-romanum-2002.pdf, pdftotext -layout,
+ extracted lines ~34107-34247), transcribed here in full because it is
+ the evidence the verdict below rests on:
+
+ A. De Tempore: Advent I-II (2); Nativity I-III (3); Epiphany (1);
+ Lent ("de Quadragesima") I-IV plus five Sunday-specific variants
+ V-IX (9); Passion ("de Passione Domini") I-III, III being Palm
+ Sunday's own (3); Paschal (Easter) I-V (5); Ascension I-II, BOTH
+ titled "De mysterio Ascensionis" (2); Pentecost (1); Sundays "per
+ annum" I-VIII (8); Praefationes communes I-VII (7).
+ B. In festis et mysteriis Domini: Trinity (1); the Annunciation's own
+ "De mysterio Incarnationis" (1); the Baptism of the Lord (1);
+ Sacred Heart (1); "De victoria Crucis gloriosae", 14 September (1);
+ Corpus Christi ("de Ss.ma Eucharistia") I-II (2); the Presentation
+ (1); Christ the King (1); the Transfiguration (1); the Dedication
+ of a Church I-II (2); "de Spiritu Sancto" I-II (2).
+ C. In festis sanctorum: the BVM I-V (5); the Angels (1); the
+ Precursor/John the Baptist (1); St Joseph, "De missione S.
+ Ioseph", 19 March/1 May (1); Peter-and-Paul (1); Apostles I-II
+ (2); Saints I-III (3); Martyrs I-II (2); Pastors (1); Virgins and
+ Religious (1).
+ Pro defunctis (Requiem): I-V (5).
+
+ AGAINST THIS CENSUS, {!Preface.t}'s 15 constructors split into two
+ failure shapes, and every single one fails one of them -- NONE is safe
+ to reuse:
+
+ (1) NINE constructors name an OF occasion with MULTIPLE distinct texts:
+ [Nativity] (3 OF variants), [Lent] (4, or 9 counting the
+ Sunday-specific set), [Easter] (5), [Ascension] (2), [Holy_spirit]
+ (2), [Bvm] (5), [Apostles] (2), [Common] (15, split across two
+ independent numbered families -- "per annum" I-VIII and
+ "communes" I-VII, neither of which [Preface.t]'s single [Common]
+ constructor could ever pick between), [Requiem] (5). Returning any
+ one of them would silently pick ONE of several genuinely different
+ texts and call it "the" answer -- structurally lossy, independent
+ of any wording question.
+
+ (2) SIX constructors name an OF occasion with exactly ONE text, the same
+ cardinality EF has for it -- [Epiphany], [Trinity], [Sacred_heart],
+ [Christ_the_king], [St_joseph], and [Holy_cross] (EF's RG 487 "de
+ sancta Cruce" against OF's single "De victoria Crucis gloriosae",
+ both keyed to 14 September). These are the genuinely tempting
+ cases -- same feast, same "exactly one" cardinality on both sides --
+ and each was checked and rejected anyway, on TWO grounds:
+ (a) {!Preface.t}'s own header ties every constructor to an RG
+ paragraph ("Nativity RG 484", "Trinity RG 494", ...) -- it
+ denotes THAT SPECIFIC EF TEXT, not an abstract cross-rite
+ theme, by its own documented contract.
+ (b) IGMR n. 364's own opening word choice is direct evidence the
+ 2002 Missal does not consider its preface collection a bare
+ carry-over: "praefationes, QUIBUS Missale Romanum DITATUR"
+ ("the prefaces WITH WHICH the Missal is ENRICHED") -- language
+ describing an addition/expansion, not an inheritance,
+ corroborating the well-documented fact that the post-1970
+ preface collection was substantially newly composed, the
+ Sacred Heart preface actually read while building this
+ module ("Qui, mira caritate, exaltatus in cruce... de
+ transfixo latere sanguinem fudit et aquam...",
+ missale-romanum-2002.pdf line ~30575) being the one
+ instance directly compared line-by-line against what its RG
+ 491 EF namesake is known to open with, and found to be a
+ different composition, not merely a different printing of
+ the same one.
+ Reusing any of the six on the strength of matching cardinality and
+ matching feast alone would be exactly the "sounds similar" trap
+ this task's own brief warns against -- confidently wrong output
+ every test in this suite would agree with, because nothing in this
+ codebase yet stores preface TEXT to catch the mismatch against.
+
+ VERDICT: [preface] returns [None] unconditionally. This is not a stub
+ -- every branch above was checked and its failure mode recorded -- and
+ it is not a defect in {!Preface.t} either: that type was built for one
+ rite and correctly refuses to pretend it fits a second one it was never
+ shown. The finding worth carrying forward (task brief's own "spec §7"
+ framing): OF preface selection needs its own type, most plausibly one
+ with a per-occasion NUMBERED-VARIANT dimension ("Nativity of 1 | 2 | 3",
+ ...) rather than {!Preface.t}'s flat enumeration, plus its own IGMR/
+ Missale-cited population census -- table-driven, the same shape as
+ {!requiem_slugs} above, once a task is actually chartered to build it.
+ Building that type is explicitly out of this task's own scope. *)
+let preface ~(temporal : (Vocab_of.season, Vocab_of.rank) Temporal.t)
+ ~(observed : Vocab_of.rank Celebration.t) ~(date : Date.t) : Preface.t option =
+ ignore temporal;
+ ignore observed;
+ ignore date;
+ None
diff --git a/lib/rites/rite_of/rubrics_of.mli b/lib/rites/rite_of/rubrics_of.mli
new file mode 100644
index 0000000..5325f12
--- /dev/null
+++ b/lib/rites/rite_of/rubrics_of.mli
@@ -0,0 +1,78 @@
+(** IGMR n. 53 (Gloria), n. 67-68 (Symbolum/Credo), n. 364-365 and the
+ Missale Romanum editio typica tertia (2002)'s own printed "INDEX
+ PRAEFATIONUM" (preface choice) -- see the .ml's own header for every
+ paragraph quoted in full and every branch's own citation. The OF
+ counterpart of {!Rite_ef.Rubrics_ef}; same shape, same three functions,
+ same [Rite.t] signatures -- but see [preface]'s own citation for why
+ this file's verdict on {!Colitur_kernel.Preface.t} differs sharply from
+ what {!Rite_ef.Rubrics_ef.preface} was able to build. *)
+
+open Colitur_kernel
+
+(** The one Requiem-shaped entity the shipped OF calendar can ever construct
+ as [observed] (All Souls, 2 November) -- see the .ml's own header for
+ why colour cannot serve as the Requiem proxy here the way
+ {!Colour.Black} does for {!Rite_ef.Rubrics_ef} (OF permits, but never
+ requires, black vestments for the dead -- IGMR n. 346 e -- and the
+ shipped data uses violet), so a closed slug list, checked against
+ [data/of/calendar-2002.sexp] directly, is the only signal available.
+ Exposed so the test suite can assert this population stays exactly
+ what it was measured to be, the same discipline
+ {!Rite_ef.Rubrics_ef.creed_apostle_slugs} already establishes. *)
+val requiem_slugs : string list
+
+(** Whether the Gloria in excelsis is said at this day's Mass (IGMR n. 53).
+ [temporal] is read for exactly one fact -- n. 53's own "diebus dominicis
+ EXTRA TEMPUS ADVENTUS ET QUADRAGESIMAE": {!Rite_of.Temporal_of.temporal}
+ tags every Sunday of Advent/Lent/Easter [Sollemnitas] (the same rank a
+ genuine solemnity carries), so testing [observed.rank] alone would
+ wrongly grant an Advent or Lent Sunday the Gloria n. 53 explicitly
+ denies it -- the identical "read temporal, not observed, for a day-of-
+ week/season fact" discipline {!Rite_ef.Rubrics_ef.creed}'s own RG 475(a)
+ citation already establishes. [date] is read once, against the rite's
+ own (Gregorian) Easter, for the two Missale-Romanum-own-rubric
+ exceptions (Holy Thursday evening Mass, the Easter Vigil Mass) -- see
+ the .ml's own citation for the primary text, found in the Missal's
+ Proper of Time rather than the IGMR itself. [observed]'s [slug] is
+ checked once against {!requiem_slugs} -- see that value's own citation
+ for why this is an INFERENCE, not an IGMR paragraph, and exactly how
+ narrowly it is applied. *)
+val gloria :
+ temporal:(Vocab_of.season, Vocab_of.rank) Temporal.t ->
+ observed:Vocab_of.rank Celebration.t ->
+ date:Date.t ->
+ bool
+
+(** Whether the Symbolum (Credo) is said, post-homily, at this day's Mass
+ (IGMR n. 67-68). Reads [temporal] for the identical reason [gloria]
+ does -- n. 68's own "diebus dominicis" is a plain, weekday-only fact,
+ unlike n. 53's Advent/Lent carve-out, so every Sunday qualifies
+ (Advent/Lent Sundays INCLUDED, unlike [gloria]) -- and [observed]'s
+ [rank] for n. 68's own "in sollemnitatibus", and [slug] against
+ {!requiem_slugs} for the identical reason [gloria] checks it. Narrower
+ than [gloria] on purpose: n. 68 has no "et festis" clause at all, so a
+ [Festum]-rank day (an ordinary feast) does NOT get the Credo -- the one
+ place this function's shape most visibly parts from EF's own RG
+ 475(b)/(c). [date] is accepted, for signature symmetry with [gloria]/
+ [preface] and {!Rite.t}'s own shared shape, but genuinely unread: n.
+ 67-68 carries no Easter-relative window of its own, unlike RG 475(d)'s
+ three EF octaves -- checked, not assumed (see the .ml's own citation). *)
+val credo :
+ temporal:(Vocab_of.season, Vocab_of.rank) Temporal.t ->
+ observed:Vocab_of.rank Celebration.t ->
+ date:Date.t ->
+ bool
+
+(** Which preface is said at this day's Mass (IGMR n. 364-365; the Missale's
+ own "INDEX PRAEFATIONUM"). ALWAYS [None] -- not a stub, a measured
+ verdict: see the .ml's own header for the full census of the 2002
+ Missal's preface collection (over 50 individual texts across ~30 named
+ occasions) against {!Preface.t}'s 15 EF-cited constructors, and why
+ none of them is safe to reuse. Same three parameters as [gloria]/
+ [credo], all three genuinely unread -- kept only for {!Rite.t}'s shared
+ shape. *)
+val preface :
+ temporal:(Vocab_of.season, Vocab_of.rank) Temporal.t ->
+ observed:Vocab_of.rank Celebration.t ->
+ date:Date.t ->
+ Preface.t option
diff --git a/test/test_colitur.ml b/test/test_colitur.ml
index 98e08fb..3e16566 100644
--- a/test/test_colitur.ml
+++ b/test/test_colitur.ml
@@ -18,6 +18,7 @@ let () =
Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_calendar_of_data.suite;
Test_rite_ef.suite;
Test_rubrics_ef.suite;
+ Test_rubrics_of.suite;
Test_differential.suite; Test_oracle.suite; Test_oracle.suite_2038; Test_oracle.suite_2035;
Test_lms_ordo.suite_2023_2024; Test_lms_ordo.suite_2024_2025; Test_lms_ordo.suite_2025_2026;
Test_lms_ordo.suite_allow_list; Test_fiuv_ordo.suite;
diff --git a/test/test_rubrics_of.ml b/test/test_rubrics_of.ml
new file mode 100644
index 0000000..db8744a
--- /dev/null
+++ b/test/test_rubrics_of.ml
@@ -0,0 +1,307 @@
+(* IGMR n. 53 (Gloria), n. 67-68 (Symbolum/Credo), n. 364-365 and the
+ Missale's own INDEX PRAEFATIONUM -- see lib/rites/rite_of/rubrics_of.ml
+ for every rubric quoted in full and every branch's own citation. The OF
+ counterpart of test/test_rubrics_ef.ml; one Alcotest case per rule.
+
+ NO FULL OF [Colitur_kernel.Rite.t]/[Calendar] EXISTS YET (this task
+ supplies three of its four remaining fields; wiring them into a bundle
+ is a later task) -- so, unlike test_rubrics_ef.ml, there is no
+ `Cal.day`-style pipeline here to resolve a real SANCTORAL winner for a
+ date. Every TEMPORAL-origin row below therefore builds [temporal] (and,
+ where the temporal cycle's own office IS what is observed -- an ordinary
+ Sunday, a feria, a named solemnity like the Nativity) also [observed],
+ via [Temporal_of.temporal] on a real calendar date -- genuine Phase 1
+ output, per the task brief. Every SANCTORAL-origin row (a saint's
+ solemnity/feast/memorial outranking the day's own temporal office)
+ builds [temporal] the same way, via a real date, but necessarily builds
+ [observed] directly -- there is no pipeline yet to derive it. It is
+ NOT fabricated: every field is copied verbatim from the matching real
+ entry in data/of/calendar-2002.sexp (cited at each site), the same
+ "grounded in shipped data, not invented" discipline
+ test/test_precedence_of.ml's own [mk_cel] already establishes for this
+ rite. *)
+
+module RO = Rite_of.Rubrics_of
+module T = Rite_of.Temporal_of
+module D = Colitur_kernel.Date
+module Cel = Colitur_kernel.Celebration
+module Colour = Colitur_kernel.Colour
+module Subject = Colitur_kernel.Subject
+module Slug = Colitur_kernel.Slug
+module Vocab_of = Rite_of.Vocab_of
+
+let mk y m d = match D.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e
+
+(* A real, shipped sanctoral entry (data/of/calendar-2002.sexp), reused
+ directly as [observed] where no full precedence pipeline exists yet to
+ derive it. Status defaults to [Feast] (every entry cited below is). *)
+let mk_saint ~slug ~rank ~colour ~subject () =
+ Cel.make ~slug:(Slug.of_string_exn slug) ~rank ~colour ~subject ~layer:"of-universal" ()
+
+(* ---- Gloria (IGMR n. 53) ---- *)
+
+let gloria_on_temporal y m d =
+ let temporal = T.temporal (mk y m d) in
+ RO.gloria ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d)
+
+let check_gloria name expected actual = Alcotest.(check bool) name expected actual
+
+let test_gloria_advent_sunday () =
+ (* 2026-12-06: the Second Sunday of Advent -- confirmed via `date -d`,
+ [Temporal_of.temporal] tags it [Vocab_of.Sollemnitas] (the privileged-
+ season convention, temporal_of.ml's own [build]), which is exactly the
+ trap n. 53's own "extra tempus Adventus" clause exists to catch: a
+ naive [observed.rank = Sollemnitas] test would wrongly say [true]. *)
+ check_gloria "no Gloria on a Sunday of Advent" false (gloria_on_temporal 2026 12 6)
+
+let test_gloria_lent_sunday () =
+ (* 2026-03-01: the First Sunday of Lent 2026 (Ash Wednesday 2026-02-18,
+ Easter 2026-04-05). Same trap as Advent, the other of n. 53's two
+ named exceptions. *)
+ check_gloria "no Gloria on a Sunday of Lent" false (gloria_on_temporal 2026 3 1)
+
+let test_gloria_palm_sunday () =
+ (* 2026-03-29: Palm Sunday, [Vocab_of.Lent], [Sollemnitas] (named's own
+ entry) -- confirms the Advent/Lent exclusion above also correctly
+ reaches a NAMED solemnity, not only a plain numbered Sunday of Lent,
+ matching real, uncontested practice (Palm Sunday's Mass has no
+ Gloria). *)
+ check_gloria "no Gloria on Palm Sunday" false (gloria_on_temporal 2026 3 29)
+
+let test_gloria_ordinary_sunday () =
+ (* 2026-01-25: an Ordinary Time Sunday (Baptism of the Lord 2026-01-11,
+ so Ordinary Time begins 2026-01-12) -- [Festum]-ranked
+ (temporal_of.ml's own [build]), reaching [true] via n. 53's "diebus
+ dominicis" clause, not its "et festis" one (see the .ml's own
+ citation for why [temporal.weekday], not [observed.rank], is what
+ this must be read off). *)
+ check_gloria "Gloria on a Sunday per annum" true (gloria_on_temporal 2026 1 25)
+
+let test_gloria_christmas_sunday () =
+ (* 2026-12-27: a Sunday within the Octave of the Nativity (temporal_of.ml's
+ own [holy_family]/generic-Sunday dispatch), [Vocab_of.Christmas]
+ season, outside Advent/Lent -- a second, independent witness to the
+ same "diebus dominicis extra Adventum/Quadragesimam" clause as the
+ Ordinary Time row above, from the other season n. 53 does not
+ exclude. *)
+ check_gloria "Gloria on a Sunday of Christmas time" true (gloria_on_temporal 2026 12 27)
+
+let test_gloria_nativity () =
+ (* 2026-12-25: the Nativity itself -- [Vocab_of.Christmas], [Sollemnitas],
+ a Friday (never a Sunday that year), reaching [true] via n. 53's "in
+ sollemnitatibus" clause on its own, independent of the Sunday
+ clauses. Pure [Temporal_of.temporal] output; no sanctoral data
+ needed, since the Nativity IS the temporal cycle's own named office
+ (temporal_of.ml's own [named]). *)
+ check_gloria "Gloria on the Nativity" true (gloria_on_temporal 2026 12 25)
+
+let test_gloria_holy_thursday () =
+ (* 2026-04-02: the Mass of the Lord's Supper (Easter-3, Easter 2026-04-05)
+ -- Missale Romanum, "FERIA V IN CENA DOMINI" n. 7's own explicit
+ "Dicitur Gloria in excelsis" (see the .ml's own citation), overriding
+ what would otherwise be a Lenten-feria [false]. *)
+ check_gloria "Gloria at the Mass of the Lord's Supper" true (gloria_on_temporal 2026 4 2)
+
+let test_gloria_easter_vigil () =
+ (* 2026-04-04: the Easter Vigil (Easter-1) -- the same Missale rubric's
+ own "usque ad Gloria in excelsis Vigiliae paschalis", confirming this
+ Mass also has one. *)
+ check_gloria "Gloria at the Easter Vigil Mass" true (gloria_on_temporal 2026 4 4)
+
+let test_gloria_solemnity_in_lent () =
+ (* Saint Joseph, Husband of the Blessed Virgin Mary, 19 March --
+ data/of/calendar-2002.sexp: (rank Sollemnitas) (colour White)
+ (subject Saint). 2026-03-19 is a Thursday (Easter 2026-04-05, so Holy
+ Week is 2026-03-29..04-04 -- this date sits safely inside ordinary
+ Lent, not impeded) -- confirmed, not assumed, via `date -d`. 19 March
+ is ALWAYS inside Lent by construction (Gregorian Easter's earliest
+ possible date is 22 March, so Ash Wednesday can never fall later than
+ 10 March; see the .ml's own citation). Exercises n. 53's "in
+ sollemnitatibus" clause on a genuine sanctoral winner, the one row in
+ this suite [observed] cannot come from [Temporal_of.temporal] alone
+ -- see this file's own header for why. *)
+ let temporal = T.temporal (mk 2026 3 19) in
+ let observed =
+ mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_gloria "Gloria: a solemnity falling in Lent" true
+ (RO.gloria ~temporal ~observed ~date:(mk 2026 3 19))
+
+let test_gloria_festum () =
+ (* Chair of Saint Peter, apostle, 22 February -- data/of/calendar-2002.sexp:
+ (rank Festum) (colour White) (subject Saint). 2027-02-22 is a Monday
+ (checked via `date -d`, so this date is not itself impeded by an
+ Ordinary Time Sunday outranking a Festum, Tabula entries 6 vs 7).
+ Exercises n. 53's "et festis" clause specifically -- the one member of
+ n. 53's list that n. 68 (Credo, below) does NOT share. *)
+ let temporal = T.temporal (mk 2027 2 22) in
+ let observed =
+ mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White
+ ~subject:Subject.Saint ()
+ in
+ check_gloria "Gloria on an ordinary Festum" true (RO.gloria ~temporal ~observed ~date:(mk 2027 2 22))
+
+let test_gloria_obligatory_memorial () =
+ (* Saint Aloysius Gonzaga, religious, 21 June -- data/of/calendar-2002.sexp:
+ (rank Memoria_obligatoria) (colour White) (subject Saint). 2027-06-21
+ is a Monday. Neither n. 53's "sollemnitatibus et festis" nor its
+ "dominicis" clause names a memorial. *)
+ let temporal = T.temporal (mk 2027 6 21) in
+ let observed =
+ mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_gloria "no Gloria on a weekday obligatory memorial" false
+ (RO.gloria ~temporal ~observed ~date:(mk 2027 6 21))
+
+let test_gloria_ordinary_feria () =
+ (* 2026-01-27: an Ordinary Time Tuesday feria, no memorial -- pure
+ [Temporal_of.temporal] output, [Vocab_of.Feria], [Subject.Temporal].
+ Named by neither n. 53 clause. *)
+ check_gloria "no Gloria on a plain feria" false (gloria_on_temporal 2026 1 27)
+
+let test_gloria_all_souls () =
+ (* All Souls, 2 November -- data/of/calendar-2002.sexp: (rank Sollemnitas)
+ (colour Violet) (subject Saint) -- the ONE Requiem-shaped entity this
+ engine's shipped OF data can construct as [observed]
+ ({!Rite_of.Rubrics_of.requiem_slugs}). Its own [rank] is tagged
+ [Sollemnitas] by the extractor's own override (precedence_of.ml's own
+ citation), so WITHOUT the requiem guard n. 53's "in sollemnitatibus"
+ clause would wrongly grant it [true] -- exactly the case this guard
+ exists for. 2026-11-02 is a Monday. *)
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ check_gloria "no Gloria on All Souls" false (RO.gloria ~temporal ~observed ~date:(mk 2026 11 2))
+
+(* ---- Credo / Symbolum (IGMR n. 67-68) ---- *)
+
+let credo_on_temporal y m d =
+ let temporal = T.temporal (mk y m d) in
+ RO.credo ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d)
+
+let check_credo name expected actual = Alcotest.(check bool) name expected actual
+
+let test_credo_advent_sunday () =
+ (* Same date as [test_gloria_advent_sunday], deliberately: n. 68 carries
+ no Advent/Lent exception at all, unlike n. 53 -- this is [true] here
+ where the identical day was [false] for Gloria, proving the two rules
+ genuinely diverge rather than one silently mirroring the other. *)
+ check_credo "Credo on a Sunday of Advent (unlike Gloria)" true (credo_on_temporal 2026 12 6)
+
+let test_credo_ordinary_sunday () =
+ check_credo "Credo on a Sunday per annum" true (credo_on_temporal 2026 1 25)
+
+let test_credo_solemnity () =
+ (* Same St Joseph data as [test_gloria_solemnity_in_lent]. *)
+ let temporal = T.temporal (mk 2026 3 19) in
+ let observed =
+ mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_credo "Credo: a solemnity" true (RO.credo ~temporal ~observed ~date:(mk 2026 3 19))
+
+let test_credo_festum () =
+ (* Same Chair-of-Peter data as [test_gloria_festum] -- [false] here,
+ unlike Gloria's [true] on the identical day: n. 68 has no "et festis"
+ clause at all (see the .ml's own citation), the single sharpest
+ divergence between this function and [gloria]. *)
+ let temporal = T.temporal (mk 2027 2 22) in
+ let observed =
+ mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White
+ ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on an ordinary Festum (unlike Gloria)" false
+ (RO.credo ~temporal ~observed ~date:(mk 2027 2 22))
+
+let test_credo_weekday_obligatory_memorial () =
+ let temporal = T.temporal (mk 2027 6 21) in
+ let observed =
+ mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on a weekday obligatory memorial" false
+ (RO.credo ~temporal ~observed ~date:(mk 2027 6 21))
+
+let test_credo_ordinary_feria () =
+ check_credo "no Credo on a plain feria" false (credo_on_temporal 2026 1 27)
+
+let test_credo_all_souls () =
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on All Souls" false (RO.credo ~temporal ~observed ~date:(mk 2026 11 2))
+
+(* ---- Preface -- always [None]; see rubrics_of.ml's own header for the
+ full INDEX-PRAEFATIONUM census this rests on. One row per major class,
+ proving the function was deliberately built to reject every shape
+ rather than merely left unimplemented. ---- *)
+
+let check_preface_none name y m d =
+ let temporal = T.temporal (mk y m d) in
+ Alcotest.(check bool) name true
+ (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d) = None)
+
+let test_preface_none_nativity () = check_preface_none "preface: Nativity is None" 2026 12 25
+let test_preface_none_ordinary_sunday () = check_preface_none "preface: Sunday per annum is None" 2026 1 25
+let test_preface_none_feria () = check_preface_none "preface: a plain feria is None" 2026 1 27
+
+let test_preface_none_solemnity_with_own_single_text () =
+ (* Trinity Sunday -- exactly the "single OF text, same feast as an EF
+ constructor" near-miss the .ml's own header explains rejecting: still
+ [None], on purpose. *)
+ let easter = Colitur_kernel.Computus.gregorian_easter 2026 in
+ let trinity = D.add_days easter 56 in
+ let temporal = T.temporal trinity in
+ Alcotest.(check bool) "preface: Trinity Sunday is None" true
+ (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:trinity = None)
+
+let test_preface_none_all_souls () =
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ Alcotest.(check bool) "preface: All Souls is None" true
+ (RO.preface ~temporal ~observed ~date:(mk 2026 11 2) = None)
+
+(* Population guard, mirroring test_rubrics_ef.ml's own
+ [test_colour_black_population_is_exactly_two]: fails loudly the day a
+ second Requiem-shaped slug is added anywhere in the shipped OF data
+ without this list being revisited. *)
+let test_requiem_slugs_population () =
+ Alcotest.(check (list string)) "requiem_slugs is exactly {all-souls}" [ "all-souls" ] RO.requiem_slugs
+
+let suite =
+ ( "rubrics-of",
+ [ Alcotest.test_case "Gloria: no Gloria on a Sunday of Advent" `Quick test_gloria_advent_sunday;
+ Alcotest.test_case "Gloria: no Gloria on a Sunday of Lent" `Quick test_gloria_lent_sunday;
+ Alcotest.test_case "Gloria: no Gloria on Palm Sunday" `Quick test_gloria_palm_sunday;
+ Alcotest.test_case "Gloria: Gloria on a Sunday per annum" `Quick test_gloria_ordinary_sunday;
+ Alcotest.test_case "Gloria: Gloria on a Sunday of Christmas time" `Quick test_gloria_christmas_sunday;
+ Alcotest.test_case "Gloria: Gloria on the Nativity" `Quick test_gloria_nativity;
+ Alcotest.test_case "Gloria: Gloria at the Mass of the Lord's Supper" `Quick test_gloria_holy_thursday;
+ Alcotest.test_case "Gloria: Gloria at the Easter Vigil Mass" `Quick test_gloria_easter_vigil;
+ Alcotest.test_case "Gloria: a solemnity falling in Lent" `Quick test_gloria_solemnity_in_lent;
+ Alcotest.test_case "Gloria: an ordinary Festum" `Quick test_gloria_festum;
+ Alcotest.test_case "Gloria: no Gloria on a weekday obligatory memorial" `Quick
+ test_gloria_obligatory_memorial;
+ Alcotest.test_case "Gloria: no Gloria on a plain feria" `Quick test_gloria_ordinary_feria;
+ Alcotest.test_case "Gloria: no Gloria on All Souls" `Quick test_gloria_all_souls;
+ Alcotest.test_case "Credo: Credo on a Sunday of Advent" `Quick test_credo_advent_sunday;
+ Alcotest.test_case "Credo: Credo on a Sunday per annum" `Quick test_credo_ordinary_sunday;
+ Alcotest.test_case "Credo: a solemnity" `Quick test_credo_solemnity;
+ Alcotest.test_case "Credo: no Credo on an ordinary Festum" `Quick test_credo_festum;
+ Alcotest.test_case "Credo: no Credo on a weekday obligatory memorial" `Quick
+ test_credo_weekday_obligatory_memorial;
+ Alcotest.test_case "Credo: no Credo on a plain feria" `Quick test_credo_ordinary_feria;
+ Alcotest.test_case "Credo: no Credo on All Souls" `Quick test_credo_all_souls;
+ Alcotest.test_case "Preface: Nativity is None" `Quick test_preface_none_nativity;
+ Alcotest.test_case "Preface: Sunday per annum is None" `Quick test_preface_none_ordinary_sunday;
+ Alcotest.test_case "Preface: a plain feria is None" `Quick test_preface_none_feria;
+ Alcotest.test_case "Preface: Trinity Sunday is None" `Quick test_preface_none_solemnity_with_own_single_text;
+ Alcotest.test_case "Preface: All Souls is None" `Quick test_preface_none_all_souls;
+ Alcotest.test_case "requiem_slugs population is exactly {all-souls}" `Quick
+ test_requiem_slugs_population ] )