aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml215
1 files changed, 180 insertions, 35 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
index 0feb3e0..fd83383 100644
--- a/lib/rites/rite_ef/precedence_ef.ml
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -244,15 +244,60 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc
plan's scope -- see calendar.mli's own note that nothing in the EF
ruleset currently emits it. *)
-(* RG 33 (register line 383-384): a I- or II-class vigil falling on any
- Sunday or a I-class feast is entirely omitted. Every Sunday slug this
- rite's temporal cycle produces -- named (temporal_ef.ml's [named], e.g.
- "ef-easter-sunday") or the generic "ef-<season>-sunday-<n>" fallback
- ([sunday_slug]) -- contains this marker; nothing else [band] classifies
- does. Not an RG citation itself -- see [universal_layer]'s note on this
- file's own naming conventions -- exposed for the same reason as
- {!vigil_suffix}: a future rename of temporal_ef's Sunday-slug format has
- somewhere to be caught other than a silently-wrong RG 33 disposition. *)
+(* RG 33 -- CORRECTED 2026-08-12 (Task 16, primary-source-verified against
+ docs/research/1962-06-23,_SS_Ioannes_XXIII,_Missale_Romanum,_LT.pdf, the
+ General Rubrics' own Chapter XI "De Vigiliis"). The register previously
+ transcribed this as "a I/II-class vigil is entirely omitted"; the
+ PRIMARY TEXT reads the other way round:
+
+ "33. Vigilia II aut III classis penitus omittitur, si occurrat in
+ dominica quavis, aut in festo I classis, vel si festum cui
+ præmittitur in alium diem transferri aut ad commemorationem reduci
+ contingat."
+
+ -- "A vigil of the II OR III class is entirely omitted, if it occurs on
+ ANY Sunday whatsoever, or on a feast of the I class, or if the feast it
+ precedes happens to be transferred to another day or reduced to a
+ commemoration." I-class vigils (Nativity, Pentecost, RG 30) are outside
+ this rule entirely -- RG 30's own text says they "festis quibuslibet
+ præferunt, et nullam admittunt commemorationem" (are preferred to ANY
+ feast whatsoever, and admit no commemoration at all), i.e. they can never
+ lose in the first place: {!band} entries 1/5/9 already rank Nativity Eve
+ and the Pentecost Vigil above every Sunday and every other I-class row
+ that could coincide with their fixed/Easter-relative dates (verified: no
+ date collision is even representable), so no I-class vigil can ever reach
+ this function as a [loser] -- the branch below never needs to test for
+ [Class1] and, before this fix, its stray inclusion of [Class1] here was
+ simply dead code, not a second bug (see the task report for the
+ argument). The bug was the OTHER half: [Class3] (the sole III-class
+ vigil, St Lawrence, RG 32) was MISSING from this branch, so it fell
+ through to the generic "commemorated or omitted" branch below instead of
+ RG 33's mandatory omission -- confirmed wrong for real data: 9 August
+ 2026 is a Sunday, and before this fix "vigil-of-st-lawrence" competed for
+ (and could in principle win) that Sunday's single commemoration slot,
+ when RG 33 says it must not even be a candidate. The oracle comparison
+ (missalemeum, Task 16) independently confirms: 9 Aug 2026 shows no trace
+ of the vigil surviving as a commemoration.
+
+ The third omission trigger in RG 33's own text -- "or if the feast it
+ precedes is transferred to another day or reduced to a commemoration" --
+ is NOT implemented: no II/III-class vigil's own feast (Ascension,
+ Assumption, John Baptist, Sts Peter & Paul, Lawrence) is ever
+ transferred or reduced to a commemoration anywhere in this codebase's
+ current data (all fixed I-class, none coincide with anything of equal or
+ higher rank within any year this project has sampled), so no witness
+ exists to build or test this clause against; flagged in the register
+ (§6) rather than guessed. *)
+let is_omissible_vigil (rank : Vocab_ef.rank) = rank = Vocab_ef.Class2 || rank = Vocab_ef.Class3
+
+(* Every Sunday slug this rite's temporal cycle produces -- named
+ (temporal_ef.ml's [named], e.g. "ef-easter-sunday") or the generic
+ "ef-<season>-sunday-<n>" fallback ([sunday_slug]) -- contains this
+ marker; nothing else [band] classifies does. Not an RG citation itself --
+ see [universal_layer]'s note on this file's own naming conventions --
+ exposed for the same reason as {!vigil_suffix}: a future rename of
+ temporal_ef's Sunday-slug format has somewhere to be caught other than a
+ silently-wrong RG 33 disposition. *)
let sunday_marker = "-sunday"
let contains_substring s ~needle =
@@ -372,6 +417,7 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
~(loser : Vocab_ef.rank Precedence.candidate) : Precedence.disposition =
let open Vocab_ef in
let cel = loser.Precedence.cel in
+ let is_temporal = loser.Precedence.origin = Precedence.Temporal in
if cel.Celebration.status = Celebration.Commemoration_only then
(* Always -- checked before RG 33's omission and RG 95's transfer so
neither can override it: a Commemoration_only entry can never win
@@ -384,13 +430,17 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
it would to any other candidate. *)
Precedence.Commemorate (privilege_of loser)
else if
- (cel.Celebration.rank = Class1 || cel.Celebration.rank = Class2)
+ is_omissible_vigil cel.Celebration.rank
&& is_vigil (Slug.to_string cel.Celebration.slug)
&& impedes_vigil winner
then
- (* RG 33. Checked before the generic Class1 -> Transfer rule below, or a
- I-class vigil (Nativity, Pentecost) impeded on its own Sunday/
- I-class-feast terms would wrongly transfer instead of vanishing. *)
+ (* RG 33, corrected (see {!is_omissible_vigil}'s own comment): II- or
+ III-class vigils only -- a real I-class vigil can never reach this
+ function as a loser at all (see that comment), so this branch would
+ never have fired for [Class1] even before the fix; what changed is
+ that [Class3] (St Lawrence) now correctly reaches RG 33's omission
+ instead of falling through to the generic "commemorated or omitted"
+ branch below. *)
Precedence.Omit
else if
cel.Celebration.rank = Class1
@@ -416,19 +466,93 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
Rite.transfer_target's job, not this function's -- disposition only
says THAT it moves. *)
Precedence.Transfer
+ else if
+ is_temporal
+ && (not (is_vigil (Slug.to_string cel.Celebration.slug)))
+ && (match privilege_of loser with Precedence.Ordinary -> true | Precedence.Privileged -> false)
+ then
+ (* Task 16, primary-source-verified (RG 93, 95, 109, 113): an ordinary,
+ NON-privileged TEMPORAL-cycle office has no standing to be
+ commemorated at all when impeded -- it is simply omitted, not the
+ "commemorated or omitted, per rubric" residual RG 95 leaves open for
+ everything else. Three primary texts read together settle this:
+
+ - RG 95: "Alia festa, ab Officio gradus superioris accidentaliter
+ impedita, AUT COMMEMORANTUR AUT, eo anno, PENITUS OMITTUNTUR, IUXTA
+ RUBRICAS" -- impeded offices are "either commemorated or, that
+ year, entirely omitted, ACCORDING TO THE RUBRICS" -- i.e. some
+ OTHER rule decides which fate applies; RG 95 itself does not grant
+ a commemoration to everything impeded.
+ - RG 109 gives that other rule for the temporal cycle: an EXHAUSTIVE,
+ closed six-item list of the only temporal-origin circumstances that
+ ever generate a commemoration -- (a) of a Sunday; (b) of a I-class
+ day; (c) of days within the Nativity Octave; (d) of the September
+ Ember days; (e) of Advent/Lent/Passiontide ferias; (f) of the Major
+ Rogations. [privilege_of] above already implements exactly this
+ list (its own six branches, each cited to its own RG 109 letter);
+ its terminal "[else Precedence.Ordinary]" is what a TEMPORAL-origin
+ candidate falls through to when it matches NONE of (a)-(f) -- an
+ ordinary green-season feria of Time after Epiphany/Pentecost/
+ Easter, a plain (non-Ember) Advent/Lent weekday already caught by
+ (e), or a Minor Rogation day (RG 87 -- deliberately NOT named by
+ RG 109(f), see [privilege_of]'s own comment on that letter).
+ - RG 113: "Commemoratio de Tempore fit primo loco" -- the
+ commemoration OF THE TEMPORAL DAY is made FIRST [in the list, when
+ one is due] -- presupposes RG 109 already answered whether one is
+ due; it does not itself create a right for every impeded feria.
+
+ So testing [privilege_of loser = Ordinary] here, for a TEMPORAL-origin
+ loser specifically, is not a second, parallel "is this commemorable"
+ predicate that could drift from RG 109's own list -- it IS RG 109's
+ list, already computed by [privilege_of] for the commemoration this
+ branch is about to deny. SANCTORAL losers are entirely unaffected
+ (the [is_temporal] guard): RG 111(c)/(d) admit an "ordinary"
+ commemoration of a losing SAINT freely, with no such closed-list
+ gate -- this omission is specific to the temporal cycle's own
+ ferial/Sunday-tail offices, never to a saint.
+
+ Empirically confirmed against the missalemeum oracle (Task 16,
+ 2026-2027, both years): every one of ~190 days where a saint's feast
+ impedes an ordinary (non-privileged) temporal feria shows ZERO
+ commemorations in the oracle, including the exact shape this fixes
+ (e.g. "St. Marcellus I" impeding the plain "Friday after Epiphany",
+ 6/730 identical instances of the pattern per week of ordinary time)
+ -- and the SAME fix, for the same reason, independently corrects the
+ Minor Rogation days (RG 87) losing to a saint (9/730 days), which
+ [privilege_of]'s own (f) comment already flags as NOT RG 109(f).
+
+ [is_vigil] is EXCLUDED from this branch deliberately: a II/III-class
+ vigil is temporal-origin too (when it is the Ascension/Pentecost-
+ adjacent case {!of_temporal} produces) and [privilege_of] rightly
+ calls it [Ordinary] (a vigil is none of RG 109(a)-(f)), but vigils
+ are NOT governed by RG 109 at all -- they carry their OWN, separate,
+ explicit commemoration mandate: RG 31 (II class) "Hae vigiliae
+ praeferuntur diebus liturgicis III et IV classis; ET, SI
+ IMPEDIUNTUR, COMMEMORANTUR, iuxta rubricas" and RG 32 (III class, St
+ Lawrence) "si impeditur, COMMEMORATUR, iuxta rubricas" -- "if
+ impeded, ARE/IS commemorated". So a vigil impeded WITHOUT triggering
+ RG 33's full omission (the [is_omissible_vigil] branch above, e.g.
+ impeded by an ordinary sanctoral feast that is neither a Sunday nor
+ I class) must still fall through to the final [Commemorate] branch
+ below, exactly like a sanctoral loser -- RG 31/32's own text, not
+ RG 109's closed list, is what governs it. *)
+ Precedence.Omit
else
(* RG 95's other branch: "aut commemorantur aut penitus omittuntur" --
- commemorated or wholly omitted. Reached by everything below I class,
- AND by an impeded I-class Sunday (excluded from the [Transfer] branch
- above): RG 109(a) (register line 374) lists "of a Sunday" as a
- privileged commemoration category, which presupposes an impeded
- Sunday stays put rather than moving to another day the way a feast
- does -- [privilege_of] tags it [Privileged] via the same
- [is_sunday_slug] marker, with no further code needed here. Which of
- commemorate/omit survives is RG 108-111's admission count ([admit],
- below), not this function's decision; this only opens the
- commemoration, tagged with its real RG 109 privilege via
- [privilege_of].
+ commemorated or wholly omitted. Reached by every SANCTORAL loser
+ below I class (RG 111(c)/(d)'s "ordinary" commemoration, no closed
+ list the way the temporal branch above has), AND by an impeded
+ I-class Sunday (excluded from the [Transfer] branch above, and from
+ the temporal-Ordinary [Omit] branch above because [privilege_of]'s
+ (a) makes a Sunday loser [Privileged], never [Ordinary]): RG 109(a)
+ (register line 374) lists "of a Sunday" as a privileged commemoration
+ category, which presupposes an impeded Sunday stays put rather than
+ moving to another day the way a feast does -- [privilege_of] tags it
+ [Privileged] via the same [is_sunday_slug] marker, with no further
+ code needed here. Which of commemorate/omit survives is RG 108-111's
+ admission count ([admit], below), not this function's decision; this
+ only opens the commemoration, tagged with its real RG 109 privilege
+ via [privilege_of].
RG 94 (a fixed-day commemoration is not carried along with a
transferred feast) needs no code here: [Precedence.resolve] calls
@@ -512,19 +636,40 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
several are. *)
(match List.filter is_privileged sorted with [] -> [] | best :: _ -> [ best ])
| Class2, true ->
- (* RG 111: "II-class Sundays: one (dropped if a privileged one is
- due)." Read as: the day's one slot goes to a privileged
- commemoration whenever one is due, categorically -- not by
- comparing its dignity against the ordinary contender's -- so an
- ordinary commemoration that would otherwise win the slot on raw
- dignity is still dropped once any privileged commemoration is also
- due. This is the asymmetric clause the brief and task report flag
- as deliberate, not present at "other II class" below; see the task
- report for the reasoning and its residual uncertainty (the register
- does not spell out the mechanism beyond this one sentence). *)
+ (* RG 111(b), primary text: "in dominicis II classis, una tantum
+ admittitur commemoratio, SCILICET DE FESTO II CLASSIS, quæ tamen
+ omittitur si commemoratio privilegiata facienda sit" -- "on Sundays
+ of the II class, only ONE commemoration is admitted, NAMELY OF A
+ FEAST OF THE II CLASS, which however is dropped if a privileged
+ commemoration is due." Two clauses, not one: (i) a privileged
+ commemoration, whenever due, categorically takes the day's one slot
+ -- not by comparing its dignity against the ordinary contender's,
+ so an ordinary commemoration that would otherwise win on raw
+ dignity is still dropped once any privileged one is also due (the
+ asymmetric clause the brief and task report flag as deliberate, not
+ present at "other II class" below); (ii) failing that, the slot is
+ reserved SPECIFICALLY for a [Class2] candidate -- "de festo II
+ classis" is a RANK restriction, not merely "whichever ordinary
+ candidate has the best dignity": a III- or IV-class ordinary loser
+ (a plain commemoration-only saint with no privilege of its own) has
+ NO standing for this slot at all and must be entirely omitted, even
+ when it is the only candidate present.
+
+ Fix, Task 16 (primary-source-verified + missalemeum-confirmed):
+ previously this fell back to "the best of [sorted], whatever its
+ rank" once no privileged candidate was due, silently admitting a
+ III/IV-class ordinary saint that RG 111(b)'s own wording excludes.
+ Confirmed wrong for real data by the oracle comparison: e.g. 11 Jan
+ 2026 (Holy Family, a II-class Sunday) has St Hyginus (Class3,
+ commemoration-only) as its only competing candidate -- missalemeum
+ shows him "displaced" (omitted), never commemorated; the
+ pre-fix code admitted him regardless. *)
(match List.filter is_privileged sorted with
| best :: _ -> [ best ]
- | [] -> ( match sorted with [] -> [] | best :: _ -> [ best ] ))
+ | [] -> (
+ match List.filter (fun (c, _) -> c.Precedence.cel.Celebration.rank = Class2) sorted with
+ | [] -> []
+ | best :: _ -> [ best ]))
| Class2, false ->
(* RG 111: "other II class: one" -- no privilege-override clause here,
unlike the Sunday case immediately above, so the day's one slot