diff options
Diffstat (limited to 'lib/rites/rite_ef/precedence_ef.mli')
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.mli | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli new file mode 100644 index 0000000..55f947e --- /dev/null +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -0,0 +1,260 @@ +(** RG 91's Table of Precedence for the EF (1962) rite: ranks any candidate + for a given day by its RG 91 entry number. See + docs/research/rules-register.md §4, whose 28-entry transcription this + module follows line by line. *) + +open Colitur_kernel + +(** The universal (base) sanctoral layer's {!Celebration.t}.layer id. A + Sanctoral-origin candidate whose layer is anything else is an overlay: + "proper" (RG 91 entries 12, 19, 23) unless its layer id also carries + {!indult_prefix} ("indult", entries 13, 20). This id and the prefix are + colitur's own data-modelling convention, not an RG citation -- RG 91 + prescribes the ranking, not a machine encoding for it. Whichever task + loads the real EF sanctoral base layer and its overlays must either + reuse these two constants or this classifier will misfile them. *) +val universal_layer : string + +(** See {!universal_layer}. *) +val indult_prefix : string + +(** Slug suffix marking a celebration as a vigil (RG 91 entries 21, 26), + e.g. "ef-ascension-vigil" -- colitur's own temporal-cycle convention + (rite_ef/temporal_ef.ml's [named]). Also colitur's own convention, not + an RG citation, exposed for the same reason as {!universal_layer}. See + {!vigil_prefix} for the sanctoral data's own, different convention: a + vigil can arrive named either way, and {!band}/{!disposition} must + recognise both. *) +val vigil_suffix : string + +(** Slug prefix marking a celebration as a vigil, e.g. "vigil-of-st-lawrence" + -- the sanctoral data's own convention (data/ef/sanctoral.sexp, adopted + verbatim from lectio's naming, per spec §4.4's "slugs are lectionary keys, + not re-derived"). Also colitur's own convention, not an RG citation -- + see {!universal_layer}. Task 10 bootstrapped four real sanctoral vigils + named this way (St Lawrence 08-09, Sts Peter & Paul 06-28, the Assumption + 08-14, the Nativity of St John the Baptist 06-23; a fifth, Christmas, is + suppressed as a duplicate of the temporal cycle's own "ef-nativity-vigil" + -- see data/ef/adjustments.sexp), none of which end in {!vigil_suffix}: + without this prefix also being checked, {!band} would misfile all four at + 16/24 (an ordinary feast of the same rank) instead of RG 91's 21/26, and + RG 33's vigil omission ({!disposition}'s [is_vigil] test, the same + predicate) would never fire for them either -- two rubrics silently + broken for four celebrations, exactly what Task 7's review predicted + when it asked for {!vigil_suffix} to be exposed. *) +val vigil_prefix : string + +(** Slug prefixes marking a celebration as one of RG 91 entry 18's three + Ember-day sets (Advent, Lent, September -- the Pentecost/Whitsun set is + I class and matched by entry 10 before this is ever consulted). Also + colitur's own convention mirroring rite_ef/temporal_ef.ml's own "ef-<set> + -ember-<day>" slug format, not re-derived from first principles; exposed + so a rename of that format has somewhere to be caught other than a + silently-wrong entry 18. *) +val ember_prefixes : string list + +(** Returned for a candidate shape RG 91's 28-entry table has no row for -- + e.g. a [Class1] vigil that is not the Nativity or Pentecost (entries 5, + 9 are the only I-class vigils the table names), or a [Class4] candidate + also marked as a vigil. Deliberately outside 1..28 and larger than any + real entry, so an unclassified candidate can never win an occurrence + contest by accident; a caller that sees it back knows the shape needs a + new rule, not a silently wrong one. *) +val unclassified : int + +(** [band ctx c]: RG 91's Table of Precedence. Returns the table's own entry + number -- I class 1-13, II class 14-21, III class 22-26, IV class 27-28; + lower wins (see {!Precedence.rules.band}) -- EXCEPT where the table's own + text states an exception: entry 8 (All Souls) reads "yields to an + occurring Sunday", so on a Sunday this returns a value that + loses to entry 15 rather than the literal integer 8 (see the comment on + entry 8 in precedence_ef.ml for the exact value and why). Total over + every candidate {!Precedence.resolve} or {!Calendar} can construct, + including shapes the 1962 table itself does not describe (see + {!unclassified}). *) +val band : Vocab_ef.season Precedence.context -> Vocab_ef.rank Precedence.candidate -> int + +(** RG 33's marker: every Sunday slug this rite's temporal cycle produces + (temporal_ef.ml's [named] and [sunday_slug]) contains this substring; + nothing else {!band} classifies does. Also colitur's own convention, not + an RG citation -- see {!universal_layer} -- exposed for the same reason + as {!vigil_suffix}: a rename of temporal_ef's Sunday-slug format has + somewhere to be caught other than a silently-wrong RG 33 disposition. *) +val sunday_marker : 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 {!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); + - 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] + when the winner is any Sunday ({!sunday_marker}) or itself [Class1] + (RG 33 -- entirely omitted, not merely commemorated). A [Class1] + vigil (Nativity, Pentecost) is outside RG 33 entirely -- RG 30 makes + it preferred to any feast whatsoever, so a real one can never reach + this function as a [loser] in the first place (see the .ml's own + comment on [is_omissible_vigil] for the full argument); + - any other [Class1] loser that is NOT a Sunday ({!sunday_marker}) is + [Transfer] (RG 95 -- only I-class FEASTS have the right of + translation; RG 91's own table lists Sundays as a separate row, entry + 6, from feasts, entries 11-13, so a Sunday is never a "feast" in RG + 95's sense and does not transfer even when impeded by a higher + I-class day. This is also what moves All Souls, RG 91 entry 8, once + it loses to an occurring Sunday -- WHERE it lands is + {!Rite.t.transfer_target}'s job, not this function's); + - a TEMPORAL-origin, non-vigil loser of [Class4] is [Omit] (RG 26, + "Caput IV, De feriis" -- "every feria not named in [RG 23-25] is IV + class ... and IV-class ferias are NEVER commemorated." A SEPARATE + rule from RG 109's honour-class one immediately below, keyed on + ferial CLASS rather than on RG 109's privilege letters: RG 109 sorts + commemorations that already exist into honour classes (RG 108's + differing liturgical hours), it does not itself decide which offices + have the right to be commemorated at all -- that is Caput IV's own + business. A SANCTORAL loser of the same rank is unaffected by this + branch (the [is_temporal] guard): RG 21 defines "feria" as any + weekday, never a saint's day, and RG 111(c)/(d) admit an "ordinary" + commemoration of a losing SAINT freely, with no such class-keyed + gate); + - everything else -- including an impeded I-class Sunday, and a + SANCTORAL loser of any rank below I class -- is [Commemorate], + carrying its real RG 109 privilege (see {!admit} below); RG 109(a) + lists "of a Sunday" as a privileged commemoration category precisely + because an impeded Sunday stays put rather than moving to another + day, and RG 24/25 make a losing II- or III-class FERIA's own + commemoration mandatory when impeded, not merely eligible. + + Total over every winner/loser pair {!Precedence.resolve} or {!Calendar} + can construct: [Vocab_ef.rank] (RG 8) and {!Celebration.status} are both + closed variants, and the five cases above -- an if/else-if chain ending + in the unconditional [Commemorate] catch-all -- exhaust every value + those two fields can take between them; there is no sixth, + "unclassified" case the way {!band} needs one, because this function's + own return type has no such slot to fall into by accident. *) +val disposition : + winner:Vocab_ef.rank Precedence.candidate -> + loser:Vocab_ef.rank Precedence.candidate -> + Precedence.disposition + +(** Slug prefix marking a celebration as one of RG 91 entry 17's days within + the Octave of the Nativity (29-31 Dec -- 26-28 Dec are Stephen, John, the + Innocents, sanctoral, never this prefix). Also colitur's own convention + mirroring rite_ef/temporal_ef.ml's own "ef-nativity-octave-day-%d" slug + format, not an RG citation -- see {!universal_layer} -- exposed for the + same reason as {!vigil_suffix}: a rename of that format has somewhere to + be caught other than a silently-wrong RG 109(c) privilege. *) +val nativity_octave_prefix : string + +(** The September set of {!ember_prefixes}, broken out on its own because RG + 109(d) (§4, "Commemorations") privileges September Ember days under its + own name, and September sits outside RG 109(e)'s three named seasons + (Advent, Lent, Passiontide) under any reading of that clause -- NOT + because the Advent and Lent Ember sets need excluding from (e), which + they do not: (e)'s own bare text privileges them too, the same as any + other Advent/Lent feria (see {!Precedence_ef.privilege_of}'s own (e) + comment in the .ml for the full argument, corrected fix round 1). + {!ember_prefixes} is built from this constant, not a duplicated literal, + so the two cannot silently drift apart. *) +val september_ember_prefix : string + +(** [admit ~observed comms]: RG 108-111 (docs/research/rules-register.md §4, + "Commemorations"). How many of [comms] -- each already tagged with its + real RG 109 privilege by {!disposition} -- RG 111 admits, and which, + given the day actually observed: + - [observed] a [Class1] day: none, except at most one privileged + commemoration (the highest-dignity one, if several are due) -- an + ordinary one is never admitted here, no matter how many are due; + - [observed] a [Class2] Sunday (its slug carries {!sunday_marker}): one, + subject to TWO conditions, not one -- (i) a privileged commemoration, + whenever due, categorically takes the day's one slot over any + ordinary one, not by comparing dignity, so an ordinary commemoration + that would otherwise win on dignity is still dropped; (ii) failing + that, the slot is reserved for a [Class2] candidate SPECIFICALLY + ("de festo II classis", RG 111(b)'s own wording -- a RANK FLOOR, not + "whichever ordinary candidate has the best dignity"): a III- or + IV-class ordinary loser has no standing for this slot at all and is + admitted nothing, even when it is the only candidate due; + - [observed] any other [Class2] day: one, by dignity alone -- no + privilege override and no rank floor, unlike the Sunday case + immediately above; + - [observed] a [Class3] or [Class4] day: at most two, by dignity alone. + + "Dignity" here is [Vocab_ef.rank] (RG 8's four classes), NOT {!band}'s + finer RG 91 entry number -- {!band} needs a [context] (date/season/ + weekday) this function does not receive (see {!Precedence.rules.admit}). + Ties break on slug, matching {!Precedence.compare_by}, so the result + never depends on the order [comms] arrives in. + + Every candidate this returns is a value taken unchanged from [comms], + never rebuilt: {!Precedence.resolve}'s own [dropped]/[omitted] + accounting tells an admitted candidate from a dropped one by physical + equality on the candidate value, so anything this function admitted + stays admitted, and anything it did not is reported in + {!Precedence.resolution.omitted}, never silently lost. Total: every + [Vocab_ef.rank] is one of the four cases above, and every branch is + itself total over an empty or arbitrarily long [comms]. *) +val admit : + observed:Vocab_ef.rank Precedence.candidate -> + (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list -> + (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list + +(** The Annunciation's own bootstrapped slug (data/ef/sanctoral.sexp, Task + 10), reused verbatim by {!transfer_target} to recognise RG 96's named + exception. Not an RG citation -- see {!universal_layer} -- exposed so a + future re-bootstrap that renames the slug has somewhere to be caught + other than a silently-wrong transfer target. *) +val annunciation_slug : string + +(** [transfer_target c origin occupant]: RG 96 (docs/research/rules-register + .md §4, "Transfer/translation") -- where an impeded I-class feast, once + {!disposition} has decided it is [Transfer]-disposed, is placed. This + *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 96's own named exception (Attamen (a), primary-source-verified -- + see {!annunciation_slug}'s comment for the Latin and the register's own + correction note): for the Annunciation specifically, IF that general + target would fall after Easter Sunday itself ("quando est transferendum + post Pascha" -- when it is to be transferred past Easter), the + Annunciation is placed instead at the Monday after Low Sunday (its + [sedes propria]), searching onward from there only if that day is + itself occupied by a blocking class. The exception is CONDITIONAL, not + unconditional: an Annunciation impeded for a reason that resolves + BEFORE Easter (e.g. an ordinary Lent Sunday with a free feria the next + day) takes the general target like any other I-class feast. Operationally + the condition holds exactly when 25 March falls close enough to Easter + that the general walk crosses it -- concretely, when 25 March itself + falls within Holy Week or Easter Week. + + Total, terminating, and its result is always strictly later than + [origin] -- {!Colitur_kernel.Rite.t}.transfer_target's own obligations, + which {!Colitur_kernel.Calendar}'s placement pass relies on and its own + round guard does not itself enforce (calendar.ml's [place_transfers] + bounds ROUNDS across a whole year, not one call's internal walk). + Terminating by a structural bound on the internal walk (max 400 days, + an engineering ceiling, not an RG citation -- see the .ml) AND a guard + at {!Colitur_kernel.Date}'s own domain ceiling (31 December 9999, + beyond which probing [occupant] can itself raise -- see the .ml's + [domain_max_date]), not by an argument about the real 1962 calendar's + own structure, so a rite/data shape this function has not anticipated + fails FINITELY rather than hanging or crashing the caller. Strictly + later than [origin]: the general search starts at [origin + 1] and only + ever advances forward from there; the Annunciation's own alternate + starting point is provably later than 25 March for every representable + year (Easter's documented range, register §0) -- see the .ml for the + full argument. *) +val transfer_target : + Vocab_ef.rank Precedence.candidate -> Date.t -> (Date.t -> Vocab_ef.rank Celebration.t) -> Date.t |
