aboutsummaryrefslogtreecommitdiff
path: root/lib/rites
diff options
context:
space:
mode:
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. *)