aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/kernel/calendar.ml112
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml239
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli60
3 files changed, 368 insertions, 43 deletions
diff --git a/lib/kernel/calendar.ml b/lib/kernel/calendar.ml
index 5d3572c..cffdf69 100644
--- a/lib/kernel/calendar.ml
+++ b/lib/kernel/calendar.ml
@@ -304,14 +304,116 @@ let build_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.by_date)
day it landed and [transferred_out] here, not via [omitted] too --
double-booking it in both would fail Task 12's "appears exactly once"
reading of this day alone. *)
+ (* Whether a transferred/injected candidate is genuinely accounted for at
+ its assigned target -- true under any of THREE conditions, not two
+ (fix round 1, ef-major-litanies task -- the first version of this
+ function, and this comment, claimed two, missing exactly the same
+ shape one channel further out than the bug it had just fixed; see
+ below for how that was found).
+
+ (1) It won the day outright there (every [Transfer]-disposed candidate
+ this kernel produced before a rite could transfer a
+ [Celebration.status = Commemoration_only] one: a losing FEAST, which
+ RG 96's own "next day not I or II class" guarantees an unblocked day
+ to win once it arrives -- [occupant_of] alone used to answer this, and
+ still would).
+
+ (2) It survives at the target as one of the day's own admitted
+ COMMEMORATIONS instead (a shape [place_transfers] itself never used to
+ produce, because nothing before could dispose a [Commemoration_only]
+ candidate as [Transfer] -- {!Precedence.resolve} holds such a
+ candidate out of the band contest entirely, so it can never win a day
+ outright, only ever be commemorated on one; a rite is nonetheless free
+ to [Transfer] one to a named date, e.g. RG 80's Major Litanies, RG
+ 81's own "nihil fit in Officio" making [observed] structurally
+ impossible for it anywhere). The FIRST version of this function
+ stopped here, at (1) and (2) -- {!Liturgical_day.t}'s own doc comment
+ promises [observed] and [commemorations] are never silently lost, and
+ this read as "the same two channels", which is where the "two, not
+ three" miscount came from: that promise is about {!Liturgical_day.t}'s
+ OWN five fields, not an exhaustive account of every way
+ {!Precedence.resolve} can dispose of a candidate at one date.
+
+ (3) It reaches the target and is disposed there as [Omit] by
+ {!Precedence.rules.disposition} itself (RG 33's vigil omission, RG
+ 16(a)'s Sunday suppression, RG 26's IV-class-feria omission, the
+ Lord-vs-Lord exclusion, ...) OR is offered to
+ {!Precedence.rules.admit} there but capped out by an admission-count
+ limit RG 111 itself imposes (e.g. a Class1 day admits only ONE
+ privileged commemoration; a second one due the same day, or the SAME
+ Litanies candidate no longer privileged, loses that slot) -- both
+ land in the target's own [omitted], with their own accurate,
+ already-diagnostic reason ("omitted: yielded to a higher day" /
+ "omitted: admission limit reached"), and both are just as genuinely
+ "delivered and considered" as (1)/(2), not stuck anywhere. Missing
+ this third channel reproduces the EXACT ORIGINAL BUG this function was
+ written to fix, one level further out: a candidate settled (via (3))
+ at its target was still reported [unresolved] at its ORIGIN, under
+ the same wrong, hardcoded [unconverged_reason] label, and
+ double-counted by {!Validate}'s own "duplicated" check the same way.
+
+ Found, not merely reasoned to: fix round 1's review reproduced it two
+ ways. Constructively, a second privileged [Commemoration_only] entry
+ placed on the Litanies' own transfer target (Easter+2) that sorts
+ ahead of it forces the Litanies to lose {!Precedence.rules.admit}'s
+ own Class1 "one privileged commemoration only" cap there. And,
+ already present in this branch's own mutation-testing record without
+ being run to ground at the time: mutation 3 (this task's own report,
+ `privilege_of`'s RG 109(f) branch forced to [Ordinary]) makes the
+ transferred Litanies itself lose that SAME Class1 cap at its OWN
+ target -- no second candidate needed, since an [Ordinary] commemoration
+ has no standing at all against a [Class1] day's privileged-only
+ admission rule ({!Precedence_ef.admit}'s own Class1 case). That
+ mutation's 9th failure, the exhaustive `Validate` property sweep on a
+ random year, was this bug; the report noted the failure and declined
+ to diagnose it before reverting the mutation, which is precisely how
+ it survived one fix round.
+
+ Unreachable on shipped EF data today (every `Commemoration_only`
+ candidate this rite's own real data carries is at most [Class3]
+ except the Litanies themselves, and no second privileged
+ commemoration can ever fall on Easter+2 -- {!Precedence_ef
+ .privilege_of}'s own (a)-(e) categories are all Sunday/Ember/Advent-
+ Lent-Passiontide/Nativity-octave shaped, none of which Easter+2 is or
+ can be), which is exactly why it survived this far: nothing in the
+ shipped calendar has ever exercised it. Fails LOUDLY (a wrong,
+ misleading label) rather than silently, and is fixed here rather than
+ left as a documented residual, since the fix is a one-line
+ generalisation of the same check, not new machinery. Kept
+ rite-agnostic: nothing here reads anything EF-specific, only
+ {!Precedence.resolution}'s own [observed]/[commemorations]/[omitted]
+ fields -- THREE of {!Precedence.resolution}'s four fields (the fourth,
+ [deferred], denotes a candidate that has NOT yet settled at this date,
+ by definition, so it is correctly never consulted here).
+ A SIGNAL TRADED AWAY, named because it is real (fix-round re-review):
+ channel (3) accepts both shapes of [omitted] -- the admission cap, and
+ a rite whose own [disposition] omits the candidate AT the target its
+ own [transfer_target] named. For the cap this is unambiguously
+ "settled". For the second it is a judgement: before this change that
+ shape produced a loud, if mislabelled, [Validate] "unconverged"
+ failure; now it is silent at the origin and honestly reported at the
+ target. The kernel cannot tell "the rite deliberately omitted it
+ there" from "the rite chose a bad target" without rite knowledge it
+ must not have, so accepting it is the right call -- but the diagnostic
+ it used to give up is gone. Unreachable in [Rite_ef] today: only the
+ Major Litanies transfer as [Commemoration_only], and RG 96's search
+ guarantees a transferred FEAST an unblocked target. *)
+ let settled_at target slug =
+ let _, _, target_resolution = resolve_with_injected rite idx injected target in
+ let matches (c : 'r Precedence.candidate) =
+ Slug.equal c.Precedence.cel.Celebration.slug slug
+ in
+ matches target_resolution.Precedence.observed
+ || List.exists (fun (c, _) -> matches c) target_resolution.Precedence.commemorations
+ || List.exists (fun (c, _) -> matches c) target_resolution.Precedence.omitted
+ in
let unresolved c =
- let slug = Slug.to_string c.Precedence.cel.Celebration.slug in
- if Hashtbl.mem out_of_range slug then true
+ let slug = c.Precedence.cel.Celebration.slug in
+ if Hashtbl.mem out_of_range (Slug.to_string slug) then true
else
- match Hashtbl.find_opt assignment slug with
+ match Hashtbl.find_opt assignment (Slug.to_string slug) with
| None -> true
- | Some (_, target) ->
- not (Slug.equal (occupant_of rite idx injected target).Celebration.slug c.Precedence.cel.Celebration.slug)
+ | Some (_, target) -> not (settled_at target slug)
in
let reason_for c =
if Hashtbl.mem out_of_range (Slug.to_string c.Precedence.cel.Celebration.slug) then
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
index 7b64b83..c9e6a55 100644
--- a/lib/rites/rite_ef/precedence_ef.ml
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -651,6 +651,48 @@ let nativity_octave_prefix = "ef-nativity-octave-day-"
[universal_layer] -- private: nothing outside [privilege_of] needs it. *)
let alp_feria_prefixes = [ "ef-advent-"; "ef-lent-"; "ef-passiontide-" ]
+(* RG 80 (Caput X, "De Litaniis maioribus et minoribus", §A "De Litaniis
+ maioribus"; both photographic scans and the electronic transcription,
+ word for word, no scan-vs-transcription conflict to adjudicate --
+ docs/research/rules-register.md's own citation, this task): "80.
+ Litaniae maiores assignatae sunt diei 25 aprilis; si vero eo die
+ occurrit dominica Paschatis vel feria II post Pascha, transferuntur in
+ sequentem feriam III." -- the Major Litanies are assigned to 25 April;
+ but if Easter Sunday or Easter Monday falls on that day, they are
+ transferred to the FOLLOWING TUESDAY. Both trigger shapes land on the
+ SAME target, Easter+2 (worked out from the text, not independently
+ stated by it): 25 April = Easter Sunday means the "following Tuesday"
+ is Easter+2; 25 April = Easter Monday means Easter itself is 24 April,
+ and the following Tuesday is again Easter+2. {!transfer_target}'s own
+ Litanies branch, below, computes that directly.
+
+ RG 81, same division, immediately following: "81. De Litaniis
+ maioribus nihil fit in Officio, sed tantum in Missa. Earum autem
+ commemoratio non est habenda commemoratio 'de Tempore'." -- nothing is
+ done in the Office, only in the Mass, and its commemoration is not to
+ be reckoned a "de Tempore" one. This is why [major_litanies_slug]'s own
+ data/ef/adjustments.sexp entry is [Commemoration_only]: RG 91's table
+ enumerates "dies liturgici" (Office days), and this is explicitly
+ denied any Office standing at all -- the same reasoning {!band}'s own
+ top-of-function guard already gives every [Commemoration_only]
+ candidate (never a table row, never [observed]), which is exactly the
+ behaviour RG 81 requires here independently.
+
+ [major_litanies_slug] is this codebase's OWN invented English slug: RG
+ 80/81/109(f) name the observance, never a computer key for it, and
+ lectio does not compute the Major Litanies at all (this task's own
+ measurement) -- nothing to adopt verbatim, unlike every other slug this
+ file cross-checks against temporal_ef.ml's own conventions.
+ data/ef/adjustments.sexp's own [Add] directive is the ONE other place
+ this exact string is written; a rename here with no matching rename
+ there silently stops every branch below from ever seeing a live
+ candidate again -- flagged the same way [rg110_companion_slug] already
+ flags its own three hand-maintained pairs. *)
+let major_litanies_slug = "major-litanies"
+
+let is_major_litanies (c : Vocab_ef.rank Precedence.candidate) =
+ String.equal (Slug.to_string c.Precedence.cel.Celebration.slug) major_litanies_slug
+
(* RG 109 (docs/research/rules-register.md §4, "Commemorations"): the
closed list of privileged commemorations, checked in the register's own
lettered order. A candidate matching none of (a)-(f) is ordinary, per the
@@ -741,23 +783,30 @@ let privilege_of (c : Vocab_ef.rank Precedence.candidate) : Precedence.privilege
it under its own name. *)
else if is_temporal && List.exists (fun p -> String.starts_with ~prefix:p slug) alp_feria_prefixes then
Precedence.Privileged
- (* (f) RG 109(f) (§4): "of the Major Rogations, in Mass" -- the
- Major Litanies (25 April, RG 80) are STILL not computed anywhere in
- this codebase (temporal_ef.ml's own comment on [temporal]'s Rogation
- branch, CORRECTED final fix wave item 7: they did not, in fact,
- "arrive with Plan 3's sanctoral" -- Plan 3 shipped without them,
- register §6 tracks this as an open item with no plan yet committed to
- build it), so no candidate this engine can currently construct
- represents one. There is no existing slug
- convention to anchor a check to, and guessing one risks silently
- misclassifying whatever a future task does name it -- a wrong citation
- is worse than a missing one, so this is left unimplemented and flagged
- in the task report rather than guessed. Deliberately NOT matched by
- anything above: the Minor Litanies/Rogations ("ef-rogation-monday"/
- "-tuesday", RG 87) temporal_ef.ml DOES compute are a different
- observance RG 109(f) does not name (RG 88: the Minor Rogations change
- nothing in the Office at all), so they correctly fall through to
- "ordinary" below, not this category. *)
+ (* (f) RG 109(f) (Caput XVI, "De Commemorationibus", §4): "de Litaniis
+ maioribus, in Missa" -- of the Major Litanies, IN THE MASS (RG 81's
+ own restriction: never in the Office). NOW LIVE (this task,
+ ef-major-litanies): this branch used to be dead code -- "no candidate
+ this engine can currently construct represents one" -- because
+ nothing built the Major Litanies as a candidate at all.
+ data/ef/adjustments.sexp's own [major_litanies_slug] entry
+ ({!major_litanies_slug}'s own citation above has the full RG 80/81
+ text) is exactly that candidate now. Read by slug alone, the same
+ convention every other one-off entry in this file uses
+ ([nativity_octave_prefix], [rg110_companion_slug], [annunciation_slug]
+ below) -- RG 109(f) names one specific, closed-list observance, not a
+ structural property [is_temporal]/[rank] could derive the way (c)/(d)/
+ (e) above do for whole classes of temporal ferias. Deliberately NOT
+ matched by anything above it: the Minor Litanies/Rogations
+ ("ef-rogation-monday"/"-tuesday", RG 87) temporal_ef.ml DOES compute
+ are a different observance RG 109(f) does not name (RG 88: the Minor
+ Rogations change nothing in the Office at all, and [is_sunday_slug]/
+ [rank = Class1]/[nativity_octave_prefix]/the Ember prefixes/
+ [alp_feria_prefixes] never match their slugs either), so they
+ correctly fall through to "ordinary" below, not this category --
+ {!privilege_cases}'s own negative row in test_precedence_ef.ml pins
+ this boundary. *)
+ else if is_major_litanies c then Precedence.Privileged
else Precedence.Ordinary
let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
@@ -793,6 +842,63 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
exception for two commemorations invoking the identical BVM, the
exact gap this fix closes. *)
Precedence.Omit
+ else if
+ is_major_litanies loser
+ && (let wslug = Slug.to_string winner.Precedence.cel.Celebration.slug in
+ String.equal wslug "ef-easter-sunday" || String.equal wslug "ef-easter-1-monday")
+ then
+ (* RG 80 -- {!major_litanies_slug}'s own citation above has the full
+ text and the "both trigger shapes land on Easter+2" derivation.
+ data/ef/adjustments.sexp's own [major_litanies_slug] entry is
+ UNCONDITIONALLY Fixed at 25 April, so it is offered as a candidate
+ every year regardless of what 25 April turns out to be -- this is
+ where the two RETRACTED blockers this task's own brief names
+ (docs/research/rules-register.md's "the recorded blocker was
+ wrong") actually close: [disposition] already receives [~winner],
+ so a rite-local test on the WINNER's own slug alone (no kernel
+ signature change, no [context]/date needed here at all) is enough
+ to tell the 194 trigger years apart from the other 8,223 -- exactly
+ the "zero kernel surface" the retraction predicted. "ef-easter-
+ sunday" and "ef-easter-1-monday" are [Temporal_ef.named]'s/[temporal]'s
+ own slugs for Easter Sunday and Easter Monday respectively (this
+ file's own [entry_15_band]-adjacent branches above already trust
+ the same convention); Easter Monday's own reliability as a marker
+ (measured, register: 8,417 occurrences in 8,417 years, once per
+ year, never displaced, being a I-class octave day) is what makes
+ reading it off a bare slug string safe here, the same argument that
+ retracted the second blocker.
+
+ Checked ahead of the [Commemoration_only] branch immediately below:
+ that branch's own "always Commemorate, nothing overrides it" would
+ otherwise fire first, and this candidate would wrongly commemorate
+ 25 April itself in exactly the 194 years RG 80 forbids it from
+ doing so. No earlier branch in this function can pre-empt it
+ either -- [is_bvm_office] above is keyed on Marian/BVM subjects
+ this candidate never carries ({!major_litanies_slug}'s own entry is
+ [subject = Saint], and its slug is not on {!marian_slugs}).
+
+ [Transfer], not [Omit]: RG 80 does not say the Litanies simply
+ vanish in a trigger year, it says WHERE they move to -- and
+ {!Precedence.disposition}'s own [Transfer] constructor, together
+ with the placement machinery {!Calendar} already has for RG 96
+ (calendar.ml's [place_transfers]/[resolve_with_injected]), is
+ exactly "move a losing candidate to another day and inject it
+ there" -- reused here rather than invented a second time, because
+ RG 80's own transfer is structurally the same operation, just with
+ its OWN named target instead of RG 96's generic forward search
+ ({!transfer_target}'s own Litanies branch, below, computes that
+ target directly rather than searching for it). A
+ [Commemoration_only] candidate transferring is a new shape for this
+ codebase, checked rather than assumed safe: {!Precedence.resolve}'s
+ own fold matches [Transfer | Repose] uniformly, with no [status]
+ test anywhere in it, and once re-offered at the target date this
+ candidate is still [Commemoration_only], so it is still held out of
+ the band contest there too (the same [forced_comm] partition,
+ {!Precedence.resolve}'s own top comment) -- it can never
+ accidentally become [observed] at its transfer target either,
+ matching RG 81's "nihil fit in Officio" for the same reason it
+ cannot become [observed] on 25 April itself. *)
+ Precedence.Transfer
else 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
@@ -1509,7 +1615,46 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
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. *)
+ pre-fix code admitted him regardless.
+
+ CORROBORATED (ef-major-litanies task, fix round 1, F5): RG 111
+ sits in Caput XVI, "De Commemorationibus", governing "tam pro
+ Missa quam pro Officio" per RG 106 -- read alone it could be
+ mistaken for Office-shaped, incidentally reused for the Mass.
+ Rubricae Generales Missalis Romani n. 434(b) (Part "VIII - De
+ diversis Missae partibus", "D) De orationibus", "I - De
+ orationibus in genere"), verified word for word, all three
+ documents: "in dominicis II classis, nulla alia admittitur
+ oratio, praeter commemorationem festi II classis, quae tamen
+ omittitur si commemoratio privilegiata facienda sit" --
+ IDENTICAL IN ITS OPERATIVE CLAUSE to the one above -- not word
+ for word throughout: RG 111(b) opens "una tantum admittitur
+ commemoratio, SCILICET DE FESTO II CLASSIS", n. 434(b) recasts
+ that into the orations register as "NULLA ALIA ADMITTITUR ORATIO,
+ PRAETER COMMEMORATIONEM festi II classis", and only the trailing
+ "quae tamen omittitur si commemoratio privilegiata facienda sit"
+ is verbatim in both. That trailing clause is the one this
+ adjudication turns on. And n. 434 is not merely a different part
+ of the same document: the running heads show RG 111 under
+ "Rubricae generales" and n. 434 under "Rubricae generales Missalis
+ Romani" -- two distinct rubrical corpora bound in one volume,
+ which is the whole force of the corroboration. Explicitly scoped "post
+ orationem Missae" -- an independent, Mass-structure-rubric
+ confirmation of the exact same privilege-overrides-ordinary rule
+ this branch already implements, from a different part of the
+ same document. First load-bearing live witness for this branch's
+ "privileged wins even against a WORSE table position" half: the
+ Major Litanies (RG 109(f), major_litanies_slug above), whose own
+ [band] value is {!unclassified} (worse than every real table
+ entry, per that constant's own comment) yet still displaces St
+ Mark's real entry-16 table position on the four Sunday-conflict
+ years the Litanies' own data/ef/expected-divergences-missalemeum
+ .sexp M20 entry adjudicates -- every earlier witness for this
+ branch (Holy Family/Hyginus above; the RG16(a) admit_cases table)
+ happened to also be the day's own privileged Sunday/feast winning
+ OUTRIGHT (band already decided it before admit ran), not two
+ independently-offered LOSING candidates settled by this clause
+ alone. *)
(match List.filter is_privileged sorted with
| best :: _ -> [ drop_band best ]
| [] -> (
@@ -1656,14 +1801,52 @@ let rec search_from (occupant : Date.t -> Vocab_ef.rank Celebration.t) (steps :
directly tests the rubric's own words. *)
let transfer_target (c : Vocab_ef.rank Precedence.candidate) (origin : Date.t)
(occupant : Date.t -> Vocab_ef.rank Celebration.t) : Date.t =
- let general_target = search_from occupant 0 (Date.add_days origin 1) in
- let is_annunciation = Slug.to_string c.Precedence.cel.Celebration.slug = annunciation_slug in
let easter = Computus.gregorian_easter (Date.year origin) in
- if is_annunciation && Date.compare general_target easter > 0 then
- (* Low Sunday = Easter + 7 (register §0, temporal_ef.ml's [off 7]); the
- Monday after it = Easter + 8. Searched onward from there exactly
- like the general case searches from [origin + 1] -- "only if that
- day is itself blocked" (rite.mli) is [search_from]'s ordinary
- behaviour, not a second mechanism. *)
- search_from occupant 0 (Date.add_days easter 8)
- else general_target
+ if is_major_litanies c then
+ (* RG 80 -- {!major_litanies_slug}'s own citation (precedence_ef.ml,
+ above [privilege_of]) has the full text and the "both trigger
+ shapes land on Easter+2" derivation. Checked BEFORE computing
+ [general_target] at all (unlike the Annunciation branch below,
+ which computes the general RG 96 walk first and only overrides it
+ conditionally) -- this candidate's target is never the general
+ walk's own result, so running {!search_from} for it would be
+ wasted work at best.
+
+ "The following Tuesday", unconditionally: [search_from]'s ordinary
+ forward walk exists because a DISPLACED FEAST needs an unoccupied
+ (non-I/II-class) day to be fully celebrated as itself (RG 96's own
+ "next day that is not I or II class"). The Major Litanies are never
+ a feast -- RG 81: "nihil fit in Officio, sed tantum in Missa" --
+ only ever a commemoration once they arrive, and a commemoration
+ needs no unoccupied day at all (RG 108-111 admit commemorations on
+ I-class days routinely, see [privilege_of]'s own (b) branch and
+ [admit]'s [Class1] case above). Running {!search_from} here would
+ therefore be actively WRONG, not merely unnecessary: Easter+2 is
+ itself I-class (within the Easter octave, {!band}'s own entry-10
+ branch, {!is_blocking}'s own [Class1] test), so a search starting
+ there would walk PAST the exact date RG 80 names, looking for a
+ day the rubric never asked for.
+
+ Total and terminating trivially (no search performed at all).
+ Strictly after [origin], {!Rite.t.transfer_target}'s own contract
+ (rite.mli), on both trigger shapes (this file's header, worked
+ examples): 25 April = Easter Sunday (origin = Easter itself =
+ Easter+0, target = Easter+2 = origin+2) or 25 April = Easter Monday
+ (Easter itself = 24 April = origin-1, target = Easter+2 =
+ origin+1) -- either way strictly forward. Not conditioned on
+ [occupant] at all, unlike every other branch in this function --
+ deliberately: nothing about RG 80's own text makes the target
+ depend on what else is observed that year, only on Easter's own
+ date. *)
+ Date.add_days easter 2
+ else
+ let general_target = search_from occupant 0 (Date.add_days origin 1) in
+ let is_annunciation = Slug.to_string c.Precedence.cel.Celebration.slug = annunciation_slug in
+ if is_annunciation && Date.compare general_target easter > 0 then
+ (* Low Sunday = Easter + 7 (register §0, temporal_ef.ml's [off 7]); the
+ Monday after it = Easter + 8. Searched onward from there exactly
+ like the general case searches from [origin + 1] -- "only if that
+ day is itself blocked" (rite.mli) is [search_from]'s ordinary
+ behaviour, not a second mechanism. *)
+ search_from occupant 0 (Date.add_days easter 8)
+ else general_target
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli
index 765b573..419613a 100644
--- a/lib/rites/rite_ef/precedence_ef.mli
+++ b/lib/rites/rite_ef/precedence_ef.mli
@@ -120,17 +120,40 @@ val band : Vocab_ef.season Precedence.context -> Vocab_ef.rank Precedence.candid
somewhere to be caught other than a silently-wrong RG 33 disposition. *)
val sunday_marker : string
+(** The Major Litanies' own invented slug (RG 80, Caput X "De Litaniis
+ maioribus et minoribus" §A; RG 81; RG 109(f) -- see the .ml's own
+ citation, above [privilege_of], for the full text). Not adopted from
+ lectio, which does not compute the Major Litanies at all (this task's
+ own measurement) -- colitur's own key, data/ef/adjustments.sexp's
+ matching [Add] entry. Exposed for the same reason as
+ {!annunciation_slug} below: a future rename in the data with no
+ matching rename here silently stops {!disposition}, {!admit}'s RG
+ 109(f) privilege, and {!transfer_target}'s own RG 80 branch from ever
+ recognising a live candidate again. *)
+val major_litanies_slug : string
+
(** [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
own rank and status (RG 95), except RG 33's vigil omission, which also
reads the winner:
+ - a loser whose slug is {!major_litanies_slug} (ef-major-litanies task)
+ is [Transfer], NOT [Commemorate], when the WINNER's own slug is
+ Easter Sunday or Easter Monday (RG 80: 25 April's Major Litanies are
+ transferred to the following Tuesday, always Easter+2, when 25 April
+ is itself one of those two days) -- checked first, ahead of the
+ [Commemoration_only] bullet immediately below (of which this would
+ otherwise be a case), because RG 80's own condition overrides RG 81's
+ default "always Commemorate" for these two specific winners only. See
+ the .ml's own citation, and {!transfer_target}'s own RG 80 branch for
+ where the transfer lands;
- a {!Celebration.status} of [Commemoration_only] is always
- [Commemorate] (checked first: it can never win -- see
- {!Precedence.resolve} -- and, by that same status's own definition,
- already denotes an office with nothing left to translate, so it never
- transfers either; not itself a further RG citation beyond RG 93's
- general four-mechanism statement above);
+ [Commemorate] otherwise (checked first among the remaining cases: it
+ can never win -- see {!Precedence.resolve} -- and, by that same
+ status's own definition, already denotes an office with nothing left
+ to translate under RG 95's ordinary rule, so it never transfers
+ either there; not itself a further RG citation beyond RG 93's general
+ four-mechanism statement above);
- a [Class2] or [Class3] loser whose slug marks it a vigil
({!vigil_suffix} OR {!vigil_prefix} -- both conventions this
codebase's data uses, see {!vigil_prefix}'s own comment) is [Omit]
@@ -243,6 +266,14 @@ val september_ember_prefix : string
- [observed] a [Class3] or [Class4] day: at most two, by {!band}'s table
order alone.
+ RG 109(f) (ef-major-litanies task): every candidate in [comms] arrives
+ already tagged with its real privilege by {!disposition}, which now
+ includes {!major_litanies_slug} tagged [Privileged] -- this function
+ itself needed no change for it, the same "no new machinery" shape RG
+ 109(a)/(c)/(d)/(e) already had: it competes for whichever of the four
+ cases above its host day falls into, exactly like any other privileged
+ candidate.
+
ADDED, ef-holyname-rg110 task: RG 110 (docs/research/rules-register.md
§4, Caput XIV) -- "In Officio et Missa S. Petri semper fit
commemoratio S. Pauli, et vicissim... pro unica habeantur" -- whenever
@@ -339,11 +370,20 @@ val annunciation_slug : string
*is* {!Colitur_kernel.Rite.t}.transfer_target; see that field's own
fuller rationale for why the search has to be rite-supplied at all.
- RG 96's own rule: the next following day whose currently-resolved
- occupant is not I or II class (read off [Vocab_ef.rank], RG 8's dignity
- -- not {!band}'s finer occurrence-table entry, the same distinction
- {!admit} draws for RG 111). This general target is computed for EVERY
- candidate, always, first.
+ RG 80 (ef-major-litanies task): checked FIRST, ahead of everything
+ below -- when [c] is {!major_litanies_slug}, the target is Easter+2
+ directly, with no RG 96 search at all (this candidate is only ever a
+ commemoration at its target, RG 81's own "nihil fit in Officio", which
+ needs no unoccupied day the way a displaced FEAST does; the general
+ RG 96 search below would in fact walk PAST Easter+2, since it is itself
+ I-class). See the .ml's own citation for the full text and the "why not
+ the general search" argument.
+
+ RG 96's own rule, for every OTHER candidate: the next following day
+ whose currently-resolved occupant is not I or II class (read off
+ [Vocab_ef.rank], RG 8's dignity -- not {!band}'s finer occurrence-table
+ entry, the same distinction {!admit} draws for RG 111). This general
+ target is computed for every such candidate, always, first.
RG 96's own named exception (Attamen (a), primary-source-verified --
see {!annunciation_slug}'s comment for the Latin and the register's own