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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
|
(* Task 5 (2026-08-25-colitur-of-phases-3-5): [Rite_of.context], wired
together with the REAL data/of/calendar-2002.sexp + all 13 decree
overlays + data/of/lectionary.sexp, resolved through
{!Colitur_kernel.Calendar} and checked by {!Colitur_kernel.Validate.run}
-- the same pipeline `colitur day --rite of` uses, and the same
discipline test_validate.ml/test_rite_ef.ml already apply to the EF
side: exactly one observed office per day, the year covered once with no
gaps, slug-uniqueness within the liturgical year, and transfers reaching
a fixed point are all invariants {!Colitur_kernel.Validate.run} already
checks generically -- this file supplies the REAL OF rite and data and
asserts the result is clean, rather than re-deriving each invariant by
hand. *)
module Val = Colitur_kernel.Validate
module Layer = Colitur_kernel.Layer
module Overlay = Colitur_kernel.Overlay
module Cal = Colitur_kernel.Calendar
module LD = Colitur_kernel.Liturgical_day
module Slug = Colitur_kernel.Slug
module Date = Colitur_kernel.Date
module Cel = Colitur_kernel.Celebration
module P = Colitur_kernel.Precedence
module Computus = Colitur_kernel.Computus
module V = Rite_of.Vocab_of
(* Relative to this test's own build directory (_build/default/test/), same
convention test_amendments_of.ml/test_calendar_of_data.ml already use --
test/dune declares each of these as a dep of the (test ...) stanza. *)
let base_path = "../data/of/calendar-2002.sexp"
let amendments_dir = "../data/of/amendments/"
let lectionary_path = "../data/of/lectionary.sexp"
(* Decree-chronological order -- the exact list and order
test_amendments_of.ml's own [files] table already pins by SHA-256; not
re-pinning the hashes here (that is that file's own job), just applying
them in the same order so the rite under test here is the one bin/main.ml
will assemble. *)
let amendment_files =
[ "001-padre-pio.sexp"; "002-juan-diego-cuauhtlatoatzin.sexp"; "003-our-lady-of-guadalupe.sexp";
"004-john-xxiii-john-paul-ii.sexp"; "005-mary-magdalene-rank.sexp"; "006-mary-mother-of-the-church.sexp";
"007-paul-vi.sexp"; "008-our-lady-of-loreto.sexp"; "009-faustina-kowalska.sexp";
"010-narek-avila-hildegard.sexp"; "011-martha-mary-lazarus.sexp"; "012-teresa-of-calcutta.sexp";
"013-john-henry-newman.sexp" ]
let real_of_layer =
let base =
match Layer.load V.rank_of_sexp base_path with
| Ok l -> l
| Error e -> failwith (Printf.sprintf "%s: failed to load: %s" base_path e)
in
let overlays =
List.map
(fun name ->
let path = amendments_dir ^ name in
match Overlay.load V.rank_of_sexp path with
| Ok o -> o
| Error e -> failwith (Printf.sprintf "%s: failed to load: %s" path e))
amendment_files
in
let layer, diagnostics = Overlay.merge base overlays in
if diagnostics <> [] then
failwith
(Printf.sprintf "unexpected amendment diagnostics: %s"
(String.concat "; " (List.map Overlay.diagnostic_to_string diagnostics)));
layer
(* [Rite_of.context] takes [~lectionary] as a caller-supplied parameter, not
a value closed over an internal load -- see rite_of.ml's own citation for
why (the exact `colitur easter <year>` startup-death lesson
{!Rite_ef.Rite_ef.context}'s own comment records). *)
let real_of_lectionary =
match Colitur_kernel.Lectionary.load lectionary_path with
| Ok l -> l
| Error e -> failwith (Printf.sprintf "%s: failed to load: %s" lectionary_path e)
let real_of_rite = Rite_of.context ~lectionary:real_of_lectionary
let run year = Val.run real_of_rite real_of_layer ~year
(* CLOSED, fix wave I8 (final-review.md, 2026-08-25-colitur-of-phases-3-5).
This paragraph used to describe a LIVE gap: data/of/lectionary.sexp's
own provenance header named ONE informational temporal-slug gap,
[of-nativity] (25 December -- Christmas Day itself), because
niedziela.pl's own source never covered the Christmas DAY Mass at all
(only the Vigil), which made {!Colitur_kernel.Validate.run}'s
["citations-unresolved"]/["formulary"] checks fire every year, on 25
December, on real shipped data. tools/bootstrap_lectionary_of.ml's own
[hand_authored] table now injects that entry directly (Isaiah 52:7-10 /
John 1:1-18, OLM 1981's own "16 Ad Missam in die",
docs/research/of/olm-1981-ocr.txt:4513-4520) -- a primary-source
citation, not a guess, the same discipline every other hand-authored
entry in this project follows. [is_known_nativity_gap] is kept, not
deleted, but is no longer in [is_known]'s own filter below: it now
exists ONLY as an explicit, named "this must never fire again" check
({!check_year_allowing_known_gaps}'s own dedicated assertion) -- a
regression here surfaces as an ordinary unexpected-failure test error,
not a silently-absorbed known gap, which is the whole point of moving a
predicate out of the filter once its own defect closes. *)
let ends_with ~suffix s =
let ls = String.length s and lx = String.length suffix in
ls >= lx && String.sub s (ls - lx) lx = suffix
(* No [Str]/regex (deps frozen) -- a plain substring scan. *)
let contains ~substring s =
let ls = String.length s and lx = String.length substring in
let rec go i = i + lx <= ls && (String.sub s i lx = substring || go (i + 1)) in
lx = 0 || go 0
let is_known_nativity_gap (f : Val.failure) =
(f.Val.check = "citations-unresolved" || f.Val.check = "formulary") && ends_with ~suffix:"12-25" f.Val.date
(* CLOSED (W2, CLAUDE.md; fixed in temporal_of.ml). This paragraph used to
describe a SECOND, NEWLY-FOUND gap this task's own wiring surfaced --
[rite_of] was never previously wired into a real
{!Colitur_kernel.Rite.t}, so [Validate.run]'s ["anchor"] check had never
before run [Temporal_of.anchors] against [Temporal_of.temporal] for
real. Normae n. 35(a)'s own text is "Dominica infra octavam Nativitatis,
VEL, EA DEFICIENTE, die 30 decembris" -- the Holy Family fallback is a
FIXED-DATE fallback (30 December, whatever weekday it falls on that
year), confirmed by {!Temporal_of.holy_family}'s own [None -> mk y 12 30]
branch and its own .mli citation ("this 6-day window CAN be [empty of a
Sunday] -- confirmed by the fallback clause's own existence"). But
{!Temporal_of.temporal} used to only ever TEST [same d (holy_family y)]
inside its [sunday_slug d] dispatch arm -- i.e. only when [d] genuinely
WAS a Sunday -- so in a year whose 26-31 December window had NO Sunday
(equivalently: 25 December, Christmas Day, is itself a Sunday), the
fallback date was computed correctly by [holy_family]/[anchors] but
[temporal] itself silently fell through to an ordinary ferial slug
instead (["of-christmas-0-friday"] et al.) -- exactly the "generic day
slug masking a real named feast" shape CLAUDE.md already records EF's
own Holy Family/Holy Name gaps as, before they were fixed there. Not
rare: this fired whenever Christmas Day is a Sunday, roughly one year in
seven. [Temporal_of.temporal] now consults [Temporal_of.holy_family]
directly in that dispatch arm, independent of weekday, carrying the
identical identity (subject Lord, [holy_family_names], rank Festum) the
Sunday case already builds -- see temporal_of.ml's own citation on that
branch. [is_known_holy_family_fallback_gap] is kept, not deleted, but is
no longer in [is_known]'s own filter below -- the same discipline
[is_known_nativity_gap] above already established once ITS defect
closed: it exists ONLY as an explicit, named "this must never fire
again" check ({!check_year_allowing_known_gaps}'s own dedicated
assertion), not silently absorbed. *)
let is_known_holy_family_fallback_gap (f : Val.failure) =
f.Val.check = "anchor" && contains ~substring:"of-holy-family" f.Val.detail
let check_year_allowing_known_gaps year =
let fs = run year in
(* Fix wave I8: [is_known_nativity_gap] is deliberately NOT part of
[is_known] any more -- see that predicate's own header. W2 fix: neither
is [is_known_holy_family_fallback_gap] now -- see ITS own header. No
known gaps remain filtered here (landmark years validate cleanly, full
stop); [is_known] is kept as a named hook rather than inlined to
[false] so a future gap has an obvious place to land. *)
let is_known (_ : Val.failure) = false in
let unexpected = List.filter (fun f -> not (is_known f)) fs in
(match unexpected with
| [] -> ()
| fs ->
Alcotest.failf "%d: %s" year
(String.concat "; " (List.map Val.failure_to_string (List.filteri (fun i _ -> i < 5) fs))));
Alcotest.(check int)
(Printf.sprintf "%d: no of-nativity citations-unresolved/formulary gap (fix wave I8 -- was 2)" year)
0 (List.length (List.filter is_known_nativity_gap fs));
Alcotest.(check int)
(Printf.sprintf "%d: no Holy Family anchor gap (W2 fix -- was 1)" year)
0 (List.length (List.filter is_known_holy_family_fallback_gap fs))
(* Landmark years, the same choice test_validate.ml's own
[test_landmark_years] makes for EF: both domain edges plus two ordinary
years well inside it. *)
let test_landmark_years () = List.iter check_year_allowing_known_gaps [ 1583; 2026; 2035; 9998 ]
(* 9999 mirrors test_validate.ml's own [test_year_9999_does_not_raise]: the
liturgical year opening in civil year 9999 continues into out-of-domain
civil year 10000, so [Val.run]/[Calendar.year] clamp the walk to 31
December 9999 rather than raising, and the truncated season run is
*expected* to fail the "seasons" check -- pinning that this surfaces as
an ordinary failure, not an uncaught exception, and that resolution
itself ("coverage"/"lost"/"duplicated") stays clean through the clamp. *)
let test_year_9999_does_not_raise () =
let fs = run 9999 in
Alcotest.(check bool) "no coverage failures (temporal stayed total through the clamp)" true
(not (List.exists (fun f -> f.Val.check = "coverage") fs));
Alcotest.(check bool) "seasons check flags the truncated final year as incomplete" true
(List.exists (fun f -> f.Val.check = "seasons") fs)
(* A real transfer-bearing sample, the OF counterpart of test_rite_ef.ml's
own 2005-2050 sweep: every impeded Sollemnitas [Precedence_of.disposition]
defers is either placed by [transfer_target] and reaches a fixed point
(no "unconverged" failure -- Validate's own check), or is recorded
[omitted] with a reason, never silently dropped ("lost"/"duplicated"),
over the project's own 2005-2050 differential-testing window (CLAUDE.md).
STRUCTURAL checks only -- ["coverage"; "weekday"; "rank"; "colour";
"determinism"; "slugs"; "seasons"; "week"; "lost"; "duplicated";
"unconverged"; "admission"; "observed"] -- deliberately NOT the
citation-chain ones ("anchor"/"citations"/"citations-unresolved"/
"formulary"), and this exclusion is itself a finding, not a shortcut:
sweeping the full 46-year window (not just the 4 landmark years above)
surfaced that [data/of/lectionary.sexp]'s own coverage was measured
against exactly ONE civil year (2026, per that file's own provenance
header) -- OTHER years exercise the OTHER weekday-cycle letter for the
same temporal slug (OLM n.69.4, {!Lectionary_of.weekday_cycle}: which
letter applies flips on the LITURGICAL year label's parity, independent
of which civil year is being swept), and at least one such pairing is
asymmetric on real shipped data: [of-christmas-0-friday-ii] exists,
[of-christmas-0-friday-i] does not (confirmed directly against
data/of/lectionary.sexp; first reached in this sweep at 2006-12-29).
Separately, 2006-12-24 (a year Christmas Eve itself is a Sunday) also
exposed a THIRD newly-found gap: {!Temporal_of.anchors} lists
[of-nativity-vigil] on 24 December UNCONDITIONALLY, but
{!Temporal_of.named}'s own coded exception (Tabula I.2/Normae n.5:
Advent IV outranks the Vigil when they coincide) means
{!Temporal_of.temporal} does not always agree. All three are OUT OF
THIS TASK'S SCOPE to fix -- lectionary_of.* and data/of/lectionary.sexp
are excluded from this task's brief, and temporal_of.ml's own rubric
logic is Task 1's deliverable -- reported as concerns in this task's own
report rather than fixed here or silently excluded without explanation.
CORRECTED (fix-lectionary-anchors task, 2026-08-26): the of-nativity-
vigil/[anchors] disagreement described in the paragraph above is now
FIXED -- {!Temporal_of.anchors} carries the same Sunday guard [named]
already had. A dedicated exhaustive sweep
(test_temporal_of.ml's own [test_exhaustive_domain_sweep], every civil
year 1584-9997 under COLITUR_EXHAUSTIVE_SWEEP=1) confirmed this was
also the ONLY unexplained [anchors]/[temporal] drift anywhere in the
domain -- the other two items in this paragraph (the lectionary
weekday-cycle-letter asymmetry, and the Holy Family 30-December
fallback {!is_known_holy_family_fallback_gap} below already pins) were
unrelated and remained open, unchanged by this fix.
CORRECTED AGAIN (W2, CLAUDE.md, same day): the Holy Family 30-December
fallback named just above is ALSO now fixed -- {!Temporal_of.temporal}
consults {!Temporal_of.holy_family} directly, independent of weekday.
Only the lectionary weekday-cycle-letter asymmetry remains open; it is
untouched by this fix (lectionary_of.*/data/of/lectionary.sexp are out
of scope here, per the original paragraph's own citation). *)
let sample_years =
let rec range a b = if a > b then [] else a :: range (a + 1) b in
range 2005 2050
let structural_check = function
| "anchor" | "citations" | "citations-unresolved" | "formulary" -> false
| _ -> true
let check_year_structural_only year =
match List.filter (fun f -> structural_check f.Val.check) (run year) with
| [] -> ()
| fs ->
Alcotest.failf "%d: %s" year
(String.concat "; " (List.map Val.failure_to_string (List.filteri (fun i _ -> i < 5) fs)))
let test_transfers_reach_a_fixed_point_2005_2050 () =
List.iter check_year_structural_only sample_years
let slug_of (c : V.rank Cel.t) = Slug.to_string c.Cel.slug
(* RESOLVED ([of-normae-56f]/W1, 2026-08-26; previously the
precedence_of.mli-documented KNOWN-UNIMPLEMENTED fourth transfer_target
rule, final review Important #5, ledger ruling R9). Normae n. 56(f) --
St Joseph's solemnity, falling on Palm Sunday, is ANTICIPATED BACKWARD
to 18 March, not carried forward. {!Colitur_kernel.Rite.t.transfer_target}'s
own obligation was relaxed from "strictly later than origin" to
"different from origin" (rite.mli's own comment carries the argument,
traced against {!Colitur_kernel.Calendar}'s actual placement pass, not
merely assumed safe), and [Precedence_of.transfer_target] gained a
Rule 0 for exactly this collision -- see that value's own .ml/.mli for
the full citation.
Previously pinned here as KNOWN WRONG (colitur sent Joseph forward to
Easter+9 via the general rule 3 instead); now pinned as CORRECT. All
years with Gregorian Easter = 26 March put Palm Sunday on 19 March,
coinciding with Joseph; 2062 is the nearest one after today (2026)
still inside the differential-adjacent range this project favours for
worked examples.
2062 is a doubly-worked example, not chosen only for Joseph: that same
year, 25 March (the Annunciation) falls on HOLY SATURDAY (Easter - 1),
so the Annunciation is ALSO impeded and targets Easter + 8 via the
general rule -- independently of Joseph now, since Rule 0 is a fixed
one-day displacement that never enters the general search or competes
with the Annunciation's own target at all (Joseph departs 19 March for
18 March; the Annunciation departs 25 March for Easter+8 -- the two no
longer collide the way they did when both were pushed toward the same
forward date). *)
let test_joseph_palm_sunday_2062_anticipated_backward () =
let easter_2062 = Computus.gregorian_easter 2062 in
Alcotest.(check string) "2062 Easter is really 26 March (the Normae n.56(f) trigger)" "2062-03-26"
(Date.to_iso8601 easter_2062);
let palm_sunday = Date.add_days easter_2062 (-7) in
Alcotest.(check string) "Palm Sunday 2062 is really 19 March, coinciding with St Joseph" "2062-03-19"
(Date.to_iso8601 palm_sunday);
let holy_saturday = Date.add_days easter_2062 (-1) in
Alcotest.(check string) "25 March 2062 is really Holy Saturday, coinciding with the Annunciation too"
"2062-03-25" (Date.to_iso8601 holy_saturday);
let days = Cal.year real_of_rite real_of_layer 2061 in
let day_of d = Array.to_list days |> List.find (fun day -> Date.compare day.LD.date d = 0) in
let joseph_day = day_of palm_sunday in
Alcotest.(check string) "Palm Sunday itself is observed on 19 March (Joseph impeded, correctly)"
"of-palm-sunday" (slug_of joseph_day.LD.observed);
let joseph_slug = "joseph-husband-of-the-blessed-virgin-mary" in
let departure =
List.find_opt (fun (c, _) -> slug_of c = joseph_slug) joseph_day.LD.transferred_out
in
let eighteen_march = Date.add_days palm_sunday (-1) in
(match departure with
| None -> Alcotest.fail "expected Joseph to be transferred_out from 19 March 2062 (impeded by Palm Sunday)"
| Some (_, target) ->
Alcotest.(check string)
"Normae n.56(f): Joseph is anticipated BACKWARD to 18 March 2062, not carried forward"
"2062-03-18" (Date.to_iso8601 target);
Alcotest.(check bool) "which really is the day before Palm Sunday" true
(Date.compare target eighteen_march = 0));
let march18_day = day_of eighteen_march in
Alcotest.(check string) "and 18 March 2062 itself observes St Joseph (not Cyril of Jerusalem's \
ad-libitum memorial, not the ordinary Lenten Saturday feria)"
joseph_slug (slug_of march18_day.LD.observed);
let joseph_arrived =
match march18_day.LD.transferred_in with
| Some c -> slug_of c = joseph_slug
| None -> false
in
Alcotest.(check bool) "and 18 March 2062 records the arrival (transferred_in), not a silent placement"
true joseph_arrived;
(* {!Colitur_kernel.Rite.t.transfer_target}'s own second obligation
(terminate) reduces, for this liturgical year, to the whole-year
placement pass reaching a fixed point at all -- {!Val.run}'s own
"unconverged" check is exactly {!Colitur_kernel.Calendar}'s round
guard surfaced as a named failure (calendar.ml's own
[unconverged_reason]). *)
let failures = run 2061 in
Alcotest.(check bool) "the whole 2061 liturgical year still reaches a fixed point (no unconverged \
transfer placement anywhere in it)" false
(List.exists (fun f -> f.Val.check = "unconverged") failures)
(* CLOSED, W2 fix (CLAUDE.md). This test used to pin a defect the same way
[is_known_holy_family_fallback_gap]'s own citation just above describes:
1583 is the domain floor AND a year Christmas Day falls on a Sunday, so
26-31 December has no Sunday of its own and [Temporal_of.holy_family]
falls back to its own fixed 30 December -- correctly, per
[Temporal_of.anchors] -- but [Temporal_of.temporal] used to never reach
that fallback (its Holy-Family test lived inside the Sunday-only dispatch
arm), so 30 December 1583 was observed as an ordinary
Friday-of-Christmastide ferial office instead of the Feast of the Holy
Family. Now that {!Temporal_of.temporal} consults {!Temporal_of
.holy_family} directly, this test asserts the FIXED, correct behaviour
instead -- through the full real-data {!Colitur_kernel.Calendar}/
{!Colitur_kernel.Precedence} pipeline, not merely {!Temporal_of.temporal}
in isolation (test_temporal_of.ml's own
[test_holy_family_fallback_temporal] already covers that layer) --
checking identity (subject, rank), not only the slug, so a fix that
landed the right slug on the wrong grade would still be caught. *)
let test_holy_family_fallback_1583_now_correct () =
let christmas_1583 = Date.of_iso8601 "1583-12-25" |> Result.get_ok in
Alcotest.(check bool) "1583 Christmas Day really is a Sunday (the Normae n.35(a) trigger)" true
(Date.weekday christmas_1583 = Date.Sun);
let days = Cal.year real_of_rite real_of_layer 1583 in
let dec30 = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (Date.add_days christmas_1583 5) = 0) in
Alcotest.(check string) "FIXED (W2): 30 December 1583 is observed as of-holy-family, not an ordinary ferial"
"of-holy-family" (slug_of dec30.LD.observed);
Alcotest.(check string) "subject is Lord (same identity the Sunday case carries)" "lord"
(Colitur_kernel.Subject.to_string dec30.LD.observed.Cel.subject);
Alcotest.(check string) "rank is Festum, not a lesser grade" "festum"
(V.rank_to_string dec30.LD.observed.Cel.rank)
(* Fix wave I6 (final-review.md, 2026-08-25-colitur-of-phases-3-5): the
obligatory-memorial tie data/of/amendments/006-mary-mother-of-the-church
.sexp's own header documents at length (its "*** FINDING ***" comment)
was documented but never PINNED -- the ledger's own remedy (F-MEMORIAL-TIE)
asked for both halves. Mary, Mother of the Church (Easter+50) and Anthony
of Padua (13 June, fixed) both band at Tabula III.10 (100, both universal
Memoria_obligatoria) in any year Easter+50 lands on 13 June; 2011 is the
worked example that amendment header itself names (Easter 24 April 2011,
so Easter+50 = 13 June -- re-derived here, not trusted from the comment).
KNOWN ARBITRARY, not KNOWN WRONG: unlike the two tests above, there is no
rubric this contradicts. The 2018 decree (AAS 110, 437-438) is silent on
a universal-calendar coincidence with an existing obligatory memorial, and
Normae n. 14's own third clause resolves only the AD-LIBITUM/AD-LIBITUM
case ("Si eodem die plures inscribuntur in calendario memoriae ad
libitum") -- it does not reach two OBLIGATORY memorials. So colitur's
Slug.compare tie-break ("anthony-of-padua-priest-and-doctor" sorts before
"mary-mother-of-the-church") is not this project inventing a rule; it is
the kernel's own generic, rite-agnostic fallback, applied here for lack of
a rite-specific one. This test pins that CURRENT behaviour so a future
Calendar/kernel change, a slug rename, or a future overlay cannot flip
which of the two is observed with no test going red -- the same shape of
invisible-drift risk the EF side's RG 113/[band] fidelity work (`ef-rg16a`,
register §6.1) eliminated on that side. *)
let test_mater_ecclesiae_anthony_tie_2011_known_arbitrary () =
let easter_2011 = Computus.gregorian_easter 2011 in
Alcotest.(check string) "2011 Easter is really 24 April (the Easter+50 trigger)" "2011-04-24"
(Date.to_iso8601 easter_2011);
let tie_date = Date.add_days easter_2011 50 in
Alcotest.(check string) "Easter+50 2011 is really 13 June, coinciding with Anthony of Padua's fixed date"
"2011-06-13" (Date.to_iso8601 tie_date);
(* [Cal.year]'s own [y] labels an Advent-to-Advent LITURGICAL year, not a
civil one (year_bounds = [year_start y, year_start (y+1) - 1], and
[year_start] is Advent Sunday WITHIN civil year y) -- so 13 June 2011
falls inside liturgical year 2010 (Advent 2010 .. the eve of Advent
2011), the same off-by-one every other test in this file that reaches
for a spring date already accounts for (the Joseph test above uses
2061 to reach March 2062). *)
let days = Cal.year real_of_rite real_of_layer 2010 in
let june13 = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date tie_date = 0) in
Alcotest.(check string)
"KNOWN ARBITRARY (no rubric decides this; see this test's own header): the kernel's generic \
alphabetical tie-break observes Anthony of Padua, not Mary Mother of the Church"
"anthony-of-padua-priest-and-doctor" (slug_of june13.LD.observed);
let mater_ecclesiae_omitted =
List.exists (fun (c, _) -> slug_of c = "mary-mother-of-the-church") june13.LD.omitted
in
Alcotest.(check bool)
"and Mary, Mother of the Church is recorded in `omitted`, not silently dropped -- {!Colitur_kernel \
.Liturgical_day.t.omitted}'s own contract"
true mater_ecclesiae_omitted;
Alcotest.(check bool) "she carries no commemoration either (OF's own admit always returns [])" false
(List.exists (fun (c, _) -> slug_of c = "mary-mother-of-the-church") june13.LD.commemorations)
let suite =
( "Rite_of (real data: assembled Rite.t end-to-end via Calendar/Validate)",
[ Alcotest.test_case "landmark years validate cleanly (W2 and the nativity gap both closed)" `Quick
test_landmark_years;
Alcotest.test_case "year 9999 does not raise; seasons flags the clamp, coverage stays clean" `Quick
test_year_9999_does_not_raise;
Alcotest.test_case "2005-2050: transfers reach a fixed point, nothing lost or duplicated" `Slow
test_transfers_reach_a_fixed_point_2005_2050;
Alcotest.test_case
"RESOLVED (W1): St Joseph on Palm Sunday 2062 is anticipated backward to 18 March per Normae \
n.56(f), converges, and is recorded as an arrival" `Quick
test_joseph_palm_sunday_2062_anticipated_backward;
Alcotest.test_case
"FIXED (W2): Holy Family's Normae n.35(a) 30-December fallback is now reached when Christmas \
Day is a Sunday (1583)" `Quick
test_holy_family_fallback_1583_now_correct;
Alcotest.test_case
"KNOWN ARBITRARY pinned: Mary Mother of the Church vs Anthony of Padua tie, 2011-06-13 \
(Easter+50)" `Quick
test_mater_ecclesiae_anthony_tie_2011_known_arbitrary ] )
|