summaryrefslogtreecommitdiff
path: root/lib/rites
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
commit8a5da8756cf6b57aa36c01b71ce893f7967d3a29 (patch)
tree10873a692400fa25ce54c67ac9fc34d46af86272 /lib/rites
parent4f87cbde4ee79ac96aa7ebfed105b3a5ec02be43 (diff)
parent73b15551804bb63ee0081005e2869d36afb54be2 (diff)
downloadcolitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.tar.gz
colitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.zip
merge: celebrant Mass rubrics, and four rubrical corrections
colitur now prints what a real ordo prints for the Mass: which formulary is said and how it was reached, the Gloria, the Creed, the preface, and the commemorations with their Low-Mass/sung distinction. colitur rubrics joins day and readings. Four defects were found and fixed on the way, each by an external witness rather than by inspection: the Creed said at Requiem Masses (RG 476(f)), the missing bissextile shift of St Matthias and St Gabriel (2 041 leap years), Rogation Monday and Tuesday coloured violet in Paschaltide (RG 119(b)), and the ferias after the Ascension resuming the wrong Sunday's Mass rather than the Ascension's. Validation gained a sixth layer and then some: the preface is checked against three independent publishers over seven witness-years (FIUV, three LMS editions, three extraordinaryform.org editions), none of which shares the Divinum Officium -> missalemeum -> lectio lineage the older layers all descend from.
Diffstat (limited to 'lib/rites')
-rw-r--r--lib/rites/rite_ef/lectionary_ef.ml99
-rw-r--r--lib/rites/rite_ef/lectionary_ef.mli51
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli16
-rw-r--r--lib/rites/rite_ef/rite_ef.ml10
-rw-r--r--lib/rites/rite_ef/rite_ef.mli11
-rw-r--r--lib/rites/rite_ef/rubrics_ef.ml1244
-rw-r--r--lib/rites/rite_ef/rubrics_ef.mli107
-rw-r--r--lib/rites/rite_ef/temporal_ef.ml90
-rw-r--r--lib/rites/rite_ef/temporal_ef.mli11
9 files changed, 1615 insertions, 24 deletions
diff --git a/lib/rites/rite_ef/lectionary_ef.ml b/lib/rites/rite_ef/lectionary_ef.ml
index d7b2497..b996412 100644
--- a/lib/rites/rite_ef/lectionary_ef.ml
+++ b/lib/rites/rite_ef/lectionary_ef.ml
@@ -51,9 +51,9 @@ module Commons = struct
| None -> (
(* A formulary with no citations is indistinguishable at the call
site from "this saint has no Common" -- [commons_for] would
- return [Some []] and [readings] would emit [] either way. That
- is exactly the silent hole this project does not allow, so it
- is rejected here where it is still nameable. *)
+ return [Some (common, [])] and [readings] would emit [] either
+ way. That is exactly the silent hole this project does not
+ allow, so it is rejected here where it is still nameable. *)
match List.find_opt (fun (_, cs) -> cs = []) commons with
| Some (s, _) ->
Error (Printf.sprintf "commons: common %S has no citations" (Slug.to_string s))
@@ -72,10 +72,19 @@ module Commons = struct
(Slug.to_string saint) (Slug.to_string common))
| None -> Ok { commons; assigned })))
+ (* Returns the Common's own id ALONGSIDE its citations, not the citations
+ alone: [readings]' step 4 needs the id to build the day's
+ {!Colitur_kernel.Mass_formulary.t} ("the Common's own id as its slug"),
+ and the id is only ever in scope here, at the point [common] is looked
+ up -- re-deriving it afterwards would mean a second [assoc_opt] search
+ over [t.assigned] for a value this function already held. *)
let find t saint =
match List.assoc_opt saint t.assigned with
| None -> None
- | Some common -> List.assoc_opt common t.commons
+ | Some common -> (
+ match List.assoc_opt common t.commons with
+ | None -> None
+ | Some cs -> Some (common, cs))
(* Byte-for-byte the failure discipline of [Lectionary.load] (see its own
comments for why each catch-all is placed where it is): every parse and
@@ -250,7 +259,8 @@ let is_bvm_saturday_office (observed : Vocab_ef.rank Celebration.t)
let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
match observed.Celebration.citations with
- | _ :: _ as cs -> cs
+ | _ :: _ as cs ->
+ (Some { Mass_formulary.said = Some observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
| [] -> (
(* Step 4: a saint who is the day's observed office and has no proper
says his assigned Common. The assignment is explicit, never
@@ -346,7 +356,8 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
match
if sanctoral_office then commons_for ~commons observed.Celebration.slug else None
with
- | Some cs -> cs
+ | Some (common_id, cs) ->
+ (Some { Mass_formulary.said = Some common_id; via = Mass_formulary.Common }, cs)
| None -> (
(* The votive Mass of Our Lady on Saturday (RG 309(a)) runs HERE:
after the proper (step 1) and the Common (step 4), which answer
@@ -354,13 +365,73 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
lookup -- which is exactly what used to answer, with the feria's
own Mass, on a day whose office is Our Lady's. Placing it later
would be dead code; placing it earlier would let it outrank a
- real saint's proper. *)
+ real saint's proper.
+
+ FORMULARY PROVENANCE: [Votive], not [Own_slug] (CORRECTED, fix
+ round 1, coordinator review -- the first pass tagged this
+ [Own_slug] for lack of a better constructor and flagged it as a
+ judgement call; [Mass_formulary.source] has grown a [Votive]
+ case since, precisely for this branch). RG 431(e)
+ (docs/research/LT.txt, verbatim): "e) in Missis votivis IV
+ classis de Angelis, quocumque die, et de B. Maria Virg. quae in
+ sabbato celebrantur" -- votive Masses of the IV class, of the
+ Angels on any day, and of the BVM WHICH ARE CELEBRATED ON
+ SATURDAY: the Missal's own words classify this exact Mass as a
+ "Missa votiva", not as the day's own office's Mass, corroborated
+ by RG 309(a) (this branch's own header comment above, "iuxta
+ temporum diversitatem") naming the FIVE seasonal Masses RG 431(e)
+ is speaking of. Together: a votive Mass said IN PLACE of the
+ day's own office's Mass, the office (RG 78, Officium sanctae
+ Mariae in sabbato) being kept unchanged.
+
+ CORRECTED, fix round 2 (coordinator review): fix round 1's own
+ comment here claimed the Latin Mass Society Ordo witnesses
+ [Votive] directly, printing "V Mass of BVM" with "V" read as an
+ abbreviation for "votive". That was the coordinator's
+ misreading, not this codebase's, and it was wrong: the Ordo's
+ "I"-"V" are ROMAN NUMERALS naming WHICH of the Missal's five
+ seasonal "Missae de sancta Maria in sabbato" (this branch's own
+ header comment; RG 309(a)'s "iuxta temporum diversitatem") is
+ said on a given Saturday -- not an abbreviation of anything, and
+ not a marker of the Mass's class or kind. All five numerals
+ occur through the Ordo (I x1, II x4, III x3, IV x3, V x8,
+ counted across the whole document); "V Mass of BVM" merely also
+ happens to be substring-matched by a naive search for "V Mass of
+ BVM" inside "IV Mass of BVM", which is what produced the
+ original false reading. [Votive] itself is UNAFFECTED by this
+ correction and stays right, on RG 431(e) alone -- only the
+ Ordo-witness claim is retracted, not softened, removed. A
+ genuinely useful consequence survives the mistake: because the
+ Ordo's own numeral names WHICH seasonal Mass is said, a future
+ comparison against it (Task 6 of this plan) can validate
+ [bvm_saturday_citations]'s own season-keyed SELECTION, not
+ merely that some BVM Mass was chosen.
+
+ [said] used to be set HERE to the day's own temporal slug
+ (reasoning: [is_bvm_saturday_office] only ever fires when
+ [sanctoral_office] above is false, i.e. the observed
+ celebration already IS the day's own temporal office, which
+ deliberately reuses the ordinary ferial slug, [Temporal_ef]'s
+ own [bvm_saturday_names]). CORRECTED (whole-branch review fix
+ round): that value is the OFFICE's slug, not the slug whose
+ Mass is actually said -- the five seasonal Masses RG 309(a)
+ names have no slug of their own anywhere in the shipped data,
+ so [said] is [None] here, honestly, rather than silently
+ naming the wrong thing. See {!Colitur_kernel.Mass_formulary.t}'s
+ own [said] citation for the full account; the office itself is
+ not lost, it is still [observed.slug] on the very same
+ {!Colitur_kernel.Liturgical_day.t} this formulary lives on. *)
if is_bvm_saturday_office observed temporal then
- bvm_saturday_citations temporal.Temporal.season ~month:(Date.month date)
- ~day:(Date.day date)
+ ( Some { Mass_formulary.said = None; via = Mass_formulary.Votive },
+ bvm_saturday_citations temporal.Temporal.season ~month:(Date.month date)
+ ~day:(Date.day date) )
else
match Lectionary.find lectionary temporal.Temporal.office.Celebration.slug with
- | Some cs -> cs
+ | Some cs ->
+ ( Some
+ { Mass_formulary.said = Some temporal.Temporal.office.Celebration.slug;
+ via = Mass_formulary.Own_slug },
+ cs )
| None -> (
(* Step 3: a feria with no proper of its own says the preceding
Sunday's Mass. WARRANT is the same as step 2's -- lectio's own
@@ -404,12 +475,14 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
season name), so deriving one from the other textually would be
a latent bug the moment a season's naming convention differs. *)
let offset = days_since_sunday temporal.Temporal.weekday in
- if offset = 0 then []
+ if offset = 0 then (None, [])
else
let sunday = Date.add_days date (-offset) in
let sunday_temporal = temporal_at sunday in
match
Lectionary.find lectionary sunday_temporal.Temporal.office.Celebration.slug
with
- | Some cs -> cs
- | None -> [])))
+ | Some cs ->
+ let said = sunday_temporal.Temporal.office.Celebration.slug in
+ (Some { Mass_formulary.said = Some said; via = Mass_formulary.Preceding_sunday }, cs)
+ | None -> (None, []))))
diff --git a/lib/rites/rite_ef/lectionary_ef.mli b/lib/rites/rite_ef/lectionary_ef.mli
index caefc74..715e9c2 100644
--- a/lib/rites/rite_ef/lectionary_ef.mli
+++ b/lib/rites/rite_ef/lectionary_ef.mli
@@ -72,21 +72,28 @@ module Commons : sig
val assignments : t -> (Slug.t * Slug.t) list
end
-(** The Common assigned to a saint who has no proper, if any. Exposed for the
- golden pins, which must show WHICH Common fired, not merely that two
- citations appeared.
+(** The Common assigned to a saint who has no proper, if any -- its own id
+ ALONGSIDE its citations, not the citations alone: {!readings}' step 4
+ needs the id to name which Common fired in the {!Colitur_kernel.Mass_formulary.t}
+ it builds. Exposed for the golden pins too, which must show WHICH Common
+ fired, not merely that two citations appeared.
Takes the table explicitly for the same reason {!readings} takes
[~lectionary]: the data is the caller's, not this module's. *)
-val commons_for : commons:Commons.t -> Slug.t -> Citation.t list option
+val commons_for : commons:Commons.t -> Slug.t -> (Slug.t * Citation.t list) option
-(** The day's Epistle and Gospel citations, or [].
+(** The Mass actually said -- which formulary, and how that was decided --
+ paired with the day's Epistle and Gospel citations. [(None, [])] when
+ none of the four steps below answers.
Four steps, in EXECUTION order 1, 4, 2, 3 (the numbers are the plan's and
are kept as written, so that every "step 3" already recorded in a test
- name, comment or report still means the same branch):
+ name, comment or report still means the same branch). Each step's own
+ {!Colitur_kernel.Mass_formulary.source} is built at the point the step
+ decides, not re-derived afterwards from the citations it returns:
- {b Step 1} -- the observed celebration's own proper.
+ {!Colitur_kernel.Mass_formulary.Proper}, [said] the observed slug.
- {b Step 4} -- a saint who is the day's observed office and has no
proper says his assigned Common. Runs before the temporal fallbacks,
not after them: this is the only step in the chain with a direct
@@ -98,14 +105,42 @@ val commons_for : commons:Commons.t -> Slug.t -> Citation.t list option
a feria, a Sunday, the Triduum and the RG 78 Saturday Office of the
BVM (whose observed celebration is its own temporal office) are
structurally excluded, not merely absent from the data.
+ {!Colitur_kernel.Mass_formulary.Common}, [said] the Common's own id.
- {b Step 2} -- the day's own temporal slug in the lectionary.
+ {!Colitur_kernel.Mass_formulary.Own_slug}, [said] that slug. The RG
+ 309(a) Saturday votive Mass of Our Lady also answers here (structurally,
+ not as a fifth numbered step), but is tagged
+ {!Colitur_kernel.Mass_formulary.Votive} instead, not [Own_slug]: RG
+ 431(e) ("in Missis votivis IV classis ... de B. Maria Virg. quae in
+ sabbato celebrantur", docs/research/LT.txt) classifies it, in the
+ Missal's own words, as a "Missa votiva" said IN PLACE of the day's own
+ office's Mass, the office (RG 78) itself being kept, corroborated by RG
+ 309(a)'s own "iuxta temporum diversitatem" naming the five seasonal
+ Masses RG 431(e) speaks of. (CORRECTED, fix round 2: this previously
+ also cited the Latin Mass Society Ordo as witnessing the tag directly,
+ reading its "V Mass of BVM" as an abbreviation for "votive" -- the
+ coordinator's own misreading. The Ordo's "I"-"V" are ROMAN NUMERALS
+ naming which of the five seasonal Masses is said, not a marker of the
+ Mass's kind; see the implementation comment on this branch for the
+ full account and the validation use this correction still leaves
+ available for Task 6.) [said] is [None] here (whole-branch review fix
+ round, celebrant-rubrics-phase1): the five seasonal Masses RG 309(a)
+ names carry no slug of their own anywhere in the shipped data, so
+ there is no slug this field could honestly report -- see
+ {!Colitur_kernel.Mass_formulary.t}'s own [said] citation for the full
+ account. The day's own OFFICE (the guard that reaches this branch
+ only ever fires when the observed celebration already IS the day's
+ own temporal office) is unaffected and is still [observed.slug] on
+ the same {!Colitur_kernel.Liturgical_day.t}.
- {b Step 3} -- for a weekday whose own slug has no entry, the preceding
Sunday's temporal slug (never its observed one; a Sunday is guarded
out because it has no PRECEDING Sunday to resume, not because
consulting itself would loop -- [readings] is not recursive, see its
own implementation comment).
+ {!Colitur_kernel.Mass_formulary.Preceding_sunday}, [said] that
+ Sunday's temporal slug.
- A day matching none of the four gets []. *)
+ A day matching none of the four gets [(None, [])]. *)
val readings :
lectionary:Lectionary.t ->
commons:Commons.t ->
@@ -113,4 +148,4 @@ val readings :
temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
date:Date.t ->
temporal_at:(Date.t -> (Vocab_ef.season, Vocab_ef.rank) Temporal.t) ->
- Citation.t list
+ Mass_formulary.t option * Citation.t list
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli
index 4c2523a..0100d40 100644
--- a/lib/rites/rite_ef/precedence_ef.mli
+++ b/lib/rites/rite_ef/precedence_ef.mli
@@ -132,6 +132,22 @@ val sunday_marker : string
recognising a live candidate again. *)
val major_litanies_slug : string
+(** RG 112(d)'s own closed, hand-verified list of sanctoral entries that are
+ themselves a feast/commemoration OF the Blessed Virgin Mary in her own
+ right -- see the .ml's own citation for the full derivation and the
+ entries considered and excluded. A celebration is "of the BVM" for
+ {!disposition}'s RG 112(d) branch when its [subject] is [Subject.Bvm] OR
+ its slug is on this list ({!Celebration.t}'s [subject] field is NOT
+ reliably [Bvm] for most Marian sanctoral entries -- bootstrapped from
+ lectio, most carry [Saint] instead, this list is the reliable signal).
+
+ Exposed (task 5, the Creed, RG 475(c) "festis II classis... B. Mariae
+ Virg.") so a second rite-local consumer can test the identical
+ Marian-identity question without re-deriving its own list and risking
+ drift from this one -- the same reasoning {!vigil_feast_table} and
+ {!is_vigil} are already exposed for. *)
+val marian_slugs : string list
+
(** [disposition ~winner ~loser]: RG 92-95, 33, 21-27, 94 (docs/research/
rules-register.md §4, "Occurrence", "Vigils" and "Caput IV, 'De
feriis'"). What becomes of a losing candidate, decided by the LOSER's
diff --git a/lib/rites/rite_ef/rite_ef.ml b/lib/rites/rite_ef/rite_ef.ml
index ec6d2b9..808e017 100644
--- a/lib/rites/rite_ef/rite_ef.ml
+++ b/lib/rites/rite_ef/rite_ef.ml
@@ -8,6 +8,7 @@ module Vocab_ef = Vocab_ef
module Temporal_ef = Temporal_ef
module Precedence_ef = Precedence_ef
module Lectionary_ef = Lectionary_ef
+module Rubrics_ef = Rubrics_ef
open Colitur_kernel
@@ -38,6 +39,10 @@ let context ~lectionary ~commons : (Vocab_ef.season, Vocab_ef.rank) Rite.t =
Julian-reckoning rite can supply {!Computus.julian_easter}
instead. Read by [Layer.index] for [Date_spec.Easter_offset]. *)
easter = Colitur_kernel.Computus.gregorian_easter;
+ (* The calendarium's own bissextile footnote (February, LT.txt:5011-5014):
+ see {!Temporal_ef.bissextile_fixed_key} for the full citation and
+ mechanism. Read by [Layer.on_date]'s fixed half only. *)
+ fixed_key = Temporal_ef.bissextile_fixed_key;
rules =
{ Precedence.band = Precedence_ef.band;
disposition = Precedence_ef.disposition;
@@ -45,4 +50,7 @@ let context ~lectionary ~commons : (Vocab_ef.season, Vocab_ef.rank) Rite.t =
vigil_feast = Precedence_ef.vigil_feast };
season_runs = Vocab_ef.seasons;
transfer_target = Precedence_ef.transfer_target;
- readings = Lectionary_ef.readings ~lectionary ~commons }
+ readings = Lectionary_ef.readings ~lectionary ~commons;
+ creed = Rubrics_ef.creed;
+ gloria = Rubrics_ef.gloria;
+ preface = Rubrics_ef.preface }
diff --git a/lib/rites/rite_ef/rite_ef.mli b/lib/rites/rite_ef/rite_ef.mli
index 2183799..2ed61de 100644
--- a/lib/rites/rite_ef/rite_ef.mli
+++ b/lib/rites/rite_ef/rite_ef.mli
@@ -10,6 +10,7 @@ module Vocab_ef = Vocab_ef
module Temporal_ef = Temporal_ef
module Precedence_ef = Precedence_ef
module Lectionary_ef = Lectionary_ef
+module Rubrics_ef = Rubrics_ef
(** The EF rite, bundled (design spec's [RITE] signature, realised as a
{!Colitur_kernel.Rite.t} value rather than a functor -- see rite.mli):
@@ -32,6 +33,16 @@ module Lectionary_ef = Lectionary_ef
slug, else (a weekday with no entry of its own) the preceding Sunday's
temporal slug, in data/ef/lectionary.sexp. See {!Lectionary_ef.readings}
for why the Common is consulted second rather than last.
+ - [creed]: {!Rubrics_ef.creed}, RG 475-476 -- whether the Creed is said.
+ The first rubric in this phase governing a part of Mass rather than
+ occurrence/precedence.
+ - [gloria]: {!Rubrics_ef.gloria}, RG 431-432 -- whether the Gloria in
+ excelsis is said. Reads {!Rubrics_ef.te_deum} (Breviary nn. 237-238)
+ for RG 431(a)/432(a)'s own deferral.
+ - [preface]: {!Rubrics_ef.preface}, RG 482-499 -- which preface is
+ said. Same seam as [creed]/[gloria]; [None] both for a day with no
+ Mass at all (Good Friday) and for a rite that has not implemented
+ the rule.
Deliberately carries no [sanctoral]/[lectionary] fields the way the
original design-doc sketch of [RITE] does: {!Colitur_kernel.Rite.t} (the
diff --git a/lib/rites/rite_ef/rubrics_ef.ml b/lib/rites/rite_ef/rubrics_ef.ml
new file mode 100644
index 0000000..ce2cfcc
--- /dev/null
+++ b/lib/rites/rite_ef/rubrics_ef.ml
@@ -0,0 +1,1244 @@
+(* RG 475-476 (docs/research/LT.txt, grep "dicitur symbolum"; scan1.txt line
+ ~3872-3888), quoted here in full so every branch below can cite its own
+ letter without re-quoting the whole rubric:
+
+ "475. Post Evangelium aut homiliam, dicitur symbolum:
+ a) in qualibet dominica, etsi eius Officium alicui festo locum
+ cedat, vel Missa votiva II classis celebretur;
+ b) in festis I classis et in Missis votivis I classis;
+ c) in festis II classis Domini et B. Mariae Virg.;
+ d) per octavas Nativitatis Domini, Paschatis et Pentecostes, etiam
+ in festis occurrentibus et in Missis votivis;
+ e) in festis nataliciis Apostolorum et Evangelistarum, necnon in
+ festis Cathedrae S. Petri et S. Barnabae Ap.
+
+ 476. Non dicitur symbolum:
+ a) in Missis sive chrismatis sive in Cena Domini, feria V
+ Hebdomadae sanctae, et in Missa Vigiliae paschalis;
+ b) in festis II classis, iis exceptis quae supra, n. 475 c et e,
+ recensentur;
+ c) in Missis votivis II classis;
+ d) in Missis festivis et votivis III et IV classis;
+ e) ratione alicuius commemorationis in Missa occurrentis;
+ f) in Missis defunctorum."
+
+ SCOPE NOTE, checked once here rather than at every clause below: this
+ engine resolves ONE observed office and ONE Mass per civil day (see
+ Rite.t.readings' own doc comment) -- it has no separate "which votive
+ Mass is said" dimension. So the "vel/et...votivis" halves of 475(a)/(b),
+ 476(c) entirely, and 476(d)'s "et votivis" half are genuinely
+ inapplicable to this implementation -- a documented scope limit, not a
+ defect. 476(e) needs no branch at all: [creed] below reads only
+ [observed], never a day's admitted commemorations, so a commemoration
+ can never change its answer by construction.
+
+ 476(f) ("in Missis defunctorum") is DIFFERENT: this file used to carry
+ it in the same "not modelled" list above, on the reasoning that
+ [creed]'s inputs (temporal/observed/date) have no notion of "this Mass
+ is a Requiem". That reasoning was wrong, found by the LMS Ordo layer
+ (test_lms_ordo.ml, allow-list entry L1, now closed -- see
+ expected-divergences-lms.sexp) on 2025-11-03, All Souls' Day: colitur
+ said the Creed where the Ordo, correctly, does not. [observed] DOES
+ carry a usable signal -- RG 117 assigns black to Masses of the dead,
+ and {!Colour.Black} is used by exactly TWO celebrations in this whole
+ engine, verified by grepping every [colour Black]/[Colour.Black] site
+ in lib/ and data/: [commemoration-of-all-souls] in
+ data/ef/sanctoral.sexp, and Good Friday in temporal_ef.ml (which is a
+ Holy Week FERIA, RG 23(b), already excluded by the [n >= -6 && n <=
+ -1] branch below regardless of colour, and has no Mass at all in the
+ 1955-restored Holy Week). So on every day this engine can actually
+ construct, [colour = Black] if and only if the Mass is a Requiem --
+ a citable implication ON THIS DATA, not a heuristic guess. [creed]
+ below uses it as 476(f)'s own guard. This is a PROXY, not a general
+ "is this a Requiem Mass" field, and it is only as good as that
+ two-member population: {!test_rubrics_ef}'s own
+ [test_colour_black_population_is_exactly_two] fails loudly the day a
+ third [Colour.Black] celebration is added anywhere, so the proxy
+ cannot silently rot into covering (or missing) a non-Requiem black
+ Mass. If that ever happens, this guard needs re-deriving, not merely
+ re-approving. *)
+
+open Colitur_kernel
+
+(* RG 475(e): "in festis nataliciis Apostolorum et Evangelistarum, necnon in
+ festis Cathedrae S. Petri et S. Barnabae Ap." NATALICIUM means the feast
+ of the saint's own death (dies natalis) -- not every feast that merely
+ names him. That is precisely why the clause has to name the Chair of St
+ Peter and St Barnabas EXPLICITLY: neither is a natalicium (Peter's own is
+ 29 June, shared with Paul; Barnabas's is his own day, 11 June, but the
+ clause names him anyway, redundantly with the natalicium reading, rather
+ than leave it to inference), so neither would be covered without the
+ explicit "necnon".
+
+ DERIVED, not copied from any list supplied with this task: grepped
+ data/ef/sanctoral.sexp directly for every entry whose English or Polish
+ name mentions "Apostle"/"Evangelist"/"Aposto{l/ł}a", then each
+ candidate's own date, rank and status checked against the calendarium
+ and against whether it is that saint's own dies natalis. Every entry
+ below was cross-checked against the shipped data, not assumed:
+
+ andrew (30 Nov, Class2) -- his natalicium.
+ barnabas (11 June, Class3) -- named explicitly; also his own natalicium.
+ bartholomew (24 Aug, Class2) -- his natalicium.
+ chair-of-st-peter (22 Feb, Class2) -- named explicitly ("Cathedrae S.
+ Petri"); NOT a natalicium (Peter's own is 29 June, shared with Paul)
+ -- exactly why the clause has to name it.
+ james-the-greater (25 July, Class2) -- his natalicium.
+ john-the-evangelist (27 Dec, Class2) -- his natalicium (the one Apostle
+ traditionally held to have died a natural death; "natalicium" still
+ names his own feast day, not only a martyr's).
+ luke-the-evangelist (18 Oct, Class2) -- his natalicium.
+ mark (25 April, Class2) -- the Evangelist ("Marka Ewangelisty" in the
+ data's own Polish name); NOT "mark-i" (7 Oct), a different saint (a
+ Pope), excluded.
+ matthew (21 Sep, Class2) -- Apostle and Evangelist, his natalicium.
+ matthias (24 Feb, Class2) -- his natalicium.
+ sts-peter-paul (29 June, Class1) -- the natalicium of both.
+ sts-philip-james (11 May, Class2) -- the natalicium of both (James the
+ Less; there is no separate "james-the-less" entry in the data).
+ sts-simon-jude (28 Oct, Class2) -- the natalicium of both.
+ thomas (21 Dec, Class2) -- his natalicium.
+
+ Checked and DELIBERATELY EXCLUDED (the Trap this clause is built around):
+ conversion-of-st-paul (25 Jan, Class3) -- not a natalicium: it
+ commemorates an EVENT of his life, not his death.
+ in-commemoratione-sancti-pauli-apostoli (30 June, Class3, status
+ Feast, so it CAN be observed, unlike the two entries below) -- a
+ secondary commemoration of Paul, not his dies natalis (his own is 29
+ June, with Peter); its own name says so ("In Commemoratione", not a
+ feast of his martyrdom).
+ "peter" (25 Jan) and "paul" (22 Feb) -- both status
+ [Commemoration_only] (RG 110's own Peter/Paul companions, on the
+ Conversion and Chair days respectively: {!Precedence_ef.disposition}'s
+ own citation), so neither can ever be [observed]; moot either way,
+ but neither is a natalicium regardless.
+ mark-i (7 Oct) -- a different saint (Pope St Mark), not the
+ Evangelist. *)
+let creed_apostle_slugs =
+ [ "andrew"; "barnabas"; "bartholomew"; "chair-of-st-peter"; "james-the-greater";
+ "john-the-evangelist"; "luke-the-evangelist"; "mark"; "matthew"; "matthias";
+ "sts-peter-paul"; "sts-philip-james"; "sts-simon-jude"; "thomas" ]
+
+let creed ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
+ ~(observed : Vocab_ef.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
+ let m = Date.month date and dd = Date.day date in
+ let slug = Slug.to_string observed.Celebration.slug in
+ if
+ (* RG 475(d): "per octavas Nativitatis Domini, Paschatis et
+ Pentecostes, etiam in festis occurrentibus et in Missis votivis" --
+ an unconditional window override, checked first: EVEN a saint's
+ feast that wins the day within one of the three octaves (St Stephen,
+ 26 December, is the live witness -- RG 67's own "Com. octavae
+ Nativitatis" note, quoted in full in temporal_ef.ml's [named]) still
+ says the Creed. Pure date/Easter-offset arithmetic, not season or
+ rank: Ascension (Easter+39) and the Pentecost Vigil (Easter+48) are
+ both [Class1] and both fall inside the Paschaltide SEASON but
+ outside either 8-day OCTAVE, so a rank- or season-based test here
+ would wrongly include them -- checked and rejected for exactly this
+ reason.
+
+ Nativity: 25-31 December (its own day plus 7) + 1 January (RG 91
+ entry 5's own "Octave Day of the Nativity", the identical table
+ entry as 24 December's vigil -- temporal_ef.ml's [named]). Easter:
+ Easter Sunday (offset 0) through Low Sunday (offset 7) -- RG 11's
+ own "dominicae Paschatis et Pentecostes sunt pariter festa I classis
+ CUM OCTAVA". Pentecost: Pentecost (offset 49) through Trinity Sunday
+ (offset 56), the RG 91-entry-14-adjacent "octave day" Trinity Sunday
+ itself names in temporal_ef.ml. *)
+ (m = 12 && dd >= 25 && dd <= 31)
+ || (m = 1 && dd = 1)
+ || (n >= 0 && n <= 7)
+ || (n >= 49 && n <= 56)
+ then true
+ else if
+ (* RG 475(a): "in qualibet dominica, ETSI EIUS OFFICIUM ALICUI FESTO
+ LOCUM CEDAT" -- the Creed is said on any Sunday even when a feast
+ displaces the Sunday's own office (RG 16(a): a Feast of the Lord I
+ or II class occurring on a II-class Sunday takes its place "cum
+ omnibus iuribus et privilegiis"). Read off [temporal]'s own weekday
+ -- the day's calendar fact, independent of whatever [observed] turns
+ out to be -- never off [observed]'s slug or rank, which is exactly
+ what would go silently wrong the day such an impeding feast wins:
+ see {!Colitur_kernel.Precedence.rules.admit}'s own [~temporal]
+ parameter (precedence.mli) for the identical argument, made there
+ for RG 111(b) rather than RG 475(a). *)
+ temporal.Temporal.weekday = Date.Sun
+ then true
+ else if
+ (* RG 23 (Caput IV, "De Feriis"): "Feriae I classis sunt: a) feria IV
+ cinerum; b) omnes feriae Hebdomadae sanctae." Ash Wednesday and every
+ feria of Holy Week (Monday through Saturday -- RG 91 entry 2's
+ Sacred Triduum, Thursday-Saturday, is a THIRD sub-case of this same
+ "feria", not a "festum": RG 35, immediately below in Caput VI,
+ defines "festum" as a distinct liturgical-day category from "feria",
+ RG 21-27) are FERIAE, never FESTA, however high their RG 91 rank --
+ so 475(b)/(c)'s "in festis" never reaches them, regardless of rank.
+
+ This single structural check subsumes 476(a)'s own explicit naming
+ of the Chrism Mass, the Mass of the Lord's Supper (both Holy
+ Thursday) and the Easter Vigil Mass (Holy Saturday's date): both are
+ already excluded here as Holy Week feriae, so 476(a) needs no
+ separate branch. (Good Friday needs no rubric at all -- RG 28's own
+ closing sentence on the Paschal Vigil aside, Good Friday's own
+ liturgical action has no Mass in the 1955-restored Holy Week to
+ begin with, so the question is moot there independent of this
+ check -- but this structural test correctly excludes it too, since
+ it is also named in RG 23(b).)
+
+ RG 24-25 (immediately following, same Caput IV) name TWO MORE
+ ferial classes this branch does not reach: feriae II classis (RG
+ 24 -- the Advent ferias 17-23 December, and the Quatuor Temporum
+ sets of Advent, Lent and September) and feriae III classis (RG 25
+ -- the numbered Lenten/Passiontide ferias from the Thursday after
+ Ash Wednesday to the Saturday before Passion Sunday II, and the
+ un-Embered Advent ferias to 16 December). NEITHER is excluded by a
+ check anywhere in this module -- this branch and RG 33's vigil
+ branch immediately below are the whole of what [creed] tests
+ before falling through to 475(b)/(c)/(e)'s own rank/subject/slug
+ guards. They do not need an explicit exclusion of their own for
+ 475(b): temporal_ef.ml's own [ferial_rank] never returns [Class1]
+ for them (only Ash Wednesday and Holy Week do, RG 23's own population,
+ already excluded above), so that branch is unreachable for them
+ regardless of anything checked here. But RG 24's own II-class set
+ DOES reach 475(c)'s [Class2] guard below, and 475(e)'s
+ apostle-slug guard carries no rank floor at all, so both classes
+ genuinely reach a live branch of this function. Getting [false]
+ there is NOT established by this exclusion, or by any check in
+ this file: it rests on an unstated property of {!Temporal_ef} --
+ every ferial-origin office it builds carries [subject = Temporal]
+ and an "ef-"-prefixed slug, never [Lord]/[Bvm] or a name on
+ {!creed_apostle_slugs}. Verified directly in temporal_ef.ml: the
+ Ember/Rogation/generic-ferial branches all go through [build]'s
+ own default [subject = Temporal], with exactly two documented
+ exceptions, neither able to reach a live branch below -- the
+ Sacred Triduum ([subject = Lord], but [Class1], already excluded
+ above) and the votive Office of the BVM on Saturday, RG 91 entry
+ 27 ([subject = Bvm], but [Class4], never satisfying 475(c)'s own
+ [Class2] guard).
+
+ (CORRECTED, review fix: this comment, and 475(b)'s own immediately
+ below, previously read as though "the two exclusions immediately
+ above" disposed of ferias in general -- they dispose of feria I
+ classis only. RG 24/25's own higher classes were excluded by NO
+ check, an unstated-invariant gap now named here rather than
+ silently relied on: test/test_rubrics_ef.ml's own ferial-invariant
+ sweep now asserts the Temporal_ef property above directly against
+ real output, so a future change that broke it would fail that
+ test rather than silently changing the Creed.) *)
+ n = -46 || (n >= -6 && n <= -1)
+ then false
+ else if
+ (* RG 28-34 (Caput V, "De Vigiliis"): a vigil is its OWN liturgical-day
+ category, distinct from "festum" (RG 35, Caput VI) the same way a
+ feria is (immediately above) -- so 475(b)/(c)'s "in festis" does not
+ reach a vigil either, regardless of its own RG 91 rank. This is also
+ why 476(a) has to name the Easter Vigil explicitly: RG 28's own
+ closing sentence says the Paschal Vigil, uniquely, "non sit dies
+ liturgicus" [is not a liturgical day] at all, so it is not even a
+ "vigilia" in RG 29-32's numbered sense -- nothing else in this
+ taxonomy would have excluded it without that explicit clause, unlike
+ every OTHER vigil, which is excluded merely by being one.
+ {!Precedence_ef.is_vigil} already tests both slug conventions this
+ codebase's data uses (the temporal cycle's "-vigil" suffix and the
+ sanctoral bootstrap's "vigil-of-" prefix) for the identical RG 33
+ question; reused here rather than re-derived, on the same footing as
+ {!Precedence_ef.marian_slugs} just below. *)
+ Precedence_ef.is_vigil slug
+ then false
+ else if
+ (* RG 476(f): "in Missis defunctorum" -- the Creed is never said at a
+ Requiem Mass, checked here, ahead of 475(b)'s rank branch, the same
+ "category excludes regardless of rank" position as the feria and
+ vigil checks immediately above (All Souls' Day is [Class1], so
+ without this guard 475(b) would grant it [true] unopposed, which is
+ exactly the defect this guard fixes -- see this file's own header
+ for the two-member [Colour.Black] population this proxy rests on,
+ and {!Colour.Black}'s own citation, RG 117, for why colour is the
+ Requiem signal. Placed before, not after, 475(b)/(c)/(e) so a black
+ Class1 or Class2 day can never reach them; nothing above this point
+ (the Nativity/Easter/Pentecost octave override, the Sunday rule, the
+ feria and vigil exclusions) is ever reachable by a Requiem Mass on
+ the shipped data either -- a Requiem is never kept on a Sunday or
+ inside a privileged octave -- so this guard's own position relative
+ to THOSE branches is moot on real data, checked, not merely assumed:
+ {!creed_apostle_slugs} and [marian_slugs] contain no Black-coloured
+ entry, and 2 November can never fall inside any of the three
+ octaves 475(d) names. *)
+ observed.Celebration.colour = Colour.Black
+ then false
+ else if
+ (* RG 475(b): "in festis I classis". Genuine feasts only, by
+ construction of the two exclusions immediately above (feria I
+ classis -- Ash Wednesday and Holy Week, the only feriae that are
+ ever [Class1]; RG 24/25's II- and III-class feriae are a different
+ population and never reach this rank at all, see the RG 23
+ comment's own note above -- and vigils) -- every remaining
+ [Class1] candidate reaching this branch is a real festum: the
+ Nativity, Epiphany, Ascension, Corpus Christi, the Sacred Heart,
+ Christ the King, a I-class sanctoral feast (the Assumption, the
+ Immaculate Conception...), or a I-class Sunday (already [true]
+ above via 475(a), so this branch is never the FIRST to grant those
+ a [true], only ever redundant with it). *)
+ observed.Celebration.rank = Vocab_ef.Class1
+ then true
+ else if
+ (* RG 475(c): "in festis II classis Domini et B. Mariae Virg." --
+ [subject = Lord] is reliably set on genuine II-class sanctoral
+ feasts of the Lord (Exaltation of the Holy Cross, the Purification,
+ the Transfiguration, the Commemoration of the Baptism of the Lord,
+ the Dedication of the Lateran Archbasilica -- checked directly
+ against data/ef/sanctoral.sexp: six [subject = Lord] entries ship,
+ none a vigil or feria) and on the two temporal-cycle Class2 Lord
+ feasts (Holy Family, Holy Name of Jesus). [subject = Bvm], by
+ contrast, is NOT reliable for the BVM half: checked directly against
+ the data, almost every Marian sanctoral entry (the Assumption, the
+ Annunciation, the Immaculate Heart, the Nativity of the BVM...)
+ ships [subject = Saint] instead -- {!Precedence_ef.marian_slugs} is
+ the list built (and, here, reused rather than re-derived) precisely
+ because the [subject] field cannot be trusted alone for this
+ question; see its own citation in precedence_ef.mli.
+
+ This [Class2] guard is also the one live branch RG 24's own
+ II-class feriae (Advent 17-23, the Advent/Lent/September Ember
+ sets) genuinely reach -- the RG 23 comment above (this file, the
+ [n = -46 ...] branch) has the full account of why they still come
+ out [false] here: an unstated Temporal_ef property, checked by a
+ test, not a guard in this file. *)
+ observed.Celebration.rank = Vocab_ef.Class2
+ && (observed.Celebration.subject = Subject.Lord
+ || observed.Celebration.subject = Subject.Bvm
+ || List.mem slug Precedence_ef.marian_slugs)
+ then true
+ else
+ (* RG 475(e): see {!creed_apostle_slugs}'s own citation. Checked last
+ and without a rank guard, on purpose -- Barnabas is only [Class3]
+ and the Chair of St Peter's own [subject] is [Saint], so neither
+ would ever be reached by the two branches above; every [Class1]
+ entry on the list (Sts Peter & Paul) is already [true] via 475(b),
+ so this branch is redundant, never wrong, for those. *)
+ List.mem slug creed_apostle_slugs
+
+(* Breviarium Romanum, 1961 Codex Rubricarum, "N) De hymno Te Deum"
+ (docs/research/breviary/rubricae-breviarii-1961.txt; docs/research/
+ breviary/PROVENANCE.md has this source's own provenance and weakness in
+ full), quoted here in full so every branch below can cite its own
+ letter without re-quoting the whole rubric:
+
+ "237. Hymnus Te Deum dicitur ad Matutinum, post ultimam lectionem, loco
+ noni vel tertii responsorii:
+ a) in dominica in albis, in dominica Pentecostes, et in Matutino
+ dominicae Resurrectionis, quod recitatur ab iis qui Vigiliae paschali
+ non interfuerunt;
+ b) in dominicis II classis, exceptis dominicis in Septuagesima, in
+ Sexagesima et in Quinquagesima;
+ c) in omnibus festis;
+ d) per octavas Nativitatis Domini, Paschatis et Pentecostes;
+ e) in Officio feriali temporis natalicii et temporis paschalis;
+ f) in vigiliis Ascensionis et Pentecostes;
+ g) in Officio sanctae Mariae in sabbato.
+
+ 238. Omittitur vero hymnus Te Deum:
+ a) in Officiis de Tempore a dominica I Adventus usque ad vigiliam
+ Nativitatis Domini inclusive; et a dominica in Septuagesima usque ad
+ Sabbatum sanctum inclusive;
+ b) in vigiliis II et III classis, excepta vigilia Ascensionis
+ Domini;
+ c) in omnibus feriis per annum;
+ d) in Officio defunctorum."
+
+ THE WEAKNESS, restated (PROVENANCE.md has the full account): this is a
+ SINGLE WEB TRANSCRIPTION (ceremoniaire.net), not yet checked against a
+ photographic scan -- the weakest-sourced rule in this project. Mitigated,
+ not resolved, by the FIUV universal Ordo's own Te Deum column
+ (test/fixtures/fiuv-ordo-2025-2026.sexp, 262 "Te Deum" rows out of 400 --
+ an independent day-level witness, never itself derived from this
+ transcription) -- see test_fiuv_ordo.ml, which compares [te_deum]'s own
+ output against it. A mismatch there may indict this transcription rather
+ than [te_deum]; adjudicated per that file's own allow-list, not assumed
+ either way.
+
+ SCOPE: this predicate exists ONLY because RG 431(a) below defers a MASS
+ question to it. Building it is not the Divine Office arriving in scope --
+ CLAUDE.md's own "Divine Office remains out of scope" line, and the
+ 2026-08-21 design spec's own SS1, are both unchanged: this borrows ONE
+ Breviary FACT per day (whether Te Deum was said at Matins), never models
+ Matins/Vespers/the psalter/concurrence. *)
+let te_deum ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
+ ~(observed : Vocab_ef.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
+ let m = Date.month date and dd = Date.day date in
+ let slug = Slug.to_string observed.Celebration.slug in
+ if
+ (* 238(d): "in Officio defunctorum" -- checked first, the same position
+ and the same {!Colour.Black} proxy [creed]'s own 476(f) branch uses
+ (this file's own header has the full argument for why colour is a
+ sound proxy for "this is a Requiem" on the shipped data, and
+ {!test_colour_black_population_is_exactly_two} in test_rubrics_ef.ml
+ is the SAME two-member population this predicate also depends on --
+ no separate test needed). *)
+ observed.Celebration.colour = Colour.Black
+ then false
+ else if
+ (* 237(a): the three explicitly named Paschaltide days -- Easter
+ Sunday's own Matins (n=0), Low Sunday (n=7), Pentecost Sunday
+ (n=49). Pure Easter-offset arithmetic, the same style [creed]'s own
+ 475(d) uses and for the identical reason: season/rank alone cannot
+ express "this exact day", and nothing else in 237/238 names these
+ three individually.
+
+ HISTORY WORTH KEEPING: this task's own FIRST pass REPLACED this
+ branch (and 237(b) below) with a blanket "every Sunday" rule,
+ having found what looked like a clean 15-for-15 FIUV Ordo
+ contradiction of 237(b)'s own Septuagesima/Sexagesima/Quinquagesima
+ exception. That evidence was ITSELF corrupted: tools/extract_fiuv_
+ ordo.ml's own Te Deum parser recognised only ONE of the source's two
+ negative phrasings ("non dicitur Te Deum"), so every "sine Te Deum"
+ occurrence -- which is how the source actually negates a SUNDAY's
+ own Te Deum, found only by reading the raw pdftotext dump by hand,
+ not by trusting the fixture's own coverage counts -- fell through to
+ a bare "Te Deum" substring match and was wrongly recorded [true].
+ Fixed in the extractor (see its own citation, tools/extract_fiuv_
+ ordo.ml); the fixture was regenerated; 24 of the fixture's 400 rows
+ flipped, EVERY ONE true->false, EVERY ONE a day this branch or
+ 237(b) below governs. The blanket rule was reverted the moment the
+ corrected data confirmed the ORIGINAL literal reading instead:
+ 238(a)'s own window DOES silence Advent/Septuagesima/Lent/
+ Passiontide Sundays after all. Left as a worked example, not
+ scrubbed from history: the failure mode was believing a clean-
+ looking oracle correlation over re-deriving the primary text,
+ exactly backwards from what "adjudicate, don't assume" should have
+ produced -- caught only by cross-checking the raw source directly
+ once the shape looked suspiciously total. *)
+ n = 0 || n = 7 || n = 49
+ then true
+ else if
+ (* 237(f): the vigils of Ascension (n=38) and Pentecost (n=48), checked
+ BEFORE 238(b)'s general vigil omission below -- both are otherwise
+ reachable by it (Ascension's vigil is [Class2], squarely inside
+ 238(b)'s own "II et III classis"; Pentecost's is [Class1], RG 91
+ entry 9, so 238(b) could never have reached it regardless, but is
+ named here anyway rather than left to fall through to 237(c), the
+ same "cite the specific clause, not a catch-all" discipline this
+ whole module holds to). *)
+ n = 38 || n = 48
+ then true
+ else if
+ (* 237(d): the three privileged octaves -- reuses [creed]'s own three
+ windows verbatim (that function's own 475(d) comment has the full
+ citation and the argument for why a season- or rank-based test
+ would wrongly include the Ascension/Pentecost-Vigil days this
+ window must exclude). Overlaps 237(a) at n=0/7/49 -- redundant, not
+ wrong, the same "never the FIRST branch to grant those [true]"
+ pattern 475(b) documents for I-class Sundays. *)
+ (m = 12 && dd >= 25 && dd <= 31)
+ || (m = 1 && dd = 1)
+ || (n >= 0 && n <= 7)
+ || (n >= 49 && n <= 56)
+ then true
+ else if
+ (* 238(b): "vigiliis II et III classis" -- {!Precedence_ef.is_omissible_vigil}
+ is exactly this rank test (Class2 or Class3), reused rather than
+ re-derived, paired with {!Precedence_ef.is_vigil} the same way
+ [creed]'s own RG 28-34 branch already pairs them. The Ascension
+ vigil (237(f) above, already [true]) can never reach this branch;
+ the four sanctoral vigils (StJohnBaptist, SsPeter&Paul, StLawrence,
+ the Assumption -- {!Precedence_ef.vigil_feast_table}'s own
+ population) are exactly what this branch excludes. *)
+ Precedence_ef.is_omissible_vigil observed.Celebration.rank && Precedence_ef.is_vigil slug
+ then false
+ else if
+ (* NOT 238(b)'s own text (which names only "II et III classis"): the
+ Nativity Vigil ([Class1], RG 91 entry 5) is excluded here on the
+ SAME RG 21/35 taxonomy argument [creed]'s own RG 28-34 comment
+ already makes for the Paschal Vigil -- "vigilia" is its own
+ liturgical-day category, distinct from "festum", regardless of
+ class; RG 30's "beyond losing" is a PRECEDENCE exemption (nothing
+ lesser can displace it), not a claim that a vigil IS a festum for
+ Breviary purposes. Flagged honestly as an INFERENCE, not a literal
+ 238(b) citation -- checked against the FIUV Ordo's own Te Deum
+ marker for 24 December in test_fiuv_ordo.ml, since this is exactly
+ the shape a transcription gap could get wrong either direction. *)
+ Precedence_ef.is_vigil slug
+ then false
+ else if
+ (* 238(c)/RG 23: Ash Wednesday and every feria of Holy Week, the Sacred
+ Triduum included -- reuses [creed]'s own RG 23 test verbatim (that
+ function's own comment has the full citation and the argument for
+ why this excludes feria I classis specifically, not ferias in
+ general). *)
+ n = -46 || (n >= -6 && n <= -1)
+ then false
+ else if
+ (* 237(b): "in dominicis II classis, exceptis dominicis in Septuagesima,
+ in Sexagesima et in Quinquagesima" -- colitur's own single
+ [Septuagesima] season covers exactly those three Sundays (see
+ [season_colour]'s own grouping, temporal_ef.ml), so the exception is
+ one season-equality test. RE-VERIFIED, not merely restored: the
+ corrected FIUV extraction (237(a)'s own comment above has the full
+ account) shows all three Septuagesima-season Sundays [false], and
+ every OTHER, ordinary Class2 Sunday the fixture's window reaches
+ [true] -- exactly this clause's own literal text, no correction
+ needed here after all. Reads [temporal.weekday]/[.season], not
+ [observed]'s slug: unlike [creed]'s own 475(a), the Breviary's text
+ carries NO "even when a feast displaces the Sunday's own Office"
+ exception -- when a feast genuinely takes the Sunday's place (RG
+ 16(a)), Matins says the FEAST's own Office, and 237(c) below decides
+ it on the feast's own terms, not this clause. Advent/Lent/
+ Passiontide Sundays are [Class1], never [Class2] ([creed]'s own RG
+ 11-12 citation, temporal_ef.ml), so this guard correctly excludes
+ them without a separate season check; [observed.rank], not
+ [temporal]'s own season-derived rank, is read here on purpose, for
+ the same RG 16(a) reason [Precedence.rules.admit]'s own
+ [~temporal] parameter exists: a feast that wins the day can carry a
+ DIFFERENT rank than the Sunday it displaced. *)
+ temporal.Temporal.weekday = Date.Sun
+ && observed.Celebration.rank = Vocab_ef.Class2
+ && temporal.Temporal.season <> Vocab_ef.Septuagesima
+ then true
+ else if
+ (* 237(g): the votive Office of the BVM on Saturday, RG 78/91 entry 27
+ -- {!Temporal_ef}'s own [subject = Bvm]/[Class4] pairing, the same
+ shape [creed]'s own 476(d) comment and the register's RG 112(d) fix
+ already establish as unique to this office (every OTHER
+ [subject = Bvm] candidate in the shipped data is [Commemoration_only]
+ and can therefore never be [observed]). *)
+ observed.Celebration.subject = Subject.Bvm && observed.Celebration.rank = Vocab_ef.Class4
+ then true
+ else if
+ (* 237(e): "Officio feriali temporis natalicii et temporis paschalis" --
+ every remaining (non-octave, non-vigil, non-BVM-Saturday) FERIA of
+ Christmastide or Paschaltide: the 2-5 January ferias, and the
+ ordinary weeks of Paschaltide (Rogation Monday/Tuesday included).
+ [temporal.weekday <> Sun] keeps this to FERIAS only, matching the
+ clause's own "Officio FERIALI" text; a Sunday in either season is
+ already [true] via the Sunday rule above regardless, so this guard
+ changes no OUTCOME, only which clause gets credit for it. *)
+ (temporal.Temporal.season = Vocab_ef.Christmastide
+ || temporal.Temporal.season = Vocab_ef.Paschaltide)
+ && temporal.Temporal.weekday <> Date.Sun
+ then true
+ else
+ (* 237(c): "in omnibus festis" -- every remaining genuine festum. By
+ this point every named Paschaltide day, every ordinary Sunday
+ (Class2, outside Septuagesima), every vigil, every feria I classis,
+ every Christmastide/Paschaltide feria and every BVM Saturday Office
+ has already been excluded or granted above, so what reaches here is
+ exactly: {!Temporal_ef.named}'s remaining population
+ (Epiphany, Ascension, Corpus Christi, the Sacred Heart, Christ the
+ King -- tested by PRESENCE in that table, since every [named] entry
+ carries [subject = Temporal] like any other, see [creed]'s own
+ 475(c) comment); and every genuine sanctoral feast actually observed
+ ([subject = Saint], or one of the handful of [subject = Lord]/[Bvm]
+ entries -- Holy Family, Holy Name of Jesus, the six [Lord]-tagged
+ sanctoral feasts, [most-holy-name-of-mary] -- [creed]'s own 475(c)
+ comment has the full census). The two ferial exceptions that ALSO
+ carry [Lord]/[Bvm] (the Sacred Triduum, the BVM Saturday Office) are
+ unreachable here: both were already excluded above (feria I
+ classis; 237(g)).
+
+ [Temporal_ef.named]'s FIRST disjunct EXCLUDES Passion Sunday and
+ Palm Sunday BY THEIR OWN SLUG -- not by [temporal.weekday <> Sun],
+ which a first pass of this fix tried and had to REVERT: Christ the
+ King is ALSO always a Sunday (its own [christ_the_king] anchor
+ IS "the last Sunday of October"), [Class1] like Passion/Palm
+ Sunday, so a blanket weekday guard wrongly excluded it too --
+ caught immediately by the LMS Ordo's own Gloria axis
+ (2024-10-27, "colitur gloria=false, Ordo gloria=true"), a
+ regression a same-session review round found before this task
+ closed. Passion Sunday and Palm Sunday are excluded because
+ neither is a genuine "festum" (RG 35's own taxonomy makes
+ "dominica" its own category, distinct from "festum") -- {!Temporal_
+ ef.named} carries them anyway (RG 91 entry 6, for its own
+ occurrence-table reasons), so without SOME exclusion both would
+ wrongly reach [true] here BY ACCIDENT of table membership --
+ confirmed wrong directly against the corrected FIUV extraction
+ (237(a)'s own comment above has the full account of the extractor
+ bug this was found alongside): both dates are [false] in the
+ source. Epiphany, Ascension, Corpus Christi, the Sacred Heart and
+ Christ the King -- {!Temporal_ef.named}'s only OTHER population --
+ are genuine festa and must NOT be excluded, which is exactly why
+ the exclusion is two named slugs, not a day-of-week predicate. The
+ SECOND disjunct (subject) carries no such guard: a genuine feast
+ that has fully displaced a Sunday's own Office (RG16(a)) still
+ deserves 237(c)'s grant on the FEAST's own terms, regardless of
+ what day of the week it falls on.
+
+ A plain, unnamed weekday feria (no Sunday, no vigil, no octave,
+ [named] = [None], [subject = Temporal]) correctly falls through to
+ [false] here -- 238(c)'s own "in omnibus feriis". *)
+ (Temporal_ef.named date <> None && slug <> "ef-passion-sunday" && slug <> "ef-palm-sunday")
+ || observed.Celebration.subject = Subject.Saint
+ || observed.Celebration.subject = Subject.Lord
+ || observed.Celebration.subject = Subject.Bvm
+
+(* Missale Romanum, Rubricae Generales, Caput XVII("De Ritibus servandis in
+ celebratione Missae"), "C) De hymno Glória in excélsis" (docs/research/
+ LT.txt, grep "Hymnus Glória"), quoted here in full:
+
+ "431. Hymnus Gloria in excelsis dicitur:
+ a) in Missis quae respondent Officio diei, quotiescumque ad
+ Matutinum dictus est hymnus Te Deum;
+ b) in Missis festivis de quibus n. 302;
+ c) in Missis feriae V in Cena Domini, et in Missa Vigiliae
+ paschalis;
+ d) in Missis votivis I, II et III classis, nisi adhibeatur color
+ violaceus paramentorum;
+ e) in Missis votivis IV classis de Angelis, quocumque die, et de B.
+ Maria Virg. quae in sabbato celebrantur.
+
+ 432. Hymnus Gloria in excelsis omittitur:
+ a) in Missis quae respondent Officio diei, quando ad Matutinum
+ omittitur hymnus Te Deum;
+ b) in omnibus Missis in quibus adhibetur color violaceus
+ paramentorum;
+ c) in Missis votivis IV classis, iis exceptis de quibus n. 431 e;
+ d) in Missis defunctorum."
+
+ SCOPE NOTE, checked once here rather than at every clause, the same
+ discipline [creed]'s own header uses: this engine resolves ONE Mass per
+ civil day (Rite.t.readings' own doc comment) -- it has no separate
+ "which votive Mass is said" dimension. n. 301-303 (LT.txt, immediately
+ above 431), quoted in substance: 301 defines "Missa de festo" in the
+ NARROW sense as the Mass of the day's own Office -- exactly what
+ [Rite_ef.Lectionary_ef.readings] already resolves for every day,
+ including a BORROWED formulary (a weekday resuming the preceding
+ Sunday's Mass, a saint using his assigned Common): still "the Mass which
+ corresponds to the day's own Office" in 431(a)/432(a)'s own sense, so
+ 431(a)/432(a) alone already cover it. 302's WIDER sense -- (a) a
+ III-class feast's own Mass said despite being impeded by another
+ III-class feast, (b) a commemoration's own Mass said in place of the
+ day's Office, (c) a saint's Mass said on his Martyrology elogium day --
+ are all cases of a DIFFERENT Mass than the day's own resolved Office
+ being said, which this engine does not model; 431(b) is therefore
+ genuinely N/A, not merely unread. 431(d)/(e) and 432(c) are about VOTIVE
+ MASS CLASSES (I-IV), a dimension this engine has no field for at all --
+ also N/A, EXCEPT 431(e)'s own "de B. Maria Virg. quae in sabbato
+ celebrantur" half: colitur does not model that Office as a votive Mass
+ (it has no votive-Mass dimension to model it AS), it models it as an
+ ORDINARY Office (RG 78's own text, [te_deum]'s own 237(g) branch above),
+ so its Gloria is produced as a side effect of 431(a) reading [te_deum],
+ not by a dedicated 431(e) branch -- checked directly: [te_deum]'s 237(g)
+ branch is unconditional (not colour-gated), and this Office's own colour
+ is white (never violet), so 432(b) below can never suppress it either.
+ 431(e)'s "de Angelis" half (the votive Mass of the Angels) has no data
+ in this engine at all and stays N/A. *)
+let gloria ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
+ ~(observed : Vocab_ef.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
+ (* 432(d): "in Missis defunctorum" -- checked first, the same
+ {!Colour.Black} proxy [creed]'s own 476(f) and [te_deum]'s own
+ 238(d) branch both use. Correctly also excludes Good Friday
+ (n=-2, [Colour.Black], temporal_ef.ml's own RG 132 citation) --
+ which has no Mass at all in the 1955-restored Holy Week, so the
+ question is moot there regardless; the same "belt and braces"
+ stance [creed]'s own 476(f) comment takes for the identical day. *)
+ observed.Celebration.colour = Colour.Black
+ then false
+ else if
+ (* 431(c): "in Missis feriae V in Cena Domini, et in Missa Vigiliae
+ paschalis" -- Holy Thursday (n=-3) and the Easter Vigil Mass (n=-1,
+ Holy Saturday's own date). Checked BEFORE 432(b)'s general violet
+ exclusion below and before [te_deum] is ever read: this clause is
+ lex specialis over both. It must outrank 432(b) specifically
+ because colitur's own per-day colour model gives Holy Saturday
+ [Colour.Violet] (Passiontide's [season_colour], temporal_ef.ml --
+ the historical vestment change from violet to white happens AT the
+ Gloria itself, a per-action nuance this whole day/colour model
+ already cannot express, the same acknowledged gap RG 126's palm
+ procession and RG 128's Good Friday Communion carry, temporal_ef.ml's
+ own citations) -- without this clause checked first, 432(b) would
+ wrongly silence the one Mass whose own Gloria is historically
+ unmistakable (the bells and organ restored at the Vigil). It must
+ also outrank [te_deum]: neither day's own Matins says Te Deum
+ (both are governed by [te_deum]'s own feria-I-classis exclusion,
+ n=-46/[-6,-1], the Sacred Triduum included), so without this
+ explicit override the Gloria would be wrongly silenced there too. *)
+ n = -3 || n = -1
+ then true
+ else if
+ (* 432(b): "in omnibus Missis in quibus adhibetur color violaceus
+ paramentorum" -- independent and colour-keyed, exactly as the task
+ brief states; NOT a substitute for [te_deum] below, which still
+ decides every Mass this clause does not itself silence. Genuinely
+ unconditional ("in omnibus Missis") -- checked directly against
+ every violet day in the domain-wide sweep (see the module's own
+ test file), never merely assumed.
+
+ [Colour.Rose] found and DELIBERATELY NOT added here, a real
+ "checked, then reverted" episode kept for the record: a first pass
+ of this task, WHILE the (since-reverted) blanket "every Sunday
+ says Te Deum" mutation to [te_deum] was in place, found Gaudete
+ and Laetare ([is_rose_sunday]) wrongly getting [gloria]=true and
+ fixed it by unioning [Colour.Rose] into this branch. Once
+ [te_deum] reverted to 237(b)'s own literal [Class2] guard, the
+ fix became REDUNDANT, not merely coincidentally silent: Rose can
+ ONLY ever colour a Sunday of Advent or Lent
+ ({!Temporal_ef.is_rose_sunday}'s own two cases), and EVERY Sunday
+ of Advent or Lent is [Class1] BY CONSTRUCTION
+ ({!Temporal_ef.temporal}'s own [match s with Advent | Lent ->
+ Class1 | _ -> Class2]) -- a structural guarantee, not a
+ coincidence of the shipped data, so [te_deum]'s own [Class2] guard
+ ALREADY excludes every Rose day before this branch is ever
+ reached. Verified, not assumed: removing this branch's own Rose
+ arm and re-running the full suite (including the two LMS dates,
+ 2023-12-17 and 2024-03-10, this finding was originally pinned
+ against) left every test green. Left out rather than kept as
+ dead code that would misleadingly read as load-bearing. *)
+ observed.Celebration.colour = Colour.Violet
+ then false
+ else
+ (* 431(a)/432(a): "in Missis quae respondent Officio diei,
+ quotiescumque/quando... Te Deum [dictus est/omittitur]" -- the
+ Gloria mirrors [te_deum] exactly for every Mass not already decided
+ above. This is the ONE call site [te_deum] exists to serve. *)
+ te_deum ~temporal ~observed ~date
+
+(* Missale Romanum, Rubricae Generales, Caput XVII, "H) De praefatione" (RG
+ 482-499; docs/research/LT.txt, grep "praefatione dicitur quae cuique"),
+ quoted here in full so every branch below can cite its own paragraph
+ without re-quoting the whole rubric:
+
+ "482. Praefatio dicitur quae cuique Missae propria est; qua deficiente,
+ dicitur praefatio de Tempore, secus communis.
+
+ 483. Nulla commemoratio, in Missa occurrens, praefationem propriam
+ inducit.
+
+ 484. Praefatio de Nativitate Domini dicitur:
+ a) tamquam propria in Missis de Nativitate Domini et de eiusdem
+ octava, necnon in festo Purificationis B. Mariae Virg.;
+ b) tamquam de Tempore, infra octavam Nativitatis Domini, etiam in
+ Missis quae secus praefationem propriam haberent, exceptis iis Missis
+ quae praefationem propriam de divinis mysteriis vel Personis habent; et
+ a die 2 ad 5 ianuarii.
+
+ 485. Praefatio de Epiphania Domini dicitur:
+ a) tamquam propria in Missis de festo Epiphaniae et de
+ Commemoratione Baptismatis D. N. Iesu Christi;
+ b) tamquam de Tempore diebus a 7 ad 13 ianuarii.
+
+ 486. Praefatio de Quadragesima dicitur:
+ a) tamquam propria in Missis de Tempore a feria IV cinerum usque ad
+ sabbatum ante dominicam I Passionis;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 487. Praefatio de sancta Cruce dicitur:
+ a) tamquam propria in Missis de tempore a dominica I Passionis usque
+ ad feriam V in Cena Domini; in Missis tam festivis quam votivis de
+ sancta Cruce, de Passione Domini et instrumentis Passionis Domini, de
+ pretiosissimo Sanguine D. N. Iesu Christi, de Ss.mo Redemptore;
+ b) tamquam de Tempore in omnibus Missis a dominica I Passionis usque
+ ad feriam IV Hebdomadae sanctae, quae praefatione propria carent.
+
+ 488. Praefatio de Missa chrismatis dicitur feria V in Cena Domini, in
+ sua Missa.
+
+ 489. Praefatio paschalis dicitur:
+ a) tamquam propria in Missis de Tempore a Missa Vigiliae paschalis
+ usque ad vigiliam Ascensionis Domini;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 490. Praefatio de Ascensione Domini dicitur:
+ a) tamquam propria in festo Ascensionis Domini;
+ b) tamquam de Tempore in omnibus Missis a feria VI post Ascensionem
+ usque ad feriam VI ante vigiliam Pentecostes, quae praefatione propria
+ carent.
+
+ 491. Praefatio de Ss.mo Corde Iesu dicitur in Missis festivis et
+ votivis de Ss.mo Corde Iesu.
+
+ 492. Praefatio de D. N. Iesu Christo Rege dicitur in Missis festivis
+ et votivis de D. N. Iesu Christo Rege.
+
+ 493. Praefatio de Spiritu Sancto dicitur:
+ a) tamquam propria in Missis de Tempore a vigilia Pentecostes usque
+ ad subsequens sabbatum; et in Missis festivis et votivis de Spiritu
+ Sancto;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 494. Praefatio de Ss.ma Trinitate dicitur:
+ a) tamquam propria in Missis de festo et votivis Ss.mae Trinitatis;
+ b) tamquam de Tempore in dominicis Adventus, et in omnibus dominicis
+ II classis, extra tempus natalicium et paschale.
+
+ 495. Praefatio de beata Maria Virgine dicitur in Missis festivis et
+ votivis beatae Mariae Virginis, praeterquam in festo Purificationis B.
+ Mariae Virg.
+
+ 496. Praefatio de S. Ioseph dicitur in Missis festivis et votivis S.
+ Ioseph.
+
+ 497. Praefatio de Apostolis dicitur in Missis festivis et votivis
+ Apostolorum et Evangelistarum.
+
+ 498. Praefatio communis dicitur in Missis quae praefatione propria
+ carent, nec sumere debent praefationem de Tempore.
+
+ 499. Praefatio defunctorum dicitur in Missis defunctorum."
+
+ THE SHAPE OF THE RULE, once, rather than at every branch: RG 482's own
+ chain is "propria, else de Tempore, else communis". Read literally, 484-
+ 497 look like FOURTEEN SEPARATE RULES, but on inspection each numbered
+ rubric's own (a)/(b) pair (where it has both) produces the SAME preface
+ identity either way -- (a) is the propria reading ("this Mass's OWN
+ preface"), (b) is the de-Tempore reading ("this OTHER Mass, lacking one
+ of its own, borrows it") -- so for the single question this function
+ answers (WHICH preface, not WHETHER it counts as propria or de Tempore
+ for some other purpose) the two halves collapse into one PRIORITY-
+ ORDERED decision: a fixed list of "genuinely proper" triggers (title/
+ mystery feasts, independent of season), checked first in a citable
+ order, falling through to a fixed list of SEASONAL windows, falling
+ through to [Common]. RG 483 (a commemoration never induces a proper)
+ holds by construction, the same way [creed]'s own 476(e) does: every
+ branch below reads only [observed], never a day's admitted
+ commemorations.
+
+ THE PRIORITY ORDER ITSELF was cross-checked against 358 real,
+ individually classifiable entries in the FIUV Ordo's own [praef] column
+ (test/fixtures/fiuv-ordo-2025-2026.sexp, test_fiuv_ordo.ml) spanning the
+ WHOLE liturgical year -- not merely derived from the Latin text in
+ isolation. Two findings the plain text alone would not have settled,
+ both empirically confirmed rather than assumed:
+
+ - 484(b)'s own "exceptis iis Missis quae praefationem propriam de
+ divinis mysteriis vel Personis habent" is NARROWER than every other
+ window's implicit "unless it already has a genuine proper" -- St
+ John the Evangelist (27 December, on {!creed_apostle_slugs}, so his
+ OWN Apostles preface (497) would otherwise apply) is overridden to
+ [Nativity] inside the octave (confirmed: the Ordo's own 27 December
+ entry reads "de Nativ.", not "App."), while St Barnabas/Sts Philip &
+ James/the other Apostles OUTSIDE the octave keep their own Apostles
+ preface even inside another window (Sts Philip & James, 11 May,
+ inside the Easter window: confirmed "App." in the Ordo, not
+ "Pasch."). So [Apostles] is checked AFTER the Nativity window below,
+ but every OTHER title trigger (Holy Cross/Sacred Heart/Christ the
+ King/Trinity/St Joseph/BVM) is checked BEFORE it -- RG 484(b)'s own
+ narrower carve-out, read literally: unreachable on the shipped
+ calendar for the other five (no such feast falls 25 December-5
+ January), so this ordering is defensive for them, not observed live,
+ the same "checked, not merely assumed" discipline
+ {!Precedence_ef.marian_slugs}'s own citation follows elsewhere.
+ - RG 495's own "et votivis" half is live on this engine's data after
+ all, for the ONE office this project already models as a votive-
+ shaped Mass without a votive-Mass DIMENSION (RG 78/91 entry 27, the
+ Saturday Office of the BVM, {!Temporal_ef}'s own [subject = Bvm]
+ tag): confirmed directly (3 January and 10 January 2026, both the
+ Saturday Office, both read "BMV" in the Ordo) -- including on 3
+ January, itself inside the Nativity's own "2 ad 5 ianuarii" de-
+ Tempore window, where BVM still wins, corroborating the same
+ "genuine propria outranks every window" ordering the Apostles
+ finding above established from the opposite direction.
+
+ Good Friday's own printed [praef] text ("comm. Feria VI prima in
+ mense.") was NOT used to check this function's own [None] answer for
+ that day: {!test_fiuv_ordo.ml}'s own F1 (Gloria) already adjudicated
+ this exact date's raw text as unreliable (a copied, not a considered,
+ line -- see that allow-list entry's own citation for the full argument,
+ confirmed against the PDF's own page image, not merely the extracted
+ fixture) -- the same defect, read again, would apply equally to
+ whatever trails "praef." on the identical corrupted line, so this
+ function's [None] rests on RG 28's own "no Mass" structural argument
+ alone (the same argument [creed]/[gloria]/[te_deum] already give for
+ this date), not on any Ordo corroboration. *)
+
+(* RG 487(a): the two GENUINE fixed-date feast triggers in the shipped
+ universal calendar -- the Exaltation of the Holy Cross (14 September)
+ and the Most Precious Blood (1 July), both [subject = Lord]. RG 487(a)'s
+ own further-named categories ("de Passione Domini et instrumentis
+ Passionis Domini, de Ss.mo Redemptore") have NO corresponding entry
+ anywhere in data/ef/sanctoral.sexp (checked directly, grepping for
+ "instrument"/"redeem": zero hits) -- genuinely absent from the shipped
+ 1962 universal calendar, not merely unmatched by this list, so they are
+ N/A rather than silently unreachable. *)
+let preface_holy_cross_slugs =
+ [ "exaltation-of-the-holy-cross"; "precious-blood-of-our-lord-jesus-christ" ]
+
+(* RG 496: the two St Joseph feasts in the shipped calendar (19 March, 1
+ May) -- both [subject = Saint], so (unlike RG 495's own BVM feasts) no
+ [subject]-based fallback exists or is needed; this closed list is the
+ whole of what RG 496 can ever reach on shipped data. *)
+let preface_st_joseph_slugs = [ "joseph-spouse-of-the-bl-virgin-mary"; "joseph-the-workman" ]
+
+(* RG 497's own [Apostolorum et Evangelistarum] population is WIDER than
+ {!creed_apostle_slugs}: RG 475(e) is restricted to a NATALICIUM ("festis
+ NATALICIIS Apostolorum...", that module's own citation), but RG 497 has
+ no such restriction at all ("in Missis festivis et votivis Apostolorum
+ et Evangelistarum" -- ANY festive/votive Mass of an Apostle or
+ Evangelist). FOUND, not assumed: the FIUV Ordo's own 30 June entry
+ ("In Commemoratione S. Pauli Ap.", data/ef/adjustments.sexp's own RG
+ 110(c) [Add], {!Precedence_ef}'s own citation -- a genuine [Feast]-
+ status office of Paul the Apostle, but NOT his own dies natalis, so
+ {!creed_apostle_slugs} deliberately excludes it) reads "App. I", not
+ "comm." -- checked directly while building this comparison, not
+ guessed. [creed_apostle_slugs] itself is UNCHANGED (RG 475(e)'s own
+ narrower "natalicium" reading still holds for the Creed); this is a
+ SEPARATE, wider list for RG 497 alone.
+
+ "conversion-of-st-paul" (25 January, Class3, the SAME "not a
+ natalicium" shape {!creed_apostle_slugs}'s own citation excludes it
+ for) is a SECOND member, by the identical RG 497 reasoning --
+ NOW WITNESSED (Preface-witnesses task, 2026-08-23): unlike
+ {!test_fiuv_ordo.ml}'s own single-year window (where 25 January falls
+ on a Sunday, hence impeded), the Latin Mass Society Ordo's own THREE
+ editions (test/test_lms_ordo.ml) each carry an UNIMPEDED 25 January --
+ two of them fall outside a Sunday (2024, 2025) and both read "Pr of
+ the Apostles" for the Conversion of St Paul, confirmed directly
+ against two INDEPENDENT civil years, not merely two printings of the
+ same one (the third, 2026, is impeded by a Sunday exactly like FIUV's
+ own witness year -- the same date, the same reason, an entirely
+ different underlying calendar fact confirmed twice over, not a
+ coincidence). Added on this evidence.
+
+ A FOURTH and FIFTH informal confirmation, from a SECOND, independent
+ publisher (extraordinaryform.org's own three annual PDFs,
+ docs/research/ordo/2024-2025Ordo.pdf / 2025-2026Ordo.pdf /
+ 2026-2027Ordo.pdf -- characterised, not wired in as an automated layer;
+ see the Preface-witnesses task's own report for why): its own per-day
+ PREFACE table column reads "Apostles" for the Conversion of St Paul on
+ BOTH of its own unimpeded years, 2025-01-25 and 2027-01-25 (its middle
+ edition's 2026-01-25 is impeded by a Sunday, the identical fact the
+ other two sources already independently establish) -- a spot check,
+ not a fixture, but a real cross-publisher agreement on the exact
+ question this list answers.
+
+ SURVEYED, not merely patched: every other Apostle/Evangelist-named
+ slug in data/ef/sanctoral.sexp was checked against this same
+ three-edition LMS witness before deciding this list needed exactly
+ one addition, not more:
+ - "dedication-of-the-basilicas-of-sts-peter-paul" (18 November,
+ Class3) is Peter and Paul's own BASILICAS, not the Apostles
+ themselves -- confirmed NOT an RG 497 trigger in all three LMS
+ editions, identical across all three ("Pr of the Dedication of a
+ Church or Common Pr"), corroborating {!preface_bvm_slugs}'s own
+ "Dedication of a Church" finding rather than RG 497: this feast's
+ own subject is the building, governed by the SAME non-RG-482
+ "extra" preface every other church dedication uses (RG 91 entry
+ 27's own header has the fuller account of this Ordo's option-list
+ shape).
+ - "vigil-of-sts-peter-paul" (28 June) reads "Common Pr" in the LMS
+ Ordo (2024-2025 edition, checked directly) -- confirming, not
+ merely assuming, this branch's own long-standing "no [is_vigil]
+ guard needed, no entry here is ever a vigil slug" comment below:
+ a vigil is not a "festum... votiva" either, the identical RG 21/35
+ taxonomy {!preface_bvm_slugs}'s own [is_vigil] guard already
+ states for the BVM branch above.
+ - No other Peter/Paul/John/Andrew/James/Philip/Bartholomew/Thomas/
+ Matthew/Jude/Simon/Matthias/Mark/Luke/Barnabas-named slug in the
+ shipped data names an Apostle or Evangelist at all (checked by
+ grepping every slug in data/ef/sanctoral.sexp against those ten
+ names): the rest are unrelated same-named saints (e.g. "mark-i", a
+ Pope, {!creed_apostle_slugs}'s own citation; "sts-john-paul", two
+ Roman martyrs unrelated to the Evangelist and the Apostle) or
+ genuinely unreachable RG 110 companions ("peter"/"paul",
+ [Commemoration_only], {!creed_apostle_slugs}'s own citation). *)
+let preface_apostle_slugs =
+ "in-commemoratione-sancti-pauli-apostoli" :: "conversion-of-st-paul" :: creed_apostle_slugs
+
+(* RG 495's own [beatae Mariae Virginis] population is also WIDER than
+ {!Precedence_ef.marian_slugs}: that list was built for a DIFFERENT
+ rubric (RG 112(d), whether a commemoration invokes HER OWN
+ intercession specifically) with a correspondingly narrower, oration-
+ checked standard, and its own citation explicitly EXCLUDES "dedication-
+ of-the-basilica-of-st-mary-major" (5 August) for exactly that reason --
+ "whose own oration could not be found... to confirm it invokes her
+ intercession". RG 495 asks a different, WIDER question ("is this Mass
+ festive or votive OF the Blessed Virgin Mary at all"), which the
+ Dedication of St Mary Major answers on its own title alone, without
+ needing the oration-level standard RG 112(d) requires. FOUND, not
+ assumed: the FIUV Ordo's own 5 August entry reads "BMV Et te in
+ Festivitate.", not "comm." -- checked directly. *)
+let preface_bvm_slugs = "dedication-of-the-basilica-of-st-mary-major" :: Precedence_ef.marian_slugs
+
+let preface ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
+ ~(observed : Vocab_ef.rank Celebration.t) ~(date : Date.t) : Preface.t option =
+ let easter = Computus.gregorian_easter (Date.year date) in
+ let n = Date.to_rata date - Date.to_rata easter in
+ let m = Date.month date and dd = Date.day date in
+ let slug = Slug.to_string observed.Celebration.slug in
+ if
+ (* RG 28-34/RG 23(b), the same structural "no Mass at all" position
+ [creed]'s own vigil comment and [gloria]'s own 432(d) comment both
+ take for Good Friday specifically: the 1955-restored Holy Week has
+ no Mass whatsoever that day (only the afternoon liturgical action),
+ so there is no Mass to preface. Checked ahead of the
+ {!Colour.Black} Requiem proxy immediately below -- unlike All
+ Souls, Good Friday sharing that colour is coincidental, not
+ diagnostic (temporal_ef.ml's own RG 132 citation), and the two need
+ DIFFERENT answers here (unlike [creed]/[gloria]/[te_deum], where
+ both collapse to the same boolean) -- so this function cannot reuse
+ their shared single guard and must split Good Friday out first. *)
+ n = -2
+ then None
+ else if
+ (* RG 499: "in Missis defunctorum" -- the same {!Colour.Black} proxy
+ [creed]'s own 476(f), [te_deum]'s own 238(d) and [gloria]'s own
+ 432(d) already use (this file's own header has the full argument
+ and the two-member population this proxy rests on). With Good
+ Friday split out above, the one remaining member is All Souls. *)
+ observed.Celebration.colour = Colour.Black
+ then Some Preface.Requiem
+ else if
+ (* RG 487(a)'s own fixed-feast half -- checked first among the title
+ triggers per this file's own header (arbitrary among these six,
+ since none can ever co-occur with another on shipped data; Holy
+ Cross is placed first only because it is also the anchor for the
+ Passiontide WINDOW checked later below, keeping both citations
+ adjacent in this file). *)
+ List.mem slug preface_holy_cross_slugs
+ then Some Preface.Holy_cross
+ else if
+ (* RG 491: "in Missis festivis... de Ss.mo Corde Iesu" -- the Friday
+ after the Octave of Corpus Christi (Easter+68), {!Temporal_ef}'s own
+ named slug. Confirmed against the Ordo (12 June 2026: "de Ss.mi
+ Corde Iesu"). *)
+ slug = "ef-sacred-heart"
+ then Some Preface.Sacred_heart
+ else if
+ (* RG 492: "in Missis festivis... de D. N. Iesu Christo Rege" -- the
+ last Sunday of October, {!Temporal_ef.christ_the_king}'s own named
+ slug. Confirmed against the Ordo (25 October 2026: "de Domino
+ Nostro Jesu Rege"). *)
+ slug = "ef-christ-the-king"
+ then Some Preface.Christ_the_king
+ else if
+ (* RG 490(a): "in festo Ascensionis Domini" -- the feast itself
+ (Easter+39), checked here by slug rather than folded into the
+ Ascension WINDOW below (which starts the day AFTER, Easter+40):
+ Ascension Day itself needs no window at all, its own slug already
+ identifies it uniquely. Confirmed against the Ordo (14 May 2026:
+ "Ascensionis, Communic pr."). *)
+ slug = "ef-ascension"
+ then Some Preface.Ascension
+ else if
+ (* RG 494(a): "in Missis de festo... Ss.mae Trinitatis" -- Trinity
+ Sunday itself (Easter+56), {!Temporal_ef}'s own named slug. Checked
+ ahead of 494(b)'s own WIDER de-Tempore grant (checked last below,
+ after every other window) for the same "specific propria before any
+ season fallback" reason every other title trigger is. Confirmed
+ against the Ordo (31 May 2026: "Trinit. II"). *)
+ slug = "ef-trinity"
+ then Some Preface.Trinity
+ else if
+ (* RG 496: see {!preface_st_joseph_slugs}'s own citation. *)
+ List.mem slug preface_st_joseph_slugs
+ then Some Preface.St_joseph
+ else if
+ (* RG 495: "in Missis festivis et votivis beatae Mariae Virginis" --
+ {!preface_bvm_slugs} (its own citation has the full account of why
+ it is wider than {!Precedence_ef.marian_slugs}) covers every
+ genuine Marian FEAST; [subject = Bvm] covers the one VOTIVE-shaped
+ office this engine models without a votive-Mass dimension of its
+ own (RG 78/91 entry 27, the Saturday Office of the BVM -- this
+ file's own header has the empirical confirmation, 3/10 January
+ 2026). The Purification is deliberately ABSENT from both:
+ {!Precedence_ef.marian_slugs} already excludes it by name (its own
+ citation), and it never carries [subject = Bvm] (tagged [Lord]
+ instead, register §6.0) -- RG 495's own "praeterquam in festo
+ Purificationis" exclusion therefore holds by construction, not by
+ a guard written here.
+
+ [not (is_vigil slug)]: RG 495's own "festivis" reads "festum", not
+ "vigilia" -- the SAME RG 21/35 taxonomy distinction {!creed}'s own
+ RG 28-34 comment already makes ("a vigil is its OWN liturgical-day
+ category, distinct from 'festum'"), applied here for the first
+ time in THIS function because it is the first branch a vigil can
+ actually reach: {!Precedence_ef.marian_slugs} includes
+ "vigil-of-the-assumption" (that list's own citation), which without
+ this guard would wrongly claim [Bvm] for 14 August. FOUND, not
+ assumed: the FIUV Ordo's own 14 August entry reads "comm. I", not
+ "BMV" -- checked directly, the same as every other finding in this
+ branch's own history. Corroborates, from the opposite direction,
+ {!creed}'s own RG 28-34 comment: colitur's own Nativity WINDOW
+ below already excludes 24 December (the Nativity Vigil) by
+ construction (it starts at 25 December, never 24th), so this guard
+ makes the SAME "a vigil is not a festum" answer explicit here too,
+ rather than relying on a second, unrelated accident of a date
+ range to produce it. *)
+ (not (Precedence_ef.is_vigil slug))
+ && (List.mem slug preface_bvm_slugs || observed.Celebration.subject = Subject.Bvm)
+ then Some Preface.Bvm
+ else if
+ (* RG 484(a)'s own explicit Purification clause ("necnon in festo
+ Purificationis B. Mariae Virg.") -- 2 February, nowhere near the
+ Nativity's own Christmas-to-Epiphany calendar position, so this is
+ a standalone slug check, not part of the WINDOW test below (unlike
+ every other 484 trigger, which IS date-based). Checked here, after
+ the BVM check immediately above (which the Purification's own
+ [subject = Lord] tag never reaches) and before the Nativity window
+ (which its own actual date, 2 February, never reaches either) --
+ positioned with the rest of 484's own citations for readability,
+ not because anything below could otherwise pre-empt it. *)
+ slug = "purification-of-the-blessed-virgin-mary"
+ then Some Preface.Nativity
+ else if
+ (* RG 484(a)/(b) merged, per this file's own header: 25 December-1
+ January (the Nativity itself and its octave, propria) UNION 2-5
+ January (498(b)'s own explicit extra de-Tempore days) -- one
+ contiguous window, since both halves produce the identical
+ preface. Checked BEFORE Apostles (below) but AFTER every genuine
+ "divine mysteries/Persons" propria above, per this file's own
+ header (St John the Evangelist, 27 December, is the live witness:
+ Apostles would otherwise apply and does not). *)
+ (m = 12 && dd >= 25) || (m = 1 && dd <= 5)
+ then Some Preface.Nativity
+ else if
+ (* RG 497: "in Missis festivis et votivis Apostolorum et
+ Evangelistarum" -- {!preface_apostle_slugs} (its own citation has
+ the full account of why it is wider than {!creed_apostle_slugs}),
+ confirmed by this file's own header to produce the SAME preface
+ answer as the Ordo on every Apostle date outside the Nativity
+ octave: 11 June (Barnabas), 29-30 June (Peter & Paul, In
+ Commemoratione Pauli), 11 May (Philip & James, RG 484(b)'s own
+ witness against the Nativity window immediately above). Checked
+ AFTER the Nativity window specifically (RG 484(b)'s own narrower
+ carve-out), but before every OTHER season window below -- an
+ Apostle feast keeps his own preface inside Lent, Passiontide,
+ Paschaltide etc., where nothing narrows the exception the way
+ 484(b) does. No [is_vigil] guard is needed here the way RG 495's
+ own branch above needs one: checked directly, no entry on
+ {!preface_apostle_slugs} is ever a vigil slug (every Apostle vigil
+ in the shipped data -- "vigil-of-sts-peter-paul" -- carries its own
+ distinct slug, absent from this list). *)
+ List.mem slug preface_apostle_slugs
+ then Some Preface.Apostles
+ else if
+ (* RG 485(a): "in Missis de festo Epiphaniae et de Commemoratione
+ Baptismatis D. N. Iesu Christi" -- the feast itself and its own
+ named commemoration (13 January, {!Precedence_ef}'s own
+ "commemoration-of-the-baptism-of-the-lord" -- {!creed}'s own 475(c)
+ comment already documents this entry's [subject = Lord] tag), both
+ checked by slug so 485(b)'s own WIDER window below need not repeat
+ them. *)
+ slug = "ef-epiphany" || slug = "commemoration-of-the-baptism-of-the-lord"
+ then Some Preface.Epiphany
+ else if
+ (* RG 485(b): "diebus a 7 ad 13 ianuarii" -- every OTHER Mass in this
+ window (Holy Family Sunday, an ordinary Time-after-Epiphany feria
+ or Sunday, a saint's feast with no propria of its own), confirmed
+ against the Ordo's own 11 January 2026 entry (Holy Family Sunday:
+ "de Epiphania. II", not a Holy-Family-specific preface -- this
+ engine has none to offer it anyway). Colitur's own Christmastide
+ season already spans 25 December-13 January (RG 72-73,
+ {!Vocab_ef.season}'s own citation), so this window is exactly its
+ OWN post-Epiphany tail; written as an explicit date range rather
+ than a season test only because the Nativity window above already
+ claims the season's FIRST half by date, not by season either, for
+ symmetry. *)
+ m = 1 && dd >= 6 && dd <= 13
+ then Some Preface.Epiphany
+ else if
+ (* RG 486(a)/(b) merged: Ash Wednesday (Easter-46) through the
+ Saturday before Passion Sunday I (Easter-15) inclusive -- every
+ Lenten feria/Sunday's own Mass (a), and every OTHER Mass in the
+ same span lacking a proper of its own (b). Confirmed against the
+ Ordo throughout (e.g. 18 February/19-20 February 2026: "Quadr.").
+ {!creed}'s own RG 23 comment already explains why Ash Wednesday
+ (feria I classis) reaches this branch on [observed]'s own terms
+ regardless of rank -- this function reads no rank at all here,
+ only the date. *)
+ n >= -46 && n <= -15
+ then Some Preface.Lent
+ else if
+ (* RG 487(a)/(b) merged: Passion Sunday I (Easter-14) through Holy
+ Thursday (Easter-3) inclusive -- (a)'s own "de tempore"/festive-
+ votive half extends through Holy Thursday itself (the Mass of the
+ Lord's Supper), (b)'s own narrower saint-Mass half stops one day
+ earlier (Holy Wednesday) but reaches no LIVE day this check does
+ not already cover identically (Holy Thursday is a feria I classis,
+ RG 23(b), so no saint's feast can ever occupy it -- {!creed}'s own
+ RG 23 citation). Confirmed against the Ordo throughout (22 March
+ 2026, Passion Sunday: "de Sancta Cruce."; 2 April 2026, Holy
+ Thursday: "de Sancta Cruce, Communicantes..."). *)
+ n >= -14 && n <= -3
+ then Some Preface.Holy_cross
+ else if
+ (* RG 489(a)/(b) merged: the Easter Vigil Mass (Easter-1, on Holy
+ Saturday's own date) through the vigil of the Ascension (Easter+38)
+ inclusive. [n = -1] is this engine's own OVERLOADED representation
+ of "the Vigil Mass", not Holy Saturday's daytime (which has no Mass
+ of its own at all, unlike Good Friday's [n = -2] this function
+ excludes by name above) -- [gloria]'s own RG 431(c) comment already
+ establishes the same convention for the identical date, and RG
+ 489(a) resolves the question on its own terms regardless: the
+ Paschal preface's window STARTS at the Vigil Mass, so [n = -1] is
+ correctly [Easter]. Confirmed against the Ordo throughout (5 April
+ 2026, Easter Sunday: "Pasch."; 13 May 2026, the Ascension Vigil:
+ "Pasch. I"); the fixture prints nothing at all for Holy Saturday's
+ own daytime square (4 April 2026), corroborating rather than
+ contradicting this reading -- see test_fiuv_ordo.ml's own citation. *)
+ n >= -1 && n <= 38
+ then Some Preface.Easter
+ else if
+ (* RG 490(b): "a feria VI post Ascensionem usque ad feriam VI ante
+ vigiliam Pentecostes" -- the Friday after Ascension (Easter+40)
+ through the Friday before the Pentecost vigil (Easter+47)
+ inclusive; Ascension Day itself (Easter+39) is already handled by
+ its own slug check above, not repeated here. Confirmed against the
+ Ordo throughout (15-22 May 2026: "Ascensionis"). *)
+ n >= 40 && n <= 47
+ then Some Preface.Ascension
+ else if
+ (* RG 493(a)/(b) merged: the vigil of Pentecost (Easter+48) through
+ "subsequens sabbatum" (the FOLLOWING Saturday, i.e. the Ember
+ Saturday within the Octave of Pentecost, Easter+55) inclusive.
+ Confirmed against the Ordo throughout (23-24 May 2026, the vigil
+ and Pentecost itself: "de Spirito Sancto"; 30 May 2026, the Ember
+ Saturday: "de Spirito Sancto"). *)
+ n >= 48 && n <= 55
+ then Some Preface.Holy_spirit
+ else if
+ (* RG 494(b): "in dominicis Adventus, et in omnibus dominicis II
+ classis, extra tempus natalicium et paschale". Read off
+ [temporal]'s own season, NOT [observed]'s rank -- CORRECTED from an
+ earlier version of this branch that DID read [observed.rank] and
+ required it to equal [Class2], which is wrong for the identical RG
+ 16(a) reason {!Precedence.rules.admit}'s own [~temporal] parameter
+ exists and [creed]'s own 237(b)/475(a) comments already give: a
+ feast that has WON the day can carry a different rank than the
+ Sunday it stands on. FOUND, not assumed: All Saints' Day (1
+ November), Class1, observed outright over an ordinary
+ Time-after-Pentecost Sunday it commemorates
+ ([+ef-time-after-pentecost-sunday-23]), reads "Trinit." in the
+ Ordo -- [observed.rank] there is [Class1], so the OLD guard wrongly
+ answered [Common]; there is no dedicated preface for All Saints
+ among RG 484-497's own fourteen, so RG 482's chain correctly falls
+ through to the SUNDAY's own de-Tempore grant regardless of which
+ rank actually won the day.
+
+ {!Temporal_ef.temporal}'s own [match s with Advent | Lent -> Class1
+ | _ -> Class2] means EVERY Sunday's own TEMPORAL identity is
+ [Class2] except in Advent and Lent -- so "in omnibus dominicis II
+ classis" and "in dominicis Adventus" collapse into ONE test, "any
+ Sunday outside Christmastide and Paschaltide" (Lent's own Sundays
+ need no explicit exclusion here: {!creed}'s own RG 23/Lent-window
+ reasoning already means every one of them is claimed by the LENT
+ window earlier in this very priority chain, provably unreachable
+ here, the same "checked, not merely assumed" position the previous
+ version of this comment already took for Christmastide/Paschaltide
+ -- confirmed by the SAME domain sweep in test_rubrics_ef.ml, which
+ still finds zero Christmastide/Paschaltide/Lent days reaching this
+ branch after this change). Confirmed against the Ordo throughout
+ (e.g. every Advent/Time-after-Epiphany/Septuagesima/Time-after-
+ Pentecost Sunday not otherwise claimed: "Trinit."), now including
+ All Saints' Day itself. *)
+ temporal.Temporal.weekday = Date.Sun
+ && temporal.Temporal.season <> Vocab_ef.Christmastide
+ && temporal.Temporal.season <> Vocab_ef.Paschaltide
+ then Some Preface.Trinity
+ else
+ (* RG 498: "in Missis quae praefatione propria carent, nec sumere
+ debent praefationem de Tempore" -- everything else: an ordinary
+ weekday feria outside every window above, a plain sanctoral saint
+ with no title of his own, an ordinary (non-Sunday, non-Class2, or
+ Christmastide/Paschaltide) day. Confirmed against the Ordo
+ throughout (the single most common value in the fixture, 188 of
+ 360 comparable rows). *)
+ Some Preface.Common
diff --git a/lib/rites/rite_ef/rubrics_ef.mli b/lib/rites/rite_ef/rubrics_ef.mli
new file mode 100644
index 0000000..c005aaf
--- /dev/null
+++ b/lib/rites/rite_ef/rubrics_ef.mli
@@ -0,0 +1,107 @@
+(** RG 475-476 (docs/research/LT.txt, grep "dicitur symbolum") -- whether the
+ Creed is said at Mass. The first rubric in this phase governing a PART OF
+ MASS rather than occurrence/precedence, hence its own module: see the
+ .ml's own header for the rubric quoted in full and every branch's
+ citation. *)
+
+open Colitur_kernel
+
+(** RG 475(e)'s "festis nataliciis Apostolorum et Evangelistarum, necnon in
+ festis Cathedrae S. Petri et S. Barnabae Ap." -- see the .ml's own
+ citation for how each entry was derived from and verified against
+ data/ef/sanctoral.sexp, and for what was deliberately excluded. Exposed
+ so the test suite can assert completeness against the shipped data the
+ same way {!Precedence_ef.vigil_feast_table} already lets it. *)
+val creed_apostle_slugs : string list
+
+(** Whether the Creed is said, post-Gospel/homily, at the Mass this day
+ resolves to.
+
+ [temporal] is read for exactly one fact -- RG 475(a)'s own "in qualibet
+ dominica, ETSI EIUS OFFICIUM ALICUI FESTO LOCUM CEDAT" ("on any Sunday,
+ EVEN WHEN a feast displaces its own Office"): a Sunday impeded by a
+ Feast of the Lord (RG 16(a)) still says the Creed, so this reads the
+ TEMPORAL cycle's own weekday, never [observed]'s. Every other clause
+ reads [observed] -- the celebration whose Mass is actually said -- and
+ [date], read once against the rite's own (Gregorian) Easter for the two
+ Easter-relative window questions (RG 475(d)'s three octaves, RG 23's
+ Ash-Wednesday-and-Holy-Week feriae). [creed] never reads
+ {!Liturgical_day.t.commemorations}: RG 476(e), "ratione alicuius
+ commemorationis in Missa occurrentis" [never say the Creed merely
+ because of a commemoration], holds by construction rather than by a
+ checked branch. RG 476(f) ("in Missis defunctorum") reads
+ [observed]'s [colour]: see the .ml's own header for why [Colour.Black]
+ is a sound proxy for "this Mass is a Requiem" on the shipped data, and
+ what would have to be re-checked if that ever stops being true. *)
+val creed :
+ temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
+ observed:Vocab_ef.rank Celebration.t ->
+ date:Date.t ->
+ bool
+
+(** Whether the Te Deum was said at Matins (Breviary 1961 Codex Rubricarum
+ nn. 237-238) -- NOT the Divine Office arriving in scope, but a single
+ Breviary fact {!gloria}'s own RG 431(a) defers a Mass question to. See
+ the .ml's own header for the rubric quoted in full, every branch's own
+ citation, and this source's own stated weakness (a single, not yet
+ scan-verified, web transcription -- docs/research/breviary/PROVENANCE.md).
+ Same three parameters as {!creed}, for the same reasons: [temporal] for
+ the day's own weekday/season, [observed] for the celebration whose
+ Office is actually kept, [date] for the Easter-relative window
+ questions. *)
+val te_deum :
+ temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
+ observed:Vocab_ef.rank Celebration.t ->
+ date:Date.t ->
+ bool
+
+(** Whether the Gloria in excelsis is said at this day's Mass (RG 431-432).
+ 431(a)/432(a) defer to {!te_deum}; 431(c) (Holy Thursday, the Easter
+ Vigil Mass) and 432(b)/(d) (violet vestments; a Requiem) are
+ independent overrides, checked ahead of the Te Deum-derived answer --
+ see the .ml's own header for the full account, including which of
+ 431/432's own clauses this engine has no votive-Mass-class dimension to
+ implement and are therefore marked not-applicable rather than silently
+ skipped. *)
+val gloria :
+ temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
+ observed:Vocab_ef.rank Celebration.t ->
+ date:Date.t ->
+ bool
+
+(** RG 487(a)'s own two fixed-feast triggers for the Holy Cross preference
+ (the Exaltation of the Holy Cross, the Most Precious Blood) -- see the
+ .ml's own citation for what RG 487(a)'s further-named categories (the
+ Passion/Instruments of the Passion, the Most Holy Redeemer) are absent
+ from the shipped calendar entirely, not merely from this list. *)
+val preface_holy_cross_slugs : string list
+
+(** RG 496's own two St Joseph feasts in the shipped calendar. *)
+val preface_st_joseph_slugs : string list
+
+(** RG 497's own [Apostolorum et Evangelistarum] population -- WIDER than
+ {!creed_apostle_slugs} (no natalicium restriction); includes
+ "conversion-of-st-paul" (Preface-witnesses task, 2026-08-23, four
+ independent unimpeded-year confirmations across two publishers -- see
+ the .ml's own citation for the full evidence and the survey of every
+ other Apostle/Evangelist-named slug that was checked and NOT added. *)
+val preface_apostle_slugs : string list
+
+(** RG 495's own [beatae Mariae Virginis] population -- WIDER than
+ {!Precedence_ef.marian_slugs} (a different, looser standard than that
+ list's own RG 112(d) oration-level one); see the .ml's own citation
+ for the FIUV Ordo evidence. *)
+val preface_bvm_slugs : string list
+
+(** Which preface is said at this day's Mass (RG 482-499). [None] both when
+ this engine resolves a day with no Mass at all (Good Friday) and -- for
+ a rite that has not implemented this function at all -- as the type's
+ own neutral value; see the .ml's own header for the rubric quoted in
+ full, the priority order every branch follows and why, and the FIUV
+ Ordo evidence that order rests on. Same three parameters as {!creed}/
+ {!te_deum}/{!gloria}, for the same reasons. *)
+val preface :
+ temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
+ observed:Vocab_ef.rank Celebration.t ->
+ date:Date.t ->
+ Preface.t option
diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml
index c16732b..fe08b2e 100644
--- a/lib/rites/rite_ef/temporal_ef.ml
+++ b/lib/rites/rite_ef/temporal_ef.ml
@@ -823,12 +823,36 @@ let temporal d =
"ef-rogation-monday"/"-tuesday" are colitur-only keys and a
lectionary gap for Plan 4 (CORRECTED, final fix wave, item 7 --
the lectionary bootstrap is Plan 4, not Plan 3), like the Ember
- and Nativity-vigil keys above. *)
+ and Nativity-vigil keys above.
+
+ COLOUR (L6, data/ef/expected-divergences-lms.sexp, CLOSED by this
+ fix): this branch used to hardcode [Colour.Violet] with no
+ citation at all. RG 119 (Caput XVIII, "De Colore Paramentorum"):
+ white is used "in Officio et Missa de Tempore ... a Missa Vigiliae
+ paschalis usque ad Missam vigiliae Pentecostes exclusive" -- the
+ de-Tempore Office AND Mass are white from the Paschal Vigil Mass
+ until the Pentecost Vigil Mass exclusive, and days 36/37 (Rogation
+ Monday/Tuesday) sit squarely inside that window. RG 128's own
+ exhaustive four-case list for violet (Advent I-Christmas Eve;
+ Septuagesima-Paschal Vigil; the September Ember ferias; vigils of
+ II/III class OUTSIDE Paschaltide) names Rogation days nowhere, and
+ its vigil clause is inapplicable twice over -- these are not
+ vigils, and they ARE inside Paschaltide. RG 88 ("de Litaniis
+ minoribus nihil fit in Officio, sed tantum in Missa") is why: the
+ violet belongs to the ROGATION MASS/procession alone, a distinct
+ votive text this engine does not model as a separate Mass (the
+ same per-action colour nuance temporal_ef.ml's own RG 126/RG 128
+ Good-Friday-Communion citations already flag as unmodelled
+ elsewhere) -- the day's own OFFICE, which is what [colour] here
+ expresses, stays [season_colour s] like any other Paschaltide
+ feria. Witnessed directly: LMS 2023-2024, 2024-05-06 (Rogation
+ Monday), "FERIA IV Cl W" (white), "Mass of 5th Sunday after
+ Easter". *)
let rogation = days_between easter d in
if rogation = 36 || rogation = 37 then
build ~season:s
~slug:(if rogation = 36 then "ef-rogation-monday" else "ef-rogation-tuesday")
- ~colour:Colour.Violet ~rank:(ferial_rank d s) ~week:(week d) ()
+ ~colour:(season_colour s) ~rank:(ferial_rank d s) ~week:(week d) ()
else
match ember d with
| Some (slug, rank, colour) -> build ~season:s ~slug ~colour ~rank ~week:(week d) ()
@@ -1158,6 +1182,68 @@ let anchors y =
| Some d -> [ ("ef-holy-name-sunday", d) ]
| None -> [ ("ef-holy-name", holy_name_fallback_date y) ])
+(* The Missale Romanum's own CALENDARIUM table, February, footnote
+ (docs/research/LT.txt:5011-5014, scan-corroborated at
+ docs/research/scan2.txt:3050-3058, where OCR mangles it to
+ "bi&sextili"/"Matthue"/"Cabrielis" -- transcription and scan agree, so
+ this rests on two witnesses of the same document):
+
+ "In anno bissextili mensis februarius est dierum 29, et festum S.
+ Matthiae celebratur die 25 februarii, ac festum S. Gabrielis a
+ Virgine perdolente 28 februarii, et bis dicitur sexto calendas, id
+ est die 24 et die 25; et littera dominicalis, quae assumpta fuit in
+ mense ianuario, mutetur in praecedentem..."
+
+ In a leap year February has 29 days, St Matthias is kept on 25 February
+ (not 24), St Gabriel of Our Lady of Sorrows on 28 February (not 27), and
+ the sixth kalends of March is said TWICE -- "bis dicitur sexto
+ calendas... die 24 et die 25". That last clause names the MECHANISM, not
+ two independent exceptions: the Roman calendar's intercalary day is
+ inserted by DOUBLING the sixth kalends (civil 24 February in a common
+ year), not by appending a 29th day at the month's end. Reckoned by
+ kalends position: 24 Feb = VI Kal. Mart., 25 Feb = V Kal., 26 Feb = IV
+ Kal., 27 Feb = III Kal., 28 Feb = pridie Kal. In a leap year VI Kal.
+ itself falls on TWO consecutive civil days (24th and 25th, "bis"), and
+ every kalends position after it is pushed one civil day later as a
+ result: V Kal. from 25th to 26th, IV Kal. from 26th to 27th, III Kal.
+ (Gabriel) from 27th to 28th, pridie Kal. from 28th to 29th. A feast fixed
+ at VI Kal. itself (Matthias) is kept on the SECOND occurrence, not the
+ first -- 25 February carries it, 24 February carries no fixed office at
+ all that year (confirmed against the LMS Ordo witness,
+ data/ef/expected-divergences-lms.sexp entry L3: 24 February 2024 is
+ titled plainly "EMBER SATURDAY of LENT", no Matthias anywhere near it).
+
+ IMPLEMENTED AS THE GENERAL MECHANISM, not as "shift these two named
+ feasts": only three sanctoral entries exist in 23-29 February on shipped
+ data (peter-damien the 23rd, before the doubled kalends and therefore
+ unaffected; matthias the 24th; gabriel-of-our-lady-of-sorrows the 27th),
+ so "shift the two named feasts" and "shift every fixed entry from 24
+ February" are indistinguishable on universal data -- they produce
+ identical output. They differ only for a locally-supplied entry, e.g. an
+ [--overlay] patronal feast fixed at 26 February: under the mechanism
+ reading it shifts to 27 February in a leap year (IV Kal. -> the civil day
+ IV Kal. falls on that year), exactly as a diocesan calendar compiled
+ against the same kalends convention would expect. The rubric's own text
+ states a MECHANISM ("bis dicitur sexto calendas"), not a closed list of
+ two saints, and this project has already been bitten once by a rule
+ implemented against the shipped data's coincidental shape rather than the
+ rubric itself (RG 16(a), CLAUDE.md's own carried lesson) -- so the general
+ reading is the one implemented here.
+
+ Threaded through {!Rite.t}'s [fixed_key] field, read only by
+ {!Colitur_kernel.Layer.on_date}'s FIXED half: the kernel stays
+ rite-agnostic (a Byzantine or other non-Roman rite supplies nothing and
+ is unaffected), and the MOVABLE half ({!Date_spec.Easter_offset},
+ {!Date_spec.Nth_weekday}) is untouched -- nothing in the footnote
+ concerns Easter-relative dates. *)
+let bissextile_fixed_key (d : Date.t) : (int * int) option =
+ let m = Date.month d and day = Date.day d in
+ if m = 2 && Date.is_leap (Date.year d) then
+ if day = 24 then None (* the FIRST VI Kal.: no fixed entry lands here in a leap year *)
+ else if day >= 25 && day <= 29 then Some (2, day - 1) (* the SECOND VI Kal. onward, one day later *)
+ else Some (m, day)
+ else Some (m, day)
+
(* Compile-time check that this module satisfies the kernel's rite contract. *)
module _ : Colitur_kernel.Temporal.RITE = struct
let id = id
diff --git a/lib/rites/rite_ef/temporal_ef.mli b/lib/rites/rite_ef/temporal_ef.mli
index 5e85111..2bed5b6 100644
--- a/lib/rites/rite_ef/temporal_ef.mli
+++ b/lib/rites/rite_ef/temporal_ef.mli
@@ -51,6 +51,17 @@ val week : Date.t -> int option
(** The lectionary key for a Sunday, or [None] if [d] is not a Sunday. *)
val sunday_slug : Date.t -> string option
+(** The calendarium's own bissextile (leap-year) footnote, February
+ (docs/research/LT.txt:5011-5014): in a leap year the sixth kalends of
+ March (24 February) is doubled rather than a 29th day appended, so every
+ fixed entry from 24 through 28 February is kept one civil day later than
+ usual (Matthias 24->25, Gabriel of Our Lady of Sorrows 27->28), and 24
+ February itself carries no fixed office that year. Supplied as {!Rite.t}'s
+ [fixed_key] field -- see that field's own doc comment and this function's
+ definition for the full citation and the general-mechanism-vs-two-named-
+ feasts reasoning. *)
+val bissextile_fixed_key : Date.t -> (int * int) option
+
val id : string
(** Total over 1583..9999: every date yields exactly one temporal identity. *)