aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/main.ml45
-rw-r--r--lib/kernel/calendar.ml4
-rw-r--r--lib/kernel/liturgical_day.ml1
-rw-r--r--lib/kernel/liturgical_day.mli7
-rw-r--r--lib/kernel/preface.ml59
-rw-r--r--lib/kernel/preface.mli50
-rw-r--r--lib/kernel/rite.ml1
-rw-r--r--lib/kernel/rite.mli17
-rw-r--r--lib/rites/rite_ef/rite_ef.ml3
-rw-r--r--lib/rites/rite_ef/rite_ef.mli4
-rw-r--r--lib/rites/rite_ef/rubrics_ef.ml496
-rw-r--r--lib/rites/rite_ef/rubrics_ef.mli35
-rw-r--r--man/colitur.133
-rw-r--r--test/cli.t33
-rw-r--r--test/test_calendar.ml10
-rw-r--r--test/test_fiuv_ordo.ml153
-rw-r--r--test/test_rubrics_ef.ml387
-rw-r--r--test/test_validate.ml16
18 files changed, 1306 insertions, 48 deletions
diff --git a/bin/main.ml b/bin/main.ml
index cf8cae2..b5d8d98 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -413,7 +413,18 @@ let readings_line ~lang ~sigla (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.r
Task (celebrant-rubrics-phase1, Phase 2): a FIFTH column, whether the
Gloria in excelsis is said (EF: RG 431-432, {!Rite_ef.Rubrics_ef.gloria})
-- same [string_of_bool] convention as [creed], same plain [bool] with
- no [option] to guard, same reasoning throughout. *)
+ no [option] to guard, same reasoning throughout.
+
+ Task (celebrant-rubrics-phase1, Phase 3): a SIXTH column, which preface
+ is said (EF: RG 482-499, {!Rite_ef.Rubrics_ef.preface}) --
+ {!Colitur_kernel.Preface.to_string} (e.g. "common", "holy-cross"), or
+ "-" when [d.preface] is [None]. UNLIKE [creed]/[gloria], [preface] is a
+ genuine [option]: "-" here can mean either of two things ("this rite
+ has not implemented the rule" or "this specific day has no Mass to
+ preface", {!Colitur_kernel.Rite.t.preface}'s own citation) and this
+ column does not distinguish them, the same "-" convention [formulary]'s
+ own [None] case already uses two columns to the left, for the identical
+ reason. *)
let rubrics_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t)
=
let said, via =
@@ -426,9 +437,15 @@ let rubrics_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_k
Colitur_kernel.Mass_formulary.source_to_string f.Colitur_kernel.Mass_formulary.via )
| None -> ("-", "-")
in
- Printf.printf "%s\t%s\t%s\t%s\t%s\n" (D.to_iso8601 d.Colitur_kernel.Liturgical_day.date) said via
+ let preface =
+ match d.Colitur_kernel.Liturgical_day.preface with
+ | Some p -> Colitur_kernel.Preface.to_string p
+ | None -> "-"
+ in
+ Printf.printf "%s\t%s\t%s\t%s\t%s\t%s\n" (D.to_iso8601 d.Colitur_kernel.Liturgical_day.date) said via
(string_of_bool d.Colitur_kernel.Liturgical_day.creed)
(string_of_bool d.Colitur_kernel.Liturgical_day.gloria)
+ preface
(* One civil year, Jan 1 - Dec 31, matching [temporal_report]'s own scan --
NOT one liturgical year: [Colitur_kernel.Calendar.year] resolves a single
@@ -1287,8 +1304,8 @@ output formats:
2026-04-05 sunday paschaltide 1 ef-easter-sunday class-1 white
readings date slug | Epistle | Gospel [| name]
2026-12-25 ef-nativity | Heb 1:1-12 | John 1:1-14
- rubrics date, formulary slug, source, creed, gloria -- TAB-separated
- 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true
+ rubrics date, formulary slug, source, creed, gloria, preface -- TAB-separated
+ 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true[TAB]nativity
A citation contains spaces, so readings uses " | " between its fields while
day stays space-separated; that is why they are separate commands rather
@@ -1305,16 +1322,20 @@ output formats:
(source: proper/own/preceding-sunday/common/votive) -- not always the
day's own: a weekday with no proper resumes the preceding Sunday's, a
saint with no proper says his assigned Common -- followed by whether the
- Creed is said (RG 475-476) and whether the Gloria in excelsis is said
+ Creed is said (RG 475-476), whether the Gloria in excelsis is said
(RG 431-432, deferring to the Breviary's own Te Deum rule, nn. 237-238,
- for RG 431(a)) -- both "true"/"false", OCaml's own literal, not
- "yes"/"no" or "1"/"0". TAB-separated rather than space or " | ": a
- resolved formulary NAME is a column a later version may add, and it can
- carry both spaces and punctuation a citation never does, which rules out
+ for RG 431(a)), and which preface is said (RG 482-499) -- Creed/Gloria
+ both "true"/"false", OCaml's own literal, not "yes"/"no" or "1"/"0";
+ preface one of nativity/epiphany/lent/holy-cross/easter/ascension/
+ sacred-heart/christ-the-king/holy-spirit/trinity/bvm/st-joseph/apostles/
+ common/requiem, or "-" when this engine resolves no Mass at all that day
+ (Good Friday). TAB-separated rather than space or " | ": a resolved
+ formulary NAME is a column a later version may add, and it can carry
+ both spaces and punctuation a citation never does, which rules out
either alternative already in use above. --overlay is accepted (the
- observed celebration it changes decides the formulary, the Creed and the
- Gloria); --lang/--raw/--sigla-* are refused -- this row resolves no
- display name and no citation for any of them to affect.
+ observed celebration it changes decides the formulary, the Creed, the
+ Gloria and the preface); --lang/--raw/--sigla-* are refused -- this row
+ resolves no display name and no citation for any of them to affect.
emit one schema (season, week, slug, rank, colour, subject, names,
citations, commemorations), rendered five ways: csv (RFC 4180,
diff --git a/lib/kernel/calendar.ml b/lib/kernel/calendar.ml
index 7178780..a1309f9 100644
--- a/lib/kernel/calendar.ml
+++ b/lib/kernel/calendar.ml
@@ -554,6 +554,9 @@ let build_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.index)
let gloria =
rite.Rite.gloria ~temporal ~observed:resolution.Precedence.observed.Precedence.cel ~date
in
+ let preface =
+ rite.Rite.preface ~temporal ~observed:resolution.Precedence.observed.Precedence.cel ~date
+ in
{
Liturgical_day.date;
rite = rite.Rite.id;
@@ -568,6 +571,7 @@ let build_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.index)
formulary;
creed;
gloria;
+ preface;
}
let year (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) (y : int) :
diff --git a/lib/kernel/liturgical_day.ml b/lib/kernel/liturgical_day.ml
index 98c5084..c3ecb37 100644
--- a/lib/kernel/liturgical_day.ml
+++ b/lib/kernel/liturgical_day.ml
@@ -28,5 +28,6 @@ type ('s, 'r) t = {
a rite with no lectionary -- see {!Mass_formulary} *)
creed : bool; (** whether the Creed is said at this day's Mass; see {!Rite.t.creed} *)
gloria : bool; (** whether the Gloria in excelsis is said; see {!Rite.t.gloria} *)
+ preface : Preface.t option; (** which preface is said; see {!Rite.t.preface} *)
}
[@@deriving sexp]
diff --git a/lib/kernel/liturgical_day.mli b/lib/kernel/liturgical_day.mli
index be17d73..643097b 100644
--- a/lib/kernel/liturgical_day.mli
+++ b/lib/kernel/liturgical_day.mli
@@ -51,5 +51,12 @@ type ('s, 'r) t = {
(** Whether the Gloria in excelsis is said at this day's Mass --
{!Rite.t.gloria}, EF: RG 431-432. Same seam as [creed] in every
respect. *)
+ preface : Preface.t option;
+ (** Which preface is said at this day's Mass -- {!Rite.t.preface},
+ EF: RG 482-499. Same seam as [creed]/[gloria], but [option], not
+ a bare {!Preface.t}: see {!Rite.t.preface}'s own citation for why
+ -- [None] both for a rite that has not implemented the rule and
+ for a real day this engine resolves that has no Mass at all
+ (Good Friday). *)
}
[@@deriving sexp]
diff --git a/lib/kernel/preface.ml b/lib/kernel/preface.ml
new file mode 100644
index 0000000..08d00b4
--- /dev/null
+++ b/lib/kernel/preface.ml
@@ -0,0 +1,59 @@
+(* RG 482-499 -- see the .mli for the rubric quoted in full and the
+ placement rationale. Argument-less variants only, so no
+ [Sexplib0.Sexp_conv] open is needed -- see CLAUDE.md's own gotcha note. *)
+type t =
+ | Nativity
+ | Epiphany
+ | Lent
+ | Holy_cross
+ | Easter
+ | Ascension
+ | Sacred_heart
+ | Christ_the_king
+ | Holy_spirit
+ | Trinity
+ | Bvm
+ | St_joseph
+ | Apostles
+ | Common
+ | Requiem
+[@@deriving sexp]
+
+let all =
+ [ Nativity; Epiphany; Lent; Holy_cross; Easter; Ascension; Sacred_heart; Christ_the_king;
+ Holy_spirit; Trinity; Bvm; St_joseph; Apostles; Common; Requiem ]
+
+let to_string = function
+ | Nativity -> "nativity"
+ | Epiphany -> "epiphany"
+ | Lent -> "lent"
+ | Holy_cross -> "holy-cross"
+ | Easter -> "easter"
+ | Ascension -> "ascension"
+ | Sacred_heart -> "sacred-heart"
+ | Christ_the_king -> "christ-the-king"
+ | Holy_spirit -> "holy-spirit"
+ | Trinity -> "trinity"
+ | Bvm -> "bvm"
+ | St_joseph -> "st-joseph"
+ | Apostles -> "apostles"
+ | Common -> "common"
+ | Requiem -> "requiem"
+
+let of_string = function
+ | "nativity" -> Some Nativity
+ | "epiphany" -> Some Epiphany
+ | "lent" -> Some Lent
+ | "holy-cross" -> Some Holy_cross
+ | "easter" -> Some Easter
+ | "ascension" -> Some Ascension
+ | "sacred-heart" -> Some Sacred_heart
+ | "christ-the-king" -> Some Christ_the_king
+ | "holy-spirit" -> Some Holy_spirit
+ | "trinity" -> Some Trinity
+ | "bvm" -> Some Bvm
+ | "st-joseph" -> Some St_joseph
+ | "apostles" -> Some Apostles
+ | "common" -> Some Common
+ | "requiem" -> Some Requiem
+ | _ -> None
diff --git a/lib/kernel/preface.mli b/lib/kernel/preface.mli
new file mode 100644
index 0000000..b43890e
--- /dev/null
+++ b/lib/kernel/preface.mli
@@ -0,0 +1,50 @@
+(** The Mass preface (Missale Romanum, Rubricae Generales, Caput XVII("De
+ Ritibus servandis in celebratione Missae"), "H) De praefatione", RG
+ 482-499; docs/research/LT.txt, grep "praefatione dicitur quae cuique").
+
+ RG 482 gives the resolution chain in full: {i "Praefatio dicitur quae
+ cuique Missae propria est; qua deficiente, dicitur praefatio de
+ Tempore, secus communis"} -- the Mass's own PROPER preface; failing
+ that, the SEASONAL ("de Tempore") one; failing that, the COMMON. RG
+ 483: {i "Nulla commemoratio, in Missa occurrens, praefationem propriam
+ inducit"} -- a commemoration never induces its own proper preface,
+ which is why {!Rite_ef.Rubrics_ef.preface} (the one rite that
+ implements this so far) reads only the day's OBSERVED celebration,
+ never its commemorations, the identical discipline {!Rite.t.creed}'s
+ own RG 476(e) already established.
+
+ RG 484-497 enumerate the fourteen named propers -- Nativity, Epiphany,
+ Lent, Holy Cross, [the Chrism Mass, out of scope: see the .ml's own
+ N/A note], Easter, Ascension, the Sacred Heart, Christ the King, the
+ Holy Spirit, the Trinity, the Blessed Virgin Mary, St Joseph, the
+ Apostles. RG 498 is the residual Common; RG 499 the Requiem.
+
+ A KERNEL type, not a rite-specific one (the same placement as
+ {!Colour}, {!Subject}, {!Mass_formulary}): {!Liturgical_day.t} is
+ parameterised only over a rite's season/rank, so any field it carries
+ generically must live here, even though only the EF rite constructs a
+ value of it today -- CLAUDE.md's own "generalise when forced, not
+ speculatively" stance, already the precedent {!Mass_formulary}
+ follows. *)
+
+type t =
+ | Nativity (** RG 484 *)
+ | Epiphany (** RG 485 *)
+ | Lent (** RG 486, "de Quadragesima" *)
+ | Holy_cross (** RG 487, "de sancta Cruce" *)
+ | Easter (** RG 489, "paschalis" *)
+ | Ascension (** RG 490 *)
+ | Sacred_heart (** RG 491, "de Ss.mo Corde Iesu" *)
+ | Christ_the_king (** RG 492, "de D. N. Iesu Christo Rege" *)
+ | Holy_spirit (** RG 493, "de Spiritu Sancto" *)
+ | Trinity (** RG 494, "de Ss.ma Trinitate" *)
+ | Bvm (** RG 495, "de beata Maria Virgine" *)
+ | St_joseph (** RG 496, "de S. Ioseph" *)
+ | Apostles (** RG 497, "de Apostolis" *)
+ | Common (** RG 498, "communis" -- the residual when nothing else applies *)
+ | Requiem (** RG 499, "defunctorum" *)
+[@@deriving sexp]
+
+val all : t list
+val to_string : t -> string
+val of_string : string -> t option
diff --git a/lib/kernel/rite.ml b/lib/kernel/rite.ml
index 915941d..e44c2c6 100644
--- a/lib/kernel/rite.ml
+++ b/lib/kernel/rite.ml
@@ -21,4 +21,5 @@ type ('s, 'r) t = {
Mass_formulary.t option * Citation.t list;
creed : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> bool;
gloria : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> bool;
+ preface : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> Preface.t option;
}
diff --git a/lib/kernel/rite.mli b/lib/kernel/rite.mli
index b9d1961..d53e4e6 100644
--- a/lib/kernel/rite.mli
+++ b/lib/kernel/rite.mli
@@ -140,4 +140,21 @@ type ('s, 'r) t = {
parameters and the same reasons for each, a [bool] not an
[option], and [false] is the answer a rite that has not
implemented the rule returns explicitly. *)
+ preface : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> Preface.t option;
+ (** Which preface is said at this day's Mass (EF: RG 482-499). Same
+ seam and same three parameters as {!creed}/{!gloria}, for the
+ same reasons -- {!Preface}'s own citation has the rubric in full.
+ [Preface.t option], not a bare [Preface.t]: unlike [creed]/
+ [gloria], where "not said" is itself a legitimate answer a
+ [bool] can carry, a preface is said only at a MASS, and this
+ engine constructs at least one day (Good Friday, the
+ 1955-restored Holy Week) that resolves an [observed] celebration
+ but has no Mass at all -- [None] is the honest answer there, not
+ a manufactured preface. [None] is also the value a rite that has
+ not implemented this rule returns, the same "the type's own
+ neutral value" contract {!creed}/{!gloria} give for [false] --
+ the two meanings ("this rite does not model the question" and
+ "this specific day has no Mass to preface") collapse onto the
+ same representation deliberately: a caller with no rite-specific
+ context to distinguish them should not need one. *)
}
diff --git a/lib/rites/rite_ef/rite_ef.ml b/lib/rites/rite_ef/rite_ef.ml
index d7b3d83..808e017 100644
--- a/lib/rites/rite_ef/rite_ef.ml
+++ b/lib/rites/rite_ef/rite_ef.ml
@@ -52,4 +52,5 @@ let context ~lectionary ~commons : (Vocab_ef.season, Vocab_ef.rank) Rite.t =
transfer_target = Precedence_ef.transfer_target;
readings = Lectionary_ef.readings ~lectionary ~commons;
creed = Rubrics_ef.creed;
- gloria = Rubrics_ef.gloria }
+ gloria = Rubrics_ef.gloria;
+ preface = Rubrics_ef.preface }
diff --git a/lib/rites/rite_ef/rite_ef.mli b/lib/rites/rite_ef/rite_ef.mli
index 7025143..2ed61de 100644
--- a/lib/rites/rite_ef/rite_ef.mli
+++ b/lib/rites/rite_ef/rite_ef.mli
@@ -39,6 +39,10 @@ module Rubrics_ef = Rubrics_ef
- [gloria]: {!Rubrics_ef.gloria}, RG 431-432 -- whether the Gloria in
excelsis is said. Reads {!Rubrics_ef.te_deum} (Breviary nn. 237-238)
for RG 431(a)/432(a)'s own deferral.
+ - [preface]: {!Rubrics_ef.preface}, RG 482-499 -- which preface is
+ said. Same seam as [creed]/[gloria]; [None] both for a day with no
+ Mass at all (Good Friday) and for a rite that has not implemented
+ the rule.
Deliberately carries no [sanctoral]/[lectionary] fields the way the
original design-doc sketch of [RITE] does: {!Colitur_kernel.Rite.t} (the
diff --git a/lib/rites/rite_ef/rubrics_ef.ml b/lib/rites/rite_ef/rubrics_ef.ml
index a497b57..eea92a9 100644
--- a/lib/rites/rite_ef/rubrics_ef.ml
+++ b/lib/rites/rite_ef/rubrics_ef.ml
@@ -697,3 +697,499 @@ let gloria ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
Gloria mirrors [te_deum] exactly for every Mass not already decided
above. This is the ONE call site [te_deum] exists to serve. *)
te_deum ~temporal ~observed ~date
+
+(* Missale Romanum, Rubricae Generales, Caput XVII, "H) De praefatione" (RG
+ 482-499; docs/research/LT.txt, grep "praefatione dicitur quae cuique"),
+ quoted here in full so every branch below can cite its own paragraph
+ without re-quoting the whole rubric:
+
+ "482. Praefatio dicitur quae cuique Missae propria est; qua deficiente,
+ dicitur praefatio de Tempore, secus communis.
+
+ 483. Nulla commemoratio, in Missa occurrens, praefationem propriam
+ inducit.
+
+ 484. Praefatio de Nativitate Domini dicitur:
+ a) tamquam propria in Missis de Nativitate Domini et de eiusdem
+ octava, necnon in festo Purificationis B. Mariae Virg.;
+ b) tamquam de Tempore, infra octavam Nativitatis Domini, etiam in
+ Missis quae secus praefationem propriam haberent, exceptis iis Missis
+ quae praefationem propriam de divinis mysteriis vel Personis habent; et
+ a die 2 ad 5 ianuarii.
+
+ 485. Praefatio de Epiphania Domini dicitur:
+ a) tamquam propria in Missis de festo Epiphaniae et de
+ Commemoratione Baptismatis D. N. Iesu Christi;
+ b) tamquam de Tempore diebus a 7 ad 13 ianuarii.
+
+ 486. Praefatio de Quadragesima dicitur:
+ a) tamquam propria in Missis de Tempore a feria IV cinerum usque ad
+ sabbatum ante dominicam I Passionis;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 487. Praefatio de sancta Cruce dicitur:
+ a) tamquam propria in Missis de tempore a dominica I Passionis usque
+ ad feriam V in Cena Domini; in Missis tam festivis quam votivis de
+ sancta Cruce, de Passione Domini et instrumentis Passionis Domini, de
+ pretiosissimo Sanguine D. N. Iesu Christi, de Ss.mo Redemptore;
+ b) tamquam de Tempore in omnibus Missis a dominica I Passionis usque
+ ad feriam IV Hebdomadae sanctae, quae praefatione propria carent.
+
+ 488. Praefatio de Missa chrismatis dicitur feria V in Cena Domini, in
+ sua Missa.
+
+ 489. Praefatio paschalis dicitur:
+ a) tamquam propria in Missis de Tempore a Missa Vigiliae paschalis
+ usque ad vigiliam Ascensionis Domini;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 490. Praefatio de Ascensione Domini dicitur:
+ a) tamquam propria in festo Ascensionis Domini;
+ b) tamquam de Tempore in omnibus Missis a feria VI post Ascensionem
+ usque ad feriam VI ante vigiliam Pentecostes, quae praefatione propria
+ carent.
+
+ 491. Praefatio de Ss.mo Corde Iesu dicitur in Missis festivis et
+ votivis de Ss.mo Corde Iesu.
+
+ 492. Praefatio de D. N. Iesu Christo Rege dicitur in Missis festivis
+ et votivis de D. N. Iesu Christo Rege.
+
+ 493. Praefatio de Spiritu Sancto dicitur:
+ a) tamquam propria in Missis de Tempore a vigilia Pentecostes usque
+ ad subsequens sabbatum; et in Missis festivis et votivis de Spiritu
+ Sancto;
+ b) tamquam de Tempore in ceteris Missis quae celebrantur eodem
+ tempore, et praefatione propria carent.
+
+ 494. Praefatio de Ss.ma Trinitate dicitur:
+ a) tamquam propria in Missis de festo et votivis Ss.mae Trinitatis;
+ b) tamquam de Tempore in dominicis Adventus, et in omnibus dominicis
+ II classis, extra tempus natalicium et paschale.
+
+ 495. Praefatio de beata Maria Virgine dicitur in Missis festivis et
+ votivis beatae Mariae Virginis, praeterquam in festo Purificationis B.
+ Mariae Virg.
+
+ 496. Praefatio de S. Ioseph dicitur in Missis festivis et votivis S.
+ Ioseph.
+
+ 497. Praefatio de Apostolis dicitur in Missis festivis et votivis
+ Apostolorum et Evangelistarum.
+
+ 498. Praefatio communis dicitur in Missis quae praefatione propria
+ carent, nec sumere debent praefationem de Tempore.
+
+ 499. Praefatio defunctorum dicitur in Missis defunctorum."
+
+ THE SHAPE OF THE RULE, once, rather than at every branch: RG 482's own
+ chain is "propria, else de Tempore, else communis". Read literally, 484-
+ 497 look like FOURTEEN SEPARATE RULES, but on inspection each numbered
+ rubric's own (a)/(b) pair (where it has both) produces the SAME preface
+ identity either way -- (a) is the propria reading ("this Mass's OWN
+ preface"), (b) is the de-Tempore reading ("this OTHER Mass, lacking one
+ of its own, borrows it") -- so for the single question this function
+ answers (WHICH preface, not WHETHER it counts as propria or de Tempore
+ for some other purpose) the two halves collapse into one PRIORITY-
+ ORDERED decision: a fixed list of "genuinely proper" triggers (title/
+ mystery feasts, independent of season), checked first in a citable
+ order, falling through to a fixed list of SEASONAL windows, falling
+ through to [Common]. RG 483 (a commemoration never induces a proper)
+ holds by construction, the same way [creed]'s own 476(e) does: every
+ branch below reads only [observed], never a day's admitted
+ commemorations.
+
+ THE PRIORITY ORDER ITSELF was cross-checked against 358 real,
+ individually classifiable entries in the FIUV Ordo's own [praef] column
+ (test/fixtures/fiuv-ordo-2025-2026.sexp, test_fiuv_ordo.ml) spanning the
+ WHOLE liturgical year -- not merely derived from the Latin text in
+ isolation. Two findings the plain text alone would not have settled,
+ both empirically confirmed rather than assumed:
+
+ - 484(b)'s own "exceptis iis Missis quae praefationem propriam de
+ divinis mysteriis vel Personis habent" is NARROWER than every other
+ window's implicit "unless it already has a genuine proper" -- St
+ John the Evangelist (27 December, on {!creed_apostle_slugs}, so his
+ OWN Apostles preface (497) would otherwise apply) is overridden to
+ [Nativity] inside the octave (confirmed: the Ordo's own 27 December
+ entry reads "de Nativ.", not "App."), while St Barnabas/Sts Philip &
+ James/the other Apostles OUTSIDE the octave keep their own Apostles
+ preface even inside another window (Sts Philip & James, 11 May,
+ inside the Easter window: confirmed "App." in the Ordo, not
+ "Pasch."). So [Apostles] is checked AFTER the Nativity window below,
+ but every OTHER title trigger (Holy Cross/Sacred Heart/Christ the
+ King/Trinity/St Joseph/BVM) is checked BEFORE it -- RG 484(b)'s own
+ narrower carve-out, read literally: unreachable on the shipped
+ calendar for the other five (no such feast falls 25 December-5
+ January), so this ordering is defensive for them, not observed live,
+ the same "checked, not merely assumed" discipline
+ {!Precedence_ef.marian_slugs}'s own citation follows elsewhere.
+ - RG 495's own "et votivis" half is live on this engine's data after
+ all, for the ONE office this project already models as a votive-
+ shaped Mass without a votive-Mass DIMENSION (RG 78/91 entry 27, the
+ Saturday Office of the BVM, {!Temporal_ef}'s own [subject = Bvm]
+ tag): confirmed directly (3 January and 10 January 2026, both the
+ Saturday Office, both read "BMV" in the Ordo) -- including on 3
+ January, itself inside the Nativity's own "2 ad 5 ianuarii" de-
+ Tempore window, where BVM still wins, corroborating the same
+ "genuine propria outranks every window" ordering the Apostles
+ finding above established from the opposite direction.
+
+ Good Friday's own printed [praef] text ("comm. Feria VI prima in
+ mense.") was NOT used to check this function's own [None] answer for
+ that day: {!test_fiuv_ordo.ml}'s own F1 (Gloria) already adjudicated
+ this exact date's raw text as unreliable (a copied, not a considered,
+ line -- see that allow-list entry's own citation for the full argument,
+ confirmed against the PDF's own page image, not merely the extracted
+ fixture) -- the same defect, read again, would apply equally to
+ whatever trails "praef." on the identical corrupted line, so this
+ function's [None] rests on RG 28's own "no Mass" structural argument
+ alone (the same argument [creed]/[gloria]/[te_deum] already give for
+ this date), not on any Ordo corroboration. *)
+
+(* RG 487(a): the two GENUINE fixed-date feast triggers in the shipped
+ universal calendar -- the Exaltation of the Holy Cross (14 September)
+ and the Most Precious Blood (1 July), both [subject = Lord]. RG 487(a)'s
+ own further-named categories ("de Passione Domini et instrumentis
+ Passionis Domini, de Ss.mo Redemptore") have NO corresponding entry
+ anywhere in data/ef/sanctoral.sexp (checked directly, grepping for
+ "instrument"/"redeem": zero hits) -- genuinely absent from the shipped
+ 1962 universal calendar, not merely unmatched by this list, so they are
+ N/A rather than silently unreachable. *)
+let preface_holy_cross_slugs =
+ [ "exaltation-of-the-holy-cross"; "precious-blood-of-our-lord-jesus-christ" ]
+
+(* RG 496: the two St Joseph feasts in the shipped calendar (19 March, 1
+ May) -- both [subject = Saint], so (unlike RG 495's own BVM feasts) no
+ [subject]-based fallback exists or is needed; this closed list is the
+ whole of what RG 496 can ever reach on shipped data. *)
+let preface_st_joseph_slugs = [ "joseph-spouse-of-the-bl-virgin-mary"; "joseph-the-workman" ]
+
+(* RG 497's own [Apostolorum et Evangelistarum] population is WIDER than
+ {!creed_apostle_slugs}: RG 475(e) is restricted to a NATALICIUM ("festis
+ NATALICIIS Apostolorum...", that module's own citation), but RG 497 has
+ no such restriction at all ("in Missis festivis et votivis Apostolorum
+ et Evangelistarum" -- ANY festive/votive Mass of an Apostle or
+ Evangelist). FOUND, not assumed: the FIUV Ordo's own 30 June entry
+ ("In Commemoratione S. Pauli Ap.", data/ef/adjustments.sexp's own RG
+ 110(c) [Add], {!Precedence_ef}'s own citation -- a genuine [Feast]-
+ status office of Paul the Apostle, but NOT his own dies natalis, so
+ {!creed_apostle_slugs} deliberately excludes it) reads "App. I", not
+ "comm." -- checked directly while building this comparison, not
+ guessed. [creed_apostle_slugs] itself is UNCHANGED (RG 475(e)'s own
+ narrower "natalicium" reading still holds for the Creed); this is a
+ SEPARATE, wider list for RG 497 alone.
+
+ "conversion-of-st-paul" (25 January, Class3, the SAME "not a
+ natalicium" shape {!creed_apostle_slugs}'s own citation excludes it
+ for) is a plausible SECOND candidate by the identical RG 497 reasoning
+ -- deliberately NOT added: it is UNWITNESSED (25 January falls on a
+ Sunday, hence impeded, in the one fixture year this engine's Ordo
+ evidence covers, {!test_fiuv_ordo.ml}'s own window), and "a wrong
+ citation is worse than a missing one" ({!Precedence_ef.marian_slugs}'s
+ own citation, the precedent this follows). Left for a future fixture
+ year to confirm or refute. *)
+let preface_apostle_slugs = "in-commemoratione-sancti-pauli-apostoli" :: creed_apostle_slugs
+
+(* RG 495's own [beatae Mariae Virginis] population is also WIDER than
+ {!Precedence_ef.marian_slugs}: that list was built for a DIFFERENT
+ rubric (RG 112(d), whether a commemoration invokes HER OWN
+ intercession specifically) with a correspondingly narrower, oration-
+ checked standard, and its own citation explicitly EXCLUDES "dedication-
+ of-the-basilica-of-st-mary-major" (5 August) for exactly that reason --
+ "whose own oration could not be found... to confirm it invokes her
+ intercession". RG 495 asks a different, WIDER question ("is this Mass
+ festive or votive OF the Blessed Virgin Mary at all"), which the
+ Dedication of St Mary Major answers on its own title alone, without
+ needing the oration-level standard RG 112(d) requires. FOUND, not
+ assumed: the FIUV Ordo's own 5 August entry reads "BMV Et te in
+ Festivitate.", not "comm." -- checked directly. *)
+let preface_bvm_slugs = "dedication-of-the-basilica-of-st-mary-major" :: Precedence_ef.marian_slugs
+
+let preface ~(temporal : (Vocab_ef.season, Vocab_ef.rank) Temporal.t)
+ ~(observed : Vocab_ef.rank Celebration.t) ~(date : Date.t) : Preface.t option =
+ let easter = Computus.gregorian_easter (Date.year date) in
+ let n = Date.to_rata date - Date.to_rata easter in
+ let m = Date.month date and dd = Date.day date in
+ let slug = Slug.to_string observed.Celebration.slug in
+ if
+ (* RG 28-34/RG 23(b), the same structural "no Mass at all" position
+ [creed]'s own vigil comment and [gloria]'s own 432(d) comment both
+ take for Good Friday specifically: the 1955-restored Holy Week has
+ no Mass whatsoever that day (only the afternoon liturgical action),
+ so there is no Mass to preface. Checked ahead of the
+ {!Colour.Black} Requiem proxy immediately below -- unlike All
+ Souls, Good Friday sharing that colour is coincidental, not
+ diagnostic (temporal_ef.ml's own RG 132 citation), and the two need
+ DIFFERENT answers here (unlike [creed]/[gloria]/[te_deum], where
+ both collapse to the same boolean) -- so this function cannot reuse
+ their shared single guard and must split Good Friday out first. *)
+ n = -2
+ then None
+ else if
+ (* RG 499: "in Missis defunctorum" -- the same {!Colour.Black} proxy
+ [creed]'s own 476(f), [te_deum]'s own 238(d) and [gloria]'s own
+ 432(d) already use (this file's own header has the full argument
+ and the two-member population this proxy rests on). With Good
+ Friday split out above, the one remaining member is All Souls. *)
+ observed.Celebration.colour = Colour.Black
+ then Some Preface.Requiem
+ else if
+ (* RG 487(a)'s own fixed-feast half -- checked first among the title
+ triggers per this file's own header (arbitrary among these six,
+ since none can ever co-occur with another on shipped data; Holy
+ Cross is placed first only because it is also the anchor for the
+ Passiontide WINDOW checked later below, keeping both citations
+ adjacent in this file). *)
+ List.mem slug preface_holy_cross_slugs
+ then Some Preface.Holy_cross
+ else if
+ (* RG 491: "in Missis festivis... de Ss.mo Corde Iesu" -- the Friday
+ after the Octave of Corpus Christi (Easter+68), {!Temporal_ef}'s own
+ named slug. Confirmed against the Ordo (12 June 2026: "de Ss.mi
+ Corde Iesu"). *)
+ slug = "ef-sacred-heart"
+ then Some Preface.Sacred_heart
+ else if
+ (* RG 492: "in Missis festivis... de D. N. Iesu Christo Rege" -- the
+ last Sunday of October, {!Temporal_ef.christ_the_king}'s own named
+ slug. Confirmed against the Ordo (25 October 2026: "de Domino
+ Nostro Jesu Rege"). *)
+ slug = "ef-christ-the-king"
+ then Some Preface.Christ_the_king
+ else if
+ (* RG 490(a): "in festo Ascensionis Domini" -- the feast itself
+ (Easter+39), checked here by slug rather than folded into the
+ Ascension WINDOW below (which starts the day AFTER, Easter+40):
+ Ascension Day itself needs no window at all, its own slug already
+ identifies it uniquely. Confirmed against the Ordo (14 May 2026:
+ "Ascensionis, Communic pr."). *)
+ slug = "ef-ascension"
+ then Some Preface.Ascension
+ else if
+ (* RG 494(a): "in Missis de festo... Ss.mae Trinitatis" -- Trinity
+ Sunday itself (Easter+56), {!Temporal_ef}'s own named slug. Checked
+ ahead of 494(b)'s own WIDER de-Tempore grant (checked last below,
+ after every other window) for the same "specific propria before any
+ season fallback" reason every other title trigger is. Confirmed
+ against the Ordo (31 May 2026: "Trinit. II"). *)
+ slug = "ef-trinity"
+ then Some Preface.Trinity
+ else if
+ (* RG 496: see {!preface_st_joseph_slugs}'s own citation. *)
+ List.mem slug preface_st_joseph_slugs
+ then Some Preface.St_joseph
+ else if
+ (* RG 495: "in Missis festivis et votivis beatae Mariae Virginis" --
+ {!preface_bvm_slugs} (its own citation has the full account of why
+ it is wider than {!Precedence_ef.marian_slugs}) covers every
+ genuine Marian FEAST; [subject = Bvm] covers the one VOTIVE-shaped
+ office this engine models without a votive-Mass dimension of its
+ own (RG 78/91 entry 27, the Saturday Office of the BVM -- this
+ file's own header has the empirical confirmation, 3/10 January
+ 2026). The Purification is deliberately ABSENT from both:
+ {!Precedence_ef.marian_slugs} already excludes it by name (its own
+ citation), and it never carries [subject = Bvm] (tagged [Lord]
+ instead, register ยง6.0) -- RG 495's own "praeterquam in festo
+ Purificationis" exclusion therefore holds by construction, not by
+ a guard written here.
+
+ [not (is_vigil slug)]: RG 495's own "festivis" reads "festum", not
+ "vigilia" -- the SAME RG 21/35 taxonomy distinction {!creed}'s own
+ RG 28-34 comment already makes ("a vigil is its OWN liturgical-day
+ category, distinct from 'festum'"), applied here for the first
+ time in THIS function because it is the first branch a vigil can
+ actually reach: {!Precedence_ef.marian_slugs} includes
+ "vigil-of-the-assumption" (that list's own citation), which without
+ this guard would wrongly claim [Bvm] for 14 August. FOUND, not
+ assumed: the FIUV Ordo's own 14 August entry reads "comm. I", not
+ "BMV" -- checked directly, the same as every other finding in this
+ branch's own history. Corroborates, from the opposite direction,
+ {!creed}'s own RG 28-34 comment: colitur's own Nativity WINDOW
+ below already excludes 24 December (the Nativity Vigil) by
+ construction (it starts at 25 December, never 24th), so this guard
+ makes the SAME "a vigil is not a festum" answer explicit here too,
+ rather than relying on a second, unrelated accident of a date
+ range to produce it. *)
+ (not (Precedence_ef.is_vigil slug))
+ && (List.mem slug preface_bvm_slugs || observed.Celebration.subject = Subject.Bvm)
+ then Some Preface.Bvm
+ else if
+ (* RG 484(a)'s own explicit Purification clause ("necnon in festo
+ Purificationis B. Mariae Virg.") -- 2 February, nowhere near the
+ Nativity's own Christmas-to-Epiphany calendar position, so this is
+ a standalone slug check, not part of the WINDOW test below (unlike
+ every other 484 trigger, which IS date-based). Checked here, after
+ the BVM check immediately above (which the Purification's own
+ [subject = Lord] tag never reaches) and before the Nativity window
+ (which its own actual date, 2 February, never reaches either) --
+ positioned with the rest of 484's own citations for readability,
+ not because anything below could otherwise pre-empt it. *)
+ slug = "purification-of-the-blessed-virgin-mary"
+ then Some Preface.Nativity
+ else if
+ (* RG 484(a)/(b) merged, per this file's own header: 25 December-1
+ January (the Nativity itself and its octave, propria) UNION 2-5
+ January (498(b)'s own explicit extra de-Tempore days) -- one
+ contiguous window, since both halves produce the identical
+ preface. Checked BEFORE Apostles (below) but AFTER every genuine
+ "divine mysteries/Persons" propria above, per this file's own
+ header (St John the Evangelist, 27 December, is the live witness:
+ Apostles would otherwise apply and does not). *)
+ (m = 12 && dd >= 25) || (m = 1 && dd <= 5)
+ then Some Preface.Nativity
+ else if
+ (* RG 497: "in Missis festivis et votivis Apostolorum et
+ Evangelistarum" -- {!preface_apostle_slugs} (its own citation has
+ the full account of why it is wider than {!creed_apostle_slugs}),
+ confirmed by this file's own header to produce the SAME preface
+ answer as the Ordo on every Apostle date outside the Nativity
+ octave: 11 June (Barnabas), 29-30 June (Peter & Paul, In
+ Commemoratione Pauli), 11 May (Philip & James, RG 484(b)'s own
+ witness against the Nativity window immediately above). Checked
+ AFTER the Nativity window specifically (RG 484(b)'s own narrower
+ carve-out), but before every OTHER season window below -- an
+ Apostle feast keeps his own preface inside Lent, Passiontide,
+ Paschaltide etc., where nothing narrows the exception the way
+ 484(b) does. No [is_vigil] guard is needed here the way RG 495's
+ own branch above needs one: checked directly, no entry on
+ {!preface_apostle_slugs} is ever a vigil slug (every Apostle vigil
+ in the shipped data -- "vigil-of-sts-peter-paul" -- carries its own
+ distinct slug, absent from this list). *)
+ List.mem slug preface_apostle_slugs
+ then Some Preface.Apostles
+ else if
+ (* RG 485(a): "in Missis de festo Epiphaniae et de Commemoratione
+ Baptismatis D. N. Iesu Christi" -- the feast itself and its own
+ named commemoration (13 January, {!Precedence_ef}'s own
+ "commemoration-of-the-baptism-of-the-lord" -- {!creed}'s own 475(c)
+ comment already documents this entry's [subject = Lord] tag), both
+ checked by slug so 485(b)'s own WIDER window below need not repeat
+ them. *)
+ slug = "ef-epiphany" || slug = "commemoration-of-the-baptism-of-the-lord"
+ then Some Preface.Epiphany
+ else if
+ (* RG 485(b): "diebus a 7 ad 13 ianuarii" -- every OTHER Mass in this
+ window (Holy Family Sunday, an ordinary Time-after-Epiphany feria
+ or Sunday, a saint's feast with no propria of its own), confirmed
+ against the Ordo's own 11 January 2026 entry (Holy Family Sunday:
+ "de Epiphania. II", not a Holy-Family-specific preface -- this
+ engine has none to offer it anyway). Colitur's own Christmastide
+ season already spans 25 December-13 January (RG 72-73,
+ {!Vocab_ef.season}'s own citation), so this window is exactly its
+ OWN post-Epiphany tail; written as an explicit date range rather
+ than a season test only because the Nativity window above already
+ claims the season's FIRST half by date, not by season either, for
+ symmetry. *)
+ m = 1 && dd >= 6 && dd <= 13
+ then Some Preface.Epiphany
+ else if
+ (* RG 486(a)/(b) merged: Ash Wednesday (Easter-46) through the
+ Saturday before Passion Sunday I (Easter-15) inclusive -- every
+ Lenten feria/Sunday's own Mass (a), and every OTHER Mass in the
+ same span lacking a proper of its own (b). Confirmed against the
+ Ordo throughout (e.g. 18 February/19-20 February 2026: "Quadr.").
+ {!creed}'s own RG 23 comment already explains why Ash Wednesday
+ (feria I classis) reaches this branch on [observed]'s own terms
+ regardless of rank -- this function reads no rank at all here,
+ only the date. *)
+ n >= -46 && n <= -15
+ then Some Preface.Lent
+ else if
+ (* RG 487(a)/(b) merged: Passion Sunday I (Easter-14) through Holy
+ Thursday (Easter-3) inclusive -- (a)'s own "de tempore"/festive-
+ votive half extends through Holy Thursday itself (the Mass of the
+ Lord's Supper), (b)'s own narrower saint-Mass half stops one day
+ earlier (Holy Wednesday) but reaches no LIVE day this check does
+ not already cover identically (Holy Thursday is a feria I classis,
+ RG 23(b), so no saint's feast can ever occupy it -- {!creed}'s own
+ RG 23 citation). Confirmed against the Ordo throughout (22 March
+ 2026, Passion Sunday: "de Sancta Cruce."; 2 April 2026, Holy
+ Thursday: "de Sancta Cruce, Communicantes..."). *)
+ n >= -14 && n <= -3
+ then Some Preface.Holy_cross
+ else if
+ (* RG 489(a)/(b) merged: the Easter Vigil Mass (Easter-1, on Holy
+ Saturday's own date) through the vigil of the Ascension (Easter+38)
+ inclusive. [n = -1] is this engine's own OVERLOADED representation
+ of "the Vigil Mass", not Holy Saturday's daytime (which has no Mass
+ of its own at all, unlike Good Friday's [n = -2] this function
+ excludes by name above) -- [gloria]'s own RG 431(c) comment already
+ establishes the same convention for the identical date, and RG
+ 489(a) resolves the question on its own terms regardless: the
+ Paschal preface's window STARTS at the Vigil Mass, so [n = -1] is
+ correctly [Easter]. Confirmed against the Ordo throughout (5 April
+ 2026, Easter Sunday: "Pasch."; 13 May 2026, the Ascension Vigil:
+ "Pasch. I"); the fixture prints nothing at all for Holy Saturday's
+ own daytime square (4 April 2026), corroborating rather than
+ contradicting this reading -- see test_fiuv_ordo.ml's own citation. *)
+ n >= -1 && n <= 38
+ then Some Preface.Easter
+ else if
+ (* RG 490(b): "a feria VI post Ascensionem usque ad feriam VI ante
+ vigiliam Pentecostes" -- the Friday after Ascension (Easter+40)
+ through the Friday before the Pentecost vigil (Easter+47)
+ inclusive; Ascension Day itself (Easter+39) is already handled by
+ its own slug check above, not repeated here. Confirmed against the
+ Ordo throughout (15-22 May 2026: "Ascensionis"). *)
+ n >= 40 && n <= 47
+ then Some Preface.Ascension
+ else if
+ (* RG 493(a)/(b) merged: the vigil of Pentecost (Easter+48) through
+ "subsequens sabbatum" (the FOLLOWING Saturday, i.e. the Ember
+ Saturday within the Octave of Pentecost, Easter+55) inclusive.
+ Confirmed against the Ordo throughout (23-24 May 2026, the vigil
+ and Pentecost itself: "de Spirito Sancto"; 30 May 2026, the Ember
+ Saturday: "de Spirito Sancto"). *)
+ n >= 48 && n <= 55
+ then Some Preface.Holy_spirit
+ else if
+ (* RG 494(b): "in dominicis Adventus, et in omnibus dominicis II
+ classis, extra tempus natalicium et paschale". Read off
+ [temporal]'s own season, NOT [observed]'s rank -- CORRECTED from an
+ earlier version of this branch that DID read [observed.rank] and
+ required it to equal [Class2], which is wrong for the identical RG
+ 16(a) reason {!Precedence.rules.admit}'s own [~temporal] parameter
+ exists and [creed]'s own 237(b)/475(a) comments already give: a
+ feast that has WON the day can carry a different rank than the
+ Sunday it stands on. FOUND, not assumed: All Saints' Day (1
+ November), Class1, observed outright over an ordinary
+ Time-after-Pentecost Sunday it commemorates
+ ([+ef-time-after-pentecost-sunday-23]), reads "Trinit." in the
+ Ordo -- [observed.rank] there is [Class1], so the OLD guard wrongly
+ answered [Common]; there is no dedicated preface for All Saints
+ among RG 484-497's own fourteen, so RG 482's chain correctly falls
+ through to the SUNDAY's own de-Tempore grant regardless of which
+ rank actually won the day.
+
+ {!Temporal_ef.temporal}'s own [match s with Advent | Lent -> Class1
+ | _ -> Class2] means EVERY Sunday's own TEMPORAL identity is
+ [Class2] except in Advent and Lent -- so "in omnibus dominicis II
+ classis" and "in dominicis Adventus" collapse into ONE test, "any
+ Sunday outside Christmastide and Paschaltide" (Lent's own Sundays
+ need no explicit exclusion here: {!creed}'s own RG 23/Lent-window
+ reasoning already means every one of them is claimed by the LENT
+ window earlier in this very priority chain, provably unreachable
+ here, the same "checked, not merely assumed" position the previous
+ version of this comment already took for Christmastide/Paschaltide
+ -- confirmed by the SAME domain sweep in test_rubrics_ef.ml, which
+ still finds zero Christmastide/Paschaltide/Lent days reaching this
+ branch after this change). Confirmed against the Ordo throughout
+ (e.g. every Advent/Time-after-Epiphany/Septuagesima/Time-after-
+ Pentecost Sunday not otherwise claimed: "Trinit."), now including
+ All Saints' Day itself. *)
+ temporal.Temporal.weekday = Date.Sun
+ && temporal.Temporal.season <> Vocab_ef.Christmastide
+ && temporal.Temporal.season <> Vocab_ef.Paschaltide
+ then Some Preface.Trinity
+ else
+ (* RG 498: "in Missis quae praefatione propria carent, nec sumere
+ debent praefationem de Tempore" -- everything else: an ordinary
+ weekday feria outside every window above, a plain sanctoral saint
+ with no title of his own, an ordinary (non-Sunday, non-Class2, or
+ Christmastide/Paschaltide) day. Confirmed against the Ordo
+ throughout (the single most common value in the fixture, 188 of
+ 360 comparable rows). *)
+ Some Preface.Common
diff --git a/lib/rites/rite_ef/rubrics_ef.mli b/lib/rites/rite_ef/rubrics_ef.mli
index 618af65..befbf72 100644
--- a/lib/rites/rite_ef/rubrics_ef.mli
+++ b/lib/rites/rite_ef/rubrics_ef.mli
@@ -68,3 +68,38 @@ val gloria :
observed:Vocab_ef.rank Celebration.t ->
date:Date.t ->
bool
+
+(** RG 487(a)'s own two fixed-feast triggers for the Holy Cross preference
+ (the Exaltation of the Holy Cross, the Most Precious Blood) -- see the
+ .ml's own citation for what RG 487(a)'s further-named categories (the
+ Passion/Instruments of the Passion, the Most Holy Redeemer) are absent
+ from the shipped calendar entirely, not merely from this list. *)
+val preface_holy_cross_slugs : string list
+
+(** RG 496's own two St Joseph feasts in the shipped calendar. *)
+val preface_st_joseph_slugs : string list
+
+(** RG 497's own [Apostolorum et Evangelistarum] population -- WIDER than
+ {!creed_apostle_slugs} (no natalicium restriction); see the .ml's own
+ citation for the FIUV Ordo evidence and what was deliberately left
+ off, unwitnessed. *)
+val preface_apostle_slugs : string list
+
+(** RG 495's own [beatae Mariae Virginis] population -- WIDER than
+ {!Precedence_ef.marian_slugs} (a different, looser standard than that
+ list's own RG 112(d) oration-level one); see the .ml's own citation
+ for the FIUV Ordo evidence. *)
+val preface_bvm_slugs : string list
+
+(** Which preface is said at this day's Mass (RG 482-499). [None] both when
+ this engine resolves a day with no Mass at all (Good Friday) and -- for
+ a rite that has not implemented this function at all -- as the type's
+ own neutral value; see the .ml's own header for the rubric quoted in
+ full, the priority order every branch follows and why, and the FIUV
+ Ordo evidence that order rests on. Same three parameters as {!creed}/
+ {!te_deum}/{!gloria}, for the same reasons. *)
+val preface :
+ temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
+ observed:Vocab_ef.rank Celebration.t ->
+ date:Date.t ->
+ Preface.t option
diff --git a/man/colitur.1 b/man/colitur.1
index 88c5fab..3f485d5 100644
--- a/man/colitur.1
+++ b/man/colitur.1
@@ -122,13 +122,14 @@ occurrence, commemoration and transfer.
The Mass reading citations, one line per day.
.TP
.BI rubrics " YEAR"
-Three rubrics of the Mass, one line per day: which formulary is actually
+Four rubrics of the Mass, one line per day: which formulary is actually
said \(em not always the day's own: a weekday with no proper resumes the
preceding Sunday's, a saint with no proper says his assigned Common, and
RG 78/309(a)'s votive Saturday Mass of Our Lady is said in place of an
-unoccupied office's own \(em whether the Creed is said (RG 475\-476), and
+unoccupied office's own \(em whether the Creed is said (RG 475\-476),
whether the Gloria in excelsis is said (RG 431\-432, deferring to the
-Breviary's own Te Deum rule, nn. 237\-238, for RG 431(a)). See
+Breviary's own Te Deum rule, nn. 237\-238, for RG 431(a)), and which
+preface is said (RG 482\-499). See
.B OUTPUT FORMAT
below.
.TP
@@ -461,13 +462,14 @@ own trailing field, above.
.SS rubrics
.RS
.nf
-date [TAB] formulary\-slug [TAB] source [TAB] creed [TAB] gloria
+date [TAB] formulary\-slug [TAB] source [TAB] creed [TAB] gloria [TAB] preface
.fi
.RE
.PP
The day's own Mass formulary (which slug's Mass is actually said, and how
-that was decided), followed by whether the Creed is said (RG 475\-476) and
-whether the Gloria in excelsis is said (RG 431\-432).
+that was decided), followed by whether the Creed is said (RG 475\-476),
+whether the Gloria in excelsis is said (RG 431\-432), and which preface is
+said (RG 482\-499).
.B rubrics
separates its fields with a literal TAB \(em not a plain space like
.B day
@@ -505,12 +507,25 @@ this row has no other boolean field to be consistent with). A day with no
Mass at all for a rite that has not implemented a rule reads
.B false
outright \(em it is a decision, never a third \(lqunknown\(rq state.
+.I preface
+is one of
+.BR nativity ", " epiphany ", " lent ", " holy\-cross ", " easter ", "
+.BR ascension ", " sacred\-heart ", " christ\-the\-king ", " holy\-spirit ", "
+.BR trinity ", " bvm ", " st\-joseph ", " apostles ", " common " or " requiem ,
+or a literal
+.B \-
+when this engine resolves no Mass at all that day (Good Friday) \(em unlike
+.I creed / gloria ,
+.I preface
+is a genuine option, so
+.B \-
+here can also mean a rite that has not implemented the rule at all.
.RS
.nf
-2026\-01\-01 [TAB] ef\-circumcision [TAB] own [TAB] true [TAB] true
-2038\-03\-08 [TAB] john\-of\-god [TAB] proper [TAB] false [TAB] true
-2025\-12\-01 [TAB] ef\-advent\-sunday\-1 [TAB] preceding\-sunday [TAB] false [TAB] false
+2026\-01\-01 [TAB] ef\-circumcision [TAB] own [TAB] true [TAB] true [TAB] nativity
+2038\-03\-08 [TAB] john\-of\-god [TAB] proper [TAB] false [TAB] true [TAB] common
+2025\-12\-01 [TAB] ef\-advent\-sunday\-1 [TAB] preceding\-sunday [TAB] false [TAB] false [TAB] common
.fi
.RE
.PP
diff --git a/test/cli.t b/test/cli.t
index 8bd5b9f..4ae116f 100644
--- a/test/cli.t
+++ b/test/cli.t
@@ -210,9 +210,9 @@ separate command for the same mechanical reason `readings` is: `day`'s row
is fixed-width space-separated with a variable-length "+slug" tail.
$ colitur rubrics 2026 | head -3
- 2026-01-01 ef-circumcision own true true
- 2026-01-02 ef-christmas-1-friday own false true
- 2026-01-03 ef-christmas-1-saturday votive false true
+ 2026-01-01 ef-circumcision own true true nativity
+ 2026-01-02 ef-christmas-1-friday own false true nativity
+ 2026-01-03 ef-christmas-1-saturday votive false true bvm
$ colitur rubrics 2026 | wc -l
365
@@ -224,19 +224,19 @@ apply to it -- step 2 does (the day's own temporal slug in the lectionary),
tagged `own`. Contrast a real sanctoral saint with his own proper:
$ colitur rubrics 2038 | grep '^2038-03-08'
- 2038-03-08 john-of-god proper false true
+ 2038-03-08 john-of-god proper false true common
A saint with no proper of his own says his assigned Common (step 4):
$ colitur rubrics 2038 | grep '^2038-03-06'
- 2038-03-06 common-of-non-virgins-1 common false true
+ 2038-03-06 common-of-non-virgins-1 common false true common
A weekday with no proper of its own resumes the preceding Sunday's, never
its own observed slug -- 1 December 2025 is the Monday after Advent I, and
Advent's ferias have no Mass of their own (step 3):
$ colitur rubrics 2025 | grep '^2025-12-01'
- 2025-12-01 ef-advent-sunday-1 preceding-sunday false false
+ 2025-12-01 ef-advent-sunday-1 preceding-sunday false false common
3 January 2026 above ("votive") is the RG 78/309(a) Saturday Mass of Our
Lady, said IN PLACE of the day's own office's Mass while the office (an
@@ -257,9 +257,9 @@ diocesan overlay's local patron observed instead (no proper or Common of his
own in the fixture), the chain falls all the way back to step 3:
$ colitur rubrics 2026 --overlay fixtures/overlay-example-diocesan.sexp | grep '^2026-07-11'
- 2026-07-11 ef-time-after-pentecost-sunday-6 preceding-sunday false true
+ 2026-07-11 ef-time-after-pentecost-sunday-6 preceding-sunday false true common
$ colitur rubrics 2026 | grep '^2026-07-11'
- 2026-07-11 ef-time-after-pentecost-6-saturday votive false true
+ 2026-07-11 ef-time-after-pentecost-6-saturday votive false true bvm
`--lang`/`--raw`/`--sigla-*` are refused rather than silently ignored, unlike
`readings`: this row resolves no display name and no citation for any of
@@ -343,13 +343,13 @@ prints for the identical day, so the two cannot silently drift apart
again in either direction:
$ colitur --help | grep '^ rubrics date'
- rubrics date, formulary slug, source, creed, gloria -- TAB-separated
+ rubrics date, formulary slug, source, creed, gloria, preface -- TAB-separated
$ colitur --help | sed -n '/^ rubrics date/{n;p}'
- 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true
+ 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true[TAB]nativity
$ colitur rubrics 2026 | grep '^2026-01-01' | sed $'s/\t/[TAB]/g'
- 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true
+ 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true[TAB]nativity
--version prints the version alone, to standard output, exit 0. Deliberately
not embedded in the help text above: this pin would then have to be edited
@@ -589,8 +589,17 @@ addition: 2027-01-01's own record wraps [gloria] onto its own new line,
while 2027-01-02's fits it on the same line as [creed] and [formulary] --
172 of 365 records happened to cross a wrap boundary, the rest did not.
+9197 -> 9252 (preface, celebrant-rubrics-phase1 Phase 3): {!Liturgical_day.t}
+gained a [preface] field, the same seam [gloria] just above used -- all
+365 of 2027's records print a new [(preface (<variant>))] token (checked
+directly, [grep -c "(preface"]), [(preface ())] on exactly one day
+(2027's own Good Friday, 26 March -- this engine resolves no Mass at all
+that day, {!Rite_ef.Rubrics_ef.preface}'s own citation). Same cosmetic
+reflow mechanics as every entry above, not a fixed one-line-per-record
+addition.
+
$ colitur emit --format sexp --from 2027 --to 2027 | wc -l
- 9197
+ 9252
$ colitur emit --format xml --from 2027 --to 2027 | head -2
<?xml version="1.0" encoding="UTF-8"?>
diff --git a/test/test_calendar.ml b/test/test_calendar.ml
index 157841b..9059574 100644
--- a/test/test_calendar.ml
+++ b/test/test_calendar.ml
@@ -104,11 +104,13 @@ module Fixture = struct
the sanctoral side. *)
let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = (None, [])
- (* No fixture here exercises the Creed or Gloria rubrics either -- a rite
- that has not implemented them returns [false] explicitly,
- {!Rite.t.creed}/{!Rite.t.gloria}'s own documented default. *)
+ (* No fixture here exercises the Creed, Gloria or preface rubrics either
+ -- a rite that has not implemented them returns [false]/[None]
+ explicitly, {!Rite.t.creed}/{!Rite.t.gloria}/{!Rite.t.preface}'s own
+ documented default. *)
let creed ~temporal:_ ~observed:_ ~date:_ = false
let gloria ~temporal:_ ~observed:_ ~date:_ = false
+ let preface ~temporal:_ ~observed:_ ~date:_ = None
let rite : (season, rank) Rite.t =
{ Rite.id = "synthetic-calendar"; vocab; year_start; temporal; anchors = (fun _ -> []);
@@ -120,7 +122,7 @@ module Fixture = struct
(* Not a Roman rite either, so no bissextile-doubling convention:
identity, {!Rite.t.fixed_key}'s own documented default. *)
fixed_key = (fun d -> Some (D.month d, D.day d));
- rules; season_runs = [ A; B ]; transfer_target; readings; creed; gloria }
+ rules; season_runs = [ A; B ]; transfer_target; readings; creed; gloria; preface }
let entry ~month ~day ~slug ~rank =
{ Layer.date = (match Date_spec.fixed ~month ~day with Ok d -> d | Error e -> failwith e);
diff --git a/test/test_fiuv_ordo.ml b/test/test_fiuv_ordo.ml
index 5c009a9..3bfed68 100644
--- a/test/test_fiuv_ordo.ml
+++ b/test/test_fiuv_ordo.ml
@@ -40,6 +40,7 @@ module Overlay = Colitur_kernel.Overlay
module LD = Colitur_kernel.Liturgical_day
module Date = Colitur_kernel.Date
module V = Rite_ef.Vocab_ef
+module Preface = Colitur_kernel.Preface
let sanctoral_path = "../data/ef/sanctoral.sexp"
let adjustments_path = "../data/ef/adjustments.sexp"
@@ -138,7 +139,7 @@ let window_last = "2026-12-31"
(* late). *)
(* ---------------------------------------------------------------------- *)
-type colitur_row = { c_date : string; c_creed : bool; c_gloria : bool }
+type colitur_row = { c_date : string; c_creed : bool; c_gloria : bool; c_preface : Preface.t option }
let colitur_rows () =
let layer = real_layer () in
@@ -156,7 +157,9 @@ let colitur_rows () =
(match Hashtbl.find_opt by_rata (Date.to_rata !d) with
| Some day ->
rows :=
- { c_date = Date.to_iso8601 day.LD.date; c_creed = day.LD.creed; c_gloria = day.LD.gloria } :: !rows
+ { c_date = Date.to_iso8601 day.LD.date; c_creed = day.LD.creed; c_gloria = day.LD.gloria;
+ c_preface = day.LD.preface }
+ :: !rows
| None -> Alcotest.failf "no colitur day resolved for %s" (Date.to_iso8601 !d));
d := Date.add_days !d 1
done;
@@ -346,6 +349,148 @@ let test_gloria_matches_or_is_explained () =
Alcotest.(check int) "F1 (Good Friday) count" 1 !f1_count
(* ---------------------------------------------------------------------- *)
+(* Preface (RG 482-499, {!Rite_ef.Rubrics_ef.preface}) -- Phase 3. Same *)
+(* shape as Creed/Gloria above, over the identical 400-row window, using *)
+(* the [praef] column already captured into the fixture (CAPTURED, NOT *)
+(* VALIDATED at the time -- tools/extract_fiuv_ordo.ml's own [row.praef] *)
+(* citation) but never compared until now. *)
+(* *)
+(* CHARACTERISATION FIRST, per the task's own standing instruction (this
+ project has been misled by trusting an Ordo field's surface shape
+ before, four times this session alone): [praef] is RAW TRAILING TEXT
+ after the literal token "praef." to the end of the primary Mass
+ clause -- it is not a clean enum, it can carry an arbitrary tail of
+ unrelated rubrical prose glued on by the same token-flattening that
+ produces it (Good Friday's own [gloria]/F1 finding above is the
+ worked example of exactly this shape). CLASSIFIED here by PREFIX
+ match against the FIRST WORD(S) only -- every prefix below was found
+ by direct inspection of the fixture's own distinct [praef] values
+ (test/fixtures/fiuv-ordo-2025-2026.sexp, 400 rows, all read by hand
+ while building this comparison) and cross-checked against the primary
+ rubric text, not guessed. Two genuine EXCEPTION SHAPES, THREE rows,
+ found the same way:
+
+ - Christmas Day (both of them -- the fixture's own window, 27
+ November 2025-31 December 2026, spans thirteen months and TWO 25
+ Decembers, confirmed by running the coverage test below before
+ pinning it at "two", not three, the first time) and Epiphany Day
+ (2026-01-06, the window's only one) print "praef. et Communic..."
+ -- an ELLIPSIS: the compiler treats "this feast's own [i.e.
+ obviously implied] preface" as not needing its own name restated
+ on the ONE day that IS that feast, unlike every octave/de-Tempore
+ day governed by the SAME feast (which always restates the name
+ explicitly, e.g. 26 December's own "de Nativ."). Confirmed by
+ direct inspection: no OTHER day in the fixture omits the name this
+ way. [classify_praef] returns [None] (unrecognised) for all three
+ rather than special-casing them by date -- both feasts are
+ independently, directly citable from RG 484(a)/485(a)'s own text
+ (the Nativity/Epiphany feast itself is the paradigm case each
+ clause names first), so the oracle's corroboration is not needed
+ to trust colitur's own answer on these dates, and forcing a match
+ here would mean trusting an inference about the source's own
+ ellipsis rather than reading it.
+ - Good Friday (2026-04-03) prints "comm. Feria VI prima in mense." --
+ classifies cleanly as [Common] by the same prefix rule as any
+ other day, but this file's own F1 (Gloria, above) already
+ adjudicated this EXACT date's raw text as unreliable (a copied,
+ not a considered, line -- confirmed against the PDF's own page
+ image, not merely the extracted fixture). The same defect
+ extends to whatever trails "praef." on the identical corrupted
+ line: excluded from this comparison by reusing [is_f1_good_friday]
+ directly, not re-argued. *)
+
+let has_prefix ~prefix s =
+ String.length s >= String.length prefix && String.sub s 0 (String.length prefix) = prefix
+
+(* Every prefix below maps 1:1 onto exactly one branch of
+ {!Rite_ef.Rubrics_ef.preface}'s own priority order -- see that
+ function's own header for the full citation of each. Order does not
+ matter here (unlike in [preface] itself): the Ordo's own printed text
+ never carries two of these prefixes on the same row, so this is a
+ partition, not a priority list. *)
+let praef_prefixes =
+ [ ("comm.", Preface.Common);
+ ("Trinit.", Preface.Trinity);
+ ("Quadr.", Preface.Lent);
+ ("de Sancta Cruce", Preface.Holy_cross);
+ ("Pasch.", Preface.Easter);
+ ("etc. ut in festo", Preface.Easter);
+ (* Easter-octave ferias, "as on the feast [of Easter]" -- e.g. 6
+ April 2026, "etc. ut in festo., ad Ite, missa est additur duplex
+ Alleluia." *)
+ ("Ascensionis", Preface.Ascension);
+ ("de Spirito Sancto", Preface.Holy_spirit);
+ ("BMV", Preface.Bvm);
+ ("App.", Preface.Apostles);
+ ("de Nativ.", Preface.Nativity);
+ ("de Epiphania", Preface.Epiphany);
+ ("de Ss.mi Corde", Preface.Sacred_heart);
+ ("de Ss.mo Corde", Preface.Sacred_heart);
+ ("de Domino Nostro Jesu Rege", Preface.Christ_the_king);
+ ("de S Iosepho", Preface.St_joseph);
+ ("de S Ioseph", Preface.St_joseph);
+ ("defunctorum", Preface.Requiem) ]
+
+let classify_praef (s : string) : Preface.t option =
+ let rec go = function
+ | [] -> None
+ | (prefix, p) :: rest -> if has_prefix ~prefix s then Some p else go rest
+ in
+ go praef_prefixes
+
+(* Measured directly, not assumed: every one of the 400 rows' own [praef]
+ text, classified, tallied by outcome. Two rows are genuinely
+ unclassifiable by this prefix table (the ellipsis dates above); every
+ other non-[None] [praef] value classifies. Pinned so a change to
+ either the fixture or [classify_praef] that silently drops coverage
+ fails loudly here rather than merely narrowing the comparison below. *)
+let test_praef_classification_coverage () =
+ let ordo = ordo_rows () in
+ let with_praef = List.filter_map (fun o -> o.praef) ordo in
+ Alcotest.(check int) "399 of 400 rows carry a [praef] value (Holy Saturday is the one exception)" 399
+ (List.length with_praef);
+ let unclassified = List.filter (fun s -> classify_praef s = None) with_praef in
+ (* THREE rows, not two: the fixture's own window (27 November 2025-31
+ December 2026, 13 months) spans TWO Christmas Days, both printing the
+ identical ellipsis text -- found running this exact assertion, not
+ assumed from the date count alone. *)
+ Alcotest.(check int) "the three ellipsis rows (two Christmas Days, one Epiphany) are unclassified" 3
+ (List.length unclassified);
+ Alcotest.(check bool) "every unclassified row starts with the ellipsis's own \"et Communic\"" true
+ (List.for_all (has_prefix ~prefix:"et Communic") unclassified)
+
+let describe_preface_mismatch (o : ordo_row) (c : colitur_row) (expected : Preface.t) =
+ Printf.sprintf "%s %S: colitur preface=%s, Ordo praef=%S (classified %s)" o.date o.title
+ (match c.c_preface with Some p -> Preface.to_string p | None -> "-")
+ (Option.get o.praef) (Preface.to_string expected)
+
+let test_preface_matches_or_is_explained () =
+ let ordo = ordo_rows () in
+ let colitur = colitur_rows () in
+ let unexplained = ref [] in
+ let unclassified_count = ref 0 in
+ let f1_count = ref 0 in
+ List.iter2
+ (fun (o : ordo_row) (c : colitur_row) ->
+ if not (String.equal o.date c.c_date) then Alcotest.failf "misaligned: ordo %s vs colitur %s" o.date c.c_date;
+ if is_f1_good_friday o then incr f1_count
+ else
+ match o.praef with
+ | None -> ()
+ | Some raw -> (
+ match classify_praef raw with
+ | None -> incr unclassified_count
+ | Some expected ->
+ if c.c_preface = Some expected then ()
+ else unexplained := describe_preface_mismatch o c expected :: !unexplained))
+ ordo colitur;
+ Alcotest.(check (list string)) "every classified preface difference is explained -- none unexplained" []
+ (List.rev !unexplained);
+ Alcotest.(check int) "the three ellipsis rows are skipped, not silently counted as agreement" 3
+ !unclassified_count;
+ Alcotest.(check int) "F1 (Good Friday) is skipped here too, the same root cause as the Gloria axis" 1 !f1_count
+
+(* ---------------------------------------------------------------------- *)
(* Te Deum (Breviary 237-238, {!Rite_ef.Rubrics_ef.te_deum}) -- Phase 2, *)
(* the mitigation the task brief names for this source's own stated *)
(* weakness (a single, not yet scan-verified, web transcription). A *)
@@ -440,6 +585,10 @@ let suite =
Alcotest.test_case "Ordo Gloria coverage matches the measured figure" `Quick test_gloria_coverage;
Alcotest.test_case "every Gloria difference is named in the cited allow-list -- none unexplained" `Quick
test_gloria_matches_or_is_explained;
+ Alcotest.test_case "praef classification coverage matches the measured figure" `Quick
+ test_praef_classification_coverage;
+ Alcotest.test_case "every classified preface difference is explained -- none unexplained" `Quick
+ test_preface_matches_or_is_explained;
Alcotest.test_case "Ordo Te Deum coverage matches the measured figure" `Quick test_te_deum_coverage;
Alcotest.test_case "every Te Deum difference is named in the cited allow-list -- none unexplained" `Quick
test_te_deum_matches_or_is_explained
diff --git a/test/test_rubrics_ef.ml b/test/test_rubrics_ef.ml
index d533670..bb3bfed 100644
--- a/test/test_rubrics_ef.ml
+++ b/test/test_rubrics_ef.ml
@@ -746,6 +746,333 @@ let test_exhaustive_gloria_domain_sweep () =
Alcotest.(check bool) "the full domain reached a real number of Requiem days" true (black > 1_000)
end
+(* ---- RG 482-499, the preface -- see lib/rites/rite_ef/rubrics_ef.ml for
+ the rubric quoted in full and every branch's own citation. One
+ end-to-end test per branch, resolved against REAL calendar dates
+ through the shipped data, the same discipline every other section of
+ this file already follows -- most of these dates were cross-checked
+ directly against the FIUV Ordo's own [praef] column
+ (test/fixtures/fiuv-ordo-2025-2026.sexp) before being pinned here, not
+ merely derived from the Latin text in isolation; see [preface]'s own
+ header for the full account of what that cross-check settled. ---- *)
+
+module Pref = Colitur_kernel.Preface
+
+let preface_on y m d = (day_on y m d).LD.preface
+
+let preface_string_on y m d =
+ match preface_on y m d with Some p -> Pref.to_string p | None -> "-"
+
+let check_preface name expected y m d =
+ Alcotest.(check string) name (Pref.to_string expected) (preface_string_on y m d)
+
+let check_no_preface name y m d = Alcotest.(check string) name "-" (preface_string_on y m d)
+
+(* ---- Good Friday: no Mass at all, [None] -- checked ahead of the
+ Requiem/Black-colour branch (this file's own [preface] header explains
+ why the two need different answers, unlike creed/gloria/te_deum). ---- *)
+let test_preface_good_friday_no_mass () = check_no_preface "Good Friday: no Mass, no preface" 2026 4 3
+
+(* ---- RG 499: the Requiem proxy, the SAME two-member {!Colour.Black}
+ population {!test_colour_black_population_is_exactly_two} already
+ asserts, with Good Friday split out above. ---- *)
+let test_preface_499_all_souls () =
+ check_preface "RG 499: All Souls' Day (transferred)" Pref.Requiem 2025 11 3
+
+(* ---- RG 487(a): the two fixed Holy Cross feast triggers. ---- *)
+let test_preface_487a_exaltation () =
+ check_preface "RG 487(a): the Exaltation of the Holy Cross" Pref.Holy_cross 2026 9 14
+
+let test_preface_487a_precious_blood () =
+ check_preface "RG 487(a): the Most Precious Blood" Pref.Holy_cross 2026 7 1
+
+(* ---- RG 491/492: Sacred Heart, Christ the King -- single named days. ---- *)
+let test_preface_491_sacred_heart () = check_preface "RG 491: the Sacred Heart" Pref.Sacred_heart 2026 6 12
+let test_preface_492_christ_the_king () =
+ check_preface "RG 492: Christ the King" Pref.Christ_the_king 2026 10 25
+
+(* ---- RG 490(a): Ascension Day itself. ---- *)
+let test_preface_490a_ascension_day () = check_preface "RG 490(a): Ascension Day" Pref.Ascension 2026 5 14
+
+(* ---- RG 494(a): Trinity Sunday itself. ---- *)
+let test_preface_494a_trinity_sunday () = check_preface "RG 494(a): Trinity Sunday" Pref.Trinity 2026 5 31
+
+(* ---- RG 496: St Joseph's two feasts. ---- *)
+let test_preface_496_joseph_spouse () = check_preface "RG 496: St Joseph, Spouse of the BVM" Pref.St_joseph 2026 3 19
+let test_preface_496_joseph_workman () = check_preface "RG 496: St Joseph the Workman" Pref.St_joseph 2027 5 1
+
+(* ---- RG 495: a genuine Marian FEAST via [marian_slugs] (the Assumption),
+ and the VOTIVE-shaped BVM Saturday Office via [subject = Bvm] -- the
+ latter is the live witness for RG 495's own "et votivis" half on this
+ engine's data (this file's own [preface] header has the full account,
+ corroborated against the Ordo on 3/10 January 2026). ---- *)
+let test_preface_495_assumption () = check_preface "RG 495: the Assumption (marian_slugs)" Pref.Bvm 2026 8 15
+let test_preface_495_bvm_saturday_office () =
+ check_preface "RG 495: the BVM Saturday Office (subject=Bvm, votive-shaped)" Pref.Bvm 2026 7 11
+
+(* ---- RG 484(a)'s own explicit Purification clause: 2 February, checked
+ as a standalone slug trigger regardless of season -- see [preface]'s
+ own header for why this needed to be independent of the Nativity
+ WINDOW below (2 February is nowhere near it). ---- *)
+let test_preface_484a_purification () = check_preface "RG 484(a): the Purification" Pref.Nativity 2026 2 2
+
+(* ---- RG 484(a)/(b) merged: the Nativity octave window, 25 December-1
+ January, PLUS RG 484(b)'s own extra "2 ad 5 ianuarii" days -- one
+ contiguous window. St Stephen (26 December) is the live witness that
+ this OUTRANKS an ordinary saint's own otherwise-Common preface;
+ {!creed}'s own 475(d) test picks the identical date for the identical
+ "occurring inside a privileged window" shape. ---- *)
+let test_preface_484_nativity_day () = check_preface "RG 484(a): Christmas Day itself" Pref.Nativity 2026 12 25
+let test_preface_484_stephen_in_octave () =
+ check_preface "RG 484(b): St Stephen, occurring within the Nativity octave" Pref.Nativity 2026 12 26
+
+let test_preface_484_jan1_octave_day () =
+ check_preface "RG 484(a): 1 January, the Octave Day" Pref.Nativity 2026 1 1
+
+let test_preface_484b_jan2to5 () =
+ (* 2 January 2026 (a plain Christmastide feria, "ef-christmas-1-friday",
+ confirmed via `colitur day 2026`) -- 3 January that year is the BVM
+ Saturday Office instead (RG 495's own "et votivis" outranking this
+ window, this file's own [test_preface_495_bvm_saturday_office] and
+ [preface]'s own header have the full account), so this date is picked
+ specifically to witness the PLAIN de-Tempore grant, uncomplicated by
+ that override. *)
+ check_preface "RG 484(b): 2 January, the extra de-Tempore days" Pref.Nativity 2026 1 2
+
+(* ---- RG 484(b)'s own NARROWER exception: an Apostle/Evangelist inside
+ the Nativity octave is STILL overridden to [Nativity] (unlike every
+ other window, which an Apostle's own preface outranks -- the next test
+ below). John the Evangelist, 27 December, is the live witness this
+ file's own [preface] header cites. ---- *)
+let test_preface_484b_overrides_apostle_in_octave () =
+ check_preface "RG 484(b): St John the Evangelist, inside the Nativity octave, still [Nativity]"
+ Pref.Nativity 2026 12 27
+
+(* ---- RG 497: the Apostle/Evangelist natalicia list, reused from
+ {!creed_apostle_slugs} -- OUTSIDE the Nativity octave, an Apostle keeps
+ his own preface even inside another window (Sts Philip & James, 11 May,
+ inside the Paschaltide/Easter window -- the FIUV Ordo's own witness
+ this file's own [preface] header cites for RG 484(b)'s narrower
+ carve-out, checked from the other direction). ---- *)
+let test_preface_497_barnabas () = check_preface "RG 497: St Barnabas" Pref.Apostles 2026 6 11
+let test_preface_497_peter_paul () = check_preface "RG 497: Sts Peter & Paul" Pref.Apostles 2026 6 29
+let test_preface_497_philip_james_inside_easter_window () =
+ check_preface "RG 497: Sts Philip & James, inside the Easter window, still [Apostles]" Pref.Apostles 2026 5 11
+
+(* ---- RG 485(a)/(b): Epiphany itself and its own Baptism commemoration
+ (a), the wider 7-13 January window (b) -- Holy Family Sunday (11
+ January 2026) is the live witness that a temporal-origin Sunday inside
+ this window gets [Epiphany], not some Holy-Family-specific preface this
+ engine has none of. ---- *)
+let test_preface_485a_epiphany_day () = check_preface "RG 485(a): Epiphany Day" Pref.Epiphany 2026 1 6
+let test_preface_485a_baptism_commemoration () =
+ check_preface "RG 485(a): the Commemoration of the Baptism of the Lord" Pref.Epiphany 2026 1 13
+
+let test_preface_485b_holy_family_sunday () =
+ check_preface "RG 485(b): Holy Family Sunday, inside the 7-13 January window" Pref.Epiphany 2026 1 11
+
+(* ---- RG 486(a)/(b): the Lenten window, Ash Wednesday through the
+ Saturday before Passion Sunday I. ---- *)
+let test_preface_486_ash_wednesday () = check_preface "RG 486(a): Ash Wednesday" Pref.Lent 2026 2 18
+let test_preface_486_lent_saturday_boundary () =
+ check_preface "RG 486: the Saturday immediately before Passion Sunday I" Pref.Lent 2026 3 21
+
+(* ---- RG 487(a)/(b): the Passiontide window, Passion Sunday I through
+ Holy Thursday inclusive. ---- *)
+let test_preface_487_passion_sunday () = check_preface "RG 487: Passion Sunday I" Pref.Holy_cross 2026 3 22
+let test_preface_487_palm_sunday () = check_preface "RG 487: Palm Sunday" Pref.Holy_cross 2026 3 29
+let test_preface_487_holy_thursday () = check_preface "RG 487: Holy Thursday" Pref.Holy_cross 2026 4 2
+
+(* ---- RG 489(a)/(b): the Easter window, the Vigil Mass through the vigil
+ of the Ascension. ---- *)
+let test_preface_489_easter_vigil_mass () = check_preface "RG 489(a): the Easter Vigil Mass" Pref.Easter 2026 4 4
+let test_preface_489_easter_sunday () = check_preface "RG 489(a): Easter Sunday" Pref.Easter 2026 4 5
+let test_preface_489_ascension_vigil () = check_preface "RG 489(b): the vigil of the Ascension" Pref.Easter 2026 5 13
+
+(* ---- RG 490(b): the post-Ascension window. ---- *)
+let test_preface_490b_post_ascension_feria () =
+ check_preface "RG 490(b): the Friday after Ascension" Pref.Ascension 2026 5 15
+
+let test_preface_490b_sunday_after_ascension () =
+ check_preface "RG 490(b): the Sunday after Ascension" Pref.Ascension 2026 5 17
+
+(* ---- RG 493(a)/(b): the Pentecost-octave window, the vigil through the
+ following Saturday (the Ember Saturday). ---- *)
+let test_preface_493_pentecost_vigil () = check_preface "RG 493(a): the vigil of Pentecost" Pref.Holy_spirit 2026 5 23
+let test_preface_493_pentecost_sunday () = check_preface "RG 493(a): Pentecost Sunday" Pref.Holy_spirit 2026 5 24
+let test_preface_493_ember_saturday () =
+ check_preface "RG 493(a): the Ember Saturday within the Pentecost octave" Pref.Holy_spirit 2026 5 30
+
+(* ---- RG 494(b): the Trinity de-Tempore Sundays -- an Advent Sunday
+ ([temporal.season = Advent], [Class1] by construction) and an ordinary
+ Time-after-Pentecost Sunday. Both now read off [temporal]'s own season,
+ not [observed]'s rank -- see the next test for why the distinction is
+ live, not merely stylistic. ---- *)
+let test_preface_494b_advent_sunday () = check_preface "RG 494(b): Advent I Sunday" Pref.Trinity 2026 11 29
+let test_preface_494b_ordinary_class2_sunday () =
+ check_preface "RG 494(b): an ordinary Time-after-Pentecost Sunday" Pref.Trinity 2026 8 9
+
+(* ---- RG 494(b), the RG 16(a)-shaped fix round: All Saints' Day (1
+ November), [Class1], observed OUTRIGHT over the ordinary Sunday it
+ commemorates -- found via the FIUV Ordo (its own entry reads "Trinit.
+ vel de Omnibus Sanctis et Patronis", the "vel..." half an alternate
+ ORATIO reference, the SAME pattern every other "X vel Y" [praef] value
+ in that fixture already follows, never a second genuine preface). No
+ dedicated preface exists for All Saints among RG 484-497's own
+ fourteen, so RG 482's chain falls to the SUNDAY's own de-Tempore grant
+ regardless of which rank actually won the day -- exactly why this
+ branch must read [temporal]'s season, not [observed]'s [Class1] rank,
+ which an EARLIER version of [preface] wrongly required to equal
+ [Class2] and so answered [Common] here instead.
+
+ 1 November 2026 is itself a Sunday (confirmed via `colitur day 2026`:
+ "all-saints class-1 white +ef-time-after-pentecost-sunday-23"), the
+ exact shape this fix concerns -- picked for that reason, not merely
+ because it is All Saints' Day. *)
+let test_preface_494b_all_saints_class1_sunday () =
+ check_preface "RG 494(b): All Saints' Day, Class1, still Trinity (no dedicated preface exists)" Pref.Trinity
+ 2026 11 1
+
+(* ---- RG 498: the Common residual -- Corpus Christi is the deliberately
+ chosen witness (NOT simply "any ordinary weekday"): the 1962 Missal
+ gives it no preface of its own at all, only an OPTIONAL alternative
+ (Sacred Heart's), so it takes the plain Common, confirmed against the
+ Ordo directly (4 June 2026: "comm. vel de Cor Sacratissimo") -- a real
+ trap this engine's own tier1 list does NOT fall into (no
+ "ef-corpus-christi" entry anywhere in it). ---- *)
+let test_preface_498_corpus_christi () = check_preface "RG 498: Corpus Christi (no proper of its own)" Pref.Common 2026 6 4
+
+let test_preface_498_plain_saint () = check_preface "RG 498: a plain sanctoral saint (Lawrence)" Pref.Common 2026 8 10
+
+(* ---- Corroborating real-data invariants, task requirement 5: "every day
+ in Paschaltide should take the Easter preface unless it has a proper
+ one; every Lenten feria the Lent preface". Checked as a SWEEP, not a
+ single date, over a fixed sample span (1583-2200, matching the century+
+ spans other domain checks in this file already use) -- FAST in the
+ default suite, EXHAUSTIVE (1583-9999) gated behind
+ COLITUR_EXHAUSTIVE_SWEEP the same way {!test_exhaustive_gloria_domain_sweep}
+ above already is.
+
+ The invariant actually checked is NOT the brief's own literal phrasing
+ (which is one exception too strong, the identical "pushed back, not
+ silently special-cased" stance {!check_gloria_invariants_for_year}'s own
+ header already takes for [gloria]/432(b)): every Paschaltide-season day
+ takes [Easter], [Ascension] or [Holy_spirit] (its own three seasonal
+ windows), OR one of the season-independent title triggers this engine
+ can construct inside Paschaltide's real date range ([Bvm]/[St_joseph]/
+ [Apostles]/[Requiem]) -- but NEVER [Nativity]/[Epiphany]/[Lent]/[Trinity]/
+ [Sacred_heart]/[Christ_the_king]/[Common], none of which any real date
+ inside Paschaltide can trigger. Symmetrically for Lent: [Lent] or one of
+ [Bvm]/[St_joseph]/[Apostles]/[Requiem], never a preface belonging to a
+ date-disjoint window. Two further invariants the sweep found and kept,
+ not originally asked for but load-bearing: EVERY Christmastide-season
+ day resolves [Nativity], [Epiphany] or [Bvm] (RG 484/485 between them
+ leave no gap at all in that season on their own -- confirmed
+ exhaustively -- but RG 495's own votive-Mass half, live for the BVM
+ Saturday Office, can and does fall on a Christmastide Saturday too;
+ found live at 1584-01-07 while first running this exact sweep, kept as
+ a real witness rather than narrowed away); and Passiontide, uniquely,
+ legitimately
+ ALSO produces [Easter] exactly once a year (Holy Saturday's own Vigil
+ Mass, [n = -1] -- {!Rite_ef.Rubrics_ef.preface}'s own RG 489(a) comment;
+ this engine's day/colour model keeps that date [Passiontide] by season
+ even though the Vigil Mass's own preface has already moved to Easter's,
+ the same acknowledged per-action nuance [gloria]'s own RG 431(c) comment
+ already documents) -- asserted to be EXACTLY once per year, not merely
+ allowed, so a second, unexplained (Passiontide, Easter) day anywhere in
+ the domain still fails loudly. *)
+let check_preface_season_invariants_for_year y (counts : (int * int * int * int) ref) =
+ let days = Cal.year ctx layer y in
+ let christmastide_gaps = ref 0
+ and paschaltide_gaps = ref 0
+ and lent_gaps = ref 0
+ and passiontide_easter = ref 0 in
+ let christmastide_gaps0, paschaltide_gaps0, lent_gaps0, passiontide_easter0 = !counts in
+ christmastide_gaps := christmastide_gaps0;
+ paschaltide_gaps := paschaltide_gaps0;
+ lent_gaps := lent_gaps0;
+ passiontide_easter := passiontide_easter0;
+ Array.iter
+ (fun (d : (V.season, V.rank) LD.t) ->
+ let label = Printf.sprintf "%s (preface=%s)" (Date.to_iso8601 d.LD.date)
+ (match d.LD.preface with Some p -> Pref.to_string p | None -> "NONE")
+ in
+ (match d.LD.temporal.Temporal.season with
+ | V.Christmastide -> (
+ match d.LD.preface with
+ | Some (Pref.Nativity | Pref.Epiphany | Pref.Bvm) -> ()
+ | _ ->
+ incr christmastide_gaps;
+ Alcotest.failf "%s: Christmastide day with neither Nativity, Epiphany nor Bvm" label)
+ | V.Paschaltide -> (
+ match d.LD.preface with
+ | Some (Pref.Easter | Pref.Ascension | Pref.Holy_spirit | Pref.Bvm | Pref.St_joseph
+ | Pref.Apostles | Pref.Requiem) ->
+ ()
+ | _ ->
+ incr paschaltide_gaps;
+ Alcotest.failf "%s: Paschaltide day with an out-of-window preface" label)
+ | V.Lent -> (
+ match d.LD.preface with
+ | Some (Pref.Lent | Pref.Bvm | Pref.St_joseph | Pref.Apostles | Pref.Requiem) -> ()
+ | _ ->
+ incr lent_gaps;
+ Alcotest.failf "%s: Lent day with an out-of-window preface" label)
+ | V.Passiontide ->
+ if d.LD.preface = Some Pref.Easter then begin
+ incr passiontide_easter;
+ let easter = Computus.gregorian_easter (Date.year d.LD.date) in
+ Alcotest.(check int)
+ (Printf.sprintf "%s: the ONLY Passiontide/Easter day is the Vigil Mass (Easter-1)" label)
+ (-1) (Date.to_rata d.LD.date - Date.to_rata easter)
+ end
+ | _ -> ()))
+ days;
+ counts := (!christmastide_gaps, !paschaltide_gaps, !lent_gaps, !passiontide_easter)
+
+let test_preface_season_invariants_sample () =
+ let counts = ref (0, 0, 0, 0) in
+ for y = 1583 to 1782 do
+ check_preface_season_invariants_for_year y counts
+ done;
+ let _, _, _, passiontide_easter = !counts in
+ Alcotest.(check bool) "the 200-year sample found the once-a-year Passiontide/Easter exception" true
+ (passiontide_easter >= 200)
+
+let test_exhaustive_preface_season_domain_sweep () =
+ if Sys.getenv_opt colitur_exhaustive_sweep_env = None then Alcotest.skip ()
+ else begin
+ let counts = ref (0, 0, 0, 0) in
+ for y = 1583 to 9999 do
+ check_preface_season_invariants_for_year y counts
+ done;
+ let christmastide_gaps, paschaltide_gaps, lent_gaps, passiontide_easter = !counts in
+ Printf.printf
+ "preface season sweep 1583..9999: christmastide_gaps=%d paschaltide_gaps=%d lent_gaps=%d \
+ passiontide_easter=%d\n%!"
+ christmastide_gaps paschaltide_gaps lent_gaps passiontide_easter;
+ Alcotest.(check int) "zero Christmastide gaps anywhere in the domain" 0 christmastide_gaps;
+ Alcotest.(check int) "zero Paschaltide out-of-window prefaces anywhere in the domain" 0 paschaltide_gaps;
+ Alcotest.(check int) "zero Lent out-of-window prefaces anywhere in the domain" 0 lent_gaps;
+ (* 8416, not 8417 (the domain's own year count): a genuine, pre-existing
+ DOMAIN-BOUNDARY edge, not a preface defect -- {!Cal.year}'s own
+ liturgical year "opening in civil year 9999" cannot construct any
+ date past 1583-9999 ({!Date.make}'s own validated range), so it
+ returns only 34 days (28 November-31 December), never reaching its
+ own Easter/Holy Saturday (which would fall in year 10000). Found by
+ running this exact sweep: it returned 8416 first, not asserted
+ blindly at 8417 -- checked directly ([Cal.year ctx layer 9999]
+ alone, printed length 34) before writing this comment, the same
+ "measure before adjudicating" discipline every allow-list in this
+ project follows. The identical edge is why
+ docs/research/rules-register.md's own Layer.index citation already
+ reads "both edges, 1582 and 10000, bit during development" --
+ this sweep is a second witness to the same known boundary, not a
+ new one. *)
+ Alcotest.(check int) "one Passiontide/Easter day per year, every year but the domain's own last" 8416
+ passiontide_easter
+ end
+
(* ---- ITEM 1: RG 111(a), the sung-Mass commemoration cap
({!Colitur_kernel.Precedence.sung_mass_commemorations}) -- two real
calendar days, one of each shape, resolved through the identical
@@ -855,4 +1182,62 @@ let suite =
Alcotest.test_case "RG 111(a): an ordinary-only Low-Mass set is empty at Sung Mass" `Quick
test_rg111a_ordinary_only_dropped;
Alcotest.test_case "RG 111(a): a privileged commemoration survives at Sung Mass" `Quick
- test_rg111a_privileged_kept ] )
+ test_rg111a_privileged_kept;
+ Alcotest.test_case "preface: Good Friday, no Mass" `Quick test_preface_good_friday_no_mass;
+ Alcotest.test_case "RG 499: All Souls' Day (transferred)" `Quick test_preface_499_all_souls;
+ Alcotest.test_case "RG 487(a): the Exaltation of the Holy Cross" `Quick test_preface_487a_exaltation;
+ Alcotest.test_case "RG 487(a): the Most Precious Blood" `Quick test_preface_487a_precious_blood;
+ Alcotest.test_case "RG 491: the Sacred Heart" `Quick test_preface_491_sacred_heart;
+ Alcotest.test_case "RG 492: Christ the King" `Quick test_preface_492_christ_the_king;
+ Alcotest.test_case "RG 490(a): Ascension Day" `Quick test_preface_490a_ascension_day;
+ Alcotest.test_case "RG 494(a): Trinity Sunday" `Quick test_preface_494a_trinity_sunday;
+ Alcotest.test_case "RG 496: St Joseph, Spouse of the BVM" `Quick test_preface_496_joseph_spouse;
+ Alcotest.test_case "RG 496: St Joseph the Workman" `Quick test_preface_496_joseph_workman;
+ Alcotest.test_case "RG 495: the Assumption (marian_slugs)" `Quick test_preface_495_assumption;
+ Alcotest.test_case "RG 495: the BVM Saturday Office (subject=Bvm)" `Quick
+ test_preface_495_bvm_saturday_office;
+ Alcotest.test_case "RG 484(a): the Purification" `Quick test_preface_484a_purification;
+ Alcotest.test_case "RG 484(a): Christmas Day itself" `Quick test_preface_484_nativity_day;
+ Alcotest.test_case "RG 484(b): St Stephen, inside the Nativity octave" `Quick
+ test_preface_484_stephen_in_octave;
+ Alcotest.test_case "RG 484(a): 1 January, the Octave Day" `Quick test_preface_484_jan1_octave_day;
+ Alcotest.test_case "RG 484(b): 3 January, the extra de-Tempore days" `Quick test_preface_484b_jan2to5;
+ Alcotest.test_case "RG 484(b): St John the Evangelist, inside the Nativity octave" `Quick
+ test_preface_484b_overrides_apostle_in_octave;
+ Alcotest.test_case "RG 497: St Barnabas" `Quick test_preface_497_barnabas;
+ Alcotest.test_case "RG 497: Sts Peter & Paul" `Quick test_preface_497_peter_paul;
+ Alcotest.test_case "RG 497: Sts Philip & James, inside the Easter window" `Quick
+ test_preface_497_philip_james_inside_easter_window;
+ Alcotest.test_case "RG 485(a): Epiphany Day" `Quick test_preface_485a_epiphany_day;
+ Alcotest.test_case "RG 485(a): the Commemoration of the Baptism of the Lord" `Quick
+ test_preface_485a_baptism_commemoration;
+ Alcotest.test_case "RG 485(b): Holy Family Sunday, inside the 7-13 January window" `Quick
+ test_preface_485b_holy_family_sunday;
+ Alcotest.test_case "RG 486(a): Ash Wednesday" `Quick test_preface_486_ash_wednesday;
+ Alcotest.test_case "RG 486: the Saturday before Passion Sunday I" `Quick
+ test_preface_486_lent_saturday_boundary;
+ Alcotest.test_case "RG 487: Passion Sunday I" `Quick test_preface_487_passion_sunday;
+ Alcotest.test_case "RG 487: Palm Sunday" `Quick test_preface_487_palm_sunday;
+ Alcotest.test_case "RG 487: Holy Thursday" `Quick test_preface_487_holy_thursday;
+ Alcotest.test_case "RG 489(a): the Easter Vigil Mass" `Quick test_preface_489_easter_vigil_mass;
+ Alcotest.test_case "RG 489(a): Easter Sunday" `Quick test_preface_489_easter_sunday;
+ Alcotest.test_case "RG 489(b): the vigil of the Ascension" `Quick test_preface_489_ascension_vigil;
+ Alcotest.test_case "RG 490(b): the Friday after Ascension" `Quick test_preface_490b_post_ascension_feria;
+ Alcotest.test_case "RG 490(b): the Sunday after Ascension" `Quick
+ test_preface_490b_sunday_after_ascension;
+ Alcotest.test_case "RG 493(a): the vigil of Pentecost" `Quick test_preface_493_pentecost_vigil;
+ Alcotest.test_case "RG 493(a): Pentecost Sunday" `Quick test_preface_493_pentecost_sunday;
+ Alcotest.test_case "RG 493(a): the Ember Saturday within the Pentecost octave" `Quick
+ test_preface_493_ember_saturday;
+ Alcotest.test_case "RG 494(b): Advent I Sunday" `Quick test_preface_494b_advent_sunday;
+ Alcotest.test_case "RG 494(b): an ordinary Time-after-Pentecost Sunday" `Quick
+ test_preface_494b_ordinary_class2_sunday;
+ Alcotest.test_case "RG 494(b): All Saints' Day, Class1, still Trinity" `Quick
+ test_preface_494b_all_saints_class1_sunday;
+ Alcotest.test_case "RG 498: Corpus Christi (no proper of its own)" `Quick test_preface_498_corpus_christi;
+ Alcotest.test_case "RG 498: a plain sanctoral saint (Lawrence)" `Quick test_preface_498_plain_saint;
+ Alcotest.test_case "preface domain sanity: Christmastide/Paschaltide/Lent window invariants (sample)"
+ `Quick test_preface_season_invariants_sample;
+ Alcotest.test_case
+ "preface domain sweep 1583..9999: window invariants, committed not sampled" `Slow
+ test_exhaustive_preface_season_domain_sweep ] )
diff --git a/test/test_validate.ml b/test/test_validate.ml
index b53f915..3da8fbb 100644
--- a/test/test_validate.ml
+++ b/test/test_validate.ml
@@ -324,17 +324,19 @@ module Synthetic = struct
{ Colitur_kernel.Mass_formulary.said = Some (Slug.of_string_exn "syn-formulary");
via = Colitur_kernel.Mass_formulary.Own_slug }
- (* No fixture here exercises the Creed or Gloria rubrics -- a rite that
- has not implemented them returns [false] explicitly,
- {!Rite.t.creed}/{!Rite.t.gloria}'s own documented default. Made
- overridable ([?creed]/[?gloria] below) on the same footing as
- [?readings] just above, for Task 6's own fixtures. *)
+ (* No fixture here exercises the Creed, Gloria or preface rubrics -- a
+ rite that has not implemented them returns [false]/[None] explicitly,
+ {!Rite.t.creed}/{!Rite.t.gloria}/{!Rite.t.preface}'s own documented
+ default. Made overridable ([?creed]/[?gloria]/[?preface] below) on
+ the same footing as [?readings] just above, for Task 6's own
+ fixtures. *)
let creed ~temporal:_ ~observed:_ ~date:_ = false
let gloria ~temporal:_ ~observed:_ ~date:_ = false
+ let preface ~temporal:_ ~observed:_ ~date:_ = None
let rite ?(vocab = vocab) ?(anchors = fun _ -> []) ?(season_runs = [ A; B ]) ?(rules = rules)
?(transfer_target = fun _ origin _ -> origin) ?(readings = readings) ?(creed = creed)
- ?(gloria = gloria) temporal : (season, rank) Rite.t =
+ ?(gloria = gloria) ?(preface = preface) temporal : (season, rank) Rite.t =
{ Rite.id = "synthetic"; vocab; year_start; temporal; anchors; rules; season_runs;
(* Not a Roman rite, but a Rite.t must supply SOME Easter now that
movable Date_spec variants exist. The Gregorian one is as good as
@@ -344,7 +346,7 @@ module Synthetic = struct
(* Not a Roman rite either, so no bissextile-doubling convention:
identity, {!Rite.t.fixed_key}'s own documented default. *)
fixed_key = (fun d -> Some (D.month d, D.day d));
- transfer_target; readings; creed; gloria }
+ transfer_target; readings; creed; gloria; preface }
(* Empty by default: every check built before Task 12 exercises the
TEMPORAL-only pass, where an empty layer is exactly the fixture that