From 044aa0dccbfe76ac9afd299af6074296eeb2cca3 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 15:57:00 +0200 Subject: temporal(ef): the votive Office of the BVM on Saturday, RG 91 entry 27 RG 91 entry 27, "Officium sanctae Mariae in sabbato", sits in the Table of Precedence between entry 26 (III-class vigils) and entry 28 (IV-class ferias). Precedence_ef.band already routed a plain IV-class Saturday feria to entry 27's own band value, but nothing in Temporal_ef.temporal ever constructed the office itself, so an otherwise-unoccupied Saturday still surfaced with a bare ferial slug and its season's ordinary colour. Established from Caput IX of the Rubricae Generales, both photographic scans and the electronic transcription, word for word (no scan-vs-transcription conflict: RG 78/79 are General Rubrics prose, not the Mass-propers body text the transcription is documented elsewhere as missing almost all of): "78. In sabbatis, in quibus occurrit Officium de feria IV classis, fit de sancta Maria in sabbato. 79. Officium sanctae Mariae in sabbato incipit a Matutino et explicit post Nonam." RG 78's own protasis IS "otherwise unoccupied IV-class Saturday" -- every Saturday whose temporal candidate would otherwise be Class4, across every season that rank reaches (Christmastide, Septuagesima, Time after Epiphany, Time after Pentecost, ordinary Paschaltide). Nothing else was needed to decide "otherwise unoccupied": band's own entry-27 branch already reads rank/weekday on the temporal candidate unconditionally and only wins the day when nothing outranks it -- RG 78's own condition, decided by the existing occurrence machinery. Colour is white, unconditionally, per RG 120(b) ("Adhibetur color albus in Officio et Missa de festis: ... b) B. Mariae Virg."), not RG 119/127/ 128's seasonal rules -- this office is never "de Tempore" for colour purposes, so it overrides the season's own violet/green. The slug is deliberately left UNCHANGED (reused from whatever the generic -- ferial fallback would already have produced), for two independent reasons: it matches the Sacred Triduum's own precedent (identified structurally, off rank/weekday, never off the slug), and a bespoke uniform slug would have broken Colitur_kernel.Validate's own slug-uniqueness-per-liturgical-year invariant, sighted zero times before this task -- confirmed directly by writing it that way first and watching the landmark-years test fail. Subject is tagged Bvm (currently inert for precedence -- only Lord is ever read by band/disposition/admit) and the name is the Latin "Officium sanctae Mariae in sabbato" (RG 91 entry 27's own table title and RG 79's own heading), deliberately not English, the same zero-circularity discipline Holy Family/Holy Name/the Sacred Triduum already established. The I-V numbered "Missae de sancta Maria in sabbato" (both scans) are a Mass-propers selection detail (RG 309(a): "iuxta temporum diversitatem") governing which readings/texts are said, not which office is kept -- colitur computes no citations yet (Plan 4), so this is deliberately not modelled here. Two pre-existing tests needed correction, not because they were wrong before, but because RG 78 genuinely changes their dates' own facts: a Saturday inside the Holy Name of Jesus window (3 January) and two Saturdays inside christmastide_feria_slug's own "-0-"/"-2-" stretches (26 December, 10 January) are all otherwise-unoccupied Class4 Saturdays, so they now legitimately carry the BVM office instead of staying a bare Temporal-subject feria. --- lib/rites/rite_ef/temporal_ef.ml | 180 +++++++++++++++++++++++++++++++++++++-- test/test_temporal_ef.ml | 131 +++++++++++++++++++++++++++- 2 files changed, 301 insertions(+), 10 deletions(-) diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index 4b59ff4..2188ce3 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -580,6 +580,143 @@ let ferial_rank d s = let weekday_word d = Date.weekday_to_string (Date.weekday d) +(* RG 91 entry 27, "Officium sanctae Mariae in sabbato" -- the votive Office of + the BVM on Saturday. Caput IX of the Rubricae Generales, both photographic + scans and the electronic transcription, word for word (docs/research/ + rules-register.md §4/§6; no scan-vs-transcription conflict -- RG 78/79 are + General Rubrics prose, not the Mass-propers body text the transcription is + documented elsewhere as missing almost all of): + + "78. In sabbatis, in quibus occurrit Officium de feria IV classis, fit + de sancta Maria in sabbato. + 79. Officium sanctae Mariae in sabbato incipit a Matutino et explicit + post Nonam." + + -- "On Saturdays on which the Office of a IV-class feria occurs, [the + Office] is made of Holy Mary on Saturday [instead]. The Office of Holy + Mary on Saturday begins at Matins and ends after None." RG 78's own + protasis is exactly "otherwise unoccupied IV-class Saturday": WHICH + Saturdays qualify is not a season list to hand-maintain -- it is simply + every Saturday whose temporal candidate would otherwise be + {!Vocab_ef.Class4} (RG 91 entry 28's own unqualified ferial catch-all, + [ferial_rank] above), from ANY season that ferial rank reaches + (Septuagesima, Time after Epiphany, Time after Pentecost, and ordinary + Paschaltide Saturdays outside the privileged Easter/Pentecost octaves, and + the Christmastide ferial stretches [christmastide_feria_slug] builds -- + Advent/Lent/Passiontide Saturdays are already Class2/Class3, higher than + IV class, so RG 78's protasis never fires there, and Ember/Rogation/ + privileged-octave Saturdays are excluded the same way). Nothing else is + needed to decide "otherwise unoccupied": {!Precedence_ef.band}'s own + entry-27 branch already reads [rank = Class4 && weekday = Sat] on the + TEMPORAL candidate unconditionally (312 966 times across the domain, per + the task report) and only ever WINS the day (becomes [observed]) when + nothing of better table position contests it -- exactly RG 78's own + condition, decided by the existing occurrence machinery, not re-derived + here. When a real sanctoral feast DOES win such a Saturday outright, RG + 78's protasis is false for that day (the IV-class Office never "occurs" + there to begin with), and this candidate simply loses/omits exactly as + the plain ferial candidate it replaces already did (RG 26, {!disposition} + in precedence_ef.ml) -- verified: no change to {!Precedence_ef.band}, + {!Precedence_ef.disposition} or {!Precedence_ef.admit} was needed for + this, since RANK stays {!Vocab_ef.Class4} either way and none of those + three functions reads slug/colour/name to decide who wins, what a loser's + fate is, or how many commemorations are admitted. + + Rank: IV class, RG 91's own table position, unconditional ("Dies + liturgici IV classis: 27. Officium sanctae Mariae in sabbato. 28. Feriae + IV classis." -- both scans, register §4). + + Colour: white, ALWAYS, regardless of season -- RG 120(b) (Caput XVIII, + "De coloribus paramentorum", "B) De colore albo"), both photographic + scans: "120. Adhibetur color albus in Officio et Missa de festis: ... b) + B. Mariae Virg." -- white is used in the Office and Mass of feasts ... of + the Blessed Virgin Mary. NOT RG 119 (the "de Tempore" white rule for + Christmastide/Paschaltide) or RG 127/128 (season green/violet): this + Office is never "de Tempore" for colour purposes, so it overrides + [season_colour] unconditionally, including on Septuagesima (violet) and + Time-after-Epiphany/-Pentecost (green) Saturdays -- confirmed against the + oracle (missalemeum, 1 August 2026, a Time-after-Pentecost Saturday: + white, not the season's green). The Missal's own "Missae de sancta Maria + in sabbato" section (both scans) carries no per-Mass colour override the + way some other votive Masses do elsewhere in the same scan ("In hac Missa + adhibetur color albus") -- it needs none, RG 120(b) already settles it + unconditionally for every occurrence. + + Name: "Officium sanctae Mariae in sabbato" -- RG 91 entry 27's own table + title AND RG 79's own heading, both photographic scans and the electronic + transcription, word for word. Latin only, not English -- the same + zero-circularity discipline {!holy_family_sunday}/{!holy_name_names}/ + {!triduum_names} already established: an English name would mean reading + missalemeum's own title text, the oracle this axis is compared against, + to decide colitur's own "ground truth" name. + + Subject: [Bvm] -- simply true (RG 91 entry 27's own title names the BVM + directly) and currently inert for precedence purposes: neither + {!Precedence_ef.band}, {!Precedence_ef.disposition} nor + {!Precedence_ef.admit} tests [subject = Bvm] anywhere today (only [= + Lord] is ever read) -- the same "tagged anyway because it is simply true" + reasoning {!triduum_names}'s own [subject = Lord] tag already used for + the identical shape. Checked for a live interaction with the one other + [Bvm]-tagged entry this codebase's data carries + (data/ef/adjustments.sexp's `most-holy-name-of-mary`, 12 September, + Class3 Feast, not Commemoration_only): whenever 12 September falls on a + Saturday it wins outright over any Class4 candidate ({!Precedence_ef.band} + entry 24 beats entry 27 on table order alone), so this Office and that + entry can never coexist as a winner/commemorated-loser pair -- no live + RG 112(d) witness is created by this tag. + + Slug: UNCHANGED -- deliberately reuses whatever + [christmastide_feria_slug]/the generic -- ferial + fallback would already have produced for this date (e.g. + "ef-septuagesima-2-saturday", "ef-time-after-pentecost-8-saturday"), not + a new bespoke key. Two independent reasons, not one: + (1) PRECEDENT: the SAME shape as the Sacred Triduum ({!triduum_names}'s + own citation above) -- "the slug deliberately STAYS the generic ... + key... identified structurally... never off the slug" -- and + {!Precedence_ef.band}'s own entry-27 branch already reads [rank = Class4 + && weekday = Sat] on the temporal candidate structurally, never its slug, + so nothing needs a bespoke key to find this office. + (2) A bespoke season-independent slug would have been actively WRONG: + [Colitur_kernel.Validate]'s own ["slugs"] check (validate.ml) asserts + every OBSERVED slug is sighted at most once per liturgical year, verified + with ZERO exceptions across the whole 1583-9999 domain before this task + (register/CLAUDE.md's own carried item 4) -- a single uniform + "ef-bvm-saturday" would be sighted 4-9 times in most liturgical years + (every otherwise-unoccupied Saturday), breaking that invariant for real, + not merely in theory (confirmed by writing it that way first: [Validate]'s + own landmark-years test failed immediately, 1583 alone sighting it on 5 + dates). Reusing the season-keyed ferial slug keeps the invariant intact + with no change to [Validate] at all, since season+week+weekday is already + guaranteed unique within a liturgical year by construction. It also + dovetails with the FIVE numbered "Missae de sancta Maria in sabbato" the + Missal's own Common of the BVM prints immediately afterward ("MISSAE DE + S. MARIA IN SABBATO QUAE DICI ETIAM POSSUNT ALIIS DIEBUS UT VOTIVAE DE B. + MARIA VIRGINE, IUXTA RUBRICAS VEL, EX INDULTO, PRO TEMPORUM DIVERSITATE": + I "Tempore Adventus", II "A Nativitate Domini usque ad Purificationem", + III "A die 3 Februarii usque ad feriam IV Hebdomadae sanctae", IV + "Tempore Paschali", V "A festo Ss. Trinitatis usque ad sabbatum ante + dominicam I Adventus" -- both photographic scans, word for word, matching + the oracle's own "V Mass of the B. V. M." title on a Time-after-Pentecost + Saturday): a season-keyed slug leaves room for a future lectionary + bootstrap (Plan 4) to map each one onto its corresponding numbered Mass's + propers, the same way [sunday_slug]'s own season-keyed Sunday slugs + already anchor the ordinary lectionary. RG 309(a)'s own words -- "Missae + quae pro sancta Maria in sabbato, IUXTA TEMPORUM DIVERSITATEM, in Missali + assignantur" -- confirm this is a MASS-TEXT selection detail (which + PROPERS are said), not an OFFICE-identity one; colitur computes no + citations/readings at all yet ([citations] is empty on every day) -- + deliberately not modelled further here, recorded per the task report. The + Missal's own five-season partition for the Mass numbering does not even + align with this rite's own RG 71-77 season boundaries (its own "III" + spans Septuagesima, all of Lent and most of Passiontide as ONE bracket), + one further reason not to try to derive a "Mass number" field from + [Vocab_ef.season] here. *) +let bvm_saturday_names = + Colitur_kernel.Names.of_list + [ (Colitur_kernel.Lang.of_string_exn "la", "Officium sanctae Mariae in sabbato") ] + +let is_bvm_saturday d (rank : Vocab_ef.rank) = Date.weekday d = Date.Sat && rank = Class4 + let temporal d = let y = Date.year d in let easter = Computus.gregorian_easter y in @@ -773,8 +910,22 @@ let temporal d = else ( match christmastide_feria_slug d with | Some slug -> - build ~season:s ~slug ~colour:(season_colour s) ~rank:(ferial_rank d s) - ~week:(week d) () + (* Every Christmastide feria this branch builds is + Class4 ([ferial_rank]'s own catch-all -- Christmastide + is never Advent/Lent/Passiontide), so + [is_bvm_saturday]'s rank test is always true here; the + weekday is the only real condition. See + [bvm_saturday_names]'s own citation above for the full + argument (rank/colour/name/subject) -- the SLUG stays + [slug], the same one this branch would otherwise have + built, per that same citation's own "Slug" paragraph + (not re-quoted at this function's two call sites). *) + if is_bvm_saturday d Class4 then + build ~subject:Colitur_kernel.Subject.Bvm ~names:bvm_saturday_names ~season:s ~slug + ~colour:Colour.White ~rank:Class4 ~week:(week d) () + else + build ~season:s ~slug ~colour:(season_colour s) ~rank:(ferial_rank d s) + ~week:(week d) () | None -> (* The days between Ash Wednesday and Lent I have proper Masses and belong to no numbered week. *) @@ -784,6 +935,26 @@ let temporal d = ~slug:(Printf.sprintf "ef-lent-after-ashes-%s" (weekday_word d)) ~colour:Colour.Violet ~rank:Class3 ~week:None () else + let week_n = week d in + let slug = + Printf.sprintf "ef-%s-%d-%s" (season_slug_word s) + (Option.value week_n ~default:0) (weekday_word d) + in + if is_bvm_saturday d (ferial_rank d s) then + (* RG 78 (Caput IX, "De sancta Maria in sabbato") -- + see [bvm_saturday_names]'s own citation above for + the full argument. This is the ordinary-season + half of the two call sites: Septuagesima, Time + after Epiphany, Time after Pentecost, and + ordinary Paschaltide Saturdays outside the + privileged Easter/Pentecost octaves are exactly + the seasons [ferial_rank] gives Class4 outside + Christmastide (the other call site, above). The + SLUG stays [slug], just computed above, the same + one this branch would otherwise have built. *) + build ~subject:Colitur_kernel.Subject.Bvm ~names:bvm_saturday_names ~season:s ~slug + ~colour:Colour.White ~rank:Class4 ~week:week_n () + else let colour = (* The Pentecost octave weekdays are red, not Paschaltide's white. *) if days_between easter d >= 50 && days_between easter d <= 55 then Colour.Red @@ -817,11 +988,6 @@ let temporal d = else if days_between easter d = -3 then Colour.White else season_colour s in - let week_n = week d in - let slug = - Printf.sprintf "ef-%s-%d-%s" (season_slug_word s) - (Option.value week_n ~default:0) (weekday_word d) - in (* [triduum_names]'s own citation above: identity only, for the three days of the Sacred Triduum -- every other ferial day here stays unnamed, subject diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index 5394173..ff3cc0b 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -296,13 +296,31 @@ let test_holy_name_sunday_shape () = compared against)" None (Colitur_kernel.Names.find (office (d 2026 1 4)).Cel.names en); (* Negative controls: every OTHER day in the 2-5 January window that year - stays [subject Temporal] -- the override is scoped to exactly the one - Sunday [T.holy_name_sunday] names, not the whole window. *) + is NOT Lord -- RG 17(a)'s own override is scoped to exactly the one + Sunday [T.holy_name_sunday] names, not the whole window. 2 and 5 January + (Friday, Monday) stay plain [Temporal] ferias, as before. 3 January is a + SATURDAY -- an unrelated rubric (RG 78, "De sancta Maria in sabbato", + {!bvm_saturday_names}'s own citation in temporal_ef.ml) legitimately + retags it [Bvm], not [Temporal]: it is an otherwise-unoccupied IV-class + Saturday (Christmastide's "ef-christmas-1-*" ferial branch, [ferial_rank]'s + unconditional Class4 there), so RG 78 fires regardless of RG 17(a) being + in play the same week -- the two rubrics are independent and this is not + a collision. This is deliberately NOT folded into the [Temporal] check + above: asserting "stays Temporal" for 3 January would be WRONG once RG + 78 is built, not merely incomplete. *) List.iter (fun dd -> Alcotest.(check bool) (Printf.sprintf "2026-01-%02d: NOT Lord (not the Sunday)" dd) true - (subject_of (d 2026 1 dd) = Sub.Temporal)) + (subject_of (d 2026 1 dd) <> Sub.Lord)) [ 2; 3; 5 ]; + Alcotest.(check bool) "2026-01-02: plain Temporal feria (not a Saturday)" true + (subject_of (d 2026 1 2) = Sub.Temporal); + Alcotest.(check bool) "2026-01-05: plain Temporal feria (not a Saturday)" true + (subject_of (d 2026 1 5) = Sub.Temporal); + Alcotest.(check bool) "2026-01-03: a Saturday -- RG 78's BVM Saturday office fires here \ + independently of RG 17(a)" + true + (subject_of (d 2026 1 3) = Sub.Bvm); (* 2027: 3 January is the Sunday (`date -d 2027-01-03 +%u` = 7) -- the oracle window's own second instance of the Sunday shape. missalemeum's 2027-01-03 row independently corroborates: rank 2, white, title "Holy @@ -489,7 +507,20 @@ let test_colours () = (6 Jan) is a Tuesday, so the actual first-Sunday-after-Epiphany origin is 11 Jan -- exercising all four sub-stretches of the fix. *) let test_christmastide_feria_slugs () = + (* 26 Dec 2026 and 10 Jan 2026 are both SATURDAYS -- ef-bvm-saturday task + (RG 78, "De sancta Maria in sabbato"): every Christmastide feria + [christmastide_feria_slug] builds is unconditionally Class4 + ([ferial_rank]'s own catch-all outside Advent/Lent/Passiontide), so on a + Saturday {!Rite_ef.Temporal_ef.bvm_saturday_names}'s own override fires + here too -- but per that citation's own "Slug" paragraph, the SLUG is + deliberately left UNCHANGED (reused from this same "-0-"/"-2-" naming, + not replaced): only colour/subject/name change. So the slug assertions + below are unaffected by the fix; what is NEW is the colour/subject + override on these two specific dates, asserted alongside them. *) Alcotest.(check string) "26 Dec (Sat)" "ef-christmas-0-saturday" (slug_of (d 2026 12 26)); + Alcotest.(check string) "26 Dec (Sat): the BVM Saturday office overrides the season colour (white, RG 120(b))" + "white" (colour_of (d 2026 12 26)); + Alcotest.(check bool) "26 Dec (Sat): subject Bvm" true (subject_of (d 2026 12 26) = Sub.Bvm); Alcotest.(check string) "28 Dec (Mon)" "ef-christmas-0-monday" (slug_of (d 2026 12 28)); Alcotest.(check string) "2 Jan (Fri)" "ef-christmas-1-friday" (slug_of (d 2026 1 2)); Alcotest.(check string) "5 Jan (Mon)" "ef-christmas-1-monday" (slug_of (d 2026 1 5)); @@ -497,6 +528,7 @@ let test_christmastide_feria_slugs () = lectio's "week 1" key, precisely to avoid colliding with 12-13 Jan below. *) Alcotest.(check string) "7 Jan (Wed, before the origin)" "ef-christmas-2-wednesday" (slug_of (d 2026 1 7)); Alcotest.(check string) "10 Jan (Sat, before the origin)" "ef-christmas-2-saturday" (slug_of (d 2026 1 10)); + Alcotest.(check bool) "10 Jan (Sat): subject Bvm" true (subject_of (d 2026 1 10) = Sub.Bvm); (* 12-13 Jan are on/after the origin: genuinely week 1, lectio's key. *) Alcotest.(check string) "12 Jan (Mon, on/after the origin)" "ef-time-after-epiphany-1-monday" (slug_of (d 2026 1 12)); @@ -813,6 +845,98 @@ let test_sacred_triduum_identity () = Alcotest.(check bool) "1666-04-22 Holy Thursday: subject Lord" true (subject_of (d 1666 4 22) = Sub.Lord) +(* RG 91 entry 27 / RG 78-79 (Caput IX, "De sancta Maria in sabbato") -- + {!Rite_ef.Temporal_ef.bvm_saturday_names}'s own citation has the full + primary-source argument (both photographic scans and the electronic + transcription for RG 78/79 themselves; both photographic scans for RG + 120(b)'s colour rule and the Missal's own "Missae de sancta Maria in + sabbato" heading). This test proves it holds across every season family + [ferial_rank] can give Class4 outside a privileged run -- Christmastide, + Septuagesima, Time after Epiphany, Time after Pentecost and ordinary + Paschaltide -- and that it does NOT fire where RG 78's own protasis is + false (Advent/Lent/Passiontide Saturdays, already II/III class; Ember + Saturdays, II/I class; a non-Saturday day in the very same week). Every + date below was independently checked against `colitur day `'s own + real output (data/ef/sanctoral.sexp + adjustments.sexp) before being + pinned here, exactly like test_golden.ml's own discipline -- not merely + asserted against [T.temporal]'s own predicted shape a second time. *) +let test_bvm_saturday () = + (* Septuagesima, clean (no competing sanctoral candidate that year): + 19 February 2028, Septuagesima I Saturday. Colour is the clearest + possible witness here -- Septuagesima's own season colour is violet + (RG 128(b)), so a white result can only come from RG 120(b)'s override, + never from [season_colour] agreeing by coincidence the way + Christmastide/Paschaltide do below. *) + Alcotest.(check string) "2028-02-19 (Septuagesima I Saturday): slug stays the ordinary ferial key" + "ef-septuagesima-1-saturday" (slug_of (d 2028 2 19)); + Alcotest.(check string) "2028-02-19: class-4, unconditional (RG 91's own IV-class table position)" + "class-4" (rank_of (d 2028 2 19)); + Alcotest.(check string) "2028-02-19: white overrides Septuagesima's own violet (RG 120(b), not RG 128(b))" + "white" (colour_of (d 2028 2 19)); + Alcotest.(check bool) "2028-02-19: subject Bvm" true (subject_of (d 2028 2 19) = Sub.Bvm); + Alcotest.(check (option string)) "2028-02-19: Latin name, both photographic scans and the transcription, \ + word for word" + (Some "Officium sanctae Mariae in sabbato") (name_la_of (d 2028 2 19)); + (* Time after Epiphany, clean: 16 February 2030. Season colour there is + green (RG 127), like Time after Pentecost below -- a second, + independent witness that the override is season-blind, not merely + "true for Septuagesima's violet and Time after Pentecost's green + happen to share a fix". *) + Alcotest.(check string) "2030-02-16 (Time after Epiphany V Saturday): slug stays the ordinary ferial key" + "ef-time-after-epiphany-5-saturday" (slug_of (d 2030 2 16)); + Alcotest.(check string) "2030-02-16: white overrides Time after Epiphany's own green (RG 127)" "white" + (colour_of (d 2030 2 16)); + Alcotest.(check bool) "2030-02-16: subject Bvm" true (subject_of (d 2030 2 16) = Sub.Bvm); + (* Time after Pentecost, clean: 27 June 2026. *) + Alcotest.(check string) "2026-06-27 (Time after Pentecost IV Saturday): slug stays the ordinary ferial key" + "ef-time-after-pentecost-4-saturday" (slug_of (d 2026 6 27)); + Alcotest.(check string) "2026-06-27: white overrides Time after Pentecost's own green (RG 127)" "white" + (colour_of (d 2026 6 27)); + Alcotest.(check bool) "2026-06-27: subject Bvm" true (subject_of (d 2026 6 27) = Sub.Bvm); + (* Ordinary Paschaltide, clean, outside the privileged Easter octave: 18 + April 2026. Season colour there is ALREADY white (RG 119) -- colour + alone cannot distinguish the override firing from it simply not being + needed, so subject/name are the load-bearing assertions on this one, + not colour (the SAME reasoning [test_holy_family]'s own "Christmastide + already white" gap does not arise for -- this is a genuine, deliberate + exception recorded here, not an oversight). *) + Alcotest.(check string) "2026-04-18 (Paschaltide II Saturday): slug stays the ordinary ferial key" + "ef-easter-2-saturday" (slug_of (d 2026 4 18)); + Alcotest.(check string) "2026-04-18: white (coincides with Paschaltide's own RG 119 white -- not a \ + distinguishing witness on its own)" + "white" (colour_of (d 2026 4 18)); + Alcotest.(check bool) "2026-04-18: subject Bvm (the real witness this date proves, since colour alone \ + cannot)" + true (subject_of (d 2026 4 18) = Sub.Bvm); + Alcotest.(check (option string)) "2026-04-18: Latin name present too" (Some "Officium sanctae Mariae in sabbato") + (name_la_of (d 2026 4 18)); + (* Negative controls: RG 78's own protasis ("in quibus occurrit Officium + de feria IV classis") is false for these -- each is already II or III + class, never reaching Class4 at all, so none of them is overridden. *) + Alcotest.(check string) "2026-03-14 (an ordinary Lent Saturday): stays III class, violet, Temporal" + "class-3" (rank_of (d 2026 3 14)); + Alcotest.(check string) "2026-03-14: violet, NOT overridden (RG 78 never applies -- already III class)" + "violet" (colour_of (d 2026 3 14)); + Alcotest.(check bool) "2026-03-14: subject stays Temporal" true (subject_of (d 2026 3 14) = Sub.Temporal); + Alcotest.(check string) "2026-12-12 (an ordinary Advent Saturday, before 17 Dec): stays III class" + "class-3" (rank_of (d 2026 12 12)); + Alcotest.(check bool) "2026-12-12: subject stays Temporal (not overridden)" true + (subject_of (d 2026 12 12) = Sub.Temporal); + Alcotest.(check string) "2026-02-28 (Lent Ember Saturday): stays II class, NOT overridden" "class-2" + (rank_of (d 2026 2 28)); + Alcotest.(check bool) "2026-02-28: subject stays Temporal" true (subject_of (d 2026 2 28) = Sub.Temporal); + Alcotest.(check string) "2026-09-26 (September Ember Saturday): stays II class, NOT overridden" "class-2" + (rank_of (d 2026 9 26)); + (* A non-Saturday day in the SAME week as a clean BVM Saturday stays an + ordinary ferial -- the override is exactly one weekday wide, not the + whole week. 26 June 2026 is the Friday immediately before the 27 June + Saturday pinned above. *) + Alcotest.(check string) "2026-06-26 (Friday, day before a BVM Saturday): plain ferial, not overridden" + "ef-time-after-pentecost-4-friday" (slug_of (d 2026 6 26)); + Alcotest.(check string) "2026-06-26: green, the season's own colour, unaffected" "green" + (colour_of (d 2026 6 26)); + Alcotest.(check bool) "2026-06-26: subject stays Temporal" true (subject_of (d 2026 6 26) = Sub.Temporal) + 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, @@ -848,6 +972,7 @@ let suite_extra = 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 "BVM Saturday office (RG 91 entry 27, RG 78-79)" `Quick test_bvm_saturday; 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 c7f9b66ffe0c265ab049afc8948317e9daded9e7 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 15:57:14 +0200 Subject: test(ef): reach RG 91 entry 27 through the real pipeline, end-to-end Entry 27's band branch has been unreachable for the right reason since it was written: precedence_ef.ml's own comment on it acknowledged the row existed, but the one test exercising it built a hand-typed candidate, never a real Temporal_ef.temporal output, and nothing built the office itself. Closes that gap on both sides: - The band-table row now sources its candidate from Temporal_ef.temporal directly (of_temporal), the same review-finding-3 discipline the Ember/vigil rows already use, so a future regression in the office's own construction fails this row too, not only the unit-level test. - Two new resolve-level end-to-end tests, using the real band/ disposition/admit triple (not stubs): the office winning a real otherwise-unoccupied Saturday and admitting an ordinary commemoration alongside it (1 August 2026, Holy Machabees -- the task brief's own worked oracle example), and the office genuinely losing to a real competing feast and being omitted under RG 26, with no confusion from the one live data witness that also carries subject Bvm (12 September, Most Holy Name of Mary) -- proving RG 26's rank-keyed omission fires before any subject-keyed rule ever could. - Two golden pins mirroring the same two dates, checked by hand against `colitur day 2026` before being typed in, following this file's own "a golden test blesses whatever it is given, verified independently" discipline. The existing 2038-03-06 pin (RG 91 entry 24 outranking a Class4 candidate) is corrected in passing: that date is itself a Saturday, so the displaced candidate the pin's own omitted-list check names is now precisely entry 27's own shape, not entry 28's bare catch-all -- the win/lose outcome the pin proves is unchanged, only its own more precise citation. Mutation-tested (recorded in the task report): reverting is_bvm_saturday to a constant false reddens the dedicated unit test, the new end-to-end resolve test, and the 2026-08-01 golden pin, while leaving the 2026-09-12 "loses to a real feast" pin untouched, exactly as expected since that date's outcome never depended on the fix. --- test/test_golden.ml | 83 +++++++++++++++++++++++++++++++++++- test/test_precedence_ef.ml | 102 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 179 insertions(+), 6 deletions(-) diff --git a/test/test_golden.ml b/test/test_golden.ml index 594ea6b..bb4bcfc 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -851,8 +851,22 @@ let test_iii_class_feast_wins_class4_feria () = "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 + (* ef-bvm-saturday task: 6 March 2038 is a SATURDAY, so the displaced + temporal candidate is now RG 78's own BVM Saturday office + ({!Rite_ef.Temporal_ef}'s own [bvm_saturday_names] citation) rather than + a bare ferial -- but per that citation's own "Slug" paragraph the SLUG + is deliberately left unchanged (reused, not replaced), precisely so + this test's own [omitted_has] probe still needs no update: the displaced + candidate is still named "ef-septuagesima-2-saturday", only its + colour/subject/name would have differed had it won. RG 91 entry 24 + (Sts Perpetua and Felicity) still outranks it either way -- entry 24 < + entry 27, both above entry 28 -- so the disposition/win outcome this + test proves is genuinely unchanged by this task, now more precisely + entry 24 > entry 27 rather than > 28 for this one date (a Saturday), not + that the test itself needed touching. *) Alcotest.(check bool) - "2038-03-06: the displaced class-4 Septuagesima feria is in [omitted] (RG26), proving RG91 entry24>28 fired" + "2038-03-06: the displaced class-4 feria (RG78's own BVM-Saturday office, \ + for this Saturday) is in [omitted] (RG26), proving RG91 entry24>27 fired" true (omitted_has d4 "ef-septuagesima-2-saturday"); Alcotest.(check string) @@ -869,6 +883,63 @@ let test_iii_class_feast_wins_class4_feria () = "2038-03-09 tuesday season=septuagesima week=3 slug=frances-rome rank=class-3 colour=white subject=saint name_la=- comms=[] in=- \ out=[]" +(* ------------------------------------------------------------------ *) +(* RG 91 entry 27 / RG 78-79 (Caput IX, "De sancta Maria in sabbato") -- + ef-bvm-saturday task, Rite_ef.Temporal_ef's own [bvm_saturday_names] + citation has the full primary-source argument. Two real-data pins, the + task brief's own worked example and its mirror-image negative case, + matching this file's own header rule: EVERY literal below was checked by + hand against `colitur day 2026` before being typed in here (this task's + own report has the full session log), never copied from a run and + rationalised after. *) + +(* The task brief's own worked example, checked against the oracle + (missalemeum) directly: "2026-08-01|4|w|V Mass of the B. V. M. -- Salve, + Sancta Parens|Saturday_after_IX_Sunday_after_Pentecost|Holy Machabees". + colitur's own rank (4) and colour (white) now match; the oracle's own + numbered-Mass title (RG 309(a)'s own "iuxta temporum diversitatem", not + modelled here -- {!bvm_saturday_names}'s own "Slug" paragraph) and the + observed day's own English identity remain outside what this comparator + can see (M18's own limit) -- this pin instead proves what colitur DOES + compute: white/class-4/subject Bvm/the Latin title, and Holy Machabees + (real data/ef/sanctoral.sexp entry, Commemoration_only, Class3, 1 August) + still admitted as an ORDINARY commemoration alongside it, matching the + oracle's own commemoration list exactly. *) +let test_bvm_saturday_2026 () = + check ~msg:"2026-08-01: the BVM Saturday office observed, white overriding Time after Pentecost's green, \ + Holy Machabees commemorated -- matches the task brief's own oracle example" + 2026 8 1 + "2026-08-01 saturday season=time-after-pentecost week=9 slug=ef-time-after-pentecost-9-saturday \ + rank=class-4 colour=white subject=bvm name_la=Officium sanctae Mariae in sabbato \ + comms=[holy-machabees:ordinary] in=- out=[]" + +(* The mirror-image case: RG 78's own protasis is FALSE here (a real feast + wins the Saturday outright), so the BVM-Saturday-shaped temporal + candidate must lose and be omitted under RG 26, exactly like the plain + ferial candidate it replaces always was -- no commemoration at all, + proving [band]/[disposition] need no special case for this office (the + design claim the task report makes explicitly). ALSO the one live data + witness where the winning candidate itself carries [subject = Bvm] + (data/ef/adjustments.sexp's own `Edit most-holy-name-of-mary + ((Set_subject Bvm))`) -- proving no Bvm-vs-Bvm confusion with a + RG112(a)-style subject rule: [omitted_has] below confirms the loser is + genuinely reported omitted (RG26 fires on [rank = Class4] alone, checked + ahead of any subject-based branch in {!Rite_ef.Precedence_ef.disposition}), + not silently swallowed by a same-subject exclusion that does not exist + for [Bvm] anywhere in this codebase. *) +let test_bvm_saturday_loses_to_most_holy_name_of_mary_2026 () = + let d = fetch 2026 9 12 in + Alcotest.(check bool) + "2026-09-12: the displaced BVM-Saturday-shaped candidate is in [omitted] (RG26), not silently absent" + true + (omitted_has d "ef-time-after-pentecost-15-saturday"); + Alcotest.(check string) + "2026-09-12: Most Holy Name of Mary (III class, subject Bvm, data/ef/adjustments.sexp) outranks the \ + class-4 BVM-Saturday candidate; no commemoration at all" + "2026-09-12 saturday season=time-after-pentecost week=15 slug=most-holy-name-of-mary rank=class-3 \ + colour=white subject=bvm name_la=- comms=[] in=- out=[]" + (describe d) + let suite = ( "golden pins (known-tricky years)", [ Alcotest.test_case "Easter extreme: 1598 earliest (22 Mar, Gauss-verified)" `Quick @@ -925,5 +996,13 @@ let suite = Alcotest.test_case "ef-rebootstrap: RG91 entry24 (III-class feast) outranks entry28 (class-4 feria) -- the six dates \ deferred since 2038's own Easter-extreme pin (2008-04-02/04/05, 2038-03-06/08/09)" - `Quick test_iii_class_feast_wins_class4_feria + `Quick test_iii_class_feast_wins_class4_feria; + Alcotest.test_case + "RG91 e27/RG78: the BVM Saturday office wins, Holy Machabees commemorated -- matches the task \ + brief's own oracle example (2026-08-01)" + `Quick test_bvm_saturday_2026; + Alcotest.test_case + "RG91 e27/RG78/RG26: the BVM Saturday office loses to Most Holy Name of Mary (subject Bvm too) \ + and is omitted, no confusion (2026-09-12)" + `Quick test_bvm_saturday_loses_to_most_holy_name_of_mary_2026 ] ) diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml index ed9a933..475ae6e 100644 --- a/test/test_precedence_ef.ml +++ b/test/test_precedence_ef.ml @@ -291,10 +291,21 @@ let cases = cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "vigil-of-st-lawrence", 260 ); (* Entry 27 -- RG 91 entry 27 (§4): an otherwise-unoccupied IV-class - Saturday. *) - ( "27 Office of the BVM on Saturday", off 62, - cand ~rank:V.Class4 "ef-time-after-pentecost-1-sat", - 270 ); + Saturday. ef-bvm-saturday task: sourced from [Temporal_ef.temporal]'s + own real output (see [of_temporal], the same review-finding-3 + discipline entry 18's/21's own rows above use), not a hand-typed + slug/rank pair -- this row used to be the one place in this whole + suite where [band]'s entry-27 branch was exercised by a value nothing + in [Temporal_ef.temporal] could actually produce (the office itself + was unbuilt): [band] read [rank = Class4 && weekday = Sat] + correctly, but nothing proved [temporal] ever handed it a candidate + shaped that way for the RIGHT reason. Now it does: [of_temporal (off + 62)] is 6 June 2026, a real otherwise-contested Time-after-Pentecost + Saturday (Norbert, Class3, wins the DAY outright in the real + pipeline -- irrelevant here, since this row tests [band] on the + TEMPORAL CANDIDATE alone, which [Temporal_ef.temporal] builds as the + BVM Saturday office regardless of what wins). *) + ( "27 Office of the BVM on Saturday (from Temporal_ef.temporal)", off 62, of_temporal (off 62), 270 ); (* Entry 28 -- RG 91 entry 28 (§4): the unqualified IV-class catch-all. *) ("28 IV-class feria", off 65, cand ~rank:V.Class4 "ef-time-after-pentecost-1-tue", 280); (* Not an RG 91 row at all: a I-class candidate marked as a vigil, which @@ -1421,6 +1432,81 @@ let test_ii_class_sunday_privileged_witness_admitted_end_to_end () = [ ("ef-some-saint", "omitted: admission limit reached") ] (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted) +(* RG 91 entry 27 / RG 78-79 (Caput IX, "De sancta Maria in sabbato") reached + end-to-end through the REAL pipeline -- this is THE hazard this task's own + brief names explicitly: {!PE.band}'s entry-27 branch has been reachable + only in isolation (the table-driven row above) since it was written, never + through a real [Temporal_ef.temporal] candidate winning an actual day, so + this test is checked for whether it exercises entry 27 for the RIGHT + reason -- proved by the mutation test recorded in the task report + (reverting temporal_ef.ml's own [is_bvm_saturday] branches back to the + generic ferial fallback reddens THIS test, not merely the [band]-table + row, which would still pass unchanged since it is sourced from real + [T.temporal] output either way). + + 1 August 2026, matching the task brief's own worked example against the + oracle exactly: a Time-after-Pentecost Saturday, otherwise unoccupied but + for Holy Machabees (Commemoration_only, Class3, real data/ef/sanctoral.sexp + entry, 1 August) -- the office wins outright (band 270, ahead of Holy + Machabees's own [unclassified]) and the Commemoration_only loser still + rides along as an ordinary commemoration, the SAME shape the oracle's own + row for this date shows ("V Mass of the B. V. M. -- Salve, Sancta Parens", + commemorating Holy Machabees). *) +let test_bvm_saturday_wins_and_commemorates_end_to_end () = + let date = mk 2026 8 1 in + let day_ctx = ctx date in + let bvm_saturday = of_temporal date in + let holy_machabees = + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~layer:PE.universal_layer + "holy-machabees" + in + let resolution = P.resolve real_rules day_ctx ~temporal:bvm_saturday ~sanctoral:[ holy_machabees ] in + Alcotest.(check string) "the BVM Saturday office is observed, not Holy Machabees" + "ef-time-after-pentecost-9-saturday" (S.to_string resolution.P.observed.P.cel.Cel.slug); + Alcotest.(check string) "observed rank is class-4, unconditional" "class-4" + (V.rank_to_string resolution.P.observed.P.cel.Cel.rank); + Alcotest.(check string) "observed colour is white, overriding the season's own green" "white" + (Col.to_string resolution.P.observed.P.cel.Cel.colour); + Alcotest.(check bool) "observed subject is Bvm" true (resolution.P.observed.P.cel.Cel.subject = Sub.Bvm); + Alcotest.(check (list (pair string string))) "Holy Machabees still rides along as an ordinary commemoration" + [ ("holy-machabees", "Ordinary") ] + (List.map + (fun (c, p) -> (S.to_string c.P.cel.Cel.slug, match p with P.Privileged -> "Privileged" | P.Ordinary -> "Ordinary")) + resolution.P.commemorations); + Alcotest.(check int) "nothing omitted" 0 (List.length resolution.P.omitted) + +(* The other half of the SAME hazard: a real Saturday where RG 78's own + protasis is FALSE (a genuine sanctoral feast wins the day outright), so + the BVM-Saturday-shaped temporal candidate must lose, and be OMITTED under + RG 26 (a temporal Class4 non-vigil loser is never commemorated) exactly + like the plain ferial candidate it replaces always was -- proving [band]/ + [disposition] need no special case for this office at all, the design + claim the task report makes explicitly. 12 September 2026 is a Saturday + where `most-holy-name-of-mary` (data/ef/adjustments.sexp's own [Set_subject + Bvm] entry, Class3 Feast) wins outright -- ALSO tagged [subject = Bvm], + the one live data witness that could in principle confuse a + subject-keyed disposition rule with RG 26's own rank-keyed one: this test + proves they do NOT collide (RG 26 fires first, unconditionally, on the + loser's own [rank = Class4], never reaching a subject comparison at all). *) +let test_bvm_saturday_loses_to_a_real_feast_end_to_end () = + let date = mk 2026 9 12 in + let day_ctx = ctx date in + let bvm_saturday = of_temporal date in + let name_of_mary = + cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Bvm ~layer:PE.universal_layer + "most-holy-name-of-mary" + in + let resolution = P.resolve real_rules day_ctx ~temporal:bvm_saturday ~sanctoral:[ name_of_mary ] in + Alcotest.(check string) "Most Holy Name of Mary is observed, not the BVM Saturday office" + "most-holy-name-of-mary" (S.to_string resolution.P.observed.P.cel.Cel.slug); + Alcotest.(check (list string)) "nothing is commemorated" [] + (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.commemorations); + Alcotest.(check (list (pair string string))) + "the BVM-Saturday-shaped temporal candidate is omitted under RG26, not commemorated -- no Bvm-vs-Bvm \ + confusion with RG112(a)-style subject logic" + [ ("ef-time-after-pentecost-15-saturday", "omitted: yielded to a higher day") ] + (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted) + (* RG 16(a) (register §6.0) reached end-to-end through the REAL pipeline ([PE.band], [PE.disposition] AND [PE.admit] together, not any one of them in isolation as this file's own hand-tagged rows test them @@ -1894,6 +1980,14 @@ let suite = Alcotest.test_case "II-class Sunday override: RG109(b) witness admitted over an ordinary saint, end-to-end" `Quick test_ii_class_sunday_privileged_witness_admitted_end_to_end; + Alcotest.test_case + "RG91 e27/RG78: the BVM Saturday office wins and commemorates Holy Machabees, end-to-end \ + (2026-08-01)" + `Quick test_bvm_saturday_wins_and_commemorates_end_to_end; + Alcotest.test_case + "RG91 e27/RG78/RG26: the BVM Saturday office loses to a real feast and is omitted, end-to-end \ + (2026-09-12)" + `Quick test_bvm_saturday_loses_to_a_real_feast_end_to_end; Alcotest.test_case "RG16(a): a Feast of the Lord suppresses the Sunday's own commemoration entirely, \ end-to-end (2028-08-06)" -- cgit v1.3 From 957a6d88ed447be16aa8c7e71250f4c9b87a832b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 15:57:26 +0200 Subject: data(ef): close and derive the RG 91 entry 27 allow-list rows The BVM Saturday office is now genuinely correct, so both comparators' allow-lists move -- every count derived from the harnesses' own failure output, never transcribed or guessed. lectio differential (data/ef/expected-divergences.sexp, C17, new, colitur): lectio builds no equivalent office at all, so it keeps showing the plain season colour on every otherwise-unoccupied Class4 Saturday where colitur now shows white. Season, slug and rank all still agree (the office deliberately reuses the ordinary ferial slug), so Colour_f alone is the complete diff set -- gated on colitur's own rank/weekday/colour rather than a date list, the same shape M2 used, at 416 rows over 2005-2050. missalemeum oracle (data/ef/expected-divergences-missalemeum.sexp): M2 is CLOSED, REMOVED, the same shape as this file's own M4/M6/M7/M9/M12/ M14/M17 precedents -- not re-adjudicated to a different verdict, gone because the colour divergence it named no longer occurs on any of its former 22 dates. What is left on all 22 is Observed_identity_unresolved alone (colitur's BVM-Saturday candidate is temporal-origin and deliberately carries no English name) -- exactly M18's own shape, not a distinct citation any more. Keeping M2 as a separate id would have kept silently absorbing these rows under a stale "colour differs" citation whose own reason had stopped firing (subset's own diff-set test admits a SMALLER set than the one named) -- the same "count proving cardinality where identity was required" trap this project's review process watches for. M18's own expected_rows moves 373 -> 395 accordingly. --- data/ef/expected-divergences-missalemeum.sexp | 62 ++++++++++++++++++------- data/ef/expected-divergences.sexp | 5 ++ test/test_differential.ml | 23 +++++++++ test/test_oracle.ml | 67 ++++++++++++++------------- 4 files changed, 108 insertions(+), 49 deletions(-) diff --git a/data/ef/expected-divergences-missalemeum.sexp b/data/ef/expected-divergences-missalemeum.sexp index 0584dfa..1fce257 100644 --- a/data/ef/expected-divergences-missalemeum.sexp +++ b/data/ef/expected-divergences-missalemeum.sexp @@ -11,9 +11,11 @@ ; genuine DATA GAPS this task found and could not fix here (the sanctoral ; data is bootstrapped from lectio, which is itself missing several of ; these entries -- CLAUDE.md's binding decision 3, and this task does not -; touch ~/git/projects/lectio) or genuine CODE gaps (RG 91 entry 27's BVM- -; Saturday office, RG 110's inseparable Peter/Paul commemoration) this task -; did not build. Those are honestly verdicted [missalemeum] -- colitur is +; touch ~/git/projects/lectio) or genuine CODE gaps this task did not build +; (RG 110's inseparable Peter/Paul commemoration -- closed by a later task, +; see the ef-holyname-rg110 paragraph below; RG 91 entry 27's BVM-Saturday +; office -- closed by a later task still, see the ef-bvm-saturday paragraph +; further below, after M18's own). Those are honestly verdicted [missalemeum] -- colitur is ; short a feature or a row, not correct -- and each is cross-referenced ; into docs/research/rules-register.md §6 as an open item, not silently ; absorbed as if colitur were right. TWO entries (M11 and M13) are @@ -42,16 +44,22 @@ ; sides purely by coincidence). [M18] is [verdict unresolvable], the SAME ; fourth verdict as M15, for the SAME reason: colitur's own resolved name is ; [None] for any TEMPORAL-origin observed day, the large majority of this -; window. M1, M2, M3, M13 and M16's own subsets were each widened to absorb -; this axis where it fires alongside their pre-existing citation (see each -; entry's own note) rather than being folded into M18's own count, which -; covers only rows where [Observed_identity_unresolved] is the SOLE -; disagreement. +; window. M1, M3 and M16's own subsets were each widened to absorb this axis +; where it fires alongside their pre-existing citation (see each entry's own +; note) rather than being folded into M18's own count, which covers only +; rows where [Observed_identity_unresolved] is the SOLE disagreement. +; UPDATED, ef-bvm-saturday task: M2 used to belong on this list too (RG 91 +; entry 27's own BVM-Saturday office had not been built yet), but M2 is now +; CLOSED, REMOVED (see the ef-bvm-saturday paragraph below, after the FIVE +; MORE REMOVED note) -- its former 22 dates fall through to THIS entry +; instead now, moving M18's own count from 373 to 395 (see M18's own sexp +; note below for the precise derivation). ; ; ef-holyname-rg110 task (RG 110, the inseparable Peter/Paul commemoration -- -; PART of RG 91 entry 27's neighbour list this file's own header names at -; line 15 as unbuilt; RG 91 entry 27 itself, the BVM-on-Saturday office, -; remains unbuilt, still M2 below): [M12] CLOSED, REMOVED (same shape as +; PART of RG 91 entry 27's neighbour list this file's own header names above +; as unbuilt at the time; RG 91 entry 27 itself, the BVM-on-Saturday office, +; was closed by a LATER task, see the ef-bvm-saturday paragraph below): +; [M12] CLOSED, REMOVED (same shape as ; M17's own removal above, not re-adjudicated) -- 22 February 2027 no ; longer diverges on [Comm_count], since colitur now admits Paul ; unconditionally alongside whatever else the day's own privileged @@ -117,6 +125,29 @@ ; - theodore (9 Nov): "IN DEDICATIONE ARCHIBASILICÆ SANCTISSIMI ; SALVATORIS, II classis. / Com. S. Theodori Mart." ; +; ef-bvm-saturday task: RG 91 entry 27 ("Officium sanctae Mariae in +; sabbato") is now built (Rite_ef.Temporal_ef's own [bvm_saturday_names] +; citation has the full RG 78/RG 120(b) argument -- white, unconditionally, +; on every otherwise-unoccupied IV-class Saturday). [M2] is CLOSED, REMOVED +; -- same shape as M4/M6/M7/M9/M12/M14/M17's own removals above/below, not +; re-adjudicated to a different verdict: the divergence M2 used to name +; ([Colour_f], alone or alongside [Observed_identity_unresolved]) no longer +; occurs on any of its former 22 dates (checked directly: 0 of them retain +; [Colour_f] now). What is left on all 22 is [Observed_identity_unresolved] +; ALONE -- colitur's own BVM-Saturday candidate is temporal-origin and +; deliberately carries no ENGLISH name (Latin only, the same +; zero-circularity discipline Holy Family/Holy Name/the Sacred Triduum +; already established) -- which is [M18]'s own shape exactly, not a +; distinct citation any more (keeping M2 as a separate id here would have +; been the SAME "count proving cardinality where identity was required" +; trap this project's review process watches for: [subset]'s own diff-set +; test admits a SMALLER set than the one named, so M2's predicate would +; have kept silently absorbing these 22 rows under a stale "colour differs" +; citation whose own reason had stopped firing). [M18]'s own expected_rows +; therefore moves 373 -> 395 (373 + M2's former 22), derived directly from +; test_oracle.ml's own [test_layer_m_counts_match_citations] failure output +; after removing the M2 predicate branch, not computed by hand first. +; ; [expected_rows] is the exact row count this entry accounts for over the ; fixture's 2026-2027 span: a REGRESSION PIN, not documentation -- ; test_oracle.ml asserts actual counts equal these exactly, so an unnoticed @@ -131,11 +162,6 @@ (verdict colitur) (note "missalemeum does not implement RG 33's Sunday-omission for II/III-class vigils. 28 June 2026 (Vigil of Sts Peter & Paul, II class): still shown as a commemoration on the V Sunday after Pentecost. 9 August 2026 (Vigil of St Lawrence, III class): shown as the day's FULL OBSERVED OFFICE, displacing the XI Sunday after Pentecost entirely, not merely surviving as a commemoration.") (expected_rows 2)) - ((id M2) - (citation "RG 91 entry 27 (\"Officium sanctae Mariae in sabbato\") -- register §4") - (verdict missalemeum) - (note "Every otherwise-unoccupied IV-class Saturday should carry the votive Office of the BVM (white; missalemeum's own titles cycle \"I\"..\"V Mass of the B. V. M. -- Salve, Sancta Parens\"). precedence_ef.ml's [band] already has an entry-27 comment acknowledging this row exists (corrected attribution, final fix wave: this note previously said temporal_ef.ml, but [band] is precedence_ef.ml's own function), but [Temporal_ef.temporal] never actually CONSTRUCTS this office -- an unimpeded Time-after-Epiphany/-Pentecost Saturday gets a bare ferial slug and season green instead. A genuine feature gap, not a citation dispute; tracked in register §6, not built in this task. REFRESHED (ef-rg112-rg110 task): expected_rows moved 17 -> 22, for the SAME reason M1/M3/M16 below move too -- the new observed-identity axis (test_oracle.ml's own header) now ALSO fires on 5 Paschaltide BVM-Saturday dates that used to match CLEANLY (colour already white there, RG 119, so the pre-existing [Colour_f]-only check never even reached these 5 rows; M2's own comment already noted \"every Paschaltide occurrence... never reaches this predicate at all\" -- no longer true once [Observed_identity_unresolved] alone is enough to reach it). Not a new divergence, the identical feature gap surfacing on a second axis -- derived from the comparator's own failure output (test_layer_m_counts_match_citations), not hand-counted.") - (expected_rows 22)) ((id M3) (citation "RG 87 (Minor Litanies/Rogations, Mon/Tue before Ascension) -- the SAME citation as the lectio allow-list's own C8 (data/ef/expected-divergences.sexp)") (verdict colitur) @@ -179,8 +205,8 @@ ((id M18) (citation "test_oracle.ml's own header, \"COMMEMORATION IDENTITY\" section, restated for the OBSERVED axis (ef-rg112-rg110 task) -- not an RG citation, a comparator LIMIT: Rite_ef.Temporal_ef never sets an English [names] field on any candidate it builds, the SAME limit M15 already names for a commemoration, now visible for the OBSERVED day itself now that the observed-identity axis exists") (verdict unresolvable) - (note "373 of the 730 days in this window carry this shape alone (M1/M2/M3/M16 absorb a further 26 where it fires ALONGSIDE their own pre-existing citation -- see each entry's own widened note; 373+26=399 is the axis's own full unresolved population). CORRECTED, fix round 1 (coordinator finding 8) -- the full breakdown, precisely: of 730 days, 331 are RESOLVED (colitur's own observed celebration carries a name) -- 330 resolved-and-MATCHING, 1 resolved-and-MISMATCHED (M13, Joseph vs the Seven Sorrows, already its own entry above) -- and 399 are UNRESOLVED (colitur's own name is [None]), split between this entry's own 373 and the 26 absorbed elsewhere. The BLIND SPOT this axis exists to close, stated precisely: a TEMPORAL-origin observed day silently replaced by a DIFFERENT temporal-origin observed day of the SAME RANK AND COLOUR -- exactly the shape that hid Holy Family from every layer before this task (rank 2/white on both sides, purely coincidental). This count PIN is what stands guard against that population growing silently in either direction: an implementation change that made MORE days temporal-origin-and-unresolvable, or fewer, changes 373 and fails the pin, even though the axis itself cannot say WHICH specific day moved or why. The overwhelming majority of days whose observed celebration is TEMPORAL-origin (an ordinary Sunday, a feria, a movable named feast including Holy Family itself) never carry an English name on colitur's side -- not a rubric dispute and not a data gap either engine is wrong about, a LIMIT of this comparator, honestly counted rather than silently passed, per the brief's own explicit instruction (\"a day whose observed identity cannot be resolved must be a counted, allow-listed outcome, never a silent skip\"). Gated on the diff SHAPE alone (exactly, and only, observed-identity-unresolved), not a literal date list the way every other entry in this file is -- at this population size a date list would itself be the \"pattern that could silently widen\" this file's own header warns against, for the opposite reason a date RANGE is normally risky: the predicate (colitur's own resolved name is [None]) is the precise, falsifiable evidence, the same shape M2's own title-substring predicate already uses instead of enumerating dates, just keyed on presence-of-a-name rather than a title string. Building an English name onto every temporal-cycle candidate (a data/lectionary-bootstrap task, Plan 4, out of this task's scope -- register §6, the same open item M15's own note already tracks) would resolve this entry to Matched for every one of these days it does not instead expose as a REAL divergence (Holy Family's own two dates in this window, 2026-01-11 and 2027-01-10, are counted here, not separately -- the name comparison genuinely cannot distinguish \"Holy Family\" from any other unnamed Sunday, so proving Holy Family's own correctness rests on the golden pins and the differential's own C15, not this axis). Derived directly from the comparator's own failure output (test_layer_m_counts_match_citations), not hand-counted first and cross-checked after: 373.") - (expected_rows 373)) + (note "REVISED, ef-bvm-saturday task: 395 of the 730 days in this window now carry this shape alone (M1/M3/M16 absorb a further 4 where it fires ALONGSIDE their own pre-existing citation -- see each entry's own widened note; 395+4=399 is the axis's own full unresolved population, UNCHANGED from before this task -- only the SPLIT moved, since M2 (closed above, this task) used to separately absorb 22 of the 26 that used to be \"elsewhere\"). Before this revision the split was 373 alone / 26 elsewhere (373+26=399) -- CORRECTED, fix round 1 (coordinator finding 8) -- the full breakdown, precisely: of 730 days, 331 are RESOLVED (colitur's own observed celebration carries a name) -- 330 resolved-and-MATCHING, 1 resolved-and-MISMATCHED (M13, Joseph vs the Seven Sorrows, already its own entry above) -- and 399 are UNRESOLVED (colitur's own name is [None]), split between this entry's own 395 and the 4 absorbed elsewhere. The BLIND SPOT this axis exists to close, stated precisely: a TEMPORAL-origin observed day silently replaced by a DIFFERENT temporal-origin observed day of the SAME RANK AND COLOUR -- exactly the shape that hid Holy Family from every layer before this task (rank 2/white on both sides, purely coincidental). This count PIN is what stands guard against that population growing silently in either direction: an implementation change that made MORE days temporal-origin-and-unresolvable, or fewer, changes 373 and fails the pin, even though the axis itself cannot say WHICH specific day moved or why. The overwhelming majority of days whose observed celebration is TEMPORAL-origin (an ordinary Sunday, a feria, a movable named feast including Holy Family itself) never carry an English name on colitur's side -- not a rubric dispute and not a data gap either engine is wrong about, a LIMIT of this comparator, honestly counted rather than silently passed, per the brief's own explicit instruction (\"a day whose observed identity cannot be resolved must be a counted, allow-listed outcome, never a silent skip\"). Gated on the diff SHAPE alone (exactly, and only, observed-identity-unresolved), not a literal date list the way every other entry in this file is -- at this population size a date list would itself be the \"pattern that could silently widen\" this file's own header warns against, for the opposite reason a date RANGE is normally risky: the predicate (colitur's own resolved name is [None]) is the precise, falsifiable evidence, the same shape M2's own title-substring predicate already uses instead of enumerating dates, just keyed on presence-of-a-name rather than a title string. Building an English name onto every temporal-cycle candidate (a data/lectionary-bootstrap task, Plan 4, out of this task's scope -- register §6, the same open item M15's own note already tracks) would resolve this entry to Matched for every one of these days it does not instead expose as a REAL divergence (Holy Family's own two dates in this window, 2026-01-11 and 2027-01-10, are counted here, not separately -- the name comparison genuinely cannot distinguish \"Holy Family\" from any other unnamed Sunday, so proving Holy Family's own correctness rests on the golden pins and the differential's own C15, not this axis). Derived directly from the comparator's own failure output (test_layer_m_counts_match_citations), not hand-counted first and cross-checked after: 373.") + (expected_rows 395)) ((id M19) (citation "RG 110's OTHER direction: \"In Officio et Missa S. Petri semper fit commemoratio S. Pauli, ET VICISSIM\" -- the SAME rule M12 used to cite, applied here to Paul's own office commemorating Peter, not Peter's own office commemorating Paul; confirmed a third time by the calendarium's own June table, both photographic scans, word for word: \"In Commemoratione S. Pauli Ap., III classis. / Commemoratio S. Petri Ap.\"") (verdict colitur) diff --git a/data/ef/expected-divergences.sexp b/data/ef/expected-divergences.sexp index 261e900..444f55e 100644 --- a/data/ef/expected-divergences.sexp +++ b/data/ef/expected-divergences.sexp @@ -72,3 +72,8 @@ (verdict colitur) (note "ef-holyname-rg110 task: RG 17(a)'s own fallback, previously unbuilt (register §6.2's own open item -- colitur emitted no Holy Name office at all in a year with no Sunday 2-5 January, not merely an unnamed generic Sunday the way Holy Family's own pre-fix gap was). 2 January in a year with no Sunday in that window now carries the feast (`ef-holy-name`, class-2, subject Lord) instead of the ordinary Christmastide ferial fallback (`ef-christmas-1-`/`ef-christmas-0-` in lectio's own naming, class-4) -- a genuine SLUG AND RANK divergence against lectio, which has no fallback for this window at all (`~/git/projects/lectio/internal/caldata/tridentine-calendar.ini` carries no 01-02 entry; checked directly, not assumed) and shows the plain ferial slug on every one of these dates regardless. Gated on the literal date list AND colitur's own slug (the same identity guard C1/C6/C8/C14/C15 already apply, fix round 1's own finding 1). 20 of the 46 years in the fixture's own 2005-2050 span have no Sunday 2-5 January -- independently re-derived against `date -d -01-0{2,3,4,5} +%u` for every year in range (not the register's domain-wide 3,619-of-8,417 figure divided down; a fresh count over this file's own narrower window), then cross-checked against the OCaml comparator's own failure output before being set here: 20.") (expected_rows 20)) + ((id C17) + (citation "RG 91 entry 27 (\"Officium sanctae Mariae in sabbato\") + RG 78 (Caput IX, \"De sancta Maria in sabbato\", both photographic scans and the electronic transcription word for word): \"In sabbatis, in quibus occurrit Officium de feria IV classis, fit de sancta Maria in sabbato\" -- on Saturdays on which the Office of a IV-class feria occurs, [the Office] is made of Holy Mary on Saturday instead + RG 120(b) (\"Adhibetur color albus in Officio et Missa de festis: ... b) B. Mariae Virg.\") for the white colour, unconditional regardless of season") + (verdict colitur) + (note "ef-bvm-saturday task: colitur now builds RG 91 entry 27's votive Office of the BVM on every otherwise-unoccupied IV-class Saturday (Rite_ef.Temporal_ef's own [bvm_saturday_names] citation has the full argument) -- white (RG 120(b)), unconditionally, overriding the season's own colour (violet in Septuagesima, green in Time after Epiphany/Time after Pentecost; Christmastide and ordinary Paschaltide already happened to be white). lectio builds no equivalent office at all -- checked directly against its own tridentine-calendar.ini/generator, no comparable rule exists there, the same genuine upstream gap C15/C16 above document for their own gaps, not a colitur bootstrap miss -- so it keeps showing the plain season colour on every one of these Saturdays. Season, slug and rank all still agree: temporal_ef.ml's own [bvm_saturday_names] citation (\"Slug\" paragraph) deliberately REUSES the ordinary --saturday ferial slug rather than minting a new one (partly BECAUSE a bespoke uniform slug would itself have broken Colitur_kernel.Validate's own slug-uniqueness invariant, sighted on every otherwise-unoccupied Saturday in a year), so [Colour_f] alone is the complete diff set every one of these rows carries -- never [Slug_f] too. Gated on colitur's own [rank]=class-4, [weekday]=saturday and [colour]=white (test_differential.ml's own [is_bvm_saturday_row]) rather than a date list, unlike C14/C15/C16 above: RG 78's own condition (\"otherwise unoccupied IV-class Saturday\") reduces exactly to this triple, and the population is large (a substantial fraction of all Saturdays in the 2005-2050 window) and entirely formulaic, not a short, individually-interesting list. Derived directly from the OCaml comparator's own failure output (test_layer_c_counts_match_citations), not hand-counted first and cross-checked after.") + (expected_rows 416)) diff --git a/test/test_differential.ml b/test/test_differential.ml index bea1bf4..3f416fd 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -516,6 +516,28 @@ let holy_name_fallback_dates_2005_2050 = "2034-01-02"; "2035-01-02"; "2036-01-02"; "2040-01-02"; "2041-01-02"; "2045-01-02"; "2046-01-02"; "2047-01-02" ] +(* C17 -- ef-bvm-saturday task: RG 91 entry 27 ("Officium sanctae Mariae in + sabbato" -- see data/ef/expected-divergences.sexp's own C17 note for the + full RG 78/RG 120(b) citation). lectio builds no equivalent office at all + (checked directly against its own tridentine-calendar.ini/generator, the + same "genuine upstream gap, not a colitur bootstrap miss" shape C15/C16 + already document for their own gaps) -- it shows the ordinary season + colour on every otherwise-unoccupied Class4 Saturday where colitur's own + RG 78 fix now shows white. Season, slug and rank all still agree -- + temporal_ef.ml's own [bvm_saturday_names] citation ("Slug" paragraph) + deliberately REUSES the ordinary ferial slug rather than minting a new + one, precisely so this predicate's diff set stays [Colour_f] alone, never + [Slug_f] too. Gated on colitur's own [rank]/[weekday]/[colour] rather + than a date list, the same shape M2 (data/ef/expected-divergences- + missalemeum.sexp) already uses for the identical reason: this + population is large (a large fraction of all Saturdays domain-wide) and + entirely formulaic (RG 78's own condition, "otherwise unoccupied + IV-class Saturday", reduces exactly to this triple), not a short, + individually-interesting list of dates the way C14/C15/C16 above are. *) +let is_bvm_saturday_row (c : row) diffs = + diffs = [ Colour_f ] && String.equal c.weekday "saturday" && String.equal c.rank "class-4" + && String.equal c.colour "white" + (* [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). *) @@ -571,6 +593,7 @@ let layer_c_reason (l : row) (c : row) diffs = && subset diffs [ Slug_f; Rank ] && String.equal c.slug "ef-holy-name" then Some "C16" + else if is_bvm_saturday_row c diffs then Some "C17" else None (* ---------------------------------------------------------------------- *) diff --git a/test/test_oracle.ml b/test/test_oracle.ml index 58ceb26..c0badfe 100644 --- a/test/test_oracle.ml +++ b/test/test_oracle.ml @@ -475,25 +475,30 @@ let contains_substring s ~needle = own "in dominica quavis" (ANY Sunday, unqualified) -- verdict colitur. *) let m1_dates = [ "2026-06-28"; "2026-08-09" ] -(* M2 -- RG 91 entry 27 ("Officium sanctae Mariae in sabbato"): every - otherwise-unoccupied IV-class Saturday should carry the votive Office of - the BVM (white; missalemeum's own title cycles "I".."V Mass of the - B. V. M. - Salve, Sancta Parens"), a genuinely DISTINCT office from an - ordinary green/violet feria. temporal_ef.ml's [band] already carries an - entry-27 comment acknowledging this row of RG 91 exists, but nothing in - [Temporal_ef.temporal]'s actual CONSTRUCTION builds this office -- an - unimpeded Time-after-Epiphany/-Pentecost Saturday still gets the bare - ferial slug and [season_colour]'s green. A genuine, primary-attested - FEATURE GAP (not a citation dispute) -- verdict missalemeum: colitur is - short a whole office here, not merely differently-opinioned. Registered - as an open item (register §6) rather than built in this task: it needs - its own proper texts/citations, the same scope line "Major Litanies" - (M5 below) already sits on. Matched on the oracle's own title substring, - not a hardcoded date list -- the title IS the distinguishing evidence, - and every Paschaltide occurrence (where colitur's season colour is - already white, RG 119) never reaches this predicate at all, since its - diffs are already empty before [layer_m_reason] is ever called. *) -let bvm_saturday_title = "Salve, Sancta Parens" +(* M2 -- CLOSED, REMOVED (ef-bvm-saturday task), same shape as M12/M17's own + removal elsewhere in this suite (not re-adjudicated to a different + verdict, gone because the underlying divergence stopped occurring): RG 91 + entry 27 ("Officium sanctae Mariae in sabbato") is now built + (Rite_ef.Temporal_ef's own [bvm_saturday_names] citation has the full RG + 78/RG 120(b) argument) -- white, unconditionally, on every otherwise- + unoccupied IV-class Saturday, so [Colour_f] no longer appears in this + predicate's own diff set on ANY of these 22 dates (checked: 0 of them + retain it). What is left on every one of them is [Observed_identity_ + unresolved] ALONE -- colitur's own BVM-Saturday candidate is + temporal-origin and deliberately carries no ENGLISH name (Latin only, + the same zero-circularity discipline Holy Family/Holy Name/the Sacred + Triduum already established) -- which is EXACTLY [M18]'s own shape + ([diffs = [ Observed_identity_unresolved ]], nothing else disagreeing), + not a distinct citation any more. Kept as a separate id here would have + been the same "count proving cardinality where identity was required" + trap this project's own review process watches for: [subset] admits a + SMALLER diff set than the one named, so this branch would have kept + silently absorbing these 22 rows under a stale "colour differs" citation + even though that citation's own reason no longer fires -- checked + directly, not assumed: removing this branch and re-running moves the + count from M2's own former 22 to M18's own count rising by exactly 22 + (373 -> 395, see M18's own note below), with the "no unexplained + differences" test staying green throughout. *) (* M3 -- RG 87 (Minor Litanies/Rogations): the SAME gap the lectio differential's own C8 already names (data/ef/expected-divergences.sexp) @@ -822,7 +827,12 @@ let m19_commemoration_matches (c : colitur_row) = | [ (slug, _, _, _) ] -> String.equal slug "commemoration-of-st-peter" | _ -> false -let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = +(* [_o] (the oracle's own row) is unused now that M2 -- its one reader, via + [o.o_title] -- is closed/removed (see the comment above [m1_dates]): + every remaining predicate below reads only [c]/[diffs]. Kept as a named, + typed parameter (not dropped from the signature) so a FUTURE oracle-title + predicate has somewhere to plug back in without a signature change. *) +let layer_m_reason (c : colitur_row) (_o : oracle_row) diffs = if diffs = [] then None (* M1's own subset widened (this task): colitur's observed day on both dates is TEMPORAL-origin (the Sunday itself, RG33's own point -- the @@ -832,17 +842,12 @@ let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = else if List.mem c.c_date m1_dates && subset diffs [ Rank; Colour_f; Comm_presence; Observed_identity_unresolved ] then Some "M1" - (* M2's own subset widened (this task): every BVM-Saturday date's observed - celebration is temporal-origin (the ordinary Saturday feria colitur - computes, not the distinct Office of the BVM missalemeum's title - names -- M2's own gap), so [Observed_identity_unresolved] now fires - alongside [Colour_f] where it used to fire alone, AND alone on its own - on every Paschaltide occurrence M2's own comment already says never - reached this predicate before (colour already agrees there; only the - new axis has anything left to say). *) - else if contains_substring o.o_title ~needle:bvm_saturday_title - && subset diffs [ Colour_f; Observed_identity_unresolved ] - then Some "M2" + (* M2 -- CLOSED, REMOVED (ef-bvm-saturday task): see this file's own M2 + note above (near [m1_dates]) for the full account -- the RG 91 entry 27 + office is now built, so every one of these 22 dates now falls through + to [M18]'s own generic predicate below instead ([diffs = [ + Observed_identity_unresolved ]] exactly), which is what actually + catches them now. *) (* M3's own subset widened (this task): the Rogation Monday feria colitur observes is temporal-origin, same root cause as M1/M2 above. *) else if List.mem c.c_date m3_dates && subset diffs [ Colour_f; Observed_identity_unresolved ] then -- cgit v1.3 From d5df5e79c731ac7f7f89969c0cafe564fdc6e8ab Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 16:10:00 +0200 Subject: docs: RG 91 entry 27 (BVM Saturday) resolved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md's "Unbuilt, recorded" list and Data section move to reflect the closed gap: a new item 9 records the office's own build, citations and measured blast radius; the allow-list counts move to 7 active lectio entries (C17 new) and 11 active missalemeum entries (M2 closed). docs/research/rules-register.md (gitignored, not tracked) gets the matching update: §4's entry marked RESOLVED with a cross-reference, the §6 open-item checkbox closed, and a new §6.4 with the full primary-source account -- every scan reading quoted, the blast-radius reconciliation, and the mutation-test record. --- CLAUDE.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 68cb906..8cfa902 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -232,11 +232,11 @@ in its provenance header) · `data/ef/adjustments.sexp` (overlay — `Add` as we as `Suppress`/`Edit`: RG 110's own 30 June companion, `commemoration-of-st-peter`, is genuinely missing from lectio's own source, not merely from colitur's bootstrap, so it is hand-authored here rather than upstream) · two cited -allow-lists, `expected-divergences.sexp` (6 active entries, vs lectio — C1, C6, -C8, C14, C15, C16; several more closed and recorded in the register, not deleted) -and `expected-divergences-missalemeum.sexp` (12 active, vs the oracle — M12 -closed/M19 opened this task, net count unchanged). Fixtures live in -`test/fixtures/` with asserted SHA-256s. +allow-lists, `expected-divergences.sexp` (7 active entries, vs lectio — C1, C6, +C8, C14, C15, C16, C17; several more closed and recorded in the register, not +deleted) and `expected-divergences-missalemeum.sexp` (11 active, vs the oracle — +M2 closed/M18 widened by the `ef-bvm-saturday` task; M12 closed/M19 opened by an +earlier one). Fixtures live in `test/fixtures/` with asserted SHA-256s. **CLI**: `colitur easter `, `temporal `, `day `. @@ -474,11 +474,12 @@ 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**: 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.** +**Unbuilt, recorded**: 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.** RG 91 entry 27's +BVM Saturday Office is **RESOLVED — see item 9 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 @@ -573,6 +574,68 @@ candidate is exactly one and already spoken for). Ascension Vigil by construction, so there is no `(month, day)` pair a `Fixed` spec could anchor to and no partial build exists at all. +9. **RG 91 entry 27, the votive Office of the BVM on Saturday — RESOLVED + (2026-08-13, `ef-bvm-saturday` task).** `Precedence_ef.band` already + routed a plain IV-class Saturday feria to entry 27's own band value + (312 966 times domain-wide), but `Temporal_ef.temporal` never + constructed the office itself. Caput IX of the Rubricae Generales, both + photographic scans and the electronic transcription, word for word (no + scan-vs-transcription conflict — RG 78/79 are General Rubrics prose, not + the Mass-propers body text the transcription is missing): *"78. In + sabbatis, in quibus occurrit Officium de feria IV classis, fit de + sancta Maria in sabbato."* RG 78's own protasis IS "otherwise unoccupied + IV-class Saturday" — decided entirely by the existing occurrence + machinery (`band`'s own entry-27 branch already reads + `rank = Class4 && weekday = Sat` unconditionally and only wins when + nothing outranks it), so **no change to `band`, `disposition` or + `admit` was needed** — rank stays Class4 either way, and none of the + three reads slug/colour/name to decide anything. Colour is white, + unconditionally (RG 120(b), not RG 119/127/128's seasonal rules — this + office is never "de Tempore" for colour). The slug is deliberately + UNCHANGED (reused from the ordinary `--` ferial + fallback), the same precedent the Sacred Triduum (item 5) already set — + identified structurally, never off the slug — and for a second, load- + bearing reason found while building it: a bespoke uniform slug would + have broken `Validate`'s own slug-uniqueness-per-liturgical-year + invariant (asserted with zero exceptions since Plan 2), since the + office recurs many times a year. Subject is tagged `Bvm` (a real + `Subject.t` variant that existed, unused, since the kernel's vocabulary + was designed — no kernel change needed) and the name is Latin only + ("Officium sanctae Mariae in sabbato", RG 91 entry 27's own table title + and RG 79's own heading), the same zero-circularity discipline items + 3–5 already established. The I–V numbered "Missae de sancta Maria in + sabbato" (both scans) are a Mass-propers selection detail (RG 309(a): + *"iuxta temporum diversitatem"*) governing which readings are said, not + which office is kept — out of scope until Plan 4's lectionary. + **Blast radius, measured (`git archive` pre-change binary vs HEAD, full + 1583–9999 `Calendar`-resolved sweep, diffed): 75 853 days, every single + one the identical single-field shape (`colour` alone, always on a + Saturday, always `class-4` on both sides) — no anomaly.** Reconciled + against the full 312 930-day eligible population on the same sweep + domain: 102 144 are actually observed (the office wins), split by + season — 63 195 Time after Pentecost + 7 643 Septuagesima + 5 015 Time + after Epiphany (75 853 total, all VISIBLE, green/violet → white) and + 14 213 Paschaltide + 12 078 Christmastide (26 291, all INVISIBLE — RG + 119 already made those seasons white); the remaining 210 786 are + impeded by a real sanctoral winner and genuinely unaffected. Both + allow-lists moved: the lectio differential gained a new cited entry + (C17, colitur, 416 rows — lectio builds no equivalent office); the + missalemeum oracle's own M2 (previously `verdict missalemeum`, "colitur + is missing a whole office") is **CLOSED, REMOVED** — the colour + divergence it named no longer occurs on any of its 22 dates, and what + is left (`Observed_identity_unresolved` alone, the office is temporal- + origin and deliberately unnamed in English) is exactly `M18`'s own + shape, not a distinct citation any more; `M18` widens 373 → 395 + accordingly. Mutation-tested: reverting the office to a constant-false + guard reddens the dedicated unit test, an end-to-end resolve test, a + golden pin, and both allow-list count pins (in both directions — the + fix present with M2 still declared fails identically to the fix absent + with M2 removed), while a second end-to-end test/golden pin (the office + losing to a real competing feast, 12 September, the one live data + witness that also carries `subject = Bvm`) is correctly untouched by + the mutation, proving RG 26's rank-keyed omission fires before any + subject-keyed rule ever could. + ## How to work here - **Superpowers workflow**: `brainstorming` → `writing-plans` → `executing-plans` -- cgit v1.3 From dce208f57cfeeee0715cf35389d37e7ce98227ae Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 17:09:27 +0200 Subject: fix(ef): RG 112(d) excludes a same-BVM commemoration (fix round 1, F1) The BVM Saturday Office is itself "de B. Maria Virg."; RG 112(d) (Caput XVI, "De Commemorationibus") excludes another commemoration whose own oration invokes the SAME BVM's intercession. our-lady-of-mt-carmel (16 July, Commemoration_only) was wrongly admitted as an ordinary commemoration alongside the Office on every 16-July-Saturday -- its own collect explicitly invokes "her" (eius) patronage, and both photographic scans print a rubric anticipating exactly this collision (an either/or of Mass texts, not a joint commemoration). Precedence_ef gains marian_slugs (a closed, hand-verified list of sanctoral slugs that are themselves feasts of the BVM) and is_bvm_office (true for subject = Bvm OR a marian_slugs member -- the disjunction is load-bearing: the Office is identified only by subject, since its own slug is deliberately reused from the ordinary ferial fallback; Mt Carmel is identified only by the list, since its own subject tag is Saint, not Bvm). disposition gains a new branch, checked before the pre-existing Commemoration_only catch-all, since the one live witness is Commemoration_only and that branch's own "always Commemorate" had no exception for this shape. Checked exhaustively against every Marian-named sanctoral entry: only Commemoration_only-status entries can ever reach this branch live (a Feast-status one always wins outright instead of losing). Of the two such entries in the data, only Mt Carmel is reachable -- our-lady-of-ransom (24 September) is provably unreachable: 24 September falling on a Saturday forces 1 September to a Thursday, which by construction makes 24 September the September Ember Saturday, Class2, every time, not a sampled coincidence. Re-measured against the full pre-change binary: the original 75,853-day blast radius is unchanged in total, splitting into 74,633 colour-only + 1,220 colour+comms (16-July Saturdays). Mutation-tested: exactly 3 new tests redden, nothing else. --- lib/rites/rite_ef/precedence_ef.ml | 176 +++++++++++++++++++++++++++++++++++-- test/test_differential.ml | 32 ++++++- test/test_golden.ml | 24 ++++- test/test_precedence_ef.ml | 76 ++++++++++++++++ test/test_temporal_ef.ml | 43 ++++++--- 5 files changed, 324 insertions(+), 27 deletions(-) diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 5a2d307..302df98 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -364,14 +364,21 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc else if is_temporal && rank = Class3 && season = Advent then 250 (* 26: III-class vigils (St Lawrence). *) else if rank = Class3 && is_vigil then 260 - (* 27: Office of the BVM on Saturday -- every otherwise-unoccupied IV-class - Saturday, per the historical default that fills it; ordinary Mass - propers still make Rogation Mon/Tue/Wed proper without changing the - Office (RG 88, see temporal_ef.ml's [temporal]), so those never carry - this entry unless they happen to fall on the Saturday itself. Excludes - vigils for the same reason 11-13/14/16/19/20/23/24 do: RG 91 has no - IV-class vigil at all (RG 91's own vigil list, §4 "Vigils", - stops at III class), so one would be an anomaly, not this entry. *) + (* 27: Office of the BVM on Saturday -- RG 78 (Caput IX, "De sancta Maria + in sabbato"), both photographic scans and the electronic + transcription, word for word (docs/research/rules-register.md §4/§6.4; + Rite_ef.Temporal_ef's own [bvm_saturday_names] citation has the full + argument): "78. In sabbatis, in quibus occurrit Officium de feria IV + classis, fit de sancta Maria in sabbato" -- on Saturdays on which the + Office of a IV-class feria occurs, [the Office] is made of Holy Mary + on Saturday instead. Every otherwise-unoccupied IV-class Saturday + reaches this branch; ordinary Mass propers still make Rogation + Mon/Tue/Wed proper without changing the Office (RG 88, see + temporal_ef.ml's [temporal]), so those never carry this entry unless + they happen to fall on the Saturday itself. Excludes vigils for the + same reason 11-13/14/16/19/20/23/24 do: RG 91 has no IV-class vigil at + all (RG 91's own vigil list, §4 "Vigils", stops at III class), so one + would be an anomaly, not this entry. *) else if is_temporal && (not is_vigil) && rank = Class4 && weekday = Date.Sat then 270 (* 28: IV-class ferias -- the unqualified catch-all (temporal_ef.ml's own comment on [ferial_rank] cites the same primary text, "Feriae IV @@ -380,6 +387,148 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc else if (not is_vigil) && rank = Class4 then 280 else unclassified +(* RG 112(d) (Caput XVI, "De Commemorationibus"), the BVM half, fix round 1 + of the ef-bvm-saturday task (coordinator finding F1) -- both photographic + scans, word for word (docs/research/rules-register.md §4's own RG 112 + entry already carries this sub-clause, unbuilt until this fix): *"item, + Officium, Missa aut commemoratio de B. Maria Virg. aut de aliquo Sancto + vel Beato excludit aliam commemorationem aut orationem in qua eiusdem B. + Mariae Virg., vel Sancti aut Beati intercessio imploretur: quod tamen non + valet de oratione dominicae vel feriae, in qua fit invocatio eiusdem + Sancti."* -- the Office, Mass or commemoration OF the Blessed Virgin Mary + (or of some Saint or Blessed) excludes ANOTHER commemoration or oration + in which the intercession OF THE SAME BVM (or Saint or Blessed) is + invoked -- which however does not hold of the oration of a Sunday or + feria, in which invocation of the same Saint occurs. + + Found live by the fix-round review: the BVM Saturday Office (RG 91 entry + 27, [temporal_ef.ml]'s own [bvm_saturday_names]) is itself "de B. Maria + Virg." -- so when it is observed, RG 112(d) excludes any OTHER admitted + candidate whose own oration invokes the SAME BVM's intercession, not + merely commemorates her in passing. Real live witness: 16 July, + "our-lady-of-mt-carmel" (data/ef/sanctoral.sexp, Class3, + [Commemoration_only]) falls on a Saturday 8 times in the 2005-2050 + fixture alone; its own collect, both photographic scans, word for word + (Caput "Die 16 iulii, Beatae Mariae Virginis de Monte Carmelo, + Commemoratio"): *"...concede propitius; ut, cuius hodie Commemorationem + solemni celebramus officio, EIUS muniti praesidiis, ad gaudia sempiterna + pervenire mereamur"* -- "her" ("eius") -- grant, we beseech Thee, that we, + fortified by HER patronage, whose Commemoration we solemnly celebrate + today, may be permitted to arrive at everlasting joys -- an explicit + invocation of the SAME BVM's own intercession/patronage, precisely RG + 112(d)'s own trigger. + + The exception clause ("non valet de oratione dominicae vel feriae") does + NOT rescue this: RG 91 lists entry 27 as its own table row, separate from + entry 28's "feriae IV classis" -- this Office is neither a Sunday nor, + once RG 78 has substituted it in, a plain "feria" in Caput IV's own + sense (RG 21: "Nomine feriae intelleguntur singuli dies hebdomadae" -- + an ordinary weekday's OWN office, which RG 78 replaces, not merely + supplements). + + ALSO found on the very same page, both photographic scans, word for + word, confirming the collision is real and rubric-anticipated (not + merely this codebase's own inference): *"Si Commemoratio B. Mariae + Virg. de Monte Carmelo venerit in sabbato, Missa dici potest aut de + sancta Maria in sabbato, aut propria de Commemoratione B. Mariae Virg. + de Monte Carmelo."* -- if the Commemoration of the BVM of Mount Carmel + falls on a Saturday, the Mass MAY be said EITHER of Holy Mary on + Saturday OR properly of the Commemoration of the BVM of Mount Carmel -- + an explicit EITHER/OR between two MASS TEXTS, not an instruction to + commemorate one in the other's Office. Read together with RG 112(d): + the OFFICE question (is Mt Carmel commemorated at all) and the MASS + question (which of the two propers is said that day) are two different + questions -- RG 112(d) answers the first (excluded); this rubric answers + the second, and is a Mass-propers selection detail of exactly the same + kind as the I-V numbered cycle (RG 309(a)) [bvm_saturday_names]'s own + citation already puts out of scope for the SAME reason (colitur computes + no citations/readings at all yet, Plan 4) -- not modelled here either. + + [marian_slugs] is a CLOSED, HAND-VERIFIED list, not a claim to have read + every one of these entries' own Latin orations on the scan (only + Mt Carmel's, quoted above, was actually verified against the primary + text for this fix) -- built from data/ef/sanctoral.sexp's own [names.en] + field, restricted to entries that are themselves a feast/commemoration + OF the Blessed Virgin Mary in her own right (never merely a feast of + someone else that happens to mention her -- "St. Anne, Mother of the + Blessed Virgin", "St. Joseph, Spouse of the Bl. Virgin Mary", "St. + Anthony Mary Claret" and similarly-named entries are deliberately + EXCLUDED, checked one by one). This is the SAME modelling simplification + {!disposition}'s own pre-existing RG 112(a) branch already makes for + "eiusdem Divinae Personae" (read as simply "both [subject = Lord]", + that branch's own comment: "safe at today's data's own granularity") -- + here read as "titled a feast of the BVM", not verified oration-by- + oration, and said so explicitly rather than overclaimed. TWO entries + found and deliberately EXCLUDED after individual consideration, not + merely omitted: "dedication-of-the-basilica-of-st-mary-major" (5 + August, Class3, Feast) -- a DEDICATION feast (of the building, "In + Dedicatione S. Mariae ad Nives"), whose own oration could not be found + in either scan under this exact heading to confirm it invokes her + intercession the same way an ordinary Marian commemoration does, so left + out per this project's "a wrong citation is worse than a missing one" + rule; and "purification-of-the-blessed-virgin-mary" (2 February, + Class2), already deliberately tagged [subject = Lord] by an earlier + task's own ruling (register §6.0, following the oracle's Sunday- + displacing treatment) for an unrelated reason -- its own RG 112(a) + Lord-vs-Lord exclusion already covers it against another Lord-subject + winner, and at Class2 it can never lose to a Class4 candidate in the + first place (this or any other), so including or excluding it here has + no live consequence either way; excluded for consistency with the + earlier ruling rather than silently overriding it. + + What this rule COVERS: any admitted candidate on {!marian_slugs}' own + closed list, or tagged [subject = Bvm], losing to a WINNER that is + itself either {!marian_slugs}-listed or [subject = Bvm] -- currently + live only for the BVM Saturday Office (subject-tagged, its own slug + deliberately NOT on this list, {!bvm_saturday_names}'s own "Slug" + citation in temporal_ef.ml) as winner against Mt Carmel as loser, plus + (checked, not live: 24 September, "our-lady-of-ransom", the ONLY other + {!Celebration.status.Commemoration_only} Marian entry in the data) -- + PROVEN structurally unreachable: 24 September falling on a Saturday + forces 1 September to be a Thursday (23 days = 3 weeks 2 days earlier), + which by {!third_sunday_of_september}'s own construction makes 24 + September the September Ember Saturday EVERY time, RG 91 entry 18 + (Class2), always outranking entry 27 -- not a sampled coincidence, an + exact day-of-week identity, checked against several sample years above + before being generalised. What this rule does NOT cover, stated + plainly: a hypothetical non-Marian-titled saint whose own oration + happens to invoke the BVM's intercession in passing (RG 112(d)'s own + text does not restrict itself to Marian-TITLED commemorations) -- no + oration text is stored anywhere in this codebase to detect that, and + none is guessed at here. Also does not cover RG 112(d)'s OTHER half (two + commemorations of the SAME non-BVM saint) -- unbuilt, unaffected by this + fix, register §4/§6 already tracks it as open. + + Checked for what this fix does NOT change: {!Precedence.resolve} holds + [Commemoration_only] candidates out of the WINNER contest entirely, so + Mt Carmel could never have been [observed] either before or after this + fix -- only its own admission AS a commemoration changes. Verified live + against real 2033/2005 data (both years 16 July is a Saturday): before + this fix, [comms] included "our-lady-of-mt-carmel:ordinary"; after, + [comms] is empty and the omitted list carries it with reason "omitted: + yielded to a higher day", the same generic reason every other [Omit] + disposition in this function produces. *) +let marian_slugs = + [ "annunciation-of-the-blessed-virgin-mary"; + "assumption-of-the-blessed-virgin-mary"; + "immaculate-conception-of-the-blessed-virgin-mary"; + "immaculate-heart-of-mary"; + "maternity-of-the-blessed-virgin-mary"; + "most-holy-name-of-mary"; + "nativity-of-the-blessed-virgin-mary"; + "our-lady-of-lourdes"; + "our-lady-of-mt-carmel"; + "our-lady-of-ransom"; + "our-lady-of-the-rosary"; + "presentation-of-the-blessed-virgin-mary"; + "queenship-of-the-blessed-virgin-mary"; + "seven-sorrows-of-the-blessed-virgin-mary"; + "visitation-of-the-blessed-virgin-mary" ] + +let is_bvm_office (c : Vocab_ef.rank Precedence.candidate) = + c.Precedence.cel.Celebration.subject = Subject.Bvm + || List.mem (Slug.to_string c.Precedence.cel.Celebration.slug) marian_slugs + (* Task 8: what happens to the day's LOSING candidate (docs/research/ rules-register.md §4, "Occurrence" RG 92-95 and "Vigils" RG 33, plus RG 94; also §6.0/Caput III "De Dominicis" RG 16(a), below). [band] above @@ -602,7 +751,16 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate) let open Vocab_ef in let cel = loser.Precedence.cel in let is_temporal = loser.Precedence.origin = Precedence.Temporal in - if cel.Celebration.status = Celebration.Commemoration_only then + if is_bvm_office winner && is_bvm_office loser then + (* RG 112(d) -- see [marian_slugs]'s own citation above for the full + argument. Checked FIRST, even ahead of the [Commemoration_only] + branch immediately below, because the one live witness + (Mt Carmel, 16 July) IS [Commemoration_only] -- that branch's own + "always Commemorate, nothing overrides it" previously had no + exception for two commemorations invoking the identical BVM, the + exact gap this fix closes. *) + Precedence.Omit + else if cel.Celebration.status = Celebration.Commemoration_only then (* Always -- checked before RG 33's omission and RG 95's transfer so neither can override it: a Commemoration_only entry can never win (Precedence.resolve holds it out of the band contest entirely, see diff --git a/test/test_differential.ml b/test/test_differential.ml index 3f416fd..5093484 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -138,6 +138,7 @@ module Slug = Colitur_kernel.Slug module Date = Colitur_kernel.Date module Cel = Colitur_kernel.Celebration module Colour = Colitur_kernel.Colour +module Subject = Colitur_kernel.Subject module V = Rite_ef.Vocab_ef (* Same relative paths test_rite_ef.ml/test_sanctoral_ef.ml use: dune test @@ -216,6 +217,14 @@ type row = { slug : string; rank : string; colour : string; + (* Fix round 1 (coordinator finding F3): ONLY ever populated for colitur's + own row (see [colitur_rows_2005_2050] below) -- lectio's own fixture + format has no subject column at all, so [row_of_line] (the lectio-side + parser) sets this to "-", a placeholder never read on that side. Every + Layer C predicate that reads [subject] must therefore read it off the + COLITUR row [c], never [l] -- the same asymmetry [row]'s other fields + do not have, called out here rather than left implicit. *) + subject : string; } let read_lines path = @@ -232,7 +241,7 @@ let read_lines path = let row_of_line line = match String.split_on_char ' ' line with | date :: weekday :: season :: week :: slug :: rank :: colour :: _others -> - { date; weekday; season; week; slug; rank; colour } + { date; weekday; season; week; slug; rank; colour; subject = "-" } | _ -> Alcotest.failf "malformed fixture line (fewer than 7 fields): %S" line let lectio_rows () = List.map row_of_line (read_lines fixture_path) @@ -271,7 +280,8 @@ let colitur_rows_2005_2050 () = week; slug = Slug.to_string cel.Cel.slug; rank = V.rank_to_string cel.Cel.rank; - colour = Colour.to_string cel.Cel.colour + colour = Colour.to_string cel.Cel.colour; + subject = Subject.to_string cel.Cel.subject } :: !rows | None -> Alcotest.failf "internal error: no resolved day for %s" (Date.to_iso8601 !d)); @@ -535,8 +545,24 @@ let holy_name_fallback_dates_2005_2050 = IV-class Saturday", reduces exactly to this triple), not a short, individually-interesting list of dates the way C14/C15/C16 above are. *) let is_bvm_saturday_row (c : row) diffs = + (* Fix round 1 (coordinator finding F3): [c.subject] added -- the ONLY + field in this predicate that pins WHICH celebration is observed, not + merely its shape. Every other Layer C entry pins a colitur slug (C1's + own [jan_6_13_slug], C6's [nativity_octave_day_slugs], C8/C14/C15/C16's + own literal slug checks); this entry could not, because the office + deliberately REUSES the ordinary ferial slug (Rite_ef.Temporal_ef's own + [bvm_saturday_names] citation, "Slug" paragraph) -- there is no fixed + slug string to pin. [subject = "bvm"] is the field that DOES uniquely + identify the office (set nowhere else the differential's own + [colitur_rows_2005_2050] can produce a Saturday/Class4/white + combination for), closing the gap a shape-only predicate left open: + without this conjunct, a FUTURE bug that made some OTHER white, + Class4, Saturday candidate exist (Christmastide/Paschaltide, where + [season_colour] is already white, so a real bug there could slip + through unnoticed by colour alone) would be silently absorbed here + too. *) diffs = [ Colour_f ] && String.equal c.weekday "saturday" && String.equal c.rank "class-4" - && String.equal c.colour "white" + && String.equal c.colour "white" && String.equal c.subject "bvm" (* [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 diff --git a/test/test_golden.ml b/test/test_golden.ml index bb4bcfc..fdfc01d 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -940,6 +940,24 @@ let test_bvm_saturday_loses_to_most_holy_name_of_mary_2026 () = colour=white subject=bvm name_la=- comms=[] in=- out=[]" (describe d) +(* RG112(d), fix round 1 (coordinator finding F1): PE.marian_slugs's own + citation (precedence_ef.ml) has the full RG112(d) argument, including + the 16 July "Missa dici potest aut ... aut ..." rubric found on both + photographic scans. 16 July 2033 is a real otherwise-unoccupied + Saturday; before this fix, [comms] here read + "[our-lady-of-mt-carmel:ordinary]" (confirmed against real pre-fix + `colitur day 2033` output). *) +let test_bvm_saturday_excludes_mt_carmel_2033 () = + let d = fetch 2033 7 16 in + Alcotest.(check bool) + "2033-07-16: Mt Carmel is in [omitted] (RG112(d)), not silently dropped from the record" true + (omitted_has d "our-lady-of-mt-carmel"); + Alcotest.(check string) + "2033-07-16: the BVM Saturday office observed, white; NO commemoration of Mt Carmel (RG112(d))" + "2033-07-16 saturday season=time-after-pentecost week=5 slug=ef-time-after-pentecost-5-saturday \ + rank=class-4 colour=white subject=bvm name_la=Officium sanctae Mariae in sabbato comms=[] in=- out=[]" + (describe d) + let suite = ( "golden pins (known-tricky years)", [ Alcotest.test_case "Easter extreme: 1598 earliest (22 Mar, Gauss-verified)" `Quick @@ -1004,5 +1022,9 @@ let suite = Alcotest.test_case "RG91 e27/RG78/RG26: the BVM Saturday office loses to Most Holy Name of Mary (subject Bvm too) \ and is omitted, no confusion (2026-09-12)" - `Quick test_bvm_saturday_loses_to_most_holy_name_of_mary_2026 + `Quick test_bvm_saturday_loses_to_most_holy_name_of_mary_2026; + Alcotest.test_case + "RG112(d), fix round 1 (F1): the BVM Saturday office excludes Mt Carmel's own commemoration \ + (2033-07-16)" + `Quick test_bvm_saturday_excludes_mt_carmel_2033 ] ) diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml index 475ae6e..6f8b5b5 100644 --- a/test/test_precedence_ef.ml +++ b/test/test_precedence_ef.ml @@ -805,6 +805,52 @@ let disposition_cases = "conversion-of-st-paul", cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer "commemoration-of-the-baptism-of-the-lord", + "Commemorate(Ordinary)" ); + (* RG112(d), fix round 1 (coordinator finding F1) -- PE.marian_slugs's + own citation has the full argument (both scans, including the 16 + July "Missa dici potest aut ... aut ..." rubric). [of_temporal] on a + REAL 16-July-Saturday (2033) sources the winner from + Temporal_ef.temporal's own real output, the same review-finding-3 + discipline this file's Ember/vigil rows already use -- not a + hand-typed slug/subject pair that could silently drift from what the + office actually builds. The loser is the real data/ef/sanctoral.sexp + entry, [Commemoration_only], the exact shape that made this branch + need to be checked BEFORE the Commemoration_only catch-all. *) + ( "RG112(d): the BVM Saturday Office excludes a commemoration invoking \ + the SAME BVM's intercession -- Omit, not Commemorate (Mt Carmel, \ + 16 July 2033, a real otherwise-unoccupied Saturday)", + of_temporal (mk 2033 7 16), + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~subject:Sub.Saint + ~layer:PE.universal_layer "our-lady-of-mt-carmel", + "Omit" ); + (* Conjunct 1/2, LOSER identity: an ORDINARY Commemoration_only saint + NOT on PE.marian_slugs must still reach the ordinary commemorate + flow even when the winner is the BVM Saturday Office -- proves + RG112(d) excludes a specific same-BVM collision, not every + commemoration the office happens to outrank (the real live shape, + 1 August, Holy Machabees, PE.marian_slugs's own comment). *) + ( "RG112(d) does NOT fire when the LOSER is not a Marian commemoration \ + -- an ordinary saint stays Commemorate(Ordinary) even under the BVM \ + Saturday Office (1 August 2026, Holy Machabees)", + of_temporal (mk 2026 8 1), + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~subject:Sub.Saint + ~layer:PE.universal_layer "holy-machabees", + "Commemorate(Ordinary)" ); + (* Conjunct 2/2, WINNER identity: an ordinary (non-Bvm, non-Marian- + titled) winner must not exclude Mt Carmel either -- RG112(d) is + keyed on the WINNER also being "de B. Maria Virg.", not merely on + the loser's own identity. SYNTHETIC: no live date has an ordinary + Class3 saint winning outright while Mt Carmel also loses that same + day (Mt Carmel's own fixed date, 16 July, has no other real + sanctoral entry to construct this from) -- proves the conjunct is + real, not merely untested, the same "SYNTHETIC" discipline the + RG112(a) rows above already use for their own second conjunct. *) + ( "SYNTHETIC: RG112(d) does NOT fire when only the LOSER is Marian -- \ + an ordinary winner leaves Mt Carmel Commemorate(Ordinary)", + cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Saint ~layer:PE.universal_layer + "ef-synthetic-ordinary-winner", + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~subject:Sub.Saint + ~layer:PE.universal_layer "our-lady-of-mt-carmel", "Commemorate(Ordinary)" ) ] @@ -1507,6 +1553,32 @@ let test_bvm_saturday_loses_to_a_real_feast_end_to_end () = [ ("ef-time-after-pentecost-15-saturday", "omitted: yielded to a higher day") ] (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted) +(* RG112(d), fix round 1 (coordinator finding F1) -- PE.marian_slugs's own + citation has the full argument. End-to-end through the REAL pipeline + (PE.band, PE.disposition AND PE.admit together): 16 July 2033, a real + otherwise-unoccupied Saturday, with the real data/ef/sanctoral.sexp + [our-lady-of-mt-carmel] entry as the day's only competing candidate. + Before this fix, [resolution.P.commemorations] held + [our-lady-of-mt-carmel:Ordinary] (confirmed live against real 2005/2033 + `colitur day` output before this fix existed); after, it is empty and + Mt Carmel is reported omitted, not silently dropped. *) +let test_bvm_saturday_excludes_mt_carmel_end_to_end () = + let date = mk 2033 7 16 in + let day_ctx = ctx date in + let bvm_saturday = of_temporal date in + let mt_carmel = + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~subject:Sub.Saint + ~layer:PE.universal_layer "our-lady-of-mt-carmel" + in + let resolution = P.resolve real_rules day_ctx ~temporal:bvm_saturday ~sanctoral:[ mt_carmel ] in + Alcotest.(check string) "the BVM Saturday office is observed" + "ef-time-after-pentecost-5-saturday" (S.to_string resolution.P.observed.P.cel.Cel.slug); + Alcotest.(check (list string)) "Mt Carmel is NOT commemorated (RG112(d))" [] + (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.commemorations); + Alcotest.(check (list (pair string string))) "Mt Carmel is reported omitted, not silently dropped" + [ ("our-lady-of-mt-carmel", "omitted: yielded to a higher day") ] + (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted) + (* RG 16(a) (register §6.0) reached end-to-end through the REAL pipeline ([PE.band], [PE.disposition] AND [PE.admit] together, not any one of them in isolation as this file's own hand-tagged rows test them @@ -1988,6 +2060,10 @@ let suite = "RG91 e27/RG78/RG26: the BVM Saturday office loses to a real feast and is omitted, end-to-end \ (2026-09-12)" `Quick test_bvm_saturday_loses_to_a_real_feast_end_to_end; + Alcotest.test_case + "RG112(d), fix round 1 (F1): the BVM Saturday office excludes Mt Carmel, end-to-end \ + (2033-07-16)" + `Quick test_bvm_saturday_excludes_mt_carmel_end_to_end; Alcotest.test_case "RG16(a): a Feast of the Lord suppresses the Sunday's own commemoration entirely, \ end-to-end (2028-08-06)" diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index ff3cc0b..4e97d33 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -507,25 +507,40 @@ let test_colours () = (6 Jan) is a Tuesday, so the actual first-Sunday-after-Epiphany origin is 11 Jan -- exercising all four sub-stretches of the fix. *) let test_christmastide_feria_slugs () = - (* 26 Dec 2026 and 10 Jan 2026 are both SATURDAYS -- ef-bvm-saturday task - (RG 78, "De sancta Maria in sabbato"): every Christmastide feria - [christmastide_feria_slug] builds is unconditionally Class4 - ([ferial_rank]'s own catch-all outside Advent/Lent/Passiontide), so on a - Saturday {!Rite_ef.Temporal_ef.bvm_saturday_names}'s own override fires - here too -- but per that citation's own "Slug" paragraph, the SLUG is - deliberately left UNCHANGED (reused from this same "-0-"/"-2-" naming, - not replaced): only colour/subject/name change. So the slug assertions - below are unaffected by the fix; what is NEW is the colour/subject - override on these two specific dates, asserted alongside them. *) + (* 26 Dec 2026 is a SATURDAY -- but, fix round 1 (coordinator finding F2), + it can NEVER actually hold the BVM Saturday office in the real + resolved pipeline: 26/27/28 December are St Stephen/St John/the Holy + Innocents (RG 67-70), fixed, unconditional, real data/ef/sanctoral.sexp + entries every single year (rank >= Class2), which always outrank a + Class4 temporal candidate -- so RG 78's own protasis ("in quibus + OCCURRIT Officium de feria IV classis") is never satisfied by ANY day + in the 26-28 December stretch, in any year. [T.temporal] itself has no + visibility into the sanctoral layer at all (by design -- the temporal + candidate is computed independent of what saint's day it is; Precedence + decides the winner afterward), so it still unconditionally tags this + date [subject = Bvm] -- a genuine, correct fact about [T.temporal]'s + own pure computation, but NOT a claim that this shape is ever the real + OBSERVED office, and pinning it with RG-78 citation language as if it + were would overclaim. Only the SLUG is asserted here (unaffected + either way, unconditionally correct); the override demonstration + itself uses 3 January and 10 January below instead -- both, checked + directly against data/ef/sanctoral.sexp, have no competing entry in + any year, so the BVM Saturday office genuinely IS reachable there. *) Alcotest.(check string) "26 Dec (Sat)" "ef-christmas-0-saturday" (slug_of (d 2026 12 26)); - Alcotest.(check string) "26 Dec (Sat): the BVM Saturday office overrides the season colour (white, RG 120(b))" - "white" (colour_of (d 2026 12 26)); - Alcotest.(check bool) "26 Dec (Sat): subject Bvm" true (subject_of (d 2026 12 26) = Sub.Bvm); Alcotest.(check string) "28 Dec (Mon)" "ef-christmas-0-monday" (slug_of (d 2026 12 28)); + (* 3 Jan 2026 is a SATURDAY, genuinely reachable (data/ef/sanctoral.sexp + has no 3 January entry) -- the real demonstration for the "-1-" + stretch that 26 December's own Saturday could not honestly provide. *) + Alcotest.(check string) "3 Jan (Sat): slug stays the ordinary \"-1-\" naming" "ef-christmas-1-saturday" + (slug_of (d 2026 1 3)); + Alcotest.(check string) "3 Jan (Sat): the BVM Saturday office overrides the season colour (white, RG 120(b))" + "white" (colour_of (d 2026 1 3)); + Alcotest.(check bool) "3 Jan (Sat): subject Bvm" true (subject_of (d 2026 1 3) = Sub.Bvm); Alcotest.(check string) "2 Jan (Fri)" "ef-christmas-1-friday" (slug_of (d 2026 1 2)); Alcotest.(check string) "5 Jan (Mon)" "ef-christmas-1-monday" (slug_of (d 2026 1 5)); (* 7-10 Jan precede the actual origin Sunday (11 Jan): colitur-only, not - lectio's "week 1" key, precisely to avoid colliding with 12-13 Jan below. *) + lectio's "week 1" key, precisely to avoid colliding with 12-13 Jan below. + 10 January is ALSO genuinely reachable (no sanctoral entry that date). *) Alcotest.(check string) "7 Jan (Wed, before the origin)" "ef-christmas-2-wednesday" (slug_of (d 2026 1 7)); Alcotest.(check string) "10 Jan (Sat, before the origin)" "ef-christmas-2-saturday" (slug_of (d 2026 1 10)); Alcotest.(check bool) "10 Jan (Sat): subject Bvm" true (subject_of (d 2026 1 10) = Sub.Bvm); -- cgit v1.3 From da0eca73fa2a7b2357bcc28c4dd7d8b001066685 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 17:09:41 +0200 Subject: docs: fix round 1 ride-along findings (F2-F7) F2: test_christmastide_feria_slugs's 26 December assertions removed -- St Stephen/John/the Holy Innocents (RG 67-70) always outrank a Class4 Saturday candidate there, in every year, so the office can never actually be observed on any day in the 26-28 December stretch. 3 January (no competing sanctoral entry, any year) used for the "-1-" stretch demonstration instead, alongside the pre-existing 10 January one. F3: test_differential.ml's shared row type gains a subject field (populated for colitur's own rows only; lectio's fixture has no such column). C17's own predicate now also requires subject = "bvm" -- defence in depth against a future bug producing a white Class4 Saturday candidate some other way, particularly in Christmastide/ Paschaltide where colour alone could not catch it. F4: Precedence_ef.band's entry-27 comment gains its RG 78 citation, replacing "per the historical default that fills it". F5: two report inaccuracies corrected -- band was already receiving a real candidate 312,966 times pre-change (what was missing was its identity, not its reality); the "Missae de S. Maria in sabbato" heading quote was presented as "both scans, word for word" when both scans are OCR-noisy there AND genuinely differ in abbreviation from each other -- corrected in temporal_ef.ml's own comment and the register alongside the report. F6: the colour rule gains a tighter citation chain -- RG 431(e) (the Missal's own classification of this Mass as a "Missa votiva IV classis... de B. Maria Virg.") -> RG 121(a) (votive Masses take the colour of the feast-type they correspond to) -> RG 120(b) (BVM feasts are white) -- reaching the same conclusion through the category (votive) the Missal's own text actually places this Mass in, not by treating it as a festum RG 91's own table structure says it is not. F7: the oracle's real seasonal coverage recorded precisely in CLAUDE.md and the register -- of 26 BVM-Saturday-office days in the 2026-2027 fixture only 17 are colour-discriminating; Time after Epiphany (5,015 observed days domain-wide) has zero oracle witnesses. Full account in .superpowers/sdd/2026-08-12-colitur-rg16a/ bvm-saturday-report.md (local, gitignored). --- CLAUDE.md | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8cfa902..7875fbe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -587,11 +587,16 @@ candidate is exactly one and already spoken for). IV-class Saturday" — decided entirely by the existing occurrence machinery (`band`'s own entry-27 branch already reads `rank = Class4 && weekday = Sat` unconditionally and only wins when - nothing outranks it), so **no change to `band`, `disposition` or - `admit` was needed** — rank stays Class4 either way, and none of the - three reads slug/colour/name to decide anything. Colour is white, - unconditionally (RG 120(b), not RG 119/127/128's seasonal rules — this - office is never "de Tempore" for colour). The slug is deliberately + nothing outranks it), so **`band` and `admit` needed no change** — rank + stays Class4 either way, and neither reads slug/colour/name to decide + anything (`disposition` DID need one, in a fix round — see below, RG + 112(d)). Colour is white, unconditionally — the tighter chain, found in + a fix round (RG 431(e), the Missal's own classification of this exact + Mass as a "Missa votiva IV classis... de B. Maria Virg.", → RG 121(a), + votive Masses take the colour of the feast-type they "respondent" → + RG 120(b), BVM feasts are white — see below), not RG 120(b) alone + (a stretch: this Office is not itself a *festum*, RG 120(b)'s own + "de festis") and never RG 119/127/128's seasonal rules either way. The slug is deliberately UNCHANGED (reused from the ordinary `--` ferial fallback), the same precedent the Sacred Triduum (item 5) already set — identified structurally, never off the slug — and for a second, load- @@ -636,6 +641,32 @@ candidate is exactly one and already spoken for). the mutation, proving RG 26's rank-keyed omission fires before any subject-keyed rule ever could. + **Fix round 1 (2026-08-13, coordinator review) — one blocking finding, + fixed.** RG 112(d) (Caput XVI): the Office, itself "de B. Maria Virg.", + excludes another commemoration invoking the SAME BVM's intercession — + violated live: `our-lady-of-mt-carmel` (16 July, `Commemoration_only`) + was wrongly commemorated on every 16-July-Saturday. Fixed in + `Precedence_ef.disposition` (`marian_slugs` ∪ `subject = Bvm` on both + sides of the collision — the disjunction matters, neither signal alone + identifies both real sides). Checked exhaustively, not merely for + Mt Carmel: only `Commemoration_only`-status Marian entries can ever + reach this live, and the ONLY other one, `our-lady-of-ransom` (24 + September), is PROVABLY unreachable (forces the September Ember + Saturday every time, by construction). Re-measured: the original + 75,853-day blast radius is unchanged in total, splitting into 74,633 + `colour`-only + 1,220 `colour+comms`. Mutation-tested: exactly 3 tests + redden. Six further ride-along findings closed the same round: a + test pin corrected (26 December can never hold the office, St Stephen + always wins there — 3 January used instead); C17 gained a `subject`- + based identity guard; `band`'s own entry-27 comment gained its RG 78 + citation; a tighter RG 431(e) → RG 121(a) → RG 120(b) colour chain + (conclusion unchanged); and the oracle's own coverage recorded + precisely — of 26 office days in the 2026-2027 fixture only 17 are + colour-discriminating, and **Time after Epiphany (5,015 observed days + domain-wide) has ZERO oracle witnesses**, resting on the scan and the + dedicated unit test alone. Full account: + `.superpowers/sdd/2026-08-12-colitur-rg16a/bvm-saturday-report.md`. + ## How to work here - **Superpowers workflow**: `brainstorming` → `writing-plans` → `executing-plans` -- cgit v1.3 From f389e57e965e7bc7ea46ad1f37203d00f22e25a7 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 17:10:00 +0200 Subject: docs(ef): fix round 1 citation corrections in temporal_ef.ml (F5b, F6) bvm_saturday_names's own comment: the "Missae de S. Maria in sabbato" heading quote corrected from an overclaimed "both scans, word for word" to showing both scans' own raw OCR (noisy, and genuinely differing in abbreviation from each other) side by side, with the substance-only corroboration stated plainly. Colour citation tightened: RG 431(e) (the Missal's own classification of this Mass as a "Missa votiva IV classis... de B. Maria Virg.") -> RG 121(a) (votive Masses take the colour of the feast-type they correspond to) -> RG 120(b) (BVM feasts are white), replacing RG 120(b) alone as the primary citation -- this Office is not itself a festum, so RG 120(b)'s own "in Officio et Missa de festis" was a stretch as the sole basis. Conclusion (white) unchanged. --- lib/rites/rite_ef/temporal_ef.ml | 77 +++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index 2188ce3..cf0d22c 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -626,21 +626,42 @@ let weekday_word d = Date.weekday_to_string (Date.weekday d) liturgici IV classis: 27. Officium sanctae Mariae in sabbato. 28. Feriae IV classis." -- both scans, register §4). - Colour: white, ALWAYS, regardless of season -- RG 120(b) (Caput XVIII, - "De coloribus paramentorum", "B) De colore albo"), both photographic - scans: "120. Adhibetur color albus in Officio et Missa de festis: ... b) - B. Mariae Virg." -- white is used in the Office and Mass of feasts ... of - the Blessed Virgin Mary. NOT RG 119 (the "de Tempore" white rule for - Christmastide/Paschaltide) or RG 127/128 (season green/violet): this - Office is never "de Tempore" for colour purposes, so it overrides - [season_colour] unconditionally, including on Septuagesima (violet) and - Time-after-Epiphany/-Pentecost (green) Saturdays -- confirmed against the - oracle (missalemeum, 1 August 2026, a Time-after-Pentecost Saturday: - white, not the season's green). The Missal's own "Missae de sancta Maria - in sabbato" section (both scans) carries no per-Mass colour override the - way some other votive Masses do elsewhere in the same scan ("In hac Missa - adhibetur color albus") -- it needs none, RG 120(b) already settles it - unconditionally for every occurrence. + Colour: white, ALWAYS, regardless of season. CORRECTED, fix round 1 + (coordinator finding F6): RG 120(b) alone is a stretch as the PRIMARY + citation -- its own text reads "in Officio et Missa DE FESTIS" (of + FEASTS), and this Office is not itself a festum (RG 91's own table + position, entry 27, sits outside the Festa rows 11-13/16/19/20/23/24 + entirely). The tighter chain, found on the same scan: RG 431(e) (Caput + XX, "De hymno Gloria in excelsis") is the Missal's OWN classification of + this exact Mass, word for word: "431. Hymnus Gloria in excelsis + dicitur: ... e) in Missis votivis IV classis de Angelis, quocumque die, + et de B. Maria Virg. QUAE IN SABBATO CELEBRANTUR" -- the Gloria is said + "in IV-class VOTIVE MASSES of the Angels, on any day, and OF THE BVM + WHICH ARE CELEBRATED ON SATURDAY" -- the Missal's own words classify + this Mass as a "Missa votiva IV classis... de B. Maria Virg.", not a + festum. RG 121(a) (immediately after RG 120, same "De colore albo" + section), both photographic scans: "121. Colorem album requirunt + Missae votivae: a) quae respondent festis, de quibus numero + praecedenti" -- white colour is required by VOTIVE MASSES which + "correspond to" (respondent) the feasts named in the preceding + paragraph [120] -- 120(b)'s own "B. Mariae Virg." among them. The chain + -- RG 431(e) (this Mass IS a IV-class votive Mass of the BVM) -> RG + 121(a) (a votive Mass corresponding to a 120-listed feast-type takes + that colour) -> RG 120(b) (BVM feasts are white) -- reaches the + identical conclusion (white) through the category the Missal's own text + actually places this Mass in (votive), not by treating it as a festum + RG 91's own table structure says it is not. NOT RG 119 (the "de + Tempore" white rule for Christmastide/Paschaltide) or RG 127/128 + (season green/violet) either way: this Office is never "de Tempore" for + colour purposes, so it overrides [season_colour] unconditionally, + including on Septuagesima (violet) and Time-after-Epiphany/-Pentecost + (green) Saturdays -- confirmed against the oracle (missalemeum, 1 + August 2026, a Time-after-Pentecost Saturday: white, not the season's + green). The Missal's own "Missae de sancta Maria in sabbato" section + (both scans) carries no further per-Mass colour override the way some + OTHER votive Masses do elsewhere in the same scan ("In hac Missa + adhibetur color albus") -- it needs none, the chain above already + settles it unconditionally for every occurrence. Name: "Officium sanctae Mariae in sabbato" -- RG 91 entry 27's own table title AND RG 79's own heading, both photographic scans and the electronic @@ -689,15 +710,23 @@ let weekday_word d = Date.weekday_to_string (Date.weekday d) with no change to [Validate] at all, since season+week+weekday is already guaranteed unique within a liturgical year by construction. It also dovetails with the FIVE numbered "Missae de sancta Maria in sabbato" the - Missal's own Common of the BVM prints immediately afterward ("MISSAE DE - S. MARIA IN SABBATO QUAE DICI ETIAM POSSUNT ALIIS DIEBUS UT VOTIVAE DE B. - MARIA VIRGINE, IUXTA RUBRICAS VEL, EX INDULTO, PRO TEMPORUM DIVERSITATE": - I "Tempore Adventus", II "A Nativitate Domini usque ad Purificationem", - III "A die 3 Februarii usque ad feriam IV Hebdomadae sanctae", IV - "Tempore Paschali", V "A festo Ss. Trinitatis usque ad sabbatum ante - dominicam I Adventus" -- both photographic scans, word for word, matching - the oracle's own "V Mass of the B. V. M." title on a Time-after-Pentecost - Saturday): a season-keyed slug leaves room for a future lectionary + Missal's own Common of the BVM prints immediately afterward -- a heading + ("MISSAE DE S. MARIA IN SABBATO QUAE DICI ETIAM POSSUNT ALIIS DIEBUS UT + VOTIVAE DE B. MARIA VIRGINE, IUXTA RUBRICAS VEL, EX INDULTO, PRO TEMPORUM + DIVERSITATE") whose SUBSTANCE both scans corroborate but whose exact + wording is NOT "word for word" identical on both -- CORRECTED, fix round + 1 (coordinator finding F5b): both scans' own OCR is noisy here, and the + two additionally use different abbreviations for each other ("S. MARIA" + vs "SANCTA MARIA", "B. MARIA VIRGINE" vs "BEATA MARIA VIRGINE"), not + merely different OCR artifacts of one underlying text -- the task + report has the full raw-OCR comparison. Followed by five numbered + Masses, each with its own season heading, clean and consistent on both + scans: I "Tempore Adventus", II "A Nativitate Domini usque ad + Purificationem", III "A die 3 Februarii usque ad feriam IV Hebdomadae + sanctae", IV "Tempore Paschali", V "A festo Ss. Trinitatis usque ad + sabbatum ante dominicam I Adventus" -- matching the oracle's own "V Mass + of the B. V. M." title on a Time-after-Pentecost Saturday: a + season-keyed slug leaves room for a future lectionary bootstrap (Plan 4) to map each one onto its corresponding numbered Mass's propers, the same way [sunday_slug]'s own season-keyed Sunday slugs already anchor the ordinary lectionary. RG 309(a)'s own words -- "Missae -- cgit v1.3 From c63e37b8cbef1480b6f5677420425d22fe7061d9 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 17:37:40 +0200 Subject: docs: an invented Caput, a list that was not closed, and a blind spot Three corrections from the fix-round re-review, none changing behaviour. RG 431(e) was attributed to "Caput XX, De hymno Gloria in excelsis". There is no Caput XX -- the Rubricae Generales' Caput series ends at XIX. The paragraph number, letter and subsection title were all right; only the containing division was invented. It sits under "VIII -- De diversis Missae partibus", subsection "C) De hymno Gloria in excelsis". Shipping a new invented structural citation in the very round that corrected a source-fidelity overclaim is what the gate exists to catch. marian_slugs claimed TWO entries found and deliberately excluded after individual consideration, "not merely omitted". There was a third: vigil-of-the-assumption, 14 August, which is de B. Maria Virg. and whose collect implores her protection. It was merely omitted. Added to the list -- behaviourally inert, verified: it wins 7196 times domain-wide, always with the non-Marian eusebius-confessor as its only commemoration, and never loses -- but a closed list's whole value is its enumeration claim, and that claim was false as written. And the new rule has no comparison-layer witness at all. Reverting it reddens three tests, none of them a comparison layer: the differential's row type carries no commemorations, and the oracle's window has no 16-July Saturday. Worse, lectio's fixture DOES carry commemorations and prints +our-lady-of-mt-carmel on all 8 in-window dates where colitur now prints nothing -- a real divergence, neither fixed nor cited, swallowed by a blind spot, which binding decision 1 forbids. Recorded, with the note that colitur's reading here has no reference confirmation anywhere and only extending the oracle fixture to 2033 or later would settle it. Also records the RG 112(d) branch's ordering constraint: it precedes RG 95's Transfer branch, which it does not need to, and must move below it if a Class1 Marian entry is ever added. --- data/ef/expected-divergences.sexp | 2 +- lib/rites/rite_ef/precedence_ef.ml | 41 ++++++++++++++++++++++++++++++++++---- lib/rites/rite_ef/temporal_ef.ml | 8 ++++++-- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/data/ef/expected-divergences.sexp b/data/ef/expected-divergences.sexp index 444f55e..f5d5381 100644 --- a/data/ef/expected-divergences.sexp +++ b/data/ef/expected-divergences.sexp @@ -75,5 +75,5 @@ ((id C17) (citation "RG 91 entry 27 (\"Officium sanctae Mariae in sabbato\") + RG 78 (Caput IX, \"De sancta Maria in sabbato\", both photographic scans and the electronic transcription word for word): \"In sabbatis, in quibus occurrit Officium de feria IV classis, fit de sancta Maria in sabbato\" -- on Saturdays on which the Office of a IV-class feria occurs, [the Office] is made of Holy Mary on Saturday instead + RG 120(b) (\"Adhibetur color albus in Officio et Missa de festis: ... b) B. Mariae Virg.\") for the white colour, unconditional regardless of season") (verdict colitur) - (note "ef-bvm-saturday task: colitur now builds RG 91 entry 27's votive Office of the BVM on every otherwise-unoccupied IV-class Saturday (Rite_ef.Temporal_ef's own [bvm_saturday_names] citation has the full argument) -- white (RG 120(b)), unconditionally, overriding the season's own colour (violet in Septuagesima, green in Time after Epiphany/Time after Pentecost; Christmastide and ordinary Paschaltide already happened to be white). lectio builds no equivalent office at all -- checked directly against its own tridentine-calendar.ini/generator, no comparable rule exists there, the same genuine upstream gap C15/C16 above document for their own gaps, not a colitur bootstrap miss -- so it keeps showing the plain season colour on every one of these Saturdays. Season, slug and rank all still agree: temporal_ef.ml's own [bvm_saturday_names] citation (\"Slug\" paragraph) deliberately REUSES the ordinary --saturday ferial slug rather than minting a new one (partly BECAUSE a bespoke uniform slug would itself have broken Colitur_kernel.Validate's own slug-uniqueness invariant, sighted on every otherwise-unoccupied Saturday in a year), so [Colour_f] alone is the complete diff set every one of these rows carries -- never [Slug_f] too. Gated on colitur's own [rank]=class-4, [weekday]=saturday and [colour]=white (test_differential.ml's own [is_bvm_saturday_row]) rather than a date list, unlike C14/C15/C16 above: RG 78's own condition (\"otherwise unoccupied IV-class Saturday\") reduces exactly to this triple, and the population is large (a substantial fraction of all Saturdays in the 2005-2050 window) and entirely formulaic, not a short, individually-interesting list. Derived directly from the OCaml comparator's own failure output (test_layer_c_counts_match_citations), not hand-counted first and cross-checked after.") + (note "ef-bvm-saturday task: colitur now builds RG 91 entry 27's votive Office of the BVM on every otherwise-unoccupied IV-class Saturday (Rite_ef.Temporal_ef's own [bvm_saturday_names] citation has the full argument) -- white (RG 431(e) -> RG 121(a) -> RG 120(b); RG 120(b) alone reads \"de festis\" and this Office is not a festum), unconditionally, overriding the season's own colour (violet in Septuagesima, green in Time after Epiphany/Time after Pentecost; Christmastide and ordinary Paschaltide already happened to be white). lectio builds no equivalent office at all -- checked directly against its own tridentine-calendar.ini/generator, no comparable rule exists there, the same genuine upstream gap C15/C16 above document for their own gaps, not a colitur bootstrap miss -- so it keeps showing the plain season colour on every one of these Saturdays. Season, slug and rank all still agree: temporal_ef.ml's own [bvm_saturday_names] citation (\"Slug\" paragraph) deliberately REUSES the ordinary --saturday ferial slug rather than minting a new one (partly BECAUSE a bespoke uniform slug would itself have broken Colitur_kernel.Validate's own slug-uniqueness invariant, sighted on every otherwise-unoccupied Saturday in a year), so [Colour_f] alone is the complete diff set every one of these rows carries -- never [Slug_f] too. Gated on colitur's own [rank]=class-4, [weekday]=saturday and [colour]=white (test_differential.ml's own [is_bvm_saturday_row]) rather than a date list, unlike C14/C15/C16 above: RG 78's own condition (\"otherwise unoccupied IV-class Saturday\") reduces exactly to this triple, and the population is large (a substantial fraction of all Saturdays in the 2005-2050 window) and entirely formulaic, not a short, individually-interesting list. Derived directly from the OCaml comparator's own failure output (test_layer_c_counts_match_citations), not hand-counted first and cross-checked after.") (expected_rows 416)) diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 302df98..7b64b83 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -458,9 +458,18 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc "eiusdem Divinae Personae" (read as simply "both [subject = Lord]", that branch's own comment: "safe at today's data's own granularity") -- here read as "titled a feast of the BVM", not verified oration-by- - oration, and said so explicitly rather than overclaimed. TWO entries - found and deliberately EXCLUDED after individual consideration, not - merely omitted: "dedication-of-the-basilica-of-st-mary-major" (5 + oration, and said so explicitly rather than overclaimed. THREE entries found + and considered individually rather than merely omitted -- the third, + "vigil-of-the-assumption" (14 August, Class2, Feast), was MISSED by the + original enumeration and is recorded here by the fix-round re-review: it + is unambiguously de B. Maria Virg. and its collect implores her + protection ("sua nos defensione munitos"), so it BELONGS on the list on + the same reasoning as the rest. It is behaviourally inert either way -- + measured domain-wide, it wins 7196 times, always with the non-Marian + [eusebius-confessor] as its only commemoration, and never loses, since + no Class1 Marian falls on 14 August -- but a closed list's whole value is + its enumeration claim, and that claim was false as first written. The + other two were genuinely excluded after consideration: "dedication-of-the-basilica-of-st-mary-major" (5 August, Class3, Feast) -- a DEDICATION feast (of the building, "In Dedicatione S. Mariae ad Nives"), whose own oration could not be found in either scan under this exact heading to confirm it invokes her @@ -517,6 +526,11 @@ let marian_slugs = "most-holy-name-of-mary"; "nativity-of-the-blessed-virgin-mary"; "our-lady-of-lourdes"; + (* Added by the fix-round re-review: missed by the original enumeration, + behaviourally inert (it never loses; no Class1 Marian falls 14 Aug), + but it is de B. Maria Virg. and its collect implores her protection, + so it belongs on the list by the same test as every other member. *) + "vigil-of-the-assumption"; "our-lady-of-mt-carmel"; "our-lady-of-ransom"; "our-lady-of-the-rosary"; @@ -753,7 +767,26 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate) let is_temporal = loser.Precedence.origin = Precedence.Temporal in if is_bvm_office winner && is_bvm_office loser then (* RG 112(d) -- see [marian_slugs]'s own citation above for the full - argument. Checked FIRST, even ahead of the [Commemoration_only] + argument. + + ON THIS BRANCH'S POSITION (fix-round re-review, finding 5): it sits + ahead of RG 33's vigil branch and RG 95's Class1 Transfer branch as + well as ahead of [Commemoration_only], and only the last of those + three is a position it NEEDS. RG 112(d) governs commemorations, not + translations, so if a Class1 Marian celebration ever lost to a Marian + winner it would be silently [Omit]ed here instead of reaching RG 95 + and transferring. That is unreachable on shipped data -- the + re-review swept the whole domain and found ZERO Marian-winner / + Marian-loser pairs other than the 16 July case this branch exists + for, which is [Commemoration_only] and so could never transfer + anyway -- and it is left in place rather than moved, because + reordering a disposition branch is a behavioural change that deserves + its own measurement and review rather than a late edit. Recorded here + and in the register so the next task that touches this chain knows + the constraint: if a Class1 Marian entry is ever added, this branch + must move BELOW the Transfer branch. + + Checked FIRST, then, ahead of the [Commemoration_only] branch immediately below, because the one live witness (Mt Carmel, 16 July) IS [Commemoration_only] -- that branch's own "always Commemorate, nothing overrides it" previously had no diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index cf0d22c..4add824 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -631,8 +631,12 @@ let weekday_word d = Date.weekday_to_string (Date.weekday d) citation -- its own text reads "in Officio et Missa DE FESTIS" (of FEASTS), and this Office is not itself a festum (RG 91's own table position, entry 27, sits outside the Festa rows 11-13/16/19/20/23/24 - entirely). The tighter chain, found on the same scan: RG 431(e) (Caput - XX, "De hymno Gloria in excelsis") is the Missal's OWN classification of + entirely). The tighter chain, found on the same scan: RG 431(e) ("VIII -- De diversis + Missae partibus", subsection "C) De hymno Gloria in excelsis" -- NOT + "Caput XX", which does not exist: the Rubricae Generales' Caput series + ends at XIX. Corrected by the fix-round re-review; the paragraph number, + letter and subsection title were right, only the containing division was + invented) is the Missal's OWN classification of this exact Mass, word for word: "431. Hymnus Gloria in excelsis dicitur: ... e) in Missis votivis IV classis de Angelis, quocumque die, et de B. Maria Virg. QUAE IN SABBATO CELEBRANTUR" -- the Gloria is said -- cgit v1.3