summaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 10:40:40 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 10:40:40 +0200
commitb5dcdd8d4048295cfbdf1606f4cafd39b3c433e6 (patch)
tree3c49919b87fb33059506528441473806c316a18e /lib/rites/rite_ef
parent59fbda87d565ad9b98c2493e9703390cd988609d (diff)
downloadcolitur-b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6.tar.gz
colitur-b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6.zip
temporal(ef): Holy Name of Jesus, RG 17(a) -- fallback + Lord subject
RG 17(a): "festum Ss.mi Nominis Iesu, celebrandum dominica quae occurrit a die 2 ad 5 ianuarii (secus die 2 ianuarii)" -- both photographic scans, confirmed on the calendarium's own January table and the Mass propers' own heading. Two defects closed together, since they interact through RG 91 entry 14/RG 16(a): 1. The Sunday shape (2-5 January) now tags subject = Lord, matching RG 91 entry 14 ("Festa Domini II classis") -- previously subject = Temporal, the same defect shape Holy Family had before it. 2. The fallback (RG 17(a)'s own "secus die 2 ianuarii", the calendarium's "vel, ea deficiente, die 2 ianuarii") is now built: 2 January carries the feast whenever no Sunday falls 2-5 January that year. Before this fix colitur emitted no Holy Name office at all in such a year -- a real II-class feast simply missing, on 3,619 of 8,417 years in the domain (43%; 20 of the 46 years 2005-2050, independently re-derived against `date -d`). Both shapes band identically at RG 91 entry 14's MOVABLE half (Precedence_ef.entry_14_movable_band) -- one feast, not two differently classified ones, per the Mass propers' own single heading covering both dates. No live collision exists in the current sanctoral data (nothing fixed-date falls 2-5 January besides a Commemoration_only entry on 5 Jan), so the RG 16(a)/RG 112(a) interaction is proved with synthetic precedence-level fixtures mirroring Holy Family's own precedent, not a live-data regression. One asymmetry the synthetic tests found and kept honest rather than forced: a losing Holy-Name-Sunday is privileged (RG 109(a), "of a Sunday") and survives RG 111(a)'s cap; a losing Holy-Name-fallback is not (2 January is not a Sunday, and no other RG 109(a)-(f) category applies to it), so it gets no commemoration at all if ever outranked -- both individually correct readings of RG 109/111's closed lists. Golden pins follow the brief's own vacuity warning: the Sunday shape already emitted the right slug/rank/colour before this fix (an ordinary Sunday and Holy Name coincide on those fields), so its pin asserts subject explicitly rather than pinning the whole line uncritically; the fallback pin (2029-01-02) is the one with teeth against the larger defect, reddening on slug/rank/subject together if reverted. Mutation-tested: each half of the fix reverted separately reddens exactly its own tests (Sunday subject: the dedicated unit test, the golden pin's subject field, one precedence end-to-end test; fallback: the fallback unit/anchor/golden tests, the new C16 differential count, and both shapes of the precedence end-to-end tests) and nothing else. Adds data/ef/expected-divergences.sexp's C16 (lectio has no fallback for this window at all) -- the lectio differential's own count, independently re-derived against `date -d` for the fixture's 2005-2050 span, not transcribed from the domain-wide figure. The missalemeum oracle window (2026-2027) exercises only the Sunday shape and needed no new entry: subject is not one of its compared fields, and both years' rows already corroborate the Sunday-shape ranking (missalemeum's own 2026-01-04 and 2027-01-03 rows: rank 2, white, title "Holy Name of Jesus").
Diffstat (limited to 'lib/rites/rite_ef')
-rw-r--r--lib/rites/rite_ef/temporal_ef.ml122
-rw-r--r--lib/rites/rite_ef/temporal_ef.mli11
2 files changed, 131 insertions, 2 deletions
diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml
index 1ce9de9..721790d 100644
--- a/lib/rites/rite_ef/temporal_ef.ml
+++ b/lib/rites/rite_ef/temporal_ef.ml
@@ -104,6 +104,56 @@ let christ_the_king y = sunday_on_or_before (mk y 10 31)
date against it without reaching into [week_origin]'s implementation. *)
let holy_family_sunday y = Date.add_days (sunday_on_or_before (mk y 1 6)) 7
+(* RG 17(a) (Caput III, "De Dominicis"; full text on [holy_family_sunday]'s
+ own citation above): "festum Ss.mi Nominis Iesu, celebrandum dominica quae
+ occurrit a die 2 ad 5 ianuarii (secus die 2 ianuarii)" -- the feast of the
+ Most Holy Name of Jesus, celebrated on the Sunday falling 2-5 January,
+ OTHERWISE [on] 2 January [itself]. [None] here means no such Sunday exists
+ that year -- [holy_name_fallback_date]'s own fallback branch is for
+ exactly that case; see its own citation for why the window CAN be empty
+ (unlike (b)'s 7-13 January window, which never is).
+
+ Formula: the Sunday on or before 5 January is in [2,5] iff it is not
+ before 2 January -- the window is only 4 days wide, so there is at most
+ one Sunday in it either way. *)
+let holy_name_sunday y =
+ let s = sunday_on_or_before (mk y 1 5) in
+ if on_or_after s (mk y 1 2) then Some s else None
+
+(* RG 17(a)'s own "secus die 2 ianuarii" clause, and the calendarium's
+ January table, BOTH photographic scans, word for word (docs/research/
+ rules-register.md; also confirmed in the Mass propers' own heading for
+ this feast, both scans, Caput "Sanctissimi Nominis Iesu": "DOMINICA a die
+ 2 ad diem 5 ianuarii occurrenti, VEL, SI HAEC DEFECERIT, DIE 2 IANUARII" --
+ the SUNDAY occurring 2-5 January, OR, IF THIS IS LACKING, [on] 2 January):
+ *"Dominica inter octavam Nativitatis Domini et Epiphaniam, vel, EA
+ DEFICIENTE, die 2 ianuarii: Sanctissimi Nominis Iesu, II classis"* -- the
+ Sunday between the Octave of the Nativity and Epiphany, or, THAT FAILING,
+ 2 January. Unlike [holy_family_sunday]'s 7-13 January window (which can
+ never be empty of a Sunday), this 2-5 January window CAN be, and is in
+ 3,619 of the 8,417 years in [1583, 9999] (a plain weekday check,
+ independently cross-checked against `date -d`) -- [temporal]'s own
+ dispatch applies this fallback date, tagged exactly like the Sunday shape
+ (subject Lord, the same Latin name), only when [holy_name_sunday y] is
+ [None] for that date's own civil year. *)
+let holy_name_fallback_date y = mk y 1 2
+
+(* The ONE Latin title both of RG 17(a)'s two shapes (the Sunday and the 2
+ January fallback) carry -- ONE feast, RG 91's own "primum mobilia, deinde
+ fixa" language notwithstanding (see [temporal]'s own fallback-branch
+ comment for why both shapes band identically at entry 14's MOVABLE half,
+ not split across the two halves by year). Bound once so the two [temporal]
+ branches that use it cannot silently drift apart, the same discipline
+ [holy_family_sunday] already gets from being named instead of re-derived.
+ Both photographic scans, word for word, twice over (the calendarium's own
+ January table AND the Mass propers' own running header/heading, this
+ file's own citations on [holy_name_sunday]/[holy_name_fallback_date]
+ above): "Sanctissimi Nominis Iesu". No English name, deliberately -- the
+ SAME "would read the oracle it is compared against" reasoning
+ [holy_family_sunday]'s own [names] comment below gives for Holy Family. *)
+let holy_name_names =
+ Colitur_kernel.Names.of_list [ (Colitur_kernel.Lang.of_string_exn "la", "Sanctissimi Nominis Iesu") ]
+
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),
@@ -533,15 +583,68 @@ let temporal d =
item on Holy Name of Jesus, RG 17(a)) -- this is a
targeted addition for the one day this task built, not a
claim that the gap is closed generally. *)
+ (* RG 17(a) (this file's own [holy_name_sunday], full citation
+ there): the SECOND Sunday a year this branch must not
+ leave [subject = Temporal] -- added alongside Holy
+ Family's own check above, not replacing it (the two
+ windows, 2-5 and 7-13 January, never overlap, so at most
+ one of the two conditions is ever true for a given [d]).
+ Guarded on [holy_name_sunday y = Some d] rather than only
+ "is [d] in [2,5] January": every OTHER Sunday in that
+ range must stay unnamed, and in a year where a DIFFERENT
+ day of the window is the one true Sunday, [d] itself is
+ never reached by [sunday_slug] as a Sunday to begin with
+ (weekday alone already excludes it) -- but writing the
+ check this way, against the SAME independently-computed
+ date [holy_name_sunday] returns rather than a bare month/
+ day range, is what test_temporal_ef.ml's own anchor test
+ below cross-checks against. *)
let subject, names =
if same d (holy_family_sunday y) then
( Colitur_kernel.Subject.Lord,
Colitur_kernel.Names.of_list
[ (Colitur_kernel.Lang.of_string_exn "la", "Sanctae Familiae Iesu, Mariae, Ioseph") ] )
+ else if holy_name_sunday y = Some d then (Colitur_kernel.Subject.Lord, holy_name_names)
else (Colitur_kernel.Subject.Temporal, Colitur_kernel.Names.empty)
in
build ~subject ~names ~season:s ~slug ~colour ~rank ~week:(week d) ()
- | None -> (
+ | None ->
+ (* RG 17(a)'s own fallback, "secus die 2 ianuarii" (full
+ citation on [holy_name_fallback_date] above): ONLY when
+ [holy_name_sunday y] is [None] for [d]'s own civil year --
+ i.e. 2-5 January genuinely has no Sunday that year -- does
+ 2 January itself carry the feast; every other year, 2
+ January is an ordinary Christmastide feria, exactly what
+ [christmastide_feria_slug] below already computes for it.
+ Checked here, ahead of that generic fallback, the same way
+ [named]'s own fixed dates are checked ahead of everything
+ ferial -- NOT folded into [named] itself, because unlike
+ every one of [named]'s ~20 entries this one is
+ CONDITIONAL on a per-year fact ([holy_name_sunday y] =
+ [None]) that [named]'s bare [Date.t -> ... option] shape
+ has no way to express without threading [subject] through
+ every one of its other branches too -- the same reason
+ Holy Family, immediately above, is not one of [named]'s
+ outputs either (that branch's own comment).
+
+ Season Christmastide (RG 72-73: 2 January is always within
+ it); rank/colour/subject/names identical to the Sunday
+ shape immediately above -- ONE feast, {!holy_name_names}'s
+ own comment on why both shapes share it. Band-classified
+ at entry 14's MOVABLE half regardless of which shape fired
+ this year (Precedence_ef.band; both shapes are built
+ through this same [temporal] function, hence [origin =
+ Temporal] either way) -- RG 91's "primum mobilia, deinde
+ fixa" split is a property of the TABLE ENTRY a feast
+ occupies, and RG 17(a) names ONE feast with a fallback
+ clause, not two differently-classified feasts that happen
+ to share a Mass formulary; the Mass propers' own heading
+ (docs/research/rules-register.md) states both shapes under
+ the identical title for exactly this reason. *)
+ if same d (holy_name_fallback_date y) && holy_name_sunday y = None then
+ build ~subject:Colitur_kernel.Subject.Lord ~names:holy_name_names ~season:Christmastide
+ ~slug:"ef-holy-name" ~colour:Colour.White ~rank:Class2 ~week:(week d) ()
+ else (
match christmastide_feria_slug d with
| Some slug ->
build ~season:s ~slug ~colour:(season_colour s) ~rank:(ferial_rank d s)
@@ -608,7 +711,19 @@ let temporal d =
4-tuple), but it is exactly the same kind of independently-computed
anchor this list exists to guard -- an accidental drift in
[holy_family_sunday]'s own [+7] would silently move Holy Family without
- this guard catching it, same as any other entry here. *)
+ this guard catching it, same as any other entry here.
+
+ Holy Name of Jesus (RG 17(a)) is the SAME shape, restated per-year rather
+ than as a single fixed pair: exactly ONE of its two slugs exists for any
+ given [y] ([holy_name_sunday y] is [Some] xor [None]), so exactly one
+ entry -- "ef-holy-name-sunday" at that Sunday, or "ef-holy-name" at
+ [holy_name_fallback_date y] -- is appended below, matching whichever
+ shape [temporal] itself will actually build that year. An erosion of
+ EITHER branch is still caught: {!Validate.run} calls [anchors] for both
+ the civil year a date falls in and the one before it (a liturgical year
+ straddles two), so across any two consecutive years both shapes are
+ exercised regardless of which one civil year [y] itself happens to
+ land on. *)
let anchors y =
let easter = Computus.gregorian_easter y in
let off n = Date.add_days easter n in
@@ -633,6 +748,9 @@ let anchors y =
("ef-corpus-christi", off 60);
("ef-sacred-heart", off 68);
("ef-christ-the-king", christ_the_king y) ]
+ @ (match holy_name_sunday y with
+ | Some d -> [ ("ef-holy-name-sunday", d) ]
+ | None -> [ ("ef-holy-name", holy_name_fallback_date y) ])
(* Compile-time check that this module satisfies the kernel's rite contract. *)
module _ : Colitur_kernel.Temporal.RITE = struct
diff --git a/lib/rites/rite_ef/temporal_ef.mli b/lib/rites/rite_ef/temporal_ef.mli
index 68fe64e..5e85111 100644
--- a/lib/rites/rite_ef/temporal_ef.mli
+++ b/lib/rites/rite_ef/temporal_ef.mli
@@ -21,6 +21,17 @@ val christ_the_king : int -> Date.t
fallback for it), so this needs none. *)
val holy_family_sunday : int -> Date.t
+(** RG 17(a): the Sunday falling 2-5 January in civil year [y], the Most Holy
+ Name of Jesus's own primary date -- [None] when no such Sunday exists
+ that year (3,619 of the 8,417 years in [1583, 9999]), unlike
+ {!holy_family_sunday}'s window, which never is. *)
+val holy_name_sunday : int -> Date.t option
+
+(** RG 17(a)'s own "secus die 2 ianuarii" fallback: 2 January in civil year
+ [y]. Only actually carries the feast when {!holy_name_sunday} [y] is
+ [None] for that year -- {!temporal} is what applies that condition. *)
+val holy_name_fallback_date : int -> Date.t
+
(** The named temporal days: I-class feasts of the Lord, vigils, and days within
the Octave of the Nativity. Returns (season, slug, colour, rank). Carries
no week of its own -- {!temporal} computes it uniformly via {!week} for