aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef/precedence_ef.mli
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites/rite_ef/precedence_ef.mli')
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli260
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