From 12b97761019cfa02ca0da8a5fb50ef815d07685c Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Fri, 21 Aug 2026 14:42:30 +0200 Subject: feat(ef): implement RG 33's third omission trigger RG 33 omits a II/III-class vigil in three cases: it falls on a Sunday, it falls on a I-class feast, "vel si festum cui praemittitur in alium diem transferri aut ad commemorationem reduci contingat". Only the first two were built; the third was recorded in precedence_ef.ml as unimplemented on the grounds that no witness existed in the shipped data. That reasoning was wrong, and the rule fires on 1 744 days across 1583-9999. Both halves of the clause reduce to one observable question -- is the feast the OBSERVED office on the following day (RG 34 puts it there) -- so the kernel asks it once per candidate, after place_transfers has settled the year. No fixed point is needed: a vigil is a candidate only on its own day, never on its feast's, so suppressing it cannot change what the next day observes. Precedence.rules gains vigil_feast, which returns the slug of the feast a vigil precedes; the kernel cannot infer that itself, because only two of the five vigil/feast pairs share a slug stem. Blast radius, measured pre-change binary vs HEAD over the whole domain and classified: 1 744 days, three shapes, zero unexplained. 1 199 are the feast reduced to a commemoration (10 August on a Sunday, St Lawrence); 478 and 67 are the feast transferred under RG 96 after the Sacred Heart or Corpus Christi takes its day. The Assumption's and the Ascension's vigils never qualify -- their I-class feasts always keep their own day. Independently witnessed, which is unusual here. The published Ordo -- the only witness outside the Divinum Officium -> missalemeum -> lectio lineage -- omits St Lawrence's vigil on 2025-08-09, agreeing with colitur against both engines. That date had been read earlier as an Ordo gap; the Ordo was right, and correcting the misreading is what surfaced this clause. On 2027-08-09 the feast does keep its day and the Ordo omits a vigil colitur correctly keeps, which is a genuine Ordo gap. Allow-lists: C39 (lectio, 10 rows) and a 2038 oracle class citing the register, the 2026-2027 window having no instance. The golden pin asserting St Lawrence's vigil is violet moved 2025 -> 2027; its own comment had reasoned about the vigil's weekday and missed that RG 33 also looks at the feast's. Two new pins cover both shapes of the clause. The vigil/feast table is built with Slug.of_string_exn: mutation testing showed that of_string plus Result.to_option turns a typo into None, which this hook's contract reads as "not a vigil", switching the rule off in silence. Two tests assert the table against the shipped data in both directions. --- lib/rites/rite_ef/precedence_ef.mli | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'lib/rites/rite_ef/precedence_ef.mli') diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 419613a..4c2523a 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -217,6 +217,41 @@ val disposition : loser:Vocab_ef.rank Precedence.candidate -> Precedence.disposition +(** RG 33's third omission trigger. Given a candidate, the feast it is a + VIGIL of, when that vigil is one this rule can omit; [None] otherwise -- + which is every candidate that is not one of the five II/III-class vigils + in the 1962 universal calendar. + + Answers only the identity question. Whether the feast actually kept its + own day is {!Colitur_kernel.Calendar}'s to determine, because only the + kernel holds the settled post-transfer placement of the whole year; see + {!Colitur_kernel.Precedence.rules.vigil_feast} for the division of labour + and for why the feast is NAMED here rather than inferred there. + + The I-class vigils (Nativity Eve, Pentecost Vigil) are outside this rule + by RG 33's own wording and outside losing at all by RG 30, so they are + absent from the table and this returns [None] for them -- the same + argument {!is_omissible_vigil} already makes for the rule's other two + triggers. *) +val vigil_feast : Vocab_ef.rank Precedence.candidate -> Slug.t option + +(** The (vigil slug, feast slug) pairs {!vigil_feast} answers from, exposed so + the test suite can assert both directions against the shipped sanctoral + data: that every slug named here exists, and that no II/III-class vigil in + the data is missing from it. Neither is checkable by a type, and both fail + SILENTLY -- an unmatched slug simply makes RG 33 inert for that entry. *) +val vigil_feast_table : (string * Slug.t) list + +(** Whether a slug names a vigil, by either of the two conventions this + calendar's data uses (a "-vigil" suffix from the temporal cycle, a + "vigil-of-" prefix from the sanctoral bootstrap). Exposed for the same + table-drift assertions as {!vigil_feast_table}. *) +val is_vigil : string -> bool + +(** Whether a rank is one RG 33 can omit -- II or III class. I-class vigils are + outside the rule (RG 30). Exposed alongside {!is_vigil}. *) +val is_omissible_vigil : Vocab_ef.rank -> bool + (** 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 -- cgit v1.3