From 7d3b5ec831a60e8b63466251d63b2bd564acba2b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 12 Aug 2026 13:32:13 +0200 Subject: fix(rite-ef,kernel): follow the oracle on the Purification; admit reads the day's own Sunday-ness, not observed Fix round 1 review, two findings landed together because each golden pin's correctness depends on both. CRITICAL (item 1): reverts this branch's own first-pass retag of the Purification (2 February) from Lord back to Bvm -> Lord. The calendarium titles it 'B. Mariae Virg.', but missalemeum -- this project's designated EF oracle -- treats it as taking an occurring II-class Sunday's place outright, commemorations empty, exactly RG16(a)'s own festum Domini treatment (2020-02-02, 2014-02-02, independently fetched). Control, 2019-09-08 (the Nativity of the BVM, an ordinary Marian feast, on a Sunday): the Sunday observed, the feast merely commemorated -- the opposite pattern, proving the Purification's own oracle treatment is deliberate, not a gap. RG16(a) alone, on the untouched data, would have matched the oracle character for character; retagging Bvm is what moved colitur away from it on 1200 domain-wide days. The user has ruled: follow the oracle. Genuine primary-text counter-evidence (RG120(b), the colour rule grouping 2 February under 'B. Mariae Virg.', a category separate from 'Domini') is recorded in the register, not discarded -- a different project could reasonably rule the other way on the same evidence. most-holy-name-of-mary stays retagged Bvm; that one was never disputed. Removes the now-unneeded C12 lectio allow-list entry (data/ef/ expected-divergences.sexp, test_differential.ml): with the Purification Lord again, colitur matches lectio's own class = lord on every affected date, no divergence to allow-list. IMPORTANT (item 2): Precedence.rules.admit gains a ~temporal parameter -- a genuine kernel signature change, unlike RG16(a) itself. RG111(b)'s own Sunday rank floor ('one commemoration, namely of a II-class feast') used to be detected by reading observed's own slug for a Sunday marker, which silently assumed observed IS the day's temporal-cycle office. RG16(a) breaks that assumption on purpose: a Feast of the Lord standing in for an impeded Sunday 'cum omnibus iuribus et privilegiis' keeps the day a dominica II classis for RG111(b) too, even though the FEAST, not the Sunday, is observed. Before this fix an unrelated saint (Pope Sixtus II et al., 6 August, Class3) was wrongly admitted into the freed slot on every one of 1178 domain-wide days. Oracle-confirmed directly: 2023-08-06 (a Sunday) shows him displaced, commemorations empty; 2026-08-06 (an ordinary Thursday) shows him admitted -- being a Sunday is exactly what excludes him. admit now reads Sunday-ness off ~temporal (the day's own temporal-cycle candidate, passed through from Precedence.resolve unchanged) instead of observed. Validate's own admission fixed-point invariant updated to pass the same real temporal candidate through. Both golden pins in test_golden.ml were promoted from actual colitur day output without independently consulting the rubric or the oracle first -- a real defect the review caught. Re-derived from both before re-pinning: 2028-08-06 and 2025-02-02 now pin comms=[], no commemoration at all, matching missalemeum exactly in both cases. --- lib/rites/rite_ef/precedence_ef.ml | 46 +++++++++++++++++++++++++++++++------ lib/rites/rite_ef/precedence_ef.mli | 45 +++++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 21 deletions(-) (limited to 'lib/rites') diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 179fa13..2987312 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -707,10 +707,35 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate) classis", "dominicis II classis", "aliis diebus II classis", "diebus III et IV classis") -- read here off [observed]'s own [rank] and, for the Sunday/non-Sunday II-class split, the same slug marker [privilege_of] and - RG 33's [impedes_vigil] already use ([is_sunday_slug]). No [context] - (date/season/weekday) is available to [admit] (see precedence.mli's - [rules.admit]) or needed: [observed] IS the day's own celebration, so its - rank and slug already carry everything RG 111's own four categories test. *) + RG 33's [impedes_vigil] already use ([is_sunday_slug]). + + CORRECTED (fix round 1, RG16(a) task): the previous version of this + comment claimed "[observed] IS the day's own celebration, so its rank and + slug already carry everything RG 111's own four categories test" -- + WRONG once RG 16(a) exists. RG 16(a)'s own text says the winning Feast of + the Lord holds the Sunday's place "cum omnibus iuribus et privilegiis" + (with ALL its rights and privileges) -- the day remains a "dominica II + classis" for RG 111(b)'s own purposes even though [observed] is now the + FEAST, not the Sunday, so [observed]'s own slug is no longer a reliable + signal of "is this a Sunday" once something can legitimately observe in + the Sunday's place. Confirmed wrong for real data by the oracle: 6 August + falling on a Sunday shows the Transfiguration observed and NO + commemoration (missalemeum: `commemorations: []`, Pope Sixtus II et al. + `displaced`); the pre-fix code, reading Sunday-ness off [observed]'s own + slug ("transfiguration-of-our-lord", no Sunday marker), wrongly took the + [Class2, false] "other II class: one" branch below and admitted Sixtus + (Class3) regardless of RG 111(b)'s own "de festo II classis" rank floor. + Control, also oracle-confirmed: 6 August on an ordinary WEEKDAY (no + Sunday collision) correctly admits Sixtus -- being a Sunday is exactly + what excludes him, and [observed]'s own identity cannot tell the two + cases apart on its own. + + Fixed by reading Sunday-ness off [temporal] instead -- {!Precedence.rules.admit}'s + own [~temporal] parameter, {!Precedence.resolve}'s [~temporal] argument + passed straight through, unaffected by whether it won the day. [context] + (date/season/weekday) is still not needed: [temporal]'s own slug already + carries everything this split needs, the same way [observed]'s used to + before a competing office could occupy the Sunday's place. *) (* RG 8's four-class dignity order, Class1 highest. Deliberately NOT [band] (RG 91's much finer 28-entry table): [band] needs a [context] [admit] @@ -738,6 +763,7 @@ let rec take n = function | x :: xs -> if n <= 0 then [] else x :: take (n - 1) xs let admit ~(observed : Vocab_ef.rank Precedence.candidate) + ~(temporal : Vocab_ef.rank Precedence.candidate) (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list = (* Sorted once, by dignity then slug (see [compare_dignity]); every branch @@ -758,11 +784,17 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) let sorted = List.stable_sort compare_dignity comms in let is_privileged (_, p) = p = Precedence.Privileged in let observed_rank = observed.Precedence.cel.Celebration.rank in - let observed_is_sunday = - is_sunday_slug (Slug.to_string observed.Precedence.cel.Celebration.slug) + (* CORRECTED (fix round 1, RG16(a) task): read off [temporal], not + [observed] -- see this function's own doc comment above for the full + argument and the oracle evidence. [temporal] is the day's own + temporal-cycle candidate regardless of who is [observed]; RG 16(a) + makes that distinction live for the first time (a Feast of the Lord + can now be [observed] on a day whose [temporal] candidate is a Sunday). *) + let day_is_sunday = + is_sunday_slug (Slug.to_string temporal.Precedence.cel.Celebration.slug) in let open Vocab_ef in - match (observed_rank, observed_is_sunday) with + match (observed_rank, day_is_sunday) with | Class1, _ -> (* RG 111: "I class: none save one privileged." Ordinary commemorations never get a slot at all on a I-class day, no matter how many are diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 55f947e..ee7667d 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -164,24 +164,26 @@ val nativity_octave_prefix : string 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, +(** [admit ~observed ~temporal 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 + - the CIVIL DAY is a [Class2] Sunday ([temporal]'s slug carries + {!sunday_marker} -- CORRECTED, fix round 1, RG16(a) task: read off + [temporal], not [observed]; see below): 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; + - the civil day is 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. @@ -192,6 +194,20 @@ val september_ember_prefix : string Ties break on slug, matching {!Precedence.compare_by}, so the result never depends on the order [comms] arrives in. + [temporal] -- CORRECTED, fix round 1 (RG16(a) task): this function used + to read the Sunday/non-Sunday split off [observed]'s own slug, which + silently assumed [observed] IS the day's temporal-cycle office. RG 16(a) + breaks that assumption on purpose: a Feast of the Lord holds an impeded + II-class Sunday's own place "cum omnibus iuribus et privilegiis" + (RG 16(a)'s own text), so the day still counts as a "dominica II + classis" for RG 111(b) even though [observed] is now the FEAST. + [temporal] -- {!Precedence.rules.admit}'s own parameter, the day's + temporal-cycle candidate regardless of who won -- is the reliable + signal; oracle-confirmed (6 August: Sunday excludes a Class3 saint + Sixtus II from the freed slot, an ordinary weekday admits him). Every + row of the previous behaviour where [observed] and [temporal] coincide + (no RG 16(a) displacement occurred) is unaffected. + 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 @@ -202,6 +218,7 @@ val september_ember_prefix : string itself total over an empty or arbitrarily long [comms]. *) val admit : observed:Vocab_ef.rank Precedence.candidate -> + temporal:Vocab_ef.rank Precedence.candidate -> (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list -> (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list -- cgit v1.3