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. --- test/test_differential.ml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'test/test_differential.ml') diff --git a/test/test_differential.ml b/test/test_differential.ml index 8423888..8c1dd8d 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -1089,6 +1089,13 @@ let rg69_one_hop_slugs = [ "ef-christmas-1-saturday" ] (* [layer_c_reason l c diffs] returns the [data/ef/expected-divergences.sexp] [id] this row-pair's remaining (post Layer A/B) diff set belongs to, or [None] if nothing here explains it (a genuine, uncovered failure). *) +(* The three vigils RG 33's third trigger can actually reach on the shipped + universal calendar -- see C39 in [layer_c_reason]. Named here rather than + written inline so a future slug rename fails in one visible place. *) +let rg33_vigil_slugs = + [ "vigil-of-st-lawrence"; "vigil-of-the-nativity-of-st-john-the-baptist"; + "vigil-of-sts-peter-paul" ] + let layer_c_reason (l : row) (c : row) diffs = let m = month_of_date l.date and d = day_of_date l.date in if diffs = [] then None @@ -1135,6 +1142,22 @@ let layer_c_reason (l : row) (c : row) diffs = colour-only Saturday still falls to C17 and cannot be absorbed here. *) else if is_bvm_saturday_row c diffs then Some "C17" else if diffs = [ Colour_f ] && List.mem c.slug audit_colour_corrected_slugs then Some "C18" + (* C39 -- RG 33's THIRD omission trigger, implemented 2026-08-21. lectio + omits a II/III-class vigil that itself falls on a Sunday (the rule's + SECOND trigger) but not one whose FEAST fails to keep its own day, so on + these days lectio still shows the vigil and colitur no longer does. + + Gated on LECTIO's own slug being one of the three vigils this clause can + reach, and on colitur's NOT being a vigil -- identity on both sides, not + merely a diff-set shape, the same discipline C6 and C14 were given. The + three are exhaustive here: the Assumption's and the Ascension's vigils + can never qualify, because their I-class feasts always keep their own + day (measured over the whole 1583-9999 domain, 0 occurrences). *) + else if + List.mem l.slug rg33_vigil_slugs + && (not (List.mem c.slug rg33_vigil_slugs)) + && subset diffs [ Slug_f; Rank; Colour_f; First_f; Gospel_f ] + then Some "C39" (* C25 -- CLOSED, BVM Saturday Mass (2026-08-17), predicate removed: 0 rows. Its days are unoccupied IV-class Saturdays, so they carry RG 78's office and now RG 309(a)'s Mass for it, which answers before step 3's walkback -- cgit v1.3