1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
(** The OF reading-cycle arithmetic and the temporal + sanctoral lectionary
resolution chain (Task 4, 2026-08-25-colitur-of-phases-3-5).
Two halves with a different character, per the task's own brief:
- {b The cycle rules are CODE}, computed from the civil year, cited to
the OLM (Ordo Lectionum Missae) 1981 Praenotanda -- see {!sunday_cycle}
and {!weekday_cycle} below for the citations, each verified against the
real page image (docs/research/of/olm-1981.pdf pp.32-33/"XXXII-XXXIII"),
not the unreliable OCR text layer (design spec 2026-08-24-colitur-of-
rite-module-design.md sec4.4's own caveat).
- {b The data is bootstrapped from lectio} (binding decision 3), a
POLISH VERNACULAR pastoral lectionary (niedziela.pl), not the Latin
OLM itself -- see tools/bootstrap_lectionary_of.ml's own top-of-file
comment and data/of/lectionary.sexp's own provenance header for the
full lineage statement and what it cannot show. *)
open Colitur_kernel
(** OLM 1981 Praenotanda n.66 (page-image verified, p.32/"XXXII"): "cyclus
trium annorum proponitur" -- the three-year Sunday/solemnity cycle.
Footnote 102, same page, gives the determining rule with a worked
example: "Per litteram C designatur annus cuius numerus in tres partes
aequales dividi potest... Sic annus 1980 est annus C, annus vero
sequens, scilicet annus 1981, est annus A, annus vero 1982 est annus B,
et annus 1983 est iterum annus C" -- the year divisible by three is
Year C, the next Year A, the next Year B. The SAME footnote also gives
the Gospel-per-year identity quoted on {!sunday_cycle}'s own doc
comment below (A = Matthew, B = Mark, C = Luke), and states explicitly
that each cycle's own "year" runs "iuxta dispositionem anni liturgici,
nempe a prima hebdomada Adventus, quae cadit in anno civili
praecedente" -- according to the liturgical year, namely from the first
week of Advent, WHICH FALLS IN THE PRECEDING CIVIL YEAR. This is the
task brief's own "the cycle turns at Advent I, not 1 January": Advent
1980 (civil year 1980) already opens the 1981-labelled liturgical year
(Year A), even though most of civil year 1980 was still Year C. *)
type sunday_cycle = Year_a | Year_b | Year_c
(** OLM 1981 Praenotanda n.69 (page-image verified, p.33/"XXXIII"), the
"c) De ordinatione lectionum pro feriis" subsection's own point 4:
"Pro feriis vero triginta quattuor hebdomadarum <<per annum>> lectiones
evangelicae unico disponuntur cyclo, qui singulis annis resumitur.
Prior vero lectio, in duplici cyclo ordinatur, alternis annis legenda.
Annus primus adhibetur annis imparibus, annus secundus vero annis
paribus" -- for the ferias of the thirty-four weeks of Ordinary Time,
the GOSPEL is arranged in a single cycle repeated every year; the FIRST
reading alone alternates over two years. Year I is used in odd years,
Year II in even years. The SAME point 4, its own preceding points 2-3,
state the two-year alternation is confined to Ordinary Time: Lent has
its own single seasonal cycle (point 2) and "Pro feriis Adventus,
temporis Nativitatis et paschalis cyclus eodem modo annualis est:
lectiones ideo non mutantur" -- for the ferias of Advent, Christmastide
and Paschaltide the cycle is likewise annual: the readings DO NOT
CHANGE (point 3). {!Lectionary.t} does not need to encode that
distinction itself: a non-alternating family's own two cycle-letter
keys were collapsed to one flat entry at bootstrap time (see
tools/bootstrap_lectionary_of.ml's own [collapse_weekday]), so trying
both letters here is always safe -- the flat entry is found first
either way. *)
type weekday_cycle = Year_i | Year_ii
val sunday_cycle_letter : sunday_cycle -> string
val weekday_cycle_letter : weekday_cycle -> string
(** [sunday_cycle ~year_start date]: which of the three-year cycle's own
labelled years [date] falls in, per {!sunday_cycle}'s own citation.
A = Matthew, B = Mark, C = Luke -- OLM n.66's own footnote 102, same
passage: "primus cycli annus dicitur et est annus legendi Matthaeum,
ceteri vero, secundus nempe et tertius, respective annus legendi
Marcum et annus legendi Lucam". [year_start] is the rite's own
Advent-anchor function ({!Temporal_of.year_start}), supplied by the
caller rather than hard-coded, the same seam {!Rite_ef.Lectionary_ef
.readings}'s own [~lectionary]/[~commons] already use for rite data --
this module reads no filesystem and knows no OF-specific season
vocabulary of its own beyond what [year_start] and {!Colitur_kernel
.Temporal.t} already carry. *)
val sunday_cycle : year_start:(int -> Date.t) -> Date.t -> sunday_cycle
(** [weekday_cycle ~year_start date]: which of the two-year cycle's own
labelled years [date] falls in, per {!weekday_cycle}'s own citation. *)
val weekday_cycle : year_start:(int -> Date.t) -> Date.t -> weekday_cycle
(** The Mass's Epistle and Gospel citations for this day, and (when the
shipped data can name one) which formulary they came from -- the OF
counterpart of {!Rite_ef.Lectionary_ef.readings}, deliberately simpler:
OF has no Commons indirection table (spec's own scope for this task),
so the chain is three steps, not four:
- {b Step 1} -- the observed celebration's own embedded citations, if
any. {!Colitur_kernel.Mass_formulary.Proper}. Defensive: every
shipped data/of/calendar-2002.sexp entry carries [citations ()]
today (Task 1), so this step is currently dead on shipped data, kept
for the same forward-compatibility reason EF's own step 1 stays
first even where most callers reach it rarely.
- {b Step 2} -- when the observed celebration is NOT itself the day's
own temporal office (a real sanctoral entity won occurrence), its
own slug looked up directly in [lectionary] (tried flat, then both
cycle-letter suffixes via {!sunday_cycle}/{!weekday_cycle} -- a
sanctoral entry may be tagged either way in the shipped data, see
tools/bootstrap_lectionary_of.ml's own header, so both are tried
rather than assumed). Tagged {!Colitur_kernel.Mass_formulary.Proper}
too, not {!Colitur_kernel.Mass_formulary.Common}: OF has no shared-
formulary indirection the way EF's Commons do (this task's own
scope), so a looked-up sanctoral citation is still, substantively,
"the observed celebration's own proper" -- only ITS STORAGE differs
from step 1's (external file vs an embedded field), which
{!Colitur_kernel.Mass_formulary.source} was never designed to
distinguish (its own doc names what DECIDED the Mass, not where the
bytes happen to live). {!Colitur_kernel.Mass_formulary.source} is a
closed kernel type outside this task's own scope to extend; see
lectionary_of.ml's own implementation comment on this branch for the
full argument. [said] the observed slug.
- {b Step 3} -- the day's own temporal slug. FIRST tries
{!date_keyed_slug} (OLM n. 69.3's date-fixed windows -- the
O-Antiphons and the pre-Epiphany run of the Christmas season), THEN
falls back to the same three-way slug lookup step 2 uses.
{!Colitur_kernel.Mass_formulary.Own_slug} either way, [said] the
temporal office's own slug -- {!date_keyed_slug} changes which KEY
resolves the citations, not which office is said to have said them.
- A day matching neither gets [(None, [])] -- see data/of/lectionary
.sexp's own provenance header for exactly which days that is, both
directions, measured, not guessed.
NO preceding-Sunday fallback (EF's own step 3): unlike EF's RG 299,
the OF's own two-year FERIAL cycle assigns every Ordinary Time weekday
its own reading by design (OLM n.69 point 4, {!weekday_cycle}'s own
citation) -- there is no OF norm this task found instructing a ferial
with no proper of its own to repeat the preceding Sunday's Mass, so no
such step is built. [temporal_at] is therefore accepted, unused, to
satisfy {!Colitur_kernel.Rite.t}'s own [readings] field shape (the same
contract {!Rite_ef.Lectionary_ef.readings} satisfies) -- kept rather
than dropped so a future task that DOES find such a rule has the
callback already in place. *)
val readings :
lectionary:Lectionary.t ->
year_start:(int -> Date.t) ->
observed:Vocab_of.rank Celebration.t ->
temporal:(Vocab_of.season, Vocab_of.rank) Temporal.t ->
date:Date.t ->
temporal_at:(Date.t -> (Vocab_of.season, Vocab_of.rank) Temporal.t) ->
Mass_formulary.t option * Citation.t list
(** OLM 1981 Praenotanda n. 69.3 (the same passage {!weekday_cycle}'s own
doc comment cites: "Pro feriis Adventus, temporis Nativitatis et
paschalis cyclus eodem modo annualis est: lectiones ideo non
mutantur" -- Advent/Christmastide/Paschaltide ferial readings do not
change year to year). Every OTHER such family is non-alternating
WITHIN a weekday slot ({!weekday_cycle}); two short windows go
further and are keyed by CIVIL DATE instead: the O-Antiphon days
(17-24 December) and the pre-Epiphany run of the Christmas season
(29-31 December, then 2-5 and 7 January) -- verified concretely
against lectio's own ini, not merely inferred from the citation: 17
December is Gen 49:2,8-10 / Ps 72:1-2,3-4ab,7-8,17 / Matthew 1:1-17
regardless which weekday it falls on in a given year.
{!Rite_of.Temporal_of}'s own ferial slugs for these same civil dates
are WEEKDAY-keyed
({!Rite_of.Temporal_of.christmas_feria_slug}/the Advent ferial
branch), so looking one of THOSE slugs up would silently serve
whichever OTHER date happens to share that year's weekday alignment
-- the defect this function exists to close (bug found by review, not
by a test: colitur used to serve 17 December the unrelated
"advent-3-mon" reading in years where the two happened to align).
6 January is deliberately NOT one of the six Christmas-season dates,
despite lectio's own "christmas-jan-6" base existing:
{!Rite_of.Temporal_of}'s own [named] fixes Epiphany to 6 January
unconditionally (temporal_of.ml, "m = 1 && dd = 6"), so that date is
never reached as a ferial by {!readings}'s own step 3 in the first
place -- a real lectio entry with no reachable colitur day, the same
"structurally unreachable" shape as tools/bootstrap_lectionary_of
.ml's own "easter-6-thu"/"advent-4-sat" (see its own [excluded_bases]
comment).
[None] on a Sunday: Advent 4, Holy Family, the Second Sunday after
Christmas and the Baptism of the Lord can each land inside these two
windows, each is a NAMED office with its own distinct slug
({!Rite_of.Temporal_of.sunday_slug}), and each must keep taking
priority over a ferial date -- checked here explicitly rather than
trusted to the call site, since this function's whole contract is
"this civil date, unconditionally, is this citation", which is false
on the Sunday the window shares its calendar date with. *)
val date_keyed_slug : Date.t -> Slug.t option
|