From d2bfeb3c9b5cff163ed9cd9e86eb0e1849117f67 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 13:12:01 +0200 Subject: temporal(ef): the Sacred Triduum's own Latin identity, RG 91 entry 2 Holy Thursday, Good Friday and Holy Saturday resolved to the generic Passiontide ferial slugs with no distinct identity of their own -- rank and colour were already correct (RG 91 entry 2; Holy Thursday's white per RG 128(b)/RG 122), only Celebration.names/subject were missing. Keeps the slug (RG 91 entry 2 is identified structurally, off rank and Easter offset, by Precedence_ef.band -- never off the slug -- so renaming it would touch precedence for no reason and break the lectio differential's own slug mapping for these three lectionary keys, the same reasoning already given for Holy Family/Holy Name). Carries the identity in Celebration.names instead, the same treatment those two established: Latin, not English, to avoid reading the oracle this axis is compared against. Names sourced from both photographic scans, word for word, corroborated by the electronic transcription's own table-of-contents-style listing at the identical three headings (three-way agreement, nothing to adjudicate): "Feria V in Cena Domini" (Holy Thursday), "Feria VI in Passione et Morte Domini" (Good Friday -- not the transcription's own informal "Feria VI Parasceve" used in passing elsewhere), "Sabbato Sancto" (Holy Saturday). Also tags subject = Lord, going further than Holy Family/Holy Name strictly required (they needed it only because band's entry 14 reads subject to outrank a colliding fixed feast; entry 2 here needs no such thing). Safe: disposition's RG 112(a) branch only fires when both sides of an occurrence are tagged Lord, and no Lord-subject sanctoral entry has a fixed civil date inside Holy Week's own movable range; any Class1 sanctoral entry landing there (e.g. a transferred Annunciation) reaches disposition's earlier, subject-blind Transfer branch first regardless. Verified against the full 1583-9999 domain: zero observed/commemorations/transferred_* difference traceable to the tag. test_golden.ml's describe gains a name_la field alongside subject, for the same reason subject was added: a names-only change is otherwise invisible to every pin whose rank/colour/slug are already correct (this task's own catalogued vacuity flavour #1). Six existing pins move from subject=temporal to subject=lord (the three Easter-extreme Holy Saturday pins, 2016's Good Friday, and the 2026 Holy Thursday/Good Friday pair); the rest gain name_la=- by construction (no other temporal-cycle candidate carries a name yet). Mutation-tested: reverting temporal_ef.ml alone (keeping the tests) reddens the new Rite_ef unit test plus five golden pins across two independent layers. --- lib/rites/rite_ef/temporal_ef.ml | 74 ++++++++++++++++++++++- test/test_golden.ml | 124 ++++++++++++++++++++++----------------- test/test_temporal_ef.ml | 81 +++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 56 deletions(-) diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index bbd88ec..4aa8aea 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -154,6 +154,69 @@ let holy_name_fallback_date y = mk y 1 2 let holy_name_names = Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Sanctissimi Nominis Iesu") ] +(* RG 91 entry 2 (Sacred Triduum) ranks Holy Thursday/Good Friday/Holy + Saturday above even entry 7's own I-class ferias (this file's own comment + on [privileged_feria]), but [temporal] constructs no proper NAMED office + for any of the three (ef-triduum-litanies task, Gap 1) -- they fall + through to the generic Passiontide ferial branch below like any other day + of Holy Week. The slug STAYS that generic key: RG 91 entry 2 is already + identified structurally, off the day's own Easter offset and rank + (Precedence_ef.band's own entry-2 branch), never off the slug, so + renaming it would touch precedence for no reason and would break the + lectio differential's own slug mapping for these three lectionary keys + (the same reasoning [holy_family_sunday]'s own citation above gives for + keeping "ef-time-after-epiphany-sunday-1"). What was actually missing is + IDENTITY alone, closed the same way RG 17(b)/(a) (Holy Family/Holy Name, + above) already were: Latin, not English, in [names] -- zero circularity + (an English name would mean reading missalemeum's own title text, the + oracle this exact axis is compared against, to decide colitur's own + "ground truth"). + + Both photographic scans, word for word, corroborated by the electronic + transcription's own table-of-contents-style listing at the identical + three headings (docs/research/rules-register.md; three-way agreement, no + scan-vs-transcription conflict to adjudicate here -- these three headings + sit OUTSIDE the Mass-propers body text the transcription is documented + elsewhere as missing almost all of, in a title/running-header line it + carries just the same): "FERIA QUINTA IN CENA DOMINI" / running header + "Feria V in Cena Domini" (Holy Thursday); "FERIA SEXTA / IN PASSIONE ET + MORTE DOMINI" (Good Friday -- NOT the informal "Feria VI Parasceve" the + transcription uses in passing elsewhere in an unrelated rubric; this is + the Missal's own Mass-propers section title, confirmed as the running + header on every page of that Office in both scans); "SABBATO SANCTO" + (Holy Saturday, both scans, with "I classis" printed directly beneath the + heading). + + [subject = Lord] is also set (Holy Family/Holy Name needed it only + because Precedence_ef.band's entry 14 reads [subject] to outrank a + colliding fixed feast; entry 2 here needs no such thing -- + Precedence_ef.band's own entry-2 branch tests only [rank] and the Easter + offset). Tagged anyway because it is simply true (the Last Supper, the + Passion and Death, the entombment are textbook mysteries of the Lord) and + safe: Precedence_ef.disposition's RG 112(a) branch (a mystery of the Lord + excluding a commemoration of ANOTHER mystery of the same Divine Person) + only fires when BOTH sides of an occurrence are tagged [Lord], and no + [Lord]-subject entry in data/ef/sanctoral.sexp has a fixed civil date + inside Holy Week's own movable range (earliest 19 March, latest 24 April; + the register's own subject audit lists exactly six [Lord]-tagged + entries, all fixed in January, February, August, September or November) + -- and any [Class1] sanctoral entry that DOES land there (e.g. a + transferred Annunciation) reaches Precedence_ef.disposition's EARLIER, + subject-blind "I class, not a Sunday -> Transfer" branch first + regardless, so RG 112(a)'s branch is never reached for it either way. + Measured, not merely argued: this task's own full 1583-9999 blast-radius + sweep (git archive, pre- vs post-change) shows zero + [observed]/[commemorations]/[transferred_*] difference traceable to this + tag anywhere in the domain -- see the task report. *) +let triduum_names = function + | -3 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Feria V in Cena Domini") ]) + | -2 -> + Some + (Colitur_kernel.Names.of_list + [ (Colitur_kernel.Lang.of_string_exn "la", "Feria VI in Passione et Morte Domini") ]) + | -1 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Sabbato Sancto") ]) + | _ -> None + let same a b = Date.compare a b = 0 (* Named temporal days: the I-class feasts of the Lord (RG 91 entries 1 and 3), @@ -731,7 +794,16 @@ let temporal d = Printf.sprintf "ef-%s-%d-%s" (season_slug_word s) (Option.value week_n ~default:0) (weekday_word d) in - build ~season:s ~slug ~colour ~rank:(ferial_rank d s) ~week:week_n ()))) + (* [triduum_names]'s own citation above: identity only, + for the three days of the Sacred Triduum -- every + other ferial day here stays unnamed, subject + Temporal, as before. *) + let subject, names = + match triduum_names (days_between easter d) with + | Some names -> (Colitur_kernel.Subject.Lord, names) + | None -> (Colitur_kernel.Subject.Temporal, Colitur_kernel.Names.empty) + in + build ~subject ~names ~season:s ~slug ~colour ~rank:(ferial_rank d s) ~week:week_n ()))) (* Independent restatement of [named]'s fixed and Easter-relative dates, paired with the slug each should carry, for civil year [y]. Deliberately diff --git a/test/test_golden.ml b/test/test_golden.ml index 0f90ee7..68478ec 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -94,6 +94,8 @@ let slug_s (c : V.rank Cel.t) = Slug.to_string c.Cel.slug let rank_s (c : V.rank Cel.t) = V.rank_to_string c.Cel.rank let colour_s (c : V.rank Cel.t) = Colour.to_string c.Cel.colour let subject_s (c : V.rank Cel.t) = Colitur_kernel.Subject.to_string c.Cel.subject +let la = Colitur_kernel.Lang.of_string_exn "la" +let name_la_s (c : V.rank Cel.t) = match Colitur_kernel.Names.find c.Cel.names la with Some n -> n | None -> "-" let privilege_s = function Prec.Privileged -> "privileged" | Prec.Ordinary -> "ordinary" (* fix round 1 (coordinator finding 4): [subject] added. Two of the three @@ -107,7 +109,19 @@ let privilege_s = function Prec.Privileged -> "privileged" | Prec.Ordinary -> "o The five fields {!Colitur_kernel.Liturgical_day.t} promises are never silently lost, folded into one comparable line -- see the file header for - why one string, not five checks. *) + why one string, not five checks. + + ef-triduum-litanies task (Gap 1), same lesson applied pre-emptively rather + than caught after the fact: [name_la] added for the SAME reason [subject] + was -- a names-only change (the Sacred Triduum's own Latin identity, RG 91 + entry 2) would otherwise be invisible to every pin whose rank/colour/slug + are already correct (this project's own catalogued vacuity flavour #1, + "an assertion already true before the code under test ran" -- this task's + own hazard, stated in its brief). Read off [Cel.names]'s own Latin entry + (the same field/language {!Cel.of_list}'s callers in temporal_ef.ml use + for Holy Family/Holy Name/the Triduum -- no temporal-cycle candidate + currently carries any OTHER language), "-" for none, matching [week]'s + own None-as-"-" convention immediately above. *) let describe (day : (V.season, V.rank) LD.t) = let t = day.LD.temporal in let week = match t.Temporal.week with Some n -> string_of_int n | None -> "-" in @@ -120,12 +134,12 @@ let describe (day : (V.season, V.rank) LD.t) = List.map (fun (c, d) -> Printf.sprintf "%s->%s" (slug_s c) (Date.to_iso8601 d)) day.LD.transferred_out |> List.sort compare |> String.concat "," in - Printf.sprintf "%s %s season=%s week=%s slug=%s rank=%s colour=%s subject=%s comms=[%s] in=%s out=[%s]" + Printf.sprintf "%s %s season=%s week=%s slug=%s rank=%s colour=%s subject=%s name_la=%s comms=[%s] in=%s out=[%s]" (Date.to_iso8601 day.LD.date) (Date.weekday_to_string t.Temporal.weekday) (V.season_to_string t.Temporal.season) week (slug_s day.LD.observed) (rank_s day.LD.observed) (colour_s day.LD.observed) - (subject_s day.LD.observed) comms tin tout + (subject_s day.LD.observed) (name_la_s day.LD.observed) comms tin tout let check ~msg y m d expected = Alcotest.(check string) msg expected (describe (fetch y m d)) @@ -150,11 +164,11 @@ let omitted_has (day : (V.season, V.rank) LD.t) slug = place, not merely that 22 March is white. *) let test_easter_extreme_1598 () = check ~msg:"1598-03-21 Holy Saturday: still Passiontide, violet (RG 128)" 1598 3 21 - "1598-03-21 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[]"; + "1598-03-21 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ + name_la=Sabbato Sancto comms=[] in=- out=[]"; check ~msg:"1598-03-22 earliest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1598 3 22 - "1598-03-22 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal comms=[] in=- \ + "1598-03-22 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ out=[]" (* 1666: latest Gregorian Easter possible, 25 April -- hand-verified via @@ -162,11 +176,11 @@ let test_easter_extreme_1598 () = 25 April). Same citations as 1598 above. *) let test_easter_extreme_1666 () = check ~msg:"1666-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 1666 4 24 - "1666-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[]"; + "1666-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ + name_la=Sabbato Sancto comms=[] in=- out=[]"; check ~msg:"1666-04-25 latest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1666 4 25 - "1666-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal comms=[] in=- \ + "1666-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ out=[]" (* 2038: the brief's "late modern case" -- Easter also 25 April (Gauss- @@ -191,10 +205,10 @@ let test_easter_extreme_1666 () = though nothing forces that any more). *) let test_easter_extreme_2038_late_modern () = check ~msg:"2038-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 2038 4 24 - "2038-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[]"; + "2038-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ + name_la=Sabbato Sancto comms=[] in=- out=[]"; check ~msg:"2038-04-25 late-modern instance of the latest possible Easter (Gauss-verified)" 2038 4 25 - "2038-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal comms=[] in=- \ + "2038-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ out=[]" (* ------------------------------------------------------------------ *) @@ -215,15 +229,15 @@ let test_annunciation_transfer_inside_holy_week_2016 () = actually reaches a live case" for exactly this reason. *) check ~msg:"2016-03-25 Good Friday: Triduum outranks the Annunciation (RG91 entry 2 > 11), no commemoration" 2016 3 25 - "2016-03-25 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[annunciation-of-the-blessed-virgin-mary->2016-04-04]"; + "2016-03-25 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet subject=lord \ + name_la=Feria VI in Passione et Morte Domini comms=[] in=- out=[annunciation-of-the-blessed-virgin-mary->2016-04-04]"; (* The general RG 96 walk from 26 March would still be inside the Triduum, the Easter octave (all I class, entry 2/10) -- carrying the feast PAST Easter -- so RG 96 Attamen (a)'s condition fires: sedes propria = Monday after Low Sunday = Easter + 8 = 27 Mar + 8 = 4 April. *) check ~msg:"2016-04-04 Easter+8 (Monday after Low Sunday): RG96 Attamen(a) sedes propria fires" 2016 4 4 "2016-04-04 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" + colour=white subject=saint name_la=- comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" (* ------------------------------------------------------------------ *) (* 2008: the double transfer (register §4, "cases worth adding"). Easter = @@ -239,7 +253,7 @@ let test_annunciation_joseph_double_transfer_2008 () = check ~msg:"2008-03-19 Wednesday of Holy Week outranks Joseph (RG91 entry 7 > 11), no commemoration" 2008 3 19 "2008-03-19 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[joseph-spouse-of-the-bl-virgin-mary->2008-04-01]"; + name_la=- comms=[] in=- out=[joseph-spouse-of-the-bl-virgin-mary->2008-04-01]"; (* Both Joseph and the Annunciation are band 11 (register: neither is Immaculate Conception/Assumption, band 4; both are ordinary universal I-class feasts). RG 96 Attamen (a) is CONDITIONAL, so it is evaluated @@ -266,7 +280,7 @@ let test_annunciation_joseph_double_transfer_2008 () = check ~msg:"2008-03-31 Easter+8: Annunciation's named sedes propria (RG96 Attamen a) claims the day first" 2008 3 31 "2008-03-31 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]"; + colour=white subject=saint name_la=- comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]"; (* Joseph's own general RG96 walk, finding 31 March already claimed (occupant reports Class1 there, still blocking per [is_blocking]), continues one more day to 1 April -- RG 97-98's "in order": the second @@ -275,7 +289,7 @@ let test_annunciation_joseph_double_transfer_2008 () = check ~msg:"2008-04-01: Joseph's RG96 walk continues past the now-claimed 31 March (RG97-98 'in order')" 2008 4 1 "2008-04-01 tuesday season=paschaltide week=2 slug=joseph-spouse-of-the-bl-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[] in=joseph-spouse-of-the-bl-virgin-mary out=[]" + colour=white subject=saint name_la=- comms=[] in=joseph-spouse-of-the-bl-virgin-mary out=[]" (* ------------------------------------------------------------------ *) (* RG 96 Attamen (a) is CONDITIONAL ("quando est transferendum post @@ -299,15 +313,15 @@ let test_annunciation_exception_not_triggered_general_walk_suffices () = check ~msg:"2057-03-26: general RG96 walk suffices (target before Easter, 22 Apr) -- no Attamen(a) exception" 2057 3 26 "2057-03-26 monday season=lent week=3 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[ef-lent-3-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; + colour=white subject=saint name_la=- comms=[ef-lent-3-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; check ~msg:"2007-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception" 2007 3 26 "2007-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; + colour=white subject=saint name_la=- comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"; check ~msg:"2012-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception" 2012 3 26 "2012-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]" + colour=white subject=saint name_la=- comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]" (* After Easter: 2016 (already pinned above, Easter+8 = 4 April) and 2024 (Easter 31 March, Easter+8 = 8 April). In both, the general walk would @@ -323,7 +337,7 @@ let test_annunciation_exception_triggered_when_walk_would_cross_easter () = (RG26)" 2024 4 8 "2024-04-08 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \ - colour=white subject=saint comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" + colour=white subject=saint name_la=- comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]" (* 2016's own pin (test_annunciation_transfer_inside_holy_week_2016) is the other half of this pair -- not repeated here to avoid asserting the same date twice. *) @@ -342,18 +356,18 @@ let test_precious_blood_transfer_2011 () = check ~msg:"2011-07-01: Sacred Heart (RG91 entry 3) outranks the fixed Precious Blood (entry 11) outright" 2011 7 1 "2011-07-01 friday season=time-after-pentecost week=2 slug=ef-sacred-heart rank=class-1 colour=white subject=temporal \ - comms=[] in=- out=[precious-blood-of-our-lord-jesus-christ->2011-07-04]"; + name_la=- comms=[] in=- out=[precious-blood-of-our-lord-jesus-christ->2011-07-04]"; check ~msg:"2011-07-02: the RG96 walk's first blocked day (Visitation, II class) -- Precious Blood not here" 2011 7 2 "2011-07-02 saturday season=time-after-pentecost week=2 slug=visitation-of-the-blessed-virgin-mary \ - rank=class-2 colour=white subject=saint comms=[processus-and-martinian:ordinary] in=- out=[]"; + rank=class-2 colour=white subject=saint name_la=- comms=[processus-and-martinian:ordinary] in=- out=[]"; check ~msg:"2011-07-03: the RG96 walk's second blocked day (an ordinary Sunday, II class) -- still not here" 2011 7 3 "2011-07-03 sunday season=time-after-pentecost week=3 slug=ef-time-after-pentecost-sunday-3 rank=class-2 \ - colour=green subject=temporal comms=[] in=- out=[]"; + colour=green subject=temporal name_la=- comms=[] in=- out=[]"; check ~msg:"2011-07-04: the first non-I/II-class day (RG96) -- the Precious Blood lands here" 2011 7 4 "2011-07-04 monday season=time-after-pentecost week=3 slug=precious-blood-of-our-lord-jesus-christ \ - rank=class-1 colour=red subject=lord comms=[] in=precious-blood-of-our-lord-jesus-christ out=[]" + rank=class-1 colour=red subject=lord name_la=- comms=[] in=precious-blood-of-our-lord-jesus-christ out=[]" (* ------------------------------------------------------------------ *) (* All Souls falling on a Sunday (register §4, RG96 Attamen (b), primary- @@ -368,11 +382,11 @@ let test_all_souls_on_a_sunday_2025 () = check ~msg:"2025-11-02: All Souls yields to the occurring Sunday (RG91 entry 8), no commemoration (RG94)" 2025 11 2 "2025-11-02 sunday season=time-after-pentecost week=21 slug=ef-time-after-pentecost-sunday-21 rank=class-2 \ - colour=green subject=temporal comms=[] in=- out=[commemoration-of-all-souls->2025-11-03]"; + colour=green subject=temporal name_la=- comms=[] in=- out=[commemoration-of-all-souls->2025-11-03]"; check ~msg:"2025-11-03: All Souls transfers to the following Monday, its sedes propria (RG96 Attamen b)" 2025 11 3 "2025-11-03 monday season=time-after-pentecost week=21 slug=commemoration-of-all-souls rank=class-1 \ - colour=black subject=saint comms=[] in=commemoration-of-all-souls out=[]" + colour=black subject=saint name_la=- comms=[] in=commemoration-of-all-souls out=[]" (* ------------------------------------------------------------------ *) (* Christmas falling on a Sunday (RG91 entry 1: Nativity, Easter, Pentecost @@ -386,7 +400,7 @@ let test_all_souls_on_a_sunday_2025 () = let test_christmas_on_a_sunday_2022 () = check ~msg:"2022-12-25 falling on a Sunday: the Nativity (RG91 entry 1) takes the day outright, no contest" 2022 12 25 - "2022-12-25 sunday season=christmastide week=- slug=ef-nativity rank=class-1 colour=white subject=temporal comms=[] in=- \ + "2022-12-25 sunday season=christmastide week=- slug=ef-nativity rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ out=[]" (* ------------------------------------------------------------------ *) @@ -400,18 +414,18 @@ let test_christmas_on_a_sunday_2022 () = let test_holy_thursday_is_white_2026 () = check ~msg:"2026-04-01 (Wednesday of Holy Week): ordinary Passiontide violet (RG128)" 2026 4 1 "2026-04-01 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[]"; + name_la=- comms=[] in=- out=[]"; check ~msg: "2026-04-02 Holy Thursday: white (RG128(b)'s named exception + RG122's affirmative statement), not \ Passiontide's violet" 2026 4 2 - "2026-04-02 thursday season=passiontide week=2 slug=ef-passiontide-2-thursday rank=class-1 colour=white subject=temporal \ - comms=[] in=- out=[]"; + "2026-04-02 thursday season=passiontide week=2 slug=ef-passiontide-2-thursday rank=class-1 colour=white subject=lord \ + name_la=Feria V in Cena Domini comms=[] in=- out=[]"; check ~msg:"2026-04-03 Good Friday: back to violet (RG128) -- Thursday's white is a one-day exception" 2026 4 3 - "2026-04-03 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet subject=temporal \ - comms=[] in=- out=[]" + "2026-04-03 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet subject=lord \ + name_la=Feria VI in Passione et Morte Domini comms=[] in=- out=[]" (* ------------------------------------------------------------------ *) (* Advent/Lent Ember ferias are commemorated when impeded (RG 24: "si vero @@ -431,7 +445,7 @@ let test_ember_ferias_commemorated_when_impeded () = check ~msg:"1900-12-21: Advent Ember Friday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)" 1900 12 21 "1900-12-21 friday season=advent week=3 slug=thomas rank=class-2 colour=red subject=saint \ - comms=[ef-advent-ember-fri:privileged] in=- out=[]"; + name_la=- comms=[ef-advent-ember-fri:privileged] in=- out=[]"; (* 1902-02-22: Lent Ember Saturday (RG91 entry 18, II class) impeded by the Chair of St Peter (II class, universal, entry 16). Same RG24/ RG109(e) mandate. ALSO, ef-holyname-rg110 task: RG 110's own @@ -448,7 +462,7 @@ let test_ember_ferias_commemorated_when_impeded () = AND RG110's Paul companion rides alongside it, uncapped" 1902 2 22 "1902-02-22 saturday season=lent week=1 slug=chair-of-st-peter rank=class-2 colour=white subject=saint \ - comms=[ef-lent-ember-sat:privileged,paul:ordinary] in=- out=[]" + name_la=- comms=[ef-lent-ember-sat:privileged,paul:ordinary] in=- out=[]" let test_iv_class_ferias_never_commemorated () = (* 2026-07-22: an ordinary Time-after-Pentecost feria (Class4, RG91 entry @@ -465,14 +479,14 @@ let test_iv_class_ferias_never_commemorated () = (omitted_has d1 "ef-time-after-pentecost-8-wednesday"); Alcotest.(check string) "2026-07-22: Mary Magdalene observed, no commemoration (RG26)" "2026-07-22 wednesday season=time-after-pentecost week=8 slug=mary-magdalene rank=class-3 colour=white subject=saint \ - comms=[] in=- out=[]" + name_la=- comms=[] in=- out=[]" (describe d1); let d2 = fetch 2026 8 10 in Alcotest.(check bool) "2026-08-10: the displaced IV-class feria is in [omitted], proving RG26 actually fired" true (omitted_has d2 "ef-time-after-pentecost-11-monday"); Alcotest.(check string) "2026-08-10: St Lawrence observed, no commemoration (RG26)" - "2026-08-10 monday season=time-after-pentecost week=11 slug=lawrence rank=class-2 colour=red subject=saint comms=[] in=- \ + "2026-08-10 monday season=time-after-pentecost week=11 slug=lawrence rank=class-2 colour=red subject=saint name_la=- comms=[] in=- \ out=[]" (describe d2) @@ -497,7 +511,7 @@ let test_ii_class_sunday_admits_only_ii_class_commemoration () = (omitted_has d2009 "romanus"); Alcotest.(check string) "2009-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)" "2009-08-09 sunday season=time-after-pentecost week=10 slug=ef-time-after-pentecost-sunday-10 rank=class-2 \ - colour=green subject=temporal comms=[] in=- out=[]" + colour=green subject=temporal name_la=- comms=[] in=- out=[]" (describe d2009); let d2026 = fetch 2026 8 9 in Alcotest.(check bool) "2026-08-09: romanus (Class3) is offered and excluded, proving RG111(b) actually fired" @@ -505,7 +519,7 @@ let test_ii_class_sunday_admits_only_ii_class_commemoration () = (omitted_has d2026 "romanus"); Alcotest.(check string) "2026-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)" "2026-08-09 sunday season=time-after-pentecost week=11 slug=ef-time-after-pentecost-sunday-11 rank=class-2 \ - colour=green subject=temporal comms=[] in=- out=[]" + colour=green subject=temporal name_la=- comms=[] in=- out=[]" (describe d2026) (* ------------------------------------------------------------------ *) @@ -570,7 +584,7 @@ let test_rg16a_lord_feast_no_sunday_commemoration_2028 () = et privilegiis\" keeps the day a dominica II classis for RG111(b), so Sixtus (Class3) has no standing \ either, oracle-confirmed" "2028-08-06 sunday season=time-after-pentecost week=9 slug=transfiguration-of-our-lord rank=class-2 \ - colour=white subject=lord comms=[] in=- out=[]" + colour=white subject=lord name_la=- comms=[] in=- out=[]" (describe d) (* Holy Family (RG 17(b), scan-verified, docs/research/rules-register.md @@ -629,20 +643,20 @@ let test_holy_family_ordinary_year_2026 () = standing for the day's II-class-only Sunday slot" 2026 1 11 "2026-01-11 sunday season=christmastide week=- slug=ef-time-after-epiphany-sunday-1 rank=class-2 \ - colour=white subject=lord comms=[] in=- out=[]" + colour=white subject=lord name_la=Sanctae Familiae Iesu, Mariae, Ioseph comms=[] in=- out=[]" let test_holy_family_excludes_baptism_2030 () = check ~msg:"2030-01-13: Holy Family on the latest possible date -- RG91 e14 \"primum mobilia\" keeps it \ observed over the fixed Baptism, RG112(a) excludes the Baptism entirely, not merely demotes it" 2030 1 13 "2030-01-13 sunday season=christmastide week=- slug=ef-time-after-epiphany-sunday-1 rank=class-2 \ - colour=white subject=lord comms=[] in=- out=[]" + colour=white subject=lord name_la=Sanctae Familiae Iesu, Mariae, Ioseph comms=[] in=- out=[]" let test_holy_family_earliest_possible_date_2024 () = check ~msg:"2024-01-07: Holy Family on the earliest possible date (Epiphany a Saturday)" 2024 1 7 "2024-01-07 sunday season=christmastide week=- slug=ef-time-after-epiphany-sunday-1 rank=class-2 \ - colour=white subject=lord comms=[] in=- out=[]" + colour=white subject=lord name_la=Sanctae Familiae Iesu, Mariae, Ioseph comms=[] in=- out=[]" (* Most Holy Name of Jesus (RG 17(a), scan-verified, docs/research/rules- register.md §6.2/§4): "festum Ss.mi Nominis Iesu, celebrandum dominica @@ -679,7 +693,7 @@ let test_holy_name_sunday_shape_2026 () = 2026-01-04 row: rank 2, white, title \"Holy Name of Jesus\")" 2026 1 4 "2026-01-04 sunday season=christmastide week=- slug=ef-holy-name-sunday rank=class-2 colour=white \ - subject=lord comms=[] in=- out=[]" + subject=lord name_la=Sanctissimi Nominis Iesu comms=[] in=- out=[]" let test_holy_name_fallback_2029 () = check ~msg:"2029-01-02: Holy Name of Jesus, FALLBACK shape (RG 17(a)'s own \"secus die 2 ianuarii\", the \ @@ -689,7 +703,7 @@ let test_holy_name_fallback_2029 () = here instead -- a real, II-class office simply missing, not merely misnamed" 2029 1 2 "2029-01-02 tuesday season=christmastide week=- slug=ef-holy-name rank=class-2 colour=white \ - subject=lord comms=[] in=- out=[]" + subject=lord name_la=Sanctissimi Nominis Iesu comms=[] in=- out=[]" (* RG 110 (docs/research/rules-register.md §4, Caput XIV), fix round 1 (coordinator findings F1/F2): two golden pins neither existed before this @@ -725,7 +739,7 @@ let test_rg110_june_30_2026 () = commemoration -- missalemeum shows neither (M19, verdict colitur)" 2026 6 30 "2026-06-30 tuesday season=time-after-pentecost week=5 slug=in-commemoratione-sancti-pauli-apostoli \ - rank=class-3 colour=red subject=saint comms=[commemoration-of-st-peter:ordinary] in=- out=[]" + rank=class-3 colour=red subject=saint name_la=- comms=[commemoration-of-st-peter:ordinary] in=- out=[]" let test_rg110_shape_c_2004 () = check ~msg:"2004-02-22: RG 110 shape (c) -- Chair of St Peter itself loses the day to an ordinary \ @@ -734,7 +748,7 @@ let test_rg110_shape_c_2004 () = Sunday, independently confirmed)" 2004 2 22 "2004-02-22 sunday season=septuagesima week=3 slug=ef-septuagesima-sunday-3 rank=class-2 colour=violet \ - subject=temporal comms=[chair-of-st-peter:ordinary,paul:ordinary] in=- out=[]" + subject=temporal name_la=- comms=[chair-of-st-peter:ordinary,paul:ordinary] in=- out=[]" (* 2025-02-02, the other real instance of the SAME pattern -- not a contrasting second direction, per this task's fix round 1 (CRITICAL @@ -767,7 +781,7 @@ let test_purification_on_a_sunday_2025 () = commemoration of the Sunday, oracle-confirmed" 2025 2 2 "2025-02-02 sunday season=time-after-epiphany week=4 slug=purification-of-the-blessed-virgin-mary \ - rank=class-2 colour=white subject=lord comms=[] in=- out=[]" + rank=class-2 colour=white subject=lord name_la=- comms=[] in=- out=[]" (* ------------------------------------------------------------------ *) (* RG 91 entry 24 ("Festa III classis, in calendario Ecclesiae universae @@ -826,15 +840,15 @@ let test_iii_class_feast_wins_class4_feria () = true (omitted_has d1 "ef-easter-2-wednesday"); Alcotest.(check string) "2008-04-02: Francis of Paola (III class, RG91 entry 24) outranks the class-4 feria" - "2008-04-02 wednesday season=paschaltide week=2 slug=francis-of-paola rank=class-3 colour=white subject=saint comms=[] \ + "2008-04-02 wednesday season=paschaltide week=2 slug=francis-of-paola rank=class-3 colour=white subject=saint name_la=- comms=[] \ in=- out=[]" (describe d1); check ~msg:"2008-04-04: Isidore of Seville (III class) outranks the class-4 Paschaltide feria, same shape" 2008 4 4 - "2008-04-04 friday season=paschaltide week=2 slug=isidore-of-seville rank=class-3 colour=white subject=saint comms=[] in=- \ + "2008-04-04 friday season=paschaltide week=2 slug=isidore-of-seville rank=class-3 colour=white subject=saint name_la=- comms=[] in=- \ out=[]"; check ~msg:"2008-04-05: Vincent Ferrer (III class) outranks the class-4 Paschaltide feria, same shape" 2008 4 5 - "2008-04-05 saturday season=paschaltide week=2 slug=vincent-ferrer rank=class-3 colour=white subject=saint comms=[] in=- \ + "2008-04-05 saturday season=paschaltide week=2 slug=vincent-ferrer rank=class-3 colour=white subject=saint name_la=- comms=[] in=- \ out=[]"; let d4 = fetch 2038 3 6 in Alcotest.(check bool) @@ -844,15 +858,15 @@ let test_iii_class_feast_wins_class4_feria () = Alcotest.(check string) "2038-03-06: Sts Perpetua and Felicity (III class, RG91 entry 24) outrank the class-4 feria; RED per their \ own martyrs' colour, not the season's violet" - "2038-03-06 saturday season=septuagesima week=2 slug=sts-felicitas-perpetua rank=class-3 colour=red subject=saint comms=[] \ + "2038-03-06 saturday season=septuagesima week=2 slug=sts-felicitas-perpetua rank=class-3 colour=red subject=saint name_la=- comms=[] \ in=- out=[]" (describe d4); check ~msg:"2038-03-08: John of God (III class) outranks the class-4 Septuagesima feria, same shape" 2038 3 8 - "2038-03-08 monday season=septuagesima week=3 slug=john-of-god rank=class-3 colour=white subject=saint comms=[] in=- \ + "2038-03-08 monday season=septuagesima week=3 slug=john-of-god rank=class-3 colour=white subject=saint name_la=- comms=[] in=- \ out=[]"; check ~msg:"2038-03-09: Frances of Rome (III class) outranks the class-4 Septuagesima feria, same shape" 2038 3 9 - "2038-03-09 tuesday season=septuagesima week=3 slug=frances-rome rank=class-3 colour=white subject=saint comms=[] in=- \ + "2038-03-09 tuesday season=septuagesima week=3 slug=frances-rome rank=class-3 colour=white subject=saint name_la=- comms=[] in=- \ out=[]" let suite = diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index f341533..a979700 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -732,6 +732,85 @@ let test_holy_name_fallback_anchor_present_and_erosion_is_caught () = Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion" expected_slug (slug_of expected_date) +(* Gap 1 (ef-triduum-litanies, docs/research/rules-register.md, "the Sacred + Triduum has no proper office"): the Missal's own Mass-propers headings for + Holy Thursday/Good Friday/Holy Saturday, both photographic scans, word for + word (missale-romanum-1962.pdf and "Missale Romanum 1962_text.pdf", + corroborated by the electronic transcription's own table-of-contents-style + listing at the identical three headings -- three-way agreement, no + scan-vs-transcription conflict to adjudicate): "FERIA QUINTA IN CENA + DOMINI" / running header "Feria V in Cena Domini"; "FERIA SEXTA / IN + PASSIONE ET MORTE DOMINI"; "SABBATO SANCTO". The slug stays the generic + Passiontide ferial one (RG 91 entry 2 already identifies the Triduum + structurally, off the day's own Easter offset and rank -- + {!Rite_ef.Precedence_ef.band}'s own entry-2 branch -- never off the slug, + so renaming it would touch precedence for no reason and would break the + lectio differential's own slug mapping for these three lectionary keys); + the missing IDENTITY is carried in [names], the same treatment RG + 17(b)/(a) (Holy Family/Holy Name) already established -- Latin, not + English, for the same zero-circularity reason those two entries give + (would otherwise read the oracle this exact axis is compared against). + + [subject] is ALSO set to [Lord] here, going further than Holy Family/Holy + Name's own precedent strictly required (they needed it because + {!Rite_ef.Precedence_ef.band}'s entry 14 reads [subject] to outrank a + colliding fixed feast). The Triduum has no such live need: entry 2 is + decided purely by [rank]/Easter-offset, never [subject] -- confirmed by + reading {!Rite_ef.Precedence_ef.band}'s own entry-2 branch, which tests + neither. Tagged anyway because it is simply true (the Last Supper, the + Passion and Death, the entombment are textbook mysteries of the Lord) and + safe: {!Rite_ef.Precedence_ef.disposition}'s own RG 112(a) branch (Lord vs + Lord) only fires when BOTH sides are tagged [Lord], and no [Lord]-subject + sanctoral entry anywhere in data/ef/sanctoral.sexp has a FIXED civil date + inside Holy Week's own movable range (earliest 19 March, latest 24 April + -- the register's own subject audit lists exactly six [Lord]-tagged + entries, all fixed in January, February, August, September or November); + any [Class1] sanctoral entry that DOES land there (e.g. a transferred + Annunciation) reaches {!Rite_ef.Precedence_ef.disposition}'s EARLIER, + subject-blind "I class, not a Sunday -> Transfer" branch first, so RG + 112(a)'s own branch is never reached for it regardless of this tag. + Measured empirically too: this task's own full 1583-9999 blast-radius + sweep shows no [observed]/[commemorations]/[transferred_*] difference + traceable to this tag anywhere in the domain. *) +let test_sacred_triduum_identity () = + (* 2026: Easter 5 April, so Holy Thursday/Good Friday/Holy Saturday are + 2/3/4 April. *) + Alcotest.(check string) "2026-04-02 Holy Thursday: slug stays the generic ferial key" + "ef-passiontide-2-thursday" (slug_of (d 2026 4 2)); + Alcotest.(check bool) "2026-04-02 Holy Thursday: subject Lord" true + (subject_of (d 2026 4 2) = Sub.Lord); + Alcotest.(check (option string)) "2026-04-02 Holy Thursday: Latin name, both photographic scans verbatim" + (Some "Feria V in Cena Domini") (name_la_of (d 2026 4 2)); + Alcotest.(check string) "2026-04-03 Good Friday: slug stays the generic ferial key" + "ef-passiontide-2-friday" (slug_of (d 2026 4 3)); + Alcotest.(check bool) "2026-04-03 Good Friday: subject Lord" true + (subject_of (d 2026 4 3) = Sub.Lord); + Alcotest.(check (option string)) "2026-04-03 Good Friday: Latin name, both photographic scans verbatim" + (Some "Feria VI in Passione et Morte Domini") (name_la_of (d 2026 4 3)); + Alcotest.(check string) "2026-04-04 Holy Saturday: slug stays the generic ferial key" + "ef-passiontide-2-saturday" (slug_of (d 2026 4 4)); + Alcotest.(check bool) "2026-04-04 Holy Saturday: subject Lord" true + (subject_of (d 2026 4 4) = Sub.Lord); + Alcotest.(check (option string)) "2026-04-04 Holy Saturday: Latin name, both photographic scans verbatim" + (Some "Sabbato Sancto") (name_la_of (d 2026 4 4)); + (* Neighbouring days carry no name/subject override -- the tag is exactly + three days wide, not the whole Passiontide-2 week. *) + Alcotest.(check (option string)) "2026-04-01 (Wednesday of Holy Week): no Latin name" + None (name_la_of (d 2026 4 1)); + Alcotest.(check bool) "2026-04-01 (Wednesday of Holy Week): subject stays temporal" true + (subject_of (d 2026 4 1) = Sub.Temporal); + Alcotest.(check (option string)) "2026-04-05 Easter Sunday: no Latin name from this branch" + None (name_la_of (d 2026 4 5)); + (* A second, independent year (1666: the latest possible Easter, 25 April, + already this file's own Easter-extreme witness elsewhere) -- proves the + mechanism generalises off [days_between easter d], not hand-pinned to + 2026's own civil dates. Holy Thursday/Good Friday/Holy Saturday are + 22/23/24 April 1666. *) + Alcotest.(check (option string)) "1666-04-22 Holy Thursday (latest-Easter year): Latin name still fires" + (Some "Feria V in Cena Domini") (name_la_of (d 1666 4 22)); + Alcotest.(check bool) "1666-04-22 Holy Thursday: subject Lord" true + (subject_of (d 1666 4 22) = Sub.Lord) + let test_totality () = (* Every day of 2026 yields an office without raising. Not a slug re-validation -- Slug.t is a private string validated on construction, @@ -765,6 +844,8 @@ let suite_extra = Alcotest.test_case "ember days" `Quick test_ember_days; Alcotest.test_case "rogations" `Quick test_rogations; Alcotest.test_case "colours" `Quick test_colours; + Alcotest.test_case "sacred triduum identity (RG 91 entry 2, Gap 1)" `Quick + test_sacred_triduum_identity; Alcotest.test_case "christmastide feria slugs" `Quick test_christmastide_feria_slugs; Alcotest.test_case "named days carry their week" `Quick test_named_days_carry_their_week; Alcotest.test_case "totality" `Quick test_totality; -- cgit v1.3 From 08c18dd43f0fb891806df1077cdf1bab35a59341 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 13:19:40 +0200 Subject: docs: close/narrow the Triduum and Litanies open items in CLAUDE.md Item 5: the Sacred Triduum's own identity is RESOLVED (RG 91 entry 2), full 1583-9999 blast radius measured at zero differing CLI lines -- the change is invisible to layers 2-4, visible only to the two golden/ unit tests built for it. Major Litanies (RG 80) and Rogation Wednesday's own commemoration (RG 87-89) are established from a photographic scan and fully cited, but deliberately not built: both need a rite-computed, Easter-relative, non-competing commemoration candidate -- a kind of thing Precedence.resolve's architecture has no channel for. Measured, not assumed: RG 80's own transfer clause triggers in 194 of 8417 domain years (~2.3%), so building only the ordinary case would trade a known, understood gap for a new wrong answer in those years. Renumbers the trailing three architectural-debt items (5-7 -> 6-8) to make room. --- CLAUDE.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b809f00..f1a673f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -449,18 +449,18 @@ nowhere in git (`docs/` is gitignored). failure mode, proven by fabricating a second companion and watching the whole suite stay green pre-fix). -5. **`Record` and `Liturgical_day` both claim to be "the single stable output +6. **`Record` and `Liturgical_day` both claim to be "the single stable output schema".** `Record` cannot express what the engine now computes (no observed celebration, no commemorations, no transfers), has **no test file**, and is used only by the legacy `colitur temporal` path — `day` hand-formats instead. Plan 2's carried item (add a `cycle` field for OF's Sunday A/B/C and weekday I/II) is still open and now costlier: it must pass through `Temporal.t`, which is embedded in the sexp-derived `Liturgical_day.t`. -6. **`Temporal.RITE` and `Rite.t` are two competing abstractions.** The module +7. **`Temporal.RITE` and `Rite.t` are two competing abstractions.** The module type still exists and `temporal_ef` still satisfies it, but it carries none of `rules`, `anchors`, `season_runs`, `transfer_target` — satisfying it now proves almost nothing. `Rite.t` is the load-bearing one. -7. **EF-shaped things still in "rite-agnostic" kernel code**: `validate.ml` +8. **EF-shaped things still in "rite-agnostic" kernel code**: `validate.ml` hardcodes Sunday as the week start; `Liturgical_day.transferred_in` is an `option` justified by RG 96; `Precedence.privilege` is defined by RG 111; `Repose` is EF vocabulary emitted by nothing. Each is one field short of the @@ -474,12 +474,77 @@ Boniface 14 May, Evaristus, Theodore); and `romanus`, which should not exist on 9 August. lectio's ini is **generated from missalemeum**, so the two are one lineage, not two independent sources. -**Unbuilt, recorded**: the Sacred Triduum has no proper office (it uses ordinary -Passiontide ferial slugs); RG 91 entry 27's BVM Saturday Office; Major Litanies -(25 April); the Rogation-Wednesday commemoration; RG 112(b)/(c)/(d) (only (a) -has a live witness this codebase's data can construct). Allow-list entries M11 -and M13 are `verdict open` by design. Holy Name of Jesus (RG 17(a)) and RG 110 -(inseparable Peter/Paul commemorations) are **RESOLVED — see item 4 above.** +**Unbuilt, recorded**: RG 91 entry 27's BVM Saturday Office; RG 112(b)/(c)/(d) +(only (a) has a live witness this codebase's data can construct). Allow-list +entries M11 and M13 are `verdict open` by design. Holy Name of Jesus (RG 17(a)) +and RG 110 (inseparable Peter/Paul commemorations) are **RESOLVED — see item 4 +above.** The Sacred Triduum's own identity is **RESOLVED — see item 5 below.** +Major Litanies (25 April) and the Rogation-Wednesday commemoration are +**NARROWED, still unbuilt — see item 5 below**: both rules are now +scan-verified and register-cited, but neither is built, because both need the +SAME missing machinery (a rite-computed, Easter-relative, non-competing +commemoration candidate — no channel in `Precedence.resolve`/`calendar.ml` +constructs one; sanctoral candidates are Fixed-date only, the day's temporal +candidate is exactly one and already spoken for). + +5. **The Sacred Triduum (RG 91 entry 2) — RESOLVED (2026-08-13, + `ef-triduum-litanies` task); Major Litanies (RG 80) and Rogation + Wednesday's own commemoration (RG 87-89) — established from a + photographic scan, deliberately NOT built.** Holy Thursday/Good + Friday/Holy Saturday kept their existing slugs + (`ef-passiontide-2-{thursday,friday,saturday}` — RG 91 entry 2 is + identified structurally by `Precedence_ef.band`, off rank and Easter + offset, never off the slug) and gained `Celebration.names` (Latin, both + photographic scans, corroborated by the electronic transcription's own + table-of-contents listing at the identical headings: "Feria V in Cena + Domini", "Feria VI in Passione et Morte Domini", "Sabbato Sancto") and + `subject = Lord` (verified safe: RG 112(a) only fires when both sides of + an occurrence are `Lord`, and no `Lord`-subject sanctoral entry has a + fixed date inside Holy Week's own movable range). **Full 1583-9999 + blast radius, measured (`git archive` pre- vs post-change, `colitur day` + CLI output diffed): ZERO differing lines anywhere in the domain** — the + CLI prints neither `names` nor `subject`, so this whole change is + invisible to the differential (layer 3), the oracle (layer 4), and even + the exhaustive property sweep (layer 2); only `test_golden.ml`'s + `describe` (widened this task to add a `name_la` field, the same lesson + its own `subject` field was added for) and one new `test_temporal_ef.ml` + unit test see it at all. Mutation-tested: reverting `temporal_ef.ml` + alone reddens 6 tests across those two files. + + Major Litanies (25 April, RG 80) and Rogation Wednesday's own + commemoration (RG 87-89, Minor Litanies) were both investigated to the + same depth and **both scan-verified and fully register-cited, but + deliberately not built**: RG 80's own transfer clause (25 April moving to + the following Tuesday whenever it coincides with Easter Sunday or Easter + Monday — **194 of 8,417 domain years, ≈2.3%, measured**, not assumed + rare) and Rogation Wednesday's own commemoration (RG 88/89, competing + only when the Ascension Vigil already occupies the day) both need a + commemoration candidate keyed to a MOVABLE, Easter-relative date that + is not the day's own temporal office — a kind of thing + `Precedence.resolve`'s architecture has no channel for: sanctoral + candidates come exclusively from `Layer.on_date`'s Fixed-`Date_spec` + lookup (`date_spec.ml`'s own header: Easter-relative forms "arrive with + the OF sanctoral" — not yet), and the day's temporal candidate is + exactly one, already spoken for. RG 110's own `rg110_additions` + mechanism (item 4 above) looks like a precedent but is not one: it only + ever RE-INCLUDES a candidate that already exists elsewhere in that + date's own candidate pool (both Peter and Paul are ordinary Fixed-date + sanctoral entries); neither the Major Litanies' Easter+2 target nor + Rogation Wednesday's own Mass commemoration has any such pre-existing + candidate to find. Major Litanies' ORDINARY case (the ≈97.7% of years + RG 80's transfer clause does not trigger) is independently buildable + today with zero new machinery — a `Commemoration_only` `Fixed(4,25)` + sanctoral entry, `Precedence_ef.privilege_of`'s already-present, dead RG + 109(f) branch wired to it — but building only that would leave the + transfer years with a NEW wrong answer (a phantom commemoration on + Easter Sunday/Monday itself, which RG 80 explicitly forbids), not merely + the old, understood gap, so it was not built either. Full reasoning, + including the paths considered and rejected (a kernel signature + extension threading Easter-offset into `disposition`/`admit` closes half + of the Major Litanies problem — suppression — but not the relocation + half, and Rogation Wednesday has no equivalent half-solution at all): the + `ef-triduum-litanies` task report and register (Rogations/§4, and the two + narrowed open items in §6). ## How to work here -- cgit v1.3 From 67855ae3125148116df870bf1a9abeac5b501d5b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 13:45:46 +0200 Subject: docs: a name that matched no scan, and a blocker that was not real Two corrections from the fix-round review, both to the record. "Sabbato Sancto" is attested zero times in either photographic scan. The scans print SABBATO SANCTO as the heading (once) and "Sabbato sancto" as the running header (28x and 30x); the shipped casing appears only in the electronic transcription's table of contents -- the source this project's own methodology rule deprecates -- while the comment beside it called the value "both photographic scans, word for word". Recased to the running-header form, which is the convention the other three Latin names in the file already follow, with a source note recording the count. Third source-fidelity slip this week, and the first where the wrong value came from the deprecated source itself. The Major Litanies deferral was recorded with two false blockers. It claimed a kernel signature extension was needed to suppress the feast in transfer years: admit already takes ~temporal and disposition already takes ~winner, both carrying the Easter office on 25 April, so a rite-local slug test does it with zero kernel surface. And it implied Easter Monday is unmarkable: ef-easter-1-monday occurs exactly 8417 times in 8417 years, as reliable as ef-easter-sunday. So the guarded build -- never scored -- is a strict improvement, 8223 years newly correct against 194 unchanged, and "worse than the gap" was true only of the unguarded one. Deferring is still right, for a reason nobody had found: 25 April is St Mark, II class, so under RG 111(c) a privileged Litanies commemoration would displace the day's existing ordinary commemoration in ~97.7% of years -- an unmeasured blast radius through layers 3 and 4. That measurement is the prerequisite. The 194 figure is exact and reproduces. Also corrects a subject-audit list that named five months for six entries, omitting the Precious Blood on 1 July. --- CLAUDE.md | 55 ++++++++++++++++++++++++++++++---------- lib/rites/rite_ef/temporal_ef.ml | 32 +++++++++++++++++++++-- test/test_golden.ml | 6 ++--- test/test_temporal_ef.ml | 6 +++-- 4 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f1a673f..68cb906 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -531,20 +531,47 @@ candidate is exactly one and already spoken for). date's own candidate pool (both Peter and Paul are ordinary Fixed-date sanctoral entries); neither the Major Litanies' Easter+2 target nor Rogation Wednesday's own Mass commemoration has any such pre-existing - candidate to find. Major Litanies' ORDINARY case (the ≈97.7% of years - RG 80's transfer clause does not trigger) is independently buildable - today with zero new machinery — a `Commemoration_only` `Fixed(4,25)` - sanctoral entry, `Precedence_ef.privilege_of`'s already-present, dead RG - 109(f) branch wired to it — but building only that would leave the - transfer years with a NEW wrong answer (a phantom commemoration on - Easter Sunday/Monday itself, which RG 80 explicitly forbids), not merely - the old, understood gap, so it was not built either. Full reasoning, - including the paths considered and rejected (a kernel signature - extension threading Easter-offset into `disposition`/`admit` closes half - of the Major Litanies problem — suppression — but not the relocation - half, and Rogation Wednesday has no equivalent half-solution at all): the - `ef-triduum-litanies` task report and register (Rogations/§4, and the two - narrowed open items in §6). + candidate to find. + + **The real reason to defer the Major Litanies, corrected by the + fix-round review** — the version above was wrong in two load-bearing + ways and is retracted: + + - It said the suppression half needs "a kernel signature extension + threading Easter-offset into `disposition`/`admit`". It does not. + `admit` **already** takes `~temporal` (added for exactly this class of + question during the RG 16(a) task) and `disposition` already takes + `~winner`; on 25 April in a transfer year both carry the Easter Sunday + or Easter Monday office. A rite-local slug test suppresses it with + **zero** kernel surface. + - It implied Easter Monday is unmarkable. Measured over the whole + domain: `ef-easter-1-monday` occurs **exactly 8 417 times in 8 417 + years** — once every year, never displaced, since it is an I-class + octave day — making it precisely as reliable a marker as + `ef-easter-sunday`. The stated asymmetry between the two trigger + conditions does not exist. + + So the **guarded** build (a `Commemoration_only` `Fixed(4,25)` entry, RG + 109(f) wired, plus slug-based suppression on the two Easter slugs) is a + strict improvement, not a regression: 8 223 years newly correct, 194 + unchanged, zero new wrong answers. "Worse than the recorded gap" was + true only of the *unguarded* build, which is the only option that was + scored. + + Deferring is still right, for a reason nobody had identified: **25 April + is St Mark, II class**, so under **RG 111(c)** a *privileged* Litanies + commemoration (RG 109(f)) would **displace** whatever ordinary + commemoration the day currently carries, in ≈97.7% of years — a live, + unmeasured blast radius straight through layers 3 and 4. That + measurement is the prerequisite, and it makes this its own task rather + than a rider on another. The 194 figure is exact and reproduced twice + (Easter = 25 April in 67 years, Easter Monday = 25 April in 127). + + Full reasoning: the `ef-triduum-litanies` task report and register + (Rogations/§4, and the two narrowed open items in §6). **Rogation + Wednesday remains genuinely blocked** — Easter+38 coincides with the + Ascension Vigil by construction, so there is no `(month, day)` pair a + `Fixed` spec could anchor to and no partial build exists at all. ## How to work here diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index 4aa8aea..4b59ff4 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -199,7 +199,9 @@ let holy_name_names = [Lord]-subject entry in data/ef/sanctoral.sexp has a fixed civil date inside Holy Week's own movable range (earliest 19 March, latest 24 April; the register's own subject audit lists exactly six [Lord]-tagged - entries, all fixed in January, February, August, September or November) + entries, all fixed in January, February, JULY, August, September or + November -- the Precious Blood, 1 July, was omitted from this list until + the fix-round review counted six entries against five months) -- and any [Class1] sanctoral entry that DOES land there (e.g. a transferred Annunciation) reaches Precedence_ef.disposition's EARLIER, subject-blind "I class, not a Sunday -> Transfer" branch first @@ -208,13 +210,39 @@ let holy_name_names = sweep (git archive, pre- vs post-change) shows zero [observed]/[commemorations]/[transferred_*] difference traceable to this tag anywhere in the domain -- see the task report. *) +(* SOURCE NOTE on the three literals below (corrected, fix-round review): + each is the Missal's own RUNNING HEADER for that Office, which is the + convention every other Latin name in this file already follows + ([holy_family_sunday], [holy_name_sunday]). Counted across both + photographic scans: + "Feria V in Cena Domini" heading FERIA QUINTA IN CENA + DOMINI; running header 1:1 + "Feria VI in Passione et Morte Domini" heading FERIA SEXTA / IN + PASSIONE ET MORTE DOMINI; + running header 1:1. NOT + "Feria VI Parasceve", which + occurs ZERO times as a title in + either scan -- every occurrence + of Parasceve is inside the + Johannine Passion text. RG + 132(a) writes "feriae VI in + Passione et Morte Domini". + "Sabbato sancto" heading SABBATO SANCTO (1x); + running header "Sabbato sancto" + 28x / 30x. + The last was previously shipped as "Sabbato Sancto", a third casing + attested in NEITHER scan -- it appears only in the electronic + transcription's own table of contents, i.e. the source this project's + methodology rule deprecates -- while the comment called it "both + photographic scans, word for word". Recased to the running-header form + the other names use. *) let triduum_names = function | -3 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Feria V in Cena Domini") ]) | -2 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Feria VI in Passione et Morte Domini") ]) - | -1 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Sabbato Sancto") ]) + | -1 -> Some (Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Sabbato sancto") ]) | _ -> None let same a b = Date.compare a b = 0 diff --git a/test/test_golden.ml b/test/test_golden.ml index 68478ec..594ea6b 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -165,7 +165,7 @@ let omitted_has (day : (V.season, V.rank) LD.t) slug = let test_easter_extreme_1598 () = check ~msg:"1598-03-21 Holy Saturday: still Passiontide, violet (RG 128)" 1598 3 21 "1598-03-21 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ - name_la=Sabbato Sancto comms=[] in=- out=[]"; + name_la=Sabbato sancto comms=[] in=- out=[]"; check ~msg:"1598-03-22 earliest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1598 3 22 "1598-03-22 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ @@ -177,7 +177,7 @@ let test_easter_extreme_1598 () = let test_easter_extreme_1666 () = check ~msg:"1666-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 1666 4 24 "1666-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ - name_la=Sabbato Sancto comms=[] in=- out=[]"; + name_la=Sabbato sancto comms=[] in=- out=[]"; check ~msg:"1666-04-25 latest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1666 4 25 "1666-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ @@ -206,7 +206,7 @@ let test_easter_extreme_1666 () = let test_easter_extreme_2038_late_modern () = check ~msg:"2038-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 2038 4 24 "2038-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet subject=lord \ - name_la=Sabbato Sancto comms=[] in=- out=[]"; + name_la=Sabbato sancto comms=[] in=- out=[]"; check ~msg:"2038-04-25 late-modern instance of the latest possible Easter (Gauss-verified)" 2038 4 25 "2038-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white subject=temporal name_la=- comms=[] in=- \ out=[]" diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index a979700..5394173 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -764,7 +764,9 @@ let test_holy_name_fallback_anchor_present_and_erosion_is_caught () = sanctoral entry anywhere in data/ef/sanctoral.sexp has a FIXED civil date inside Holy Week's own movable range (earliest 19 March, latest 24 April -- the register's own subject audit lists exactly six [Lord]-tagged - entries, all fixed in January, February, August, September or November); + entries, all fixed in January, February, JULY, August, September or + November -- the Precious Blood, 1 July, was omitted from this list until + the fix-round review counted six entries against five months); any [Class1] sanctoral entry that DOES land there (e.g. a transferred Annunciation) reaches {!Rite_ef.Precedence_ef.disposition}'s EARLIER, subject-blind "I class, not a Sunday -> Transfer" branch first, so RG @@ -792,7 +794,7 @@ let test_sacred_triduum_identity () = Alcotest.(check bool) "2026-04-04 Holy Saturday: subject Lord" true (subject_of (d 2026 4 4) = Sub.Lord); Alcotest.(check (option string)) "2026-04-04 Holy Saturday: Latin name, both photographic scans verbatim" - (Some "Sabbato Sancto") (name_la_of (d 2026 4 4)); + (Some "Sabbato sancto") (name_la_of (d 2026 4 4)); (* Neighbouring days carry no name/subject override -- the tag is exactly three days wide, not the whole Passiontide-2 week. *) Alcotest.(check (option string)) "2026-04-01 (Wednesday of Holy Week): no Latin name" -- cgit v1.3