aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 17:09:27 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 17:09:27 +0200
commitdce208f57cfeeee0715cf35389d37e7ce98227ae (patch)
tree9870e58df161646d71c2f08e5de7f4eefda1434e
parentd5df5e79c731ac7f7f89969c0cafe564fdc6e8ab (diff)
downloadcolitur-dce208f57cfeeee0715cf35389d37e7ce98227ae.tar.gz
colitur-dce208f57cfeeee0715cf35389d37e7ce98227ae.zip
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.
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml176
-rw-r--r--test/test_differential.ml32
-rw-r--r--test/test_golden.ml24
-rw-r--r--test/test_precedence_ef.ml76
-rw-r--r--test/test_temporal_ef.ml43
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
@@ -1989,6 +2061,10 @@ let suite =
(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)"
`Quick test_rg16a_lord_feast_suppresses_sunday_end_to_end;
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);