diff options
Diffstat (limited to 'lib/rites/rite_ef')
| -rw-r--r-- | lib/rites/rite_ef/lectionary_ef.ml | 99 | ||||
| -rw-r--r-- | lib/rites/rite_ef/lectionary_ef.mli | 51 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.mli | 16 | ||||
| -rw-r--r-- | lib/rites/rite_ef/rite_ef.ml | 10 | ||||
| -rw-r--r-- | lib/rites/rite_ef/rite_ef.mli | 11 | ||||
| -rw-r--r-- | lib/rites/rite_ef/rubrics_ef.ml | 1244 | ||||
| -rw-r--r-- | lib/rites/rite_ef/rubrics_ef.mli | 107 | ||||
| -rw-r--r-- | lib/rites/rite_ef/temporal_ef.ml | 90 | ||||
| -rw-r--r-- | lib/rites/rite_ef/temporal_ef.mli | 11 |
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. *) |
