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
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
|
(* Task 17: golden pins -- validation layer 5 of the design spec's five
(colitur CLAUDE.md "Validation" section; layers 1-4 -- Types, Property,
the lectio differential, the missalemeum oracle -- are already built and
green: test_validate.ml, test_differential.ml, test_oracle.ml).
*** THE ONE RULE THIS FILE IS BUILT AROUND ***
A golden test blesses whatever it is given. Every other layer derives its
expectation from something independent of colitur's own output -- an
invariant, a sibling engine, a published calendar oracle. This layer's
expectation comes from ME, so every literal string below was checked by
hand, against sources OUTSIDE this codebase, BEFORE it was typed in here
-- never copied from a `colitur day` run and then rationalised:
1. The three Easter dates this file pins (1598-03-22, 1666-04-25,
2038-04-25) were independently computed by hand using the standard
Gauss/Meeus Gregorian Easter algorithm (the same algorithm register
§0's "first Sunday on/after the ecclesiastical full moon on/after 21
March" reduces to), NOT read off Computus.gregorian_easter or the CLI.
All three matched colitur's own output exactly on the first attempt.
2. EVERY weekday asserted below was independently cross-checked against
`date -d <iso-date> +%A` (GNU coreutils, glibc's own proleptic-
Gregorian calendar arithmetic -- a completely separate implementation
from anything in this repository) before being written into a literal
string. All matched.
3. Every precedence/transfer/commemoration outcome was traced by hand
against its RG citation (quoted or paraphrased in each test's own
comment) and against the actual `band`/`disposition`/`admit`/
`transfer_target` logic in precedence_ef.ml, not merely observed to
"look plausible". Where the primary text alone does not fully settle
an outcome (the 2008 Annunciation/Joseph tie, see that test's own
comment), this is said explicitly rather than papered over with a
confident-sounding citation.
*** WHY [describe] BUILDS ONE STRING PER DAY ***
Every assertion below compares ONE formatted line against a single
expected literal, rather than five or six separate field checks. This is
deliberate: a one-line diff on failure still names the day (it is the
first token) and shows exactly which field changed (Alcotest's own diff
highlights the differing substring) -- "prove the pins have teeth" in the
brief's own words -- while keeping each test's body to one literal per
date instead of five, which is what makes 30-odd pinned dates reviewable
at all. [describe]'s own five components (season/week/slug/rank/colour,
commemorations, transferred_in, transferred_out) are exactly the fields
{!Colitur_kernel.Liturgical_day.t} promises never to lose (that type's own
doc comment) -- nothing is cherry-picked to make a case look cleaner than
it is. *)
module Cal = Colitur_kernel.Calendar
module Layer = Colitur_kernel.Layer
module Overlay = Colitur_kernel.Overlay
module LD = Colitur_kernel.Liturgical_day
module Slug = Colitur_kernel.Slug
module Date = Colitur_kernel.Date
module Cel = Colitur_kernel.Celebration
module Colour = Colitur_kernel.Colour
module Temporal = Colitur_kernel.Temporal
module Prec = Colitur_kernel.Precedence
module V = Rite_ef.Vocab_ef
(* Same relative paths every other suite in this directory uses (dune test
runs from _build/default/test/). *)
let sanctoral_path = "../data/ef/sanctoral.sexp"
let adjustments_path = "../data/ef/adjustments.sexp"
(* Loaded once at module init, same convention test_validate.ml's own
[real_ef_layer] uses (not test_oracle.ml/test_differential.ml's
per-test-case reload, which recomputes a whole 46-year or 2-year sweep
per call for reasons specific to those files -- loading the immutable
layer itself has no such per-call cost and nothing here mutates it). *)
let real_ef_layer =
match Layer.load V.rank_of_sexp sanctoral_path with
| Error e -> failwith (Printf.sprintf "%s: failed to load: %s" sanctoral_path e)
| Ok layer -> (
match Overlay.load V.rank_of_sexp adjustments_path with
| Error e -> failwith (Printf.sprintf "%s: failed to load: %s" adjustments_path e)
| Ok overlay ->
let layer, diagnostics = Overlay.apply layer overlay in
if diagnostics <> [] then
failwith
(Printf.sprintf "unexpected overlay diagnostics: %s"
(String.concat "; " (List.map Overlay.diagnostic_to_string diagnostics)));
layer)
let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e
(* [Cal.day] recomputes its whole liturgical year on every call (calendar.mli
-- "pure, no cache"); acceptable here, same as test_rite_ef.ml's own use
of [Cal.year] for a handful of dates, not a hot loop. *)
let fetch y m d = Cal.day Rite_ef.context real_ef_layer (mk y m d)
let slug_s (c : V.rank Cel.t) = Slug.to_string c.Cel.slug
let rank_s (c : V.rank Cel.t) = V.rank_to_string c.Cel.rank
let colour_s (c : V.rank Cel.t) = Colour.to_string c.Cel.colour
let privilege_s = function Prec.Privileged -> "privileged" | Prec.Ordinary -> "ordinary"
(* The five fields {!Colitur_kernel.Liturgical_day.t} promises are never
silently lost, folded into one comparable line -- see the file header for
why one string, not five checks. *)
let describe (day : (V.season, V.rank) LD.t) =
let t = day.LD.temporal in
let week = match t.Temporal.week with Some n -> string_of_int n | None -> "-" in
let comms =
List.map (fun (c, p) -> Printf.sprintf "%s:%s" (slug_s c) (privilege_s p)) day.LD.commemorations
|> List.sort compare |> String.concat ","
in
let tin = match day.LD.transferred_in with None -> "-" | Some c -> slug_s c in
let tout =
List.map (fun (c, d) -> Printf.sprintf "%s->%s" (slug_s c) (Date.to_iso8601 d)) day.LD.transferred_out
|> List.sort compare |> String.concat ","
in
Printf.sprintf "%s %s season=%s week=%s slug=%s rank=%s colour=%s comms=[%s] in=%s out=[%s]"
(Date.to_iso8601 day.LD.date)
(Date.weekday_to_string t.Temporal.weekday)
(V.season_to_string t.Temporal.season)
week (slug_s day.LD.observed) (rank_s day.LD.observed) (colour_s day.LD.observed) comms tin tout
let check ~msg y m d expected = Alcotest.(check string) msg expected (describe (fetch y m d))
let omitted_has (day : (V.season, V.rank) LD.t) slug =
List.exists (fun (c, _) -> slug_s c = slug) day.LD.omitted
(* ------------------------------------------------------------------ *)
(* Easter extremes (register §0; independently Gauss-computed, see file
header point 1). extreme_years in test_validate.ml already pins that
1598/1666 are the true earliest/latest within 1583..2500 -- this file
pins what the RESOLVED DAY looks like there, not merely that Easter
lands on the expected date. *)
(* ------------------------------------------------------------------ *)
(* 1598: earliest Gregorian Easter possible, 22 March -- hand-verified via
Gauss's algorithm (a = 2, b = 15, c = 98, ..., h = 0, l = 0, m = 0 ->
22 March), independent of Computus.gregorian_easter. RG 76 (Paschaltide
begins with the Vigil Mass) + RG 91 entry 1 (Easter Sunday, I class) +
RG 119(b) (white from the Vigil Mass) fix season/rank/colour; the day
before (Holy Saturday, still Passiontide, violet per RG 128) is pinned
alongside it to prove the season boundary itself falls in the right
place, not merely that 22 March is white. *)
let test_easter_extreme_1598 () =
check ~msg:"1598-03-21 Holy Saturday: still Passiontide, violet (RG 128)" 1598 3 21
"1598-03-21 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \
comms=[] in=- out=[]";
check ~msg:"1598-03-22 earliest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1598 3
22
"1598-03-22 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \
out=[]"
(* 1666: latest Gregorian Easter possible, 25 April -- hand-verified via
Gauss's algorithm (a = 13, b = 16, c = 66, ..., h = 29, l = 5, m = 0 ->
25 April). Same citations as 1598 above. *)
let test_easter_extreme_1666 () =
check ~msg:"1666-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 1666 4 24
"1666-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \
comms=[] in=- out=[]";
check ~msg:"1666-04-25 latest possible Easter (Gauss-verified): Paschaltide begins, white, class-1" 1666 4
25
"1666-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \
out=[]"
(* 2038: the brief's "late modern case" -- Easter also 25 April (Gauss-
verified: a = 5, b = 20, c = 38, ..., h = 29, l = 5, m = 0 -> 25 April,
the SAME extreme as 1666, reached independently), chosen because it is a
near-term year rather than a 17th-century one.
DELIBERATE EXCLUSION, not an oversight: register §6's F4 finding
confirms 2038-03-06 (+sts-felicitas-perpetua), 2038-03-08 (+john-of-god)
and 2038-03-09 (+frances-rome) are WRONG -- three sanctoral entries
bootstrapped from lectio carry a Commemoration_only status that should be
a real III-class Feast (a bootstrap-generator defect traced to
missalemeum's own commemoration-id ranks, not fixable without touching
lectio). Pinning those three dates as "correct" would freeze a known bug
into a regression test that then fights its own fix -- the report's own
warning against exactly this. Only the Easter-week days (temporal-cycle
territory, untouched by the March sanctoral bug) are pinned here. *)
let test_easter_extreme_2038_late_modern () =
check ~msg:"2038-04-24 Holy Saturday: still Passiontide, violet (RG 128)" 2038 4 24
"2038-04-24 saturday season=passiontide week=2 slug=ef-passiontide-2-saturday rank=class-1 colour=violet \
comms=[] in=- out=[]";
check ~msg:"2038-04-25 late-modern instance of the latest possible Easter (Gauss-verified)" 2038 4 25
"2038-04-25 sunday season=paschaltide week=1 slug=ef-easter-sunday rank=class-1 colour=white comms=[] in=- \
out=[]"
(* ------------------------------------------------------------------ *)
(* Annunciation transfer, 25 March inside Holy Week (register §4, RG 96
Attamen (a)). 2016: Easter = 27 March (Gauss-independent of this file's
own concern -- already covered by extreme-year hand-checks above; 2016 is
an ordinary year, trusted via the property sweep + the weekday
cross-check below), so 25 March 2016 falls on GOOD FRIDAY itself --
literally inside Holy Week, not merely "impeded by the Easter octave" the
way the 1598/2008/1666 cases are. Independently confirmed: `date -d
2016-03-25 +%A` = Friday, `date -d 2016-03-27 +%A` = Sunday (Easter). *)
let test_annunciation_transfer_inside_holy_week_2016 () =
(* RG 91 entry 2 (Sacred Triduum) outranks entry 11 (an ordinary universal
I-class feast) outright -- Good Friday wins, Annunciation is
Transfer-disposed (RG 95: only I-class feasts transfer). RG 23 (I-class
ferias admit no commemoration except one privileged) leaves nothing on
the day itself: register's own note on RG 23 says this branch "never
actually reaches a live case" for exactly this reason. *)
check ~msg:"2016-03-25 Good Friday: Triduum outranks the Annunciation (RG91 entry 2 > 11), no commemoration"
2016 3 25
"2016-03-25 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet \
comms=[] in=- out=[annunciation-of-the-blessed-virgin-mary->2016-04-04]";
(* The general RG 96 walk from 26 March would still be inside the Triduum,
the Easter octave (all I class, entry 2/10) -- carrying the feast PAST
Easter -- so RG 96 Attamen (a)'s condition fires: sedes propria = Monday
after Low Sunday = Easter + 8 = 27 Mar + 8 = 4 April. *)
check ~msg:"2016-04-04 Easter+8 (Monday after Low Sunday): RG96 Attamen(a) sedes propria fires" 2016 4 4
"2016-04-04 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]"
(* ------------------------------------------------------------------ *)
(* 2008: the double transfer (register §4, "cases worth adding"). Easter =
23 March 2008 (trusted via the property sweep, not independently
Gauss-checked a fourth time -- the algorithm is already hand-verified
above; what this case adds is the TWO-CANDIDATE collision, not a fourth
Easter-date check). Independently confirmed: `date -d 2008-03-19 +%A` =
Wednesday, `-03-31` = Monday, `-04-01` = Tuesday. *)
let test_annunciation_joseph_double_transfer_2008 () =
(* St Joseph (19 March, I class, universal, band 11 -- same table entry as
the Annunciation) falls on Wednesday of Holy Week (RG 91 entry 7: an
I-class feria, band 7, above 11) -- impeded, Transfer-disposed. *)
check ~msg:"2008-03-19 Wednesday of Holy Week outranks Joseph (RG91 entry 7 > 11), no commemoration" 2008 3
19
"2008-03-19 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet \
comms=[] in=- out=[joseph-spouse-of-the-bl-virgin-mary->2008-04-01]";
(* Both Joseph and the Annunciation are band 11 (register: neither is
Immaculate Conception/Assumption, band 4; both are ordinary universal
I-class feasts). RG 96 Attamen (a) is CONDITIONAL, so it is evaluated
for BOTH: the Annunciation's own general walk from 26 March would cross
the Easter octave and land past Easter, so its exception fires --
Easter + 8 = 23 Mar + 8 = 31 March, its NAMED sedes propria. Joseph has
no such named exception; its own general walk (from 20 March) also
crosses the octave and, absent Annunciation, would land on the SAME 31
March. WEAKER-THAN-IT-LOOKS, stated honestly: RG 97-98's plain text
("the higher in the table is kept") does not itself distinguish two
candidates tied at the SAME table entry (11) the way it does for a
genuine dignity difference -- the engine breaks this specific tie by
slug (calendar.ml's own [compare_deferred]: alphabetical, an
engineering convention, not itself an RG citation), and
"annunciation-of-the-blessed-virgin-mary" < "joseph-spouse-of-the-bl-
virgin-mary" is exactly why Annunciation claims 31 March first in
[place_transfers]'s per-round sort. A textual argument beyond the bare
tie-break does exist -- the Annunciation's own Attamen clause names its
target as a "sedes propria" (proper seat), a positively-assigned day,
where Joseph is merely wherever the generic RG96 search happens to
land -- but this is this report's own reading of the primary text, not
something the register states outright, so it is offered as
corroboration, not as the citation carrying the outcome. *)
check ~msg:"2008-03-31 Easter+8: Annunciation's named sedes propria (RG96 Attamen a) claims the day first"
2008 3 31
"2008-03-31 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]";
(* Joseph's own general RG96 walk, finding 31 March already claimed
(occupant reports Class1 there, still blocking per [is_blocking]),
continues one more day to 1 April -- RG 97-98's "in order": the second
I-class candidate to reach an already-occupied target keeps searching
rather than displacing the first. *)
check ~msg:"2008-04-01: Joseph's RG96 walk continues past the now-claimed 31 March (RG97-98 'in order')" 2008
4 1
"2008-04-01 tuesday season=paschaltide week=2 slug=joseph-spouse-of-the-bl-virgin-mary rank=class-1 \
colour=white comms=[] in=joseph-spouse-of-the-bl-virgin-mary out=[]"
(* ------------------------------------------------------------------ *)
(* RG 96 Attamen (a) is CONDITIONAL ("quando est transferendum post
Pascha") -- pinning both sides so the condition itself, not merely the
exception's existence, is under test. All six weekdays independently
confirmed via `date -d`. *)
(* ------------------------------------------------------------------ *)
(* Before Easter: 2057 (Easter 22 Apr), 2007 and 2012 (both Easter 8 Apr).
In each, 25 March is a Sunday impeding the Annunciation (Lent III in
2057; Passion Sunday, Dominica I Passionis, in 2007/2012), and the
GENERAL RG96 walk lands on 26 March -- an ordinary III-class feria, band
22/25, well before Easter -- so the condition never fires: no named
exception, just the plain walk. Each arrival day's own displaced feria is
RG25/RG109(e)-privileged (an ordinary Lent/Passiontide feria, impeded,
must be commemorated) -- a second, independent confirmation of the
RG24/25/RG109(e) mandate this file also pins directly below at 1900/1902,
in a different code path (an ARRIVING transferred feast's displaced
office, not a plain sanctoral winner's). *)
let test_annunciation_exception_not_triggered_general_walk_suffices () =
check ~msg:"2057-03-26: general RG96 walk suffices (target before Easter, 22 Apr) -- no Attamen(a) exception"
2057 3 26
"2057-03-26 monday season=lent week=3 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[ef-lent-3-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]";
check ~msg:"2007-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception"
2007 3 26
"2007-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]";
check ~msg:"2012-03-26: general RG96 walk suffices (target before Easter, 8 Apr) -- no Attamen(a) exception"
2012 3 26
"2012-03-26 monday season=passiontide week=1 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[ef-passiontide-1-monday:privileged] in=annunciation-of-the-blessed-virgin-mary out=[]"
(* After Easter: 2016 (already pinned above, Easter+8 = 4 April) and 2024
(Easter 31 March, Easter+8 = 8 April). In both, the general walk would
cross Easter, so Attamen(a) fires and lands the feast on the Monday after
Low Sunday exactly. Both arrival days are Paschaltide-2 (Class4
ferias) -- RG26 (IV-class ferias never commemorated), so unlike the
"before" trio above, no commemoration is left behind; a second,
independent confirmation of RG26 in yet another code path. *)
let test_annunciation_exception_triggered_when_walk_would_cross_easter () =
check
~msg:
"2024-04-08: Easter+8 (Easter=31 Mar): RG96 Attamen(a) fires, arrival day is Class4 so no commemoration \
(RG26)"
2024 4 8
"2024-04-08 monday season=paschaltide week=2 slug=annunciation-of-the-blessed-virgin-mary rank=class-1 \
colour=white comms=[] in=annunciation-of-the-blessed-virgin-mary out=[]"
(* 2016's own pin (test_annunciation_transfer_inside_holy_week_2016) is the
other half of this pair -- not repeated here to avoid asserting the same
date twice. *)
(* ------------------------------------------------------------------ *)
(* 2011-07-04, the Precious Blood (register §4/§6, differential C10).
Sacred Heart (Easter + 68, RG 91 entry 3) coincides with the fixed 1 July
feast of the Most Precious Blood (entry 11) in 2011 (Easter = 24 April,
so Easter + 68 = 1 July). Entry 3 outranks entry 11 outright (RG 97-98:
the higher in the table is kept) -- no tie, unlike the 2008 case above.
The general RG96 walk from 2 July must skip Visitation (2 July, II
class, entry 19) and the ordinary Sunday (3 July, entry 15, II class)
before landing on 4 July, an ordinary Class4 feria. All four weekdays
independently confirmed via `date -d`. *)
let test_precious_blood_transfer_2011 () =
check ~msg:"2011-07-01: Sacred Heart (RG91 entry 3) outranks the fixed Precious Blood (entry 11) outright"
2011 7 1
"2011-07-01 friday season=time-after-pentecost week=2 slug=ef-sacred-heart rank=class-1 colour=white \
comms=[] in=- out=[precious-blood-of-our-lord-jesus-christ->2011-07-04]";
check ~msg:"2011-07-02: the RG96 walk's first blocked day (Visitation, II class) -- Precious Blood not here"
2011 7 2
"2011-07-02 saturday season=time-after-pentecost week=2 slug=visitation-of-the-blessed-virgin-mary \
rank=class-2 colour=white comms=[processus-and-martinian:ordinary] in=- out=[]";
check ~msg:"2011-07-03: the RG96 walk's second blocked day (an ordinary Sunday, II class) -- still not here"
2011 7 3
"2011-07-03 sunday season=time-after-pentecost week=3 slug=ef-time-after-pentecost-sunday-3 rank=class-2 \
colour=green comms=[] in=- out=[]";
check ~msg:"2011-07-04: the first non-I/II-class day (RG96) -- the Precious Blood lands here" 2011 7 4
"2011-07-04 monday season=time-after-pentecost week=3 slug=precious-blood-of-our-lord-jesus-christ \
rank=class-1 colour=red comms=[] in=precious-blood-of-our-lord-jesus-christ out=[]"
(* ------------------------------------------------------------------ *)
(* All Souls falling on a Sunday (register §4, RG96 Attamen (b), primary-
source-verified: "quando occurrit cum dominica, transfertur ... in feriam
II sequentem"). 2025: All Souls (2 Nov) falls on an ordinary Sunday.
Weekdays independently confirmed via `date -d`. *)
let test_all_souls_on_a_sunday_2025 () =
(* RG91 entry 8's own parenthetical ("yields to an occurring Sunday") plus
Attamen (b): the Sunday is simply observed, and RG94 ("a fixed-day
commemoration is not carried with a transferred feast") means the
transferred All Souls leaves nothing behind on the Sunday itself. *)
check ~msg:"2025-11-02: All Souls yields to the occurring Sunday (RG91 entry 8), no commemoration (RG94)" 2025
11 2
"2025-11-02 sunday season=time-after-pentecost week=21 slug=ef-time-after-pentecost-sunday-21 rank=class-2 \
colour=green comms=[] in=- out=[commemoration-of-all-souls->2025-11-03]";
check ~msg:"2025-11-03: All Souls transfers to the following Monday, its sedes propria (RG96 Attamen b)" 2025
11 3
"2025-11-03 monday season=time-after-pentecost week=21 slug=commemoration-of-all-souls rank=class-1 \
colour=black comms=[] in=commemoration-of-all-souls out=[]"
(* ------------------------------------------------------------------ *)
(* Christmas falling on a Sunday (RG91 entry 1: Nativity, Easter, Pentecost
are I class WITH THEIR OWN OCTAVE, listed first in the whole table --
temporal_ef.ml assigns 25 December the Nativity office unconditionally,
so unlike a Feast of the Lord merely REPLACING an occurring Sunday (RG
16(a), register §6), there is no Sunday-of-Advent/-per-annum candidate to
even contest here: 25 December is never, in the EF temporal cycle, an
"ordinary Sunday" in the first place. 2022: `date -d 2022-12-25 +%A` =
Sunday, independently confirmed. *)
let test_christmas_on_a_sunday_2022 () =
check ~msg:"2022-12-25 falling on a Sunday: the Nativity (RG91 entry 1) takes the day outright, no contest"
2022 12 25
"2022-12-25 sunday season=christmastide week=- slug=ef-nativity rank=class-1 colour=white comms=[] in=- \
out=[]"
(* ------------------------------------------------------------------ *)
(* Holy Thursday is white (register §3b, RG 128(b) + RG 122, Task 16) while
the surrounding Passiontide days stay violet -- the case the brief flags
because colitur and lectio previously AGREED on violet (both wrong), so
the lectio differential was structurally incapable of ever catching this;
only a golden pin (or the missalemeum oracle, layer 4) can. 2026: Easter
5 April (trusted via the property sweep). Weekdays independently
confirmed via `date -d`. *)
let test_holy_thursday_is_white_2026 () =
check ~msg:"2026-04-01 (Wednesday of Holy Week): ordinary Passiontide violet (RG128)" 2026 4 1
"2026-04-01 wednesday season=passiontide week=2 slug=ef-passiontide-2-wednesday rank=class-1 colour=violet \
comms=[] in=- out=[]";
check
~msg:
"2026-04-02 Holy Thursday: white (RG128(b)'s named exception + RG122's affirmative statement), not \
Passiontide's violet"
2026 4 2
"2026-04-02 thursday season=passiontide week=2 slug=ef-passiontide-2-thursday rank=class-1 colour=white \
comms=[] in=- out=[]";
check ~msg:"2026-04-03 Good Friday: back to violet (RG128) -- Thursday's white is a one-day exception" 2026 4
3
"2026-04-03 friday season=passiontide week=2 slug=ef-passiontide-2-friday rank=class-1 colour=violet \
comms=[] in=- out=[]"
(* ------------------------------------------------------------------ *)
(* Advent/Lent Ember ferias are commemorated when impeded (RG 24: "si vero
impediuntur, commemorari debent" -- MUST be commemorated) while IV-class
ferias never are (RG 26: "quae nunquam commemorantur") -- the fix-round-1
F1/F2 finding, a Critical bug caught late precisely because no golden pin
existed for it before now. Weekdays independently confirmed via
`date -d`. *)
(* ------------------------------------------------------------------ *)
let test_ember_ferias_commemorated_when_impeded () =
(* 1900-12-21: Advent Ember Friday (RG91 entry 18, II class) impeded by St
Thomas (II class, universal, entry 16 -- Thomas outranks an II-class
feria only because entry 16 < 18 in the table). RG24 makes the Ember
feria's commemoration MANDATORY, and RG109(e) (corrected, fix round 1)
makes it PRIVILEGED, not merely eligible. *)
check ~msg:"1900-12-21: Advent Ember Friday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)"
1900 12 21
"1900-12-21 friday season=advent week=3 slug=thomas rank=class-2 colour=red \
comms=[ef-advent-ember-fri:privileged] in=- out=[]";
(* 1902-02-22: Lent Ember Saturday (RG91 entry 18, II class) impeded by
the Chair of St Peter (II class, universal, entry 16). Same RG24/
RG109(e) mandate. *)
check ~msg:"1902-02-22: Lent Ember Saturday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)"
1902 2 22
"1902-02-22 saturday season=lent week=1 slug=chair-of-st-peter rank=class-2 colour=white \
comms=[ef-lent-ember-sat:privileged] in=- out=[]"
let test_iv_class_ferias_never_commemorated () =
(* 2026-07-22: an ordinary Time-after-Pentecost feria (Class4, RG91 entry
28) impeded by St Mary Magdalene (III class). RG26 ("IV-class ferias
are NEVER commemorated") means the displaced feria is dropped outright
-- present in [omitted], never in [commemorations]. Asserting only
"comms=[]" would be vacuous if there had been no losing candidate at
all (one of this project's own catalogued vacuity flavours); the
[omitted_has] check below proves a real candidate existed and was
actively excluded, not merely absent. *)
let d1 = fetch 2026 7 22 in
Alcotest.(check bool) "2026-07-22: the displaced IV-class feria is in [omitted], proving RG26 actually fired"
true
(omitted_has d1 "ef-time-after-pentecost-8-wednesday");
Alcotest.(check string) "2026-07-22: Mary Magdalene observed, no commemoration (RG26)"
"2026-07-22 wednesday season=time-after-pentecost week=8 slug=mary-magdalene rank=class-3 colour=white \
comms=[] in=- out=[]"
(describe d1);
let d2 = fetch 2026 8 10 in
Alcotest.(check bool) "2026-08-10: the displaced IV-class feria is in [omitted], proving RG26 actually fired"
true
(omitted_has d2 "ef-time-after-pentecost-11-monday");
Alcotest.(check string) "2026-08-10: St Lawrence observed, no commemoration (RG26)"
"2026-08-10 monday season=time-after-pentecost week=11 slug=lawrence rank=class-2 colour=red comms=[] in=- \
out=[]"
(describe d2)
(* ------------------------------------------------------------------ *)
(* RG 111(b): a II-class SUNDAY admits only a commemoration "de festo II
classis" -- a rank restriction, not "the best available ordinary
candidate". 9 August falling on a Sunday: `romanus` (Class3,
commemoration-only, register §6 notes his very existence is
questionable, but that is irrelevant here -- RG111(b) excludes him on
RANK alone, regardless of that open question) has no standing for the
day's single slot. The brief names four recurring years (2009, 2015,
2020, 2026); two are pinned here (2009 for an older-era instance, 2026
for a current one) -- 2015/2020 were hand-checked via the CLI and found
identical in shape (same slug family, same `romanus` exclusion, only the
week number differs), so pinning all four would repeat the same
assertion four times without exercising a different code path; rejected
as duplicative. *)
let test_ii_class_sunday_admits_only_ii_class_commemoration () =
let d2009 = fetch 2009 8 9 in
Alcotest.(check bool) "2009-08-09: romanus (Class3) is offered and excluded, proving RG111(b) actually fired"
true
(omitted_has d2009 "romanus");
Alcotest.(check string) "2009-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)"
"2009-08-09 sunday season=time-after-pentecost week=10 slug=ef-time-after-pentecost-sunday-10 rank=class-2 \
colour=green comms=[] in=- out=[]"
(describe d2009);
let d2026 = fetch 2026 8 9 in
Alcotest.(check bool) "2026-08-09: romanus (Class3) is offered and excluded, proving RG111(b) actually fired"
true
(omitted_has d2026 "romanus");
Alcotest.(check string) "2026-08-09: the Sunday observed, no commemoration at all (RG111(b) rank floor)"
"2026-08-09 sunday season=time-after-pentecost week=11 slug=ef-time-after-pentecost-sunday-11 rank=class-2 \
colour=green comms=[] in=- out=[]"
(describe d2026)
(* ------------------------------------------------------------------ *)
(* RG 16(a) (register §6.0, Caput III "De Dominicis", primary text quoted in
this task's own report): "festum Domini I aut II classis, in dominica II
classis occurrens, ... de dominica, proinde, nulla fit commemoratio" -- a
Feast of the Lord, I or II class, impeding a II-class Sunday, takes the
Sunday's own place with no commemoration of the Sunday at all, unlike
every OTHER impeded II-class Sunday (RG 109(a)/RG 111(b), the
[test_ii_class_sunday_admits_only_ii_class_commemoration] pin above).
Two real dates, both directions, exactly the pair the task brief asks
for. *)
(* ------------------------------------------------------------------ *)
(* 2028-08-06: the Transfiguration (6 Aug, II class, calendarium "IN
TRANSFIGURATIONE D. N. I. C.") falls on an ordinary Time-after-Pentecost
Sunday. `date -d 2028-08-06 +%A` = Sunday, independently confirmed. Also
the exact date register §6.0's own reproduction names as a live instance
of the bug this pin closes -- before this fix `colitur day` admitted
"+ef-time-after-pentecost-sunday-9" here.
CORRECTED, fix round 1 (IMPORTANT finding, item 2): this pin used to
expect Pope Sixtus II et al. (6 August's own unrelated Class3 saint,
Commemoration_only) admitted as an ordinary commemoration in the freed
slot -- promoted from `colitur day`'s own actual output without
consulting either the primary text or the oracle first (the exact
vacuous-pin flavour the review caught, and this file's own header warns
against). Re-derived properly this time:
- PRIMARY TEXT: RG 16(a) itself says the winning Feast of the Lord holds
the Sunday's place cum omnibus iuribus et privilegiis -- WITH ALL the
Sunday's own rights and privileges. RG 111(b) is one of those: in
dominicis II classis, una tantum admittitur commemoratio, SCILICET DE
FESTO II CLASSIS -- a Sunday's own slot is reserved for a Class2
candidate specifically. The day is still a dominica II classis for
this purpose even though the Transfiguration, not the Sunday, is
[observed] -- so Sixtus (Class3) has no standing for the slot at all,
the same as on any other II-class Sunday (contrast
[test_ii_class_sunday_admits_only_ii_class_commemoration] above,
romanus, Class3, excluded the identical way on an ORDINARY II-class
Sunday).
- ORACLE, independently fetched (missalemeum's own /api/v5/calendar
JSON, not the small-model webpage summary, which proved unreliable for
this exact question): 2023-08-06 (a Sunday) -- title Transfiguration of
Our Lord, commemorations empty, displaced Pope Sixtus II, Felicissimus
and Agapitus, Martyrs -- Sixtus displaced, not commemorated. Control,
2026-08-06 (a Thursday, no Sunday collision) -- Sixtus present in
commemorations. Being a Sunday is exactly what excludes him.
Deliberately NOT a bare "comms=[]" pin (this project's own catalogued
vacuity flavour: a count/emptiness assertion that would pass even if
nothing had ever been offered): [omitted_has] first proves the Sunday
really WAS a candidate that reached [disposition] and was actively
excluded, not merely never constructed. *)
let test_rg16a_lord_feast_no_sunday_commemoration_2028 () =
let d = fetch 2028 8 6 in
Alcotest.(check bool)
"2028-08-06: the impeded Sunday is in [omitted], proving RG16(a) actively fired (not merely never offered)"
true
(omitted_has d "ef-time-after-pentecost-sunday-9");
Alcotest.(check string)
"2028-08-06: the Transfiguration observed; NO commemoration at all -- RG16(a)'s own \"cum omnibus iuribus \
et privilegiis\" keeps the day a dominica II classis for RG111(b), so Sixtus (Class3) has no standing \
either, oracle-confirmed"
"2028-08-06 sunday season=time-after-pentecost week=9 slug=transfiguration-of-our-lord rank=class-2 \
colour=white comms=[] in=- out=[]"
(describe d)
(* 2025-02-02, the other real instance of the SAME pattern -- not a
contrasting second direction, per this task's fix round 1 (CRITICAL
finding, item 1, reverted; see register §6.0 for the full account).
The Purification (2 Feb, calendarium "IN PURIFICATIONE B. MARIAE VIRG.")
stays [subject = Lord] -- the user has ruled: follow the oracle.
missalemeum treats it exactly as RG 16(a)'s own "festum Domini" case
(independently fetched, `/api/v5/calendar` JSON): 2020-02-02 (a Sunday)
-- `{"title":"Purification of the Blessed Virgin Mary","tags":["IV
Sunday after Epiphany"],...,"commemorations":[],"displaced":[]}` -- the
Sunday named only as a TAG (the underlying temporal placement), not
commemorated at all; matches 2014-02-02 exactly. Contrast, the SAME
oracle on an ordinary Marian feast (2019-09-08, the Nativity of the
BVM): `{"title":"XIII Sunday after Pentecost",...,"commemorations":
[{"title":"Nativity of the Blessed Virgin Mary",...}],...}` -- the
Sunday observed, the feast merely commemorated, the OPPOSITE pattern --
proving missalemeum does not treat every Marian feast this way, only the
Purification specifically. `date -d 2025-02-02 +%A` = Sunday,
independently confirmed.
Unlike 2028-08-06, no second sanctoral candidate exists on 2 February
(data/ef/sanctoral.sexp has exactly one 2 February entry) -- a genuinely
clean "comms=[]" case, complementing the pair above rather than
duplicating it. *)
let test_purification_on_a_sunday_2025 () =
check
~msg:
"2025-02-02: the Purification observed (subject Lord, unretagged -- the oracle's own treatment); NO \
commemoration of the Sunday, oracle-confirmed"
2025 2 2
"2025-02-02 sunday season=time-after-epiphany week=4 slug=purification-of-the-blessed-virgin-mary \
rank=class-2 colour=white comms=[] in=- out=[]"
let suite =
( "golden pins (known-tricky years)",
[ Alcotest.test_case "Easter extreme: 1598 earliest (22 Mar, Gauss-verified)" `Quick
test_easter_extreme_1598;
Alcotest.test_case "Easter extreme: 1666 latest (25 Apr, Gauss-verified)" `Quick
test_easter_extreme_1666;
Alcotest.test_case "Easter extreme: 2038 late-modern instance of the latest date" `Quick
test_easter_extreme_2038_late_modern;
Alcotest.test_case "Annunciation transfer: 25 March falls inside Holy Week (2016)" `Quick
test_annunciation_transfer_inside_holy_week_2016;
Alcotest.test_case "Annunciation/Joseph double transfer (2008)" `Quick
test_annunciation_joseph_double_transfer_2008;
Alcotest.test_case "Annunciation RG96 exception NOT triggered: general walk suffices (2057, 2007, 2012)"
`Quick test_annunciation_exception_not_triggered_general_walk_suffices;
Alcotest.test_case "Annunciation RG96 exception triggered: walk would cross Easter (2024; see also 2016)"
`Quick test_annunciation_exception_triggered_when_walk_would_cross_easter;
Alcotest.test_case "2011-07-04: the Precious Blood transfer" `Quick test_precious_blood_transfer_2011;
Alcotest.test_case "All Souls falling on a Sunday (2025)" `Quick test_all_souls_on_a_sunday_2025;
Alcotest.test_case "Christmas falling on a Sunday (2022)" `Quick test_christmas_on_a_sunday_2022;
Alcotest.test_case "Holy Thursday is white amid violet Passiontide (2026)" `Quick
test_holy_thursday_is_white_2026;
Alcotest.test_case "Advent/Lent Ember ferias commemorated when impeded (1900, 1902)" `Quick
test_ember_ferias_commemorated_when_impeded;
Alcotest.test_case "IV-class ferias never commemorated (2026)" `Quick test_iv_class_ferias_never_commemorated;
Alcotest.test_case "RG111(b): a II-class Sunday admits only a de-festo-II-classis commemoration (2009, 2026)"
`Quick test_ii_class_sunday_admits_only_ii_class_commemoration;
Alcotest.test_case
"RG16(a): a Feast of the Lord leaves the impeded Sunday with NO commemoration at all, not even an \
unrelated saint's own (2028-08-06, rubric + oracle)"
`Quick test_rg16a_lord_feast_no_sunday_commemoration_2028;
Alcotest.test_case
"RG16(a), the Purification (oracle-ruled): observed outright, the Sunday not commemorated (2025-02-02)"
`Quick test_purification_on_a_sunday_2025
] )
|