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