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
|
(* Coordinator review (Task 11 fix round): integration tests wiring
[Rite_ef.context] together with the REAL data/ef/sanctoral.sexp +
data/ef/adjustments.sexp through [Colitur_kernel.Calendar] -- the same
pipeline `colitur day` uses, proven here at the OCaml level so these
properties are pinned by the test suite, not merely observable in CLI
output (which is exactly what finding 3 flagged: with the overlay's one
directive replaced by [()], `colitur day 2026`'s stdout is byte-identical
for all 365 days, since [ef-nativity-vigil] always outranks
[vigil-of-christmas] regardless -- the suppression's only observable
effect is on [Liturgical_day.omitted], which no cram assertion reads). *)
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 Date_spec = Colitur_kernel.Date_spec
module Cel = Colitur_kernel.Celebration
module Colour = Colitur_kernel.Colour
module Subject = Colitur_kernel.Subject
module P = Colitur_kernel.Precedence
module Comp = Colitur_kernel.Computus
module V = Rite_ef.Vocab_ef
module T = Rite_ef.Temporal_ef
module PE = Rite_ef.Precedence_ef
(* Relative to this test's own build directory (_build/default/test/), same
convention test_sanctoral_ef.ml uses -- test/dune declares both as deps
of the (test ...) stanza. *)
let sanctoral_path = "../data/ef/sanctoral.sexp"
let adjustments_path = "../data/ef/adjustments.sexp"
let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e
let real_layer () =
let layer =
match Layer.load V.rank_of_sexp sanctoral_path with
| Ok l -> l
| Error e -> Alcotest.failf "%s: failed to load: %s" sanctoral_path e
in
let overlay =
match Overlay.load V.rank_of_sexp adjustments_path with
| Ok o -> o
| Error e -> Alcotest.failf "%s: failed to load: %s" adjustments_path e
in
let layer, diagnostics = Overlay.apply layer overlay in
Alcotest.(check (list string)) "the committed overlay applies cleanly, no diagnostics" []
(List.map Overlay.diagnostic_to_string diagnostics);
layer
(* [Rite_ef.context] takes [~lectionary] (fix round 1, coordinator review) --
caller-supplied, same as [real_layer] above. *)
let real_lectionary () =
match Colitur_kernel.Lectionary.load "../data/ef/lectionary.sexp" with
| Ok l -> l
| Error e -> Alcotest.failf "../data/ef/lectionary.sexp: failed to load: %s" e
(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam
as the lectionary above, and [~commons] is required rather than defaulted
so that no caller can silently run with none -- nothing in layers 3-5
compares reading citations, so a rite quietly missing its Commons would
be invisible. Loaded here even where this file asserts nothing about
readings, so that the rite under test is the same one bin/main.ml
assembles. *)
let real_commons () =
match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with
| Ok c -> c
| Error e -> Alcotest.failf "../data/ef/commons.sexp: failed to load: %s" e
let context () = Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ())
let slug_of (c : V.rank Cel.t) = Slug.to_string c.Cel.slug
(* Finding 3: the suppression's ONLY observable effect is on 24 December's
[omitted] (and, belt-and-braces, on every OTHER field of every OTHER day
too -- vigil-of-christmas must not surface anywhere at all, since the
overlay removes it from the layer before resolution ever runs, unlike an
ordinary occurrence loss). Mutation-verified (see the task report):
substituting `(directives ())` for the real overlay makes this test's
first assertion fail (24 December's [omitted] gains
"vigil-of-christmas"), and confirmed the whole cram suite (test/cli.t)
still passes under that same mutation -- so this closes the "removing
the deliverable breaks no test" gap the coordinator flagged, which cram
alone structurally cannot. *)
let test_vigil_of_christmas_suppressed () =
let layer = real_layer () in
let days = Cal.year (context ()) layer 2026 in
let christmas_eve =
Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (mk 2026 12 24) = 0)
in
Alcotest.(check string) "ef-nativity-vigil is still the observed day" "ef-nativity-vigil"
(slug_of christmas_eve.LD.observed);
Alcotest.(check (list string))
"24 December has nothing omitted -- vigil-of-christmas never enters the RG91 contest at all"
[]
(List.map (fun (c, _) -> slug_of c) christmas_eve.LD.omitted);
let appears_anywhere =
Array.to_list days
|> List.exists (fun d ->
let is_it c = slug_of c = "vigil-of-christmas" in
is_it d.LD.observed
|| List.exists (fun (c, _) -> is_it c) d.LD.commemorations
|| List.exists (fun (c, _) -> is_it c) d.LD.omitted
|| (match d.LD.transferred_in with Some c -> is_it c | None -> false)
|| List.exists (fun (c, _) -> is_it c) d.LD.transferred_out)
in
Alcotest.(check bool) "vigil-of-christmas appears NOWHERE in the resolved year" false
appears_anywhere
(* RG16(a) task (register §6.0 + the sanctoral subject audit, this task's
own report). data/ef/sanctoral.sexp is a faithful mirror of lectio, not
hand-patched (see test_sanctoral_ef.ml's own "explicit class = lord"
spot-check, which loads that file WITHOUT applying this overlay and
still passes) -- the one live correction, `most-holy-name-of-mary` ->
`Bvm`, lives entirely in data/ef/adjustments.sexp's [Edit] directive.
CORRECTED, ef-rebootstrap (2026-08-12): this comment previously said the
base file itself carries `subject Lord` for `most-holy-name-of-mary`,
which this overlay then corrects. That stopped being true the moment
lectio's own generator was fixed (source SHA-256 1b303ef2...): the
regenerated INI dropped `class = lord` for that slug outright, so the
base bootstrap now already produces `Saint`, and this overlay's [Edit]
forces `Saint -> Bvm` instead of `Lord -> Bvm` -- same final answer,
different starting point. See adjustments.sexp's own "ef-rebootstrap"
note for why the directive is kept anyway (data precision + a
regression guard against `class = lord` being reintroduced upstream).
CORRECTED, fix round 1 (CRITICAL finding, item 1): this test used to
also assert `purification-of-the-blessed-virgin-mary` was retagged
`Bvm`. REVERTED -- the user has ruled: follow the oracle (register §6.0
has the full account, both the calendarium argument for `Bvm` and the
oracle evidence against it). The Purification is `Lord` again, one of
FIVE untouched entries this test now re-verifies, not four.
This is the ONE test in the suite that observes the one live retag
actually takes effect through the real pipeline -- without it, a typo in
the directive's slug (silently a no-op diagnostic, not a hard failure --
Overlay.apply's own contract) or a reverted adjustments.sexp would leave
every other test green ([test_sanctoral_ef.ml] deliberately never
applies the overlay). Also re-verifies the five entries the audit
confirmed correct and left untouched, so a future accidental retag of
one of THEM would be caught here too, not just the one that changed. *)
let test_rg16a_subject_retag_in_effect () =
let layer = real_layer () in
let subject_of slug =
match Layer.find layer (Slug.of_string_exn slug) with
| Some e -> e.Layer.cel.Cel.subject
| None -> Alcotest.failf "slug %s not found in the overlaid layer" slug
in
Alcotest.(check bool) "Most Holy Name of Mary retagged Bvm (calendarium: \"Nominis Mariae\")" true
(subject_of "most-holy-name-of-mary" = Subject.Bvm);
Alcotest.(check bool)
"Purification stays Lord (reverted, fix round 1 -- the user's ruling: follow the oracle)" true
(subject_of "purification-of-the-blessed-virgin-mary" = Subject.Lord);
Alcotest.(check bool) "Precious Blood stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true
(subject_of "precious-blood-of-our-lord-jesus-christ" = Subject.Lord);
Alcotest.(check bool) "Transfiguration stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true
(subject_of "transfiguration-of-our-lord" = Subject.Lord);
Alcotest.(check bool) "Exaltation of the Holy Cross stays Lord -- audit, not touched" true
(subject_of "exaltation-of-the-holy-cross" = Subject.Lord);
Alcotest.(check bool)
"Dedication of the Archbasilica of the Most Holy Saviour stays Lord -- audit, not touched" true
(subject_of "dedication-of-the-archbasilica-of-our-holy-savior" = Subject.Lord)
(* ef-rebootstrap fix round 1, F3: eusebius-confessor (14 Aug, one of the 5
entries this re-bootstrap added) is bootstrapped `Red` -- a lectio data
defect (both lectio's own ini and missalemeum's `:r` colour tag agree
with each other, not with the Missal). `missale-romanum-1962.pdf`'s own
proper for the day, verified directly against the scan (not transcribed
from a reviewer's reading): "S. Eusebii Conf. / Commemoratio / Missa
Iustus, ut in festo S. Pauli primi Eremitae..." -- a CONFESSOR ("Conf."),
borrowing the SAME Mass 15 January's own Paul the First Hermit uses, a
white Confessor's Common in both engines' own data. Fixed via
adjustments.sexp's new Edit directive -- proven here through the real
pipeline, the same discipline [test_rg16a_subject_retag_in_effect] above
already applies to the subject retags, so a typo'd slug or a reverted
directive fails a test rather than only silently changing unread data. *)
let test_eusebius_confessor_colour_corrected () =
let layer = real_layer () in
match Layer.find layer (Slug.of_string_exn "eusebius-confessor") with
| None -> Alcotest.fail "slug eusebius-confessor not found in the overlaid layer"
| Some e ->
Alcotest.(check bool)
"Eusebius Confessor is White (RG's own Common of Confessors, via the borrowed Mass \"Iustus\"), not \
lectio's bootstrapped Red"
true
(e.Layer.cel.Cel.colour = Colour.White)
(* ef-sanctoral-audit task (2026-08-14): a full extraction of the primary
CALENDARIUM (both photographic scans, page images read directly, not
their OCR text layers) against all 327 base entries plus the two prior
overlay Adds -- data/ef/adjustments.sexp's own audit-block comment has
the full method and RG 124 citation; docs/research/rules-register.md's
audit rewrite and .superpowers/sdd/2026-08-12-colitur-rg16a/sanctoral-
audit-report.md have the full findings. Same discipline as
[test_eusebius_confessor_colour_corrected] above (its own predecessor,
same root cause): a typo'd slug or a reverted directive fails THIS test,
not only silently changes unread data. One table, all eleven [Feast]
colour corrections plus the seven [Commemoration_only] ones (inert to
[Precedence_ef.band]/[admit] today -- colour is read nowhere in either,
confirmed by grep -- but real for `colitur day`'s own printed output the
moment either entry is ever observed/commemorated). *)
let test_audit_colours_corrected () =
let layer = real_layer () in
let check_colour slug expected reason =
match Layer.find layer (Slug.of_string_exn slug) with
| None -> Alcotest.failf "slug %s not found in the overlaid layer" slug
| Some e ->
Alcotest.(check bool) (Printf.sprintf "%s: %s" slug reason) true (e.Layer.cel.Cel.colour = expected)
in
(* RG 124(e): a Martyr's own martyrdom is red -- these SIX were White.
Three more were set Red by the audit and REVERTED by the fix-round
review: conversion-of-st-paul and chair-of-st-peter are NAMED, by name
and by date, in RG 120(f)'s own enumeration of WHITE, fifteen lines
above the RG 124(b) the audit cited; and `peter` (25 Jan) rested on
"match the host day's Red", whose host is white by that same clause.
They are asserted White below, where the reverted values belong. *)
List.iter
(fun (slug, reason) -> check_colour slug Colour.Red reason)
[ ("prisca", "Virg. et Mart., 18 Jan -- RG124(e); lectio's own NAME dropped 'et Mart.', so its name is the defective field, not its colour");
("vitus", "Ss. ... Mm., 15 Jun -- RG124(e)");
("margaret", "Virg. et Mart., 20 Jul (comm) -- RG124(e)");
("apollinaris", "Ep. et Mart., 23 Jul -- RG124(e)");
("agapitus", "Mart., 18 Aug -- RG124(e)");
("josaphat", "Ep. et Mart., 14 Nov -- RG124(e)") ];
(* RG 120(e), "Sanctorum non Martyrum" -- a POSITIVE rule, four lines
above the one the audit misapplied. These were cited to "RG 117's
five-colour residual", a negative inference from the red list's
exhaustiveness; the outcome was right and the citation was weaker than
the Missal's own. The first three are the fix-round reverts, white by
RG 120(f)'s own naming rather than by (e). *)
List.iter
(fun (slug, reason) -> check_colour slug Colour.White reason)
[ ("conversion-of-st-paul", "25 Jan -- NAMED white by RG120(f), 'Conversionis S. Pauli (25 ianuarii)'");
("chair-of-st-peter", "22 Feb -- NAMED white by RG120(f), 'Cathedrae S. Petri (22 februarii)'");
("peter", "25 Jan commemoration -- its host is white by RG120(f), so the audit's own precedent yields White");
("john-of-san-fecundo", "Conf. only, 12 Jun");
("ephrem-of-syria", "Conf. et Eccl. Doct. only, 18 Jun");
("julia-of-falconieri", "Virg. only, 19 Jun");
("john-gualbert", "Abb. (Conf.) only, 12 Jul");
("camillus-de-lellis", "Conf. only, 18 Jul");
("jerome-emiliani", "Conf. only, 20 Jul");
("liborii", "Ep. et Conf. only, 23 Jul (comm)");
("martha", "Virg. only, 29 Jul");
("alphonsus-liguori", "Ep., Conf. et Eccl. Doct. only, 2 Aug");
("augustine", "Ep., Conf. et Eccl. Doct. only, 28 Aug");
("rose-of-lima", "Virg. only, 30 Aug");
("mark-i", "Papae et Conf. only (Pope Mark I, not the Evangelist), 7 Oct (comm)") ]
(* `barbara` (4 December): a genuine DATA GAP, not a colour correction --
present in both photographic scans as a Commemoratio nested under
Peter Chrysologus's own entry, absent from data/ef/sanctoral.sexp AND
from lectio's own tridentine-calendar.ini (same upstream-gap shape as
the ef-rebootstrap task's five: agnes-secundo/boniface-martyr/
eusebius-confessor/evaristus/theodore). Added via data/ef/
adjustments.sexp's own new `Add` directive. *)
let test_barbara_added () =
let layer = real_layer () in
match Layer.find layer (Slug.of_string_exn "barbara") with
| None -> Alcotest.fail "slug barbara not found in the overlaid layer (ef-sanctoral-audit Add missing?)"
| Some e ->
Alcotest.(check bool) "barbara: 4 December" true
(match e.Layer.date with Date_spec.Fixed { month = 12; day = 4 } -> true | _ -> false);
Alcotest.(check bool) "barbara: Commemoration_only (no separate feast row in the calendarium, a bare \
\"Commemoratio\" nested under Peter Chrysologus)"
true (e.Layer.cel.Cel.status = Cel.Commemoration_only);
Alcotest.(check bool) "barbara: Class3 (the same bootstrap-inference convention every OTHER \
Commemoration_only entry in this file uses -- the calendarium gives no numeral \
for any commemoration, hers included)"
true (e.Layer.cel.Cel.rank = V.Class3);
Alcotest.(check bool) "barbara: Red (RG124(e), \"Virg. et Mart.\")" true
(e.Layer.cel.Cel.colour = Colour.Red);
Alcotest.(check bool) "barbara: appears as a commemoration on 4 December in real output" true
(let days = Cal.year (context ()) layer 2026 in
Array.to_list days
|> List.exists (fun d ->
Date.compare d.LD.date (mk 2026 12 4) = 0
&& List.exists (fun (c, _) -> slug_of c = "barbara") d.LD.commemorations))
(* Coordinator review, finding 2, reproduced through the project's OWN
extension path (an overlay), the same way the reviewer found it: adding
an I-class feast on 25 December (competing against, and losing to, the
real Nativity) forces an RG 96 search starting 26 December -- which, with
the real sanctoral data (Stephen/John/the Innocents, all II class) plus
the temporal cycle's own Nativity-octave-day entries (29-31 Dec, also II
class), is blocking every single day through 31 December 9999. Before the
domain-ceiling fix this raised (Computus: year 10000 out of range);
confirmed by mutation-testing at the precedence_ef.ml unit level (see the
task report) -- this is the same defect reproduced end to end, through
Calendar, with real data, exactly as the review found it. *)
let test_transfer_search_does_not_raise_at_domain_ceiling () =
let layer = real_layer () in
let impeding_entry : V.rank Layer.entry =
{ Layer.date = (match Date_spec.fixed ~month:12 ~day:25 with Ok d -> d | Error e -> failwith e);
cel =
Cel.make ~slug:(Slug.of_string_exn "test-domain-ceiling-impeder") ~rank:V.Class1
~colour:Colour.White ~layer:Rite_ef.Precedence_ef.universal_layer () }
in
let overlay : V.rank Overlay.t =
{ Overlay.id = "test-domain-ceiling"; directives = [ Overlay.Add impeding_entry ] }
in
let layer, _diagnostics = Overlay.apply layer overlay in
(* Must not raise -- the whole point of the fix. *)
let days = Cal.year (context ()) layer 9999 in
Alcotest.(check bool) "year 9999 resolves without raising, even with an impeded Christmas Day"
true (Array.length days > 0);
let impeder_placed_or_recorded =
Array.to_list days
|> List.exists (fun d ->
slug_of d.LD.observed = "test-domain-ceiling-impeder"
|| List.exists (fun (c, _) -> slug_of c = "test-domain-ceiling-impeder") d.LD.omitted)
in
Alcotest.(check bool) "the impeding candidate is accounted for (observed somewhere, or omitted \
with a recorded reason) -- never silently dropped"
true impeder_placed_or_recorded
(* Task 11's re-review finding, closed here: [Precedence_ef.transfer_target]'s
Annunciation/Easter condition (and, more generally, RG 96's whole "not I or
II class" test) is correct only because [Temporal_ef] happens to make
every day from Easter Sunday through Low Sunday (Easter+0..+7) blocking --
Easter itself and Low Sunday via [named], every day between via
[privileged_feria]. Nothing in the type system enforces that; a future
edit narrowing [privileged_feria]'s Easter-octave range would silently let
the RG 96 walk land a translated feast inside the octave. This tests the
CONSEQUENCE (no day in that window ever receives one), not the mechanism
([privileged_feria] itself), so it stays sensitive to any way that
consequence could break, not only the one code path that currently
protects it. *)
let easter_offset (d : Date.t) =
let easter = Comp.gregorian_easter (Date.year d) in
Date.to_rata d - Date.to_rata easter
let in_easter_octave d = let off = easter_offset d in off >= 0 && off <= 7
(* 2005-2050: the project's own differential-testing window (CLAUDE.md),
reused here as a deterministic, non-trivial sample -- 46 liturgical years,
each with several genuine transfers (All Souls onto a Sunday, impeded
universal feasts, and so on; test_precedence_ef.ml's own 21k-day manual
review already confirmed "all 7 slugs that ever transfer have a verified
rubrical cause" over a similar span), so this is not a vacuous sweep over
years where nothing ever transfers. *)
let sample_years =
let rec range a b = if a > b then [] else a :: range (a + 1) b in
range 2005 2050
(* ef-major-litanies task: [major-litanies] (RG 80, Precedence_ef's own
[major_litanies_slug]) is a DELIBERATE, cited exception to this
property, not a bug to catch -- RG 80's own text sends the Major
Litanies to "the following Tuesday" unconditionally in a transfer year,
which is always Easter+2, squarely inside [Easter, Easter+7]
(precedence_ef.ml's own [transfer_target] Litanies branch has the full
citation and the "why not the general RG96 search" argument). Every
OTHER slug this rite ever transfers is still held to the original
property below: RG 96's general walk (and its own named Annunciation
exception) is what this property actually protects, and neither of
those two mechanisms is exempted here -- only this one rite-cited,
hand-verified exception, matched by NAME so a future regression in some
OTHER slug cannot silently hide behind this exemption. *)
let is_major_litanies_slug slug = String.equal slug PE.major_litanies_slug
(* Property 1: no day in the resolved output, across the whole sample, is
EVER a transfer's landing point inside [Easter, Easter+7] -- checked two
ways. [transferred_out]'s own recorded target is what [transfer_target]
itself returned (calendar.ml's [assignment], stored verbatim), so this is
the more direct signal; [transferred_in] is also checked, in case some
future Calendar change ever let the two disagree. *)
let test_no_transfer_lands_in_easter_octave () =
let layer = real_layer () in
let violations = ref [] in
List.iter
(fun y ->
let days = Cal.year (context ()) layer y in
Array.iter
(fun (d : (V.season, V.rank) LD.t) ->
(match d.LD.transferred_in with
| Some c when in_easter_octave d.LD.date && not (is_major_litanies_slug (slug_of c)) ->
violations :=
(Printf.sprintf "%s transferred_in on %s (Easter+%d)" (slug_of c)
(Date.to_iso8601 d.LD.date) (easter_offset d.LD.date))
:: !violations
| _ -> ());
List.iter
(fun (c, target) ->
if in_easter_octave target && not (is_major_litanies_slug (slug_of c)) then
violations :=
Printf.sprintf "%s transferred_out from %s to %s (Easter+%d)" (slug_of c)
(Date.to_iso8601 d.LD.date) (Date.to_iso8601 target) (easter_offset target)
:: !violations)
d.LD.transferred_out)
days)
sample_years;
Alcotest.(check (list string))
"no day (other than the RG80-cited Major Litanies exception) in [Easter, Easter+7] is ever a transfer's \
target, 2005-2050" []
(List.rev !violations)
(* The positive contrast to the property above: RG 80's own exception DOES
fire, exactly twice in the 2005-2050 sample (register: 194/8417 domain-
wide trigger years; this task's own report has the full count), both
landing on Easter+2 as RG 80's text requires -- proving the exemption
above is not merely masking an absent case. 2011 (Easter Monday = 25
April, Easter = 24 April) and 2038 (Easter Sunday = 25 April) are this
file's own worked examples (precedence_ef.ml's [major_litanies_slug]
citation). *)
let test_major_litanies_transfers_inside_easter_octave_exactly_when_rg80_requires () =
let layer = real_layer () in
let landings = ref [] in
List.iter
(fun y ->
let days = Cal.year (context ()) layer y in
Array.iter
(fun (d : (V.season, V.rank) LD.t) ->
List.iter
(fun (c, target) ->
if is_major_litanies_slug (slug_of c) then
landings := (Date.to_iso8601 d.LD.date, Date.to_iso8601 target) :: !landings)
d.LD.transferred_out)
days)
sample_years;
Alcotest.(check (list (pair string string)))
"RG80 fires exactly twice in 2005-2050, both landing on Easter+2"
[ ("2011-04-25", "2011-04-26"); ("2038-04-25", "2038-04-27") ]
(List.rev !landings)
(* Property 2, and the LIVE case: [PE.transfer_target] called directly, with
an origin that genuinely starts the search INSIDE Holy Week -- Holy
Thursday 2026 (Easter - 3), a date no real sanctoral entry in
data/ef/sanctoral.sexp occupies (Holy Week carries none), so this is
deliberately constructed, not found. [occupant] is the REAL
[Temporal_ef.temporal] (not a synthetic stand-in), so the search is driven
by the actual blocking shape [privileged_feria] produces, not a
hand-picked one -- this is genuinely live: search_from walks origin+1
(Good Friday, Easter-2) forward through every remaining day of Holy Week,
all of Easter through Low Sunday (Easter+0..+7, all Class1), and only
stops at Easter+8 (the Monday after Low Sunday), which [named] and
[ferial_rank]/[privileged_feria] agree is Class4 -- confirmed below by
checking the OCCUPANT's own rank there, not asserted blind. Without an
origin inside the window itself, [search_from] could stop before ever
reaching it and this test would prove nothing (the vacuity trap the task
brief names explicitly) -- [test_search_genuinely_enters_the_window] pins
that it does not stop early. *)
let holy_week_origin_2026 =
let easter_2026 = Comp.gregorian_easter 2026 in
Date.add_days easter_2026 (-3)
let real_occupant d = (T.temporal d).Colitur_kernel.Temporal.office
let test_transfer_target_skips_the_whole_easter_octave () =
let easter_2026 = Comp.gregorian_easter 2026 in
let c =
{ P.cel =
Cel.make ~slug:(Slug.of_string_exn "test-impeded-in-holy-week") ~rank:V.Class1
~colour:Colour.White ~subject:Subject.Temporal ~layer:PE.universal_layer ();
origin = P.Sanctoral }
in
let target = PE.transfer_target c holy_week_origin_2026 real_occupant in
Alcotest.(check string) "lands on Easter + 8 (Monday after Low Sunday), past the entire octave"
(Date.to_iso8601 (Date.add_days easter_2026 8)) (Date.to_iso8601 target);
Alcotest.(check bool) "strictly after origin (rite.mli's own obligation)" true
(Date.compare target holy_week_origin_2026 > 0);
Alcotest.(check bool) "not inside [Easter, Easter+7]" false
(let off = Date.to_rata target - Date.to_rata easter_2026 in
off >= 0 && off <= 7)
(* The vacuity check itself: proves the search genuinely walked THROUGH the
window rather than [search_from] having some other reason to stop before
it (e.g. an off-by-one that happened to also land past the octave). Reads
the real occupant's own rank at every day from the origin through
Easter+7 and requires every one of them to be blocking (Class1 or
Class2) -- if any single one were not, [search_from] would have stopped
there instead of at Easter+8, and the test above would be passing for the
wrong reason. *)
let test_search_genuinely_enters_the_window () =
let easter_2026 = Comp.gregorian_easter 2026 in
let rec days_from a b = if Date.compare a b > 0 then [] else a :: days_from (Date.add_days a 1) b in
let walked = days_from (Date.add_days holy_week_origin_2026 1) (Date.add_days easter_2026 7) in
Alcotest.(check bool)
"every day from origin+1 through Easter+7 (the whole span search_from must cross) is blocking"
true
(List.for_all
(fun d ->
match (real_occupant d).Cel.rank with V.Class1 | V.Class2 -> true | V.Class3 | V.Class4 -> false)
walked);
Alcotest.(check bool)
"the walked span is at least 8 days -- the octave alone (Easter..Easter+7), not a one-day hop"
true (List.length walked >= 8)
(* Task B fix round 1 (coordinator finding 1), end to end with REAL data:
22 September 2027 is a September Ember Wednesday -- the OBSERVED day is
the Ember office itself ([band] entry 18, RG 91, beats both sanctoral
candidates), and RG 111(c) ("other II class: one") admits exactly ONE
of the two competing losers, "maurice-and-companions-martyrs"
([Commemoration_only], Class3) and "thomas-of-villanova" ([Feast],
Class3). Before the fix, [Precedence_ef.band] gave Maurice the SAME
table entry (24) as Thomas, so the residual slug tie-break picked
Maurice ('m' < 't') and OMITTED Thomas -- contrary to the calendarium's
own 22 September row ("S. Thomae de Villanova Ep. et Conf., III
classis. / Commemoratio Ss. Mauritii et Soc. Mm.": Thomas is the one
WITH a table entry) and to missalemeum (test/test_oracle.ml's own
former M17 allow-list entry -- oracle "commemorations": Thomas,
"displaced": Maurice -- deleted once this fix made the divergence
disappear). This is the SAME date the oracle harness itself checks,
pinned here too so a regression shows up at the OCaml level, not only
via the fixture comparison. *)
let test_maurice_thomas_band_fidelity_end_to_end () =
let layer = real_layer () in
(* [Calendar.year]'s own liturgical-year convention (Advent-anchored, see
calendar.ml's [year_bounds]): the label [y] spans December of civil
year [y] through November of civil year [y+1], so 22 September 2027
is reached via [Cal.year ... 2026], not 2027 -- the same
[y-1]/straddling indexing test_oracle.ml's own header comment already
documents for exactly this reason. *)
let days = Cal.year (context ()) layer 2026 in
let day = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (mk 2027 9 22) = 0) in
Alcotest.(check string) "the September Ember Wednesday office itself is observed (band entry 18)"
"ef-september-ember-wed" (slug_of day.LD.observed);
Alcotest.(check (list string))
"St Thomas of Villanova (band entry 24, a real table row) is the sole commemoration"
[ "thomas-of-villanova" ]
(List.map (fun (c, _) -> slug_of c) day.LD.commemorations);
Alcotest.(check (list string))
"St Maurice and Companions (Commemoration_only, unclassified -- no table row) is omitted, not \
commemorated"
[ "maurice-and-companions-martyrs" ]
(List.map (fun (c, _) -> slug_of c) day.LD.omitted)
let suite =
( "Rite_ef (real data: overlay-in-effect, domain-ceiling)",
[ Alcotest.test_case "the overlay suppression is observably in effect" `Quick
test_vigil_of_christmas_suppressed;
Alcotest.test_case "RG16(a) task: the two subject retags are in effect, the other four untouched"
`Quick test_rg16a_subject_retag_in_effect;
Alcotest.test_case "ef-rebootstrap fix round 1 (F3): eusebius-confessor is White, not lectio's Red" `Quick
test_eusebius_confessor_colour_corrected;
Alcotest.test_case "ef-sanctoral-audit: 18 colour corrections in effect (RG120(e)/124(e)); 3 reverted per RG120(f)" `Quick
test_audit_colours_corrected;
Alcotest.test_case "ef-sanctoral-audit: barbara (4 Dec) added, a genuine data gap" `Quick
test_barbara_added;
Alcotest.test_case "RG96 search does not raise at the domain ceiling (real data)" `Quick
test_transfer_search_does_not_raise_at_domain_ceiling;
Alcotest.test_case "no transfer ever lands inside [Easter, Easter+7], 2005-2050" `Quick
test_no_transfer_lands_in_easter_octave;
Alcotest.test_case "RG80: the Major Litanies DO transfer inside the octave, exactly twice in 2005-2050"
`Quick test_major_litanies_transfers_inside_easter_octave_exactly_when_rg80_requires;
Alcotest.test_case "transfer_target skips the whole Easter octave from inside Holy Week" `Quick
test_transfer_target_skips_the_whole_easter_octave;
Alcotest.test_case "the search genuinely enters the window (not vacuous)" `Quick
test_search_genuinely_enters_the_window;
Alcotest.test_case
"band fidelity end-to-end: Ember Wednesday observed, Thomas of Villanova the sole \
commemoration, Maurice omitted (2027-09-22)" `Quick
test_maurice_thomas_band_fidelity_end_to_end ] )
|