aboutsummaryrefslogtreecommitdiff
path: root/test/test_precedence_ef.ml
blob: d873e34cbf767eab25c68d6d66ba3d9e806028e6 (plain) (blame)
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
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
(* RG 91's Table of Precedence, transcribed by Rite_ef.Precedence_ef.band.
   Table-driven, one row (hence one Alcotest.test_case) per RG 91 entry, so a
   misplaced or missing entry names itself in the failure output instead of
   failing anonymously (docs/research/rules-register.md §4). Each row's date
   is checked against the register to make sure it is not ALSO an instance of
   some other entry at the same band (the vacuous-test trap this project has
   caught before -- see the Advent-Ember-day note on entry 18 below). *)

module P = Colitur_kernel.Precedence
module Cel = Colitur_kernel.Celebration
module S = Colitur_kernel.Slug
module Col = Colitur_kernel.Colour
module D = Colitur_kernel.Date
module Sub = Colitur_kernel.Subject
module Comp = Colitur_kernel.Computus
module T = Rite_ef.Temporal_ef
module V = Rite_ef.Vocab_ef
module PE = Rite_ef.Precedence_ef

let mk y m dd = match D.make ~year:y ~month:m ~day:dd with Ok t -> t | Error e -> failwith e

(* [T.season] is the same function Calendar itself would use to build a
   context, so a row's [season]/[weekday] are exactly what the real engine
   would compute for that date, not a hand-picked value that might not
   actually occur together with it. *)
let ctx date = { P.date; season = T.season date; weekday = D.weekday date }

let cand ?(origin = P.Temporal) ?(rank = V.Class1) ?(status = Cel.Feast) ?(subject = Sub.Temporal)
    ?(layer = "temporal") slug =
  { P.cel = Cel.make ~slug:(S.of_string_exn slug) ~rank ~status ~colour:Col.White ~subject ~layer ();
    origin }

(* A candidate built from [Temporal_ef.temporal]'s own real output, not a
   hand-typed slug -- review finding 3: [band]'s Ember/vigil detection reads
   temporal_ef.ml's slug conventions, and a row that also hand-types the same
   literal proves nothing if that convention ever drifts (both sides would
   drift together, silently). Rows built with this instead fail loudly on
   such a drift, because they source the slug from the same place [band]
   itself is implicitly trusting. *)
let of_temporal date =
  let day = T.temporal date in
  { P.cel = day.Colitur_kernel.Temporal.office; origin = P.Temporal }

(* Every Easter-relative date below is anchored to this single computed
   Easter rather than a hand-typed calendar date, so an arithmetic slip in a
   test date cannot silently pass by accident. *)
let easter = Comp.gregorian_easter 2026
let off n = D.add_days easter n

(* (description, date, candidate, expected RG 91 entry). *)
(* CORRECTED, fix round 1 (coordinator finding 1): every expected [int]
   below is now the real RG 91 entry number TIMES TEN (entry 1 -> 10, entry
   28 -> 280), matching [PE.band]'s own rescale -- see precedence_ef.ml's
   own [entry_14_fixed_band] comment for why the bare 1..28 scale was a real
   ordering bug, not a style choice. The description strings still say the
   bare entry number ("14 Feast of the Lord...") -- that names the RG 91
   ROW, not the band integer, and is unaffected by the scale. *)
let cases =
  [ (* Entry 1 -- RG 91 entry 1 (§4): Nativity, Easter Sunday, Pentecost Sunday. *)
    ("1 Nativity", mk 2026 12 25, cand "ef-nativity", 10);
    ("1 Easter Sunday", off 0, cand "ef-easter-sunday", 10);
    ("1 Pentecost Sunday", off 49, cand "ef-pentecost", 10);
    (* Entry 2 -- RG 91 entry 2 (§4): Sacred Triduum. Thu-Sat of Holy Week,
       NOT entry 7 (which stops at Wednesday -- see entry 7 below). *)
    ("2 Holy Thursday", off (-3), cand "ef-holy-thursday", 20);
    ("2 Good Friday", off (-2), cand "ef-good-friday", 20);
    ("2 Holy Saturday", off (-1), cand "ef-holy-saturday", 20);
    (* Entry 3 -- RG 91 entry 3 (§4). *)
    ("3 Epiphany", mk 2026 1 6, cand "ef-epiphany", 30);
    ("3 Ascension", off 39, cand "ef-ascension", 30);
    ("3 Trinity", off 56, cand "ef-trinity", 30);
    ("3 Corpus Christi", off 60, cand "ef-corpus-christi", 30);
    ("3 Sacred Heart", off 68, cand "ef-sacred-heart", 30);
    ("3 Christ the King", T.christ_the_king 2026, cand "ef-christ-the-king", 30);
    (* Entry 4 -- RG 91 entry 4 (§4). Sanctoral-origin: neither feast is part
       of temporal_ef's movable cycle. *)
    ( "4 Immaculate Conception", mk 2026 12 8,
      cand ~origin:P.Sanctoral ~subject:Sub.Bvm ~layer:PE.universal_layer
        "ef-immaculate-conception",
      40 );
    ("4 Assumption", mk 2026 8 15, cand ~origin:P.Sanctoral ~subject:Sub.Bvm ~layer:PE.universal_layer "ef-assumption", 40);
    (* Entry 5 -- RG 91 entry 5 (§4). *)
    ("5 Nativity Vigil", mk 2026 12 24, cand "ef-nativity-vigil", 50);
    ("5 Octave day (Circumcision)", mk 2026 1 1, cand "ef-circumcision", 50);
    (* Entry 6 -- RG 91 entry 6 (§4). *)
    ("6 Advent Sunday", T.advent_start 2026, cand "ef-advent-sunday-1", 60);
    ("6 Lent Sunday", off (-42), cand "ef-lent-sunday-1", 60);
    ("6 Passion Sunday (I Passiontide)", off (-14), cand "ef-passion-sunday", 60);
    ("6 Palm Sunday (II Passiontide)", off (-7), cand "ef-palm-sunday", 60);
    ("6 Low Sunday", off 7, cand "ef-low-sunday", 60);
    (* Entry 7 -- RG 91 entry 7 (§4): Ash Wednesday and Mon/Tue/Wed of Holy
       Week ONLY -- Thu-Sat are entry 2 above, not this entry. *)
    ("7 Ash Wednesday", off (-46), cand "ef-ash-wednesday", 70);
    ("7 Monday of Holy Week", off (-6), cand "ef-holy-monday", 70);
    ("7 Tuesday of Holy Week", off (-5), cand "ef-holy-tuesday", 70);
    ("7 Wednesday of Holy Week", off (-4), cand "ef-holy-wednesday", 70);
    (* Entry 8 -- RG 91 entry 8 (§4). 2 Nov 2026 is a Monday (verified
       independently below the table), so this row is the plain case. The
       register's own qualifying case -- "yields to an occurring Sunday" --
       gets its own row and its own end-to-end test after this table (2 Nov
       2025 is a real Sunday). *)
    ("8 All Souls (non-Sunday)", mk 2026 11 2, cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-all-souls", 80);
    (* Entry 8's qualifier: "(yields to an occurring Sunday)". 2 Nov 2025 is
       a Sunday, so this must NOT be 80 -- it must lose to entry 15 (151 =
       entry 15's own value, 150, + 1, the exact value precedence_ef.ml
       documents and justifies at entry 8's branch). The end-to-end
       resolve-level proof that the Sunday actually wins the day is
       [test_all_souls_yields_to_sunday] below; this row pins the specific
       integer [band] returns. *)
    ("8 All Souls (yields to a Sunday, 2 Nov 2025)", mk 2025 11 2, cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-all-souls", 151);
    (* Entry 9 -- RG 91 entry 9 (§4). *)
    ("9 Pentecost Vigil", off 48, cand "ef-pentecost-vigil", 90);
    (* Entry 10 -- RG 91 entry 10 (§4): both range boundaries, to guard the
       off-by-one an inclusive Easter-offset window invites. *)
    ("10 Easter octave, day+1", off 1, cand "ef-easter-1-mon", 100);
    ("10 Easter octave, day+6", off 6, cand "ef-easter-1-sat", 100);
    ("10 Pentecost octave, day+50", off 50, cand "ef-pentecost-1-mon", 100);
    ("10 Pentecost octave, day+55", off 55, cand "ef-pentecost-1-sat", 100);
    (* Entry 11 -- RG 91 entry 11 (§4). *)
    ( "11 Universal I-class feast", mk 2026 6 29,
      cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:PE.universal_layer "ef-ss-peter-paul",
      110 );
    (* Entry 12 -- RG 91 entry 12 (§4). The one non-base-layer case the brief
       asks for explicitly: same date/rank/subject as 11, only the layer
       differs, so this row isolates the layer test as the deciding factor. *)
    ( "12 Proper I-class feast (non-base layer)", mk 2026 6 29,
      cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:"diocese-warsaw" "ef-local-patron",
      120 );
    (* Entry 13 -- RG 91 entry 13 (§4). *)
    ( "13 Indult I-class feast", mk 2026 6 29,
      cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:(PE.indult_prefix ^ "local-grant")
        "ef-indult-feast-1",
      130 );
    (* fix round 1 (coordinator finding 1), NEW: a synthetic I-CLASS
       candidate landing INSIDE Holy Family's own 7-13 January window --
       the exact counter-example that found the original [-14] sentinel's
       inversion (Holy Family used to beat every I-class entry outright,
       not merely the fixed Baptism it was built to beat). Must still
       outrank the movable-14 entry below (110 < entry_14_movable_band),
       proving the fix, not merely the absence of the old bug's OWN
       witness (13 January). Not live against the shipped universal data
       (register §6.2) -- this is exactly the kind of I-class PROPER/
       INDULT feast an overlay could add, entries 11-13's own [not
       is_temporal] tests notwithstanding. *)
    ( "11 SYNTHETIC: a universal I-class feast inside Holy Family's own window \
       (fix round 1, coordinator finding 1) must still outrank the movable half",
      mk 2026 1 11,
      cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:PE.universal_layer
        "ef-synthetic-class1-probe",
      110 );
    (* Entry 14 -- RG 91 entry 14 (§4), deliberately UNQUALIFIED (contrast
       entry 16, which explicitly says "not of the Lord"). *)
    ( "14 Feast of the Lord, II class", mk 2026 7 1,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "ef-precious-blood",
      PE.entry_14_fixed_band );
    (* Entry 14, non-base layer: unlike 11-13/16/19/20/23/24, entry 14 draws
       no universal/proper/indult line at all, so this must STILL be
       [PE.entry_14_fixed_band], not 19 -- the exact restriction review
       finding 2 flagged and this row exists to keep from silently coming
       back. *)
    ( "14 Feast of the Lord, II class (non-base layer)", mk 2026 7 2,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:"diocese-warsaw"
        "ef-local-feast-of-the-lord",
      PE.entry_14_fixed_band );
    (* Entry 14, MOVABLE half -- RG 91 entry 14's own text (§4, scan-verified):
       "Festa Domini II classis, PRIMUM MOBILIA, DEINDE FIXA" -- movable
       FIRST, then fixed. Holy Family (RG 17(b), temporal_ef.ml's own
       [holy_family_sunday]) is the one movable Class2 Lord feast this
       codebase builds -- sourced from [Temporal_ef.temporal]'s own real
       output ([of_temporal], the same review-finding-3 discipline entry
       18's own Ember row above uses), not a hand-typed slug, so a rename
       or a dropped [~subject:Lord] on temporal_ef.ml's own side fails this
       row too, not only test_temporal_ef.ml's. Must be a value STRICTLY
       BETTER than the fixed half ([PE.entry_14_fixed_band] immediately
       above, not merely different) -- the two are structurally disjoint by
       [is_temporal] alone so they can never literally collide on any one
       candidate, but {!compare_by}'s own kernel-level tie-break
       (Slug.compare, deliberately NOT rubric-authoritative -- the exact
       anti-pattern RG 113's own fix already corrected once, this file's
       own header) is what would decide between them if they returned the
       SAME band on the one date they can coincide (13 January, when it is
       itself Holy Family's date) -- "commemoration-of-the-baptism-of-the-
       lord" sorts before "ef-time-after-epiphany-sunday-1" alphabetically,
       backwards from RG 91's own stated order. CORRECTED, fix round 1
       (coordinator finding 1): [PE.entry_14_movable_band] used to be a
       negative sentinel, which beat every I-class entry too, not merely
       entry 14's own fixed half -- see the SYNTHETIC I-class probe row
       above, and [PE.entry_14_movable_band]'s own comment in
       precedence_ef.ml, for the fix and the counter-example that found it. *)
    ( "14 Feast of the Lord, II class, MOVABLE half (Holy Family, RG 17(b))",
      T.holy_family_sunday 2026, of_temporal (T.holy_family_sunday 2026), PE.entry_14_movable_band );
    (* Entry 15 -- RG 91 entry 15 (§4): an ordinary Sunday not named at entry 6
       -- Septuagesima is II class (RG 11-12 names only Advent/Lent/
       Passiontide/Easter/Low/Pentecost as I class). *)
    ("15 II-class Sunday (Septuagesima)", off (-63), cand ~rank:V.Class2 "ef-septuagesima-sunday", 150);
    (* Entry 16 -- RG 91 entry 16 (§4). *)
    ( "16 Universal II-class feast, not of the Lord", mk 2026 1 20,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Saint ~layer:PE.universal_layer
        "ef-some-saint",
      160 );
    (* Entry 17 -- RG 91 entry 17 (§4): days WITHIN the Nativity octave (26-28
       Dec are Stephen/John/Innocents -- sanctoral, not this entry; 1 Jan is
       entry 5's Octave DAY, not this entry either). *)
    ("17 Nativity octave, 29 Dec", mk 2026 12 29, cand ~rank:V.Class2 "ef-nativity-octave-day-5", 170);
    ("17 Nativity octave, 31 Dec", mk 2026 12 31, cand ~rank:V.Class2 "ef-nativity-octave-day-7", 170);
    (* Entry 18 -- RG 91 entry 18 (§4): Advent 17-23 Dec ferias AND the
       Ember days of Advent/Lent/September share this one entry. The second
       row is deliberately a Lent date (season Lent, NOT Advent) to prove the
       Ember-slug path fires on its own, not merely because it also happens
       to fall in the Dec 17-23 window -- the exact trap the brief warns
       about, worked the other way round: this Ember day must NOT be
       mistaken for an ordinary entry-22 Lent feria either. *)
    ("18 Advent 17-23 Dec feria", mk 2026 12 21, cand ~rank:V.Class2 "ef-advent-4-mon", 180);
    (* Sourced from Temporal_ef.temporal's own output (see [of_temporal])
       rather than a hand-typed "ef-lent-ember-wed" -- closes review finding
       3's coupling concern for the Ember prefixes specifically. *)
    ("18 Lent Ember Wednesday (from Temporal_ef.temporal)", off (-39), of_temporal (off (-39)), 180);
    (* Entry 19 -- RG 91 entry 19 (§4). *)
    ( "19 Proper II-class feast", mk 2026 1 20,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Saint ~layer:"diocese-warsaw"
        "ef-local-saint-2",
      190 );
    (* Entry 20 -- RG 91 entry 20 (§4). *)
    ( "20 Indult II-class feast", mk 2026 1 20,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Saint
        ~layer:(PE.indult_prefix ^ "local-grant-2") "ef-indult-feast-2",
      200 );
    (* Entry 21 -- RG 91 entry 21 (§4, RG 28-34). Two rows: the Ascension
       Vigil is the one II-class vigil temporal_ef already produces today
       (temporal-origin); the Assumption Vigil stands in for the
       sanctoral-origin case no task has loaded data for yet -- proving
       [band] does not gate this entry on [origin] (see precedence_ef.ml's
       file comment). *)
    (* Sourced from Temporal_ef.temporal's own output (see [of_temporal])
       rather than a hand-typed "ef-ascension-vigil" -- closes review finding
       3's coupling concern for [vigil_suffix]. *)
    ("21 Ascension Vigil (from Temporal_ef.temporal)", off 38, of_temporal (off 38), 210);
    ( "21 Assumption Vigil (sanctoral-origin)", mk 2026 8 14,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-assumption-vigil",
      210 );
    (* Also review finding 3 / "worth doing": a UNIVERSAL-layer Class2 vigil
       whose subject is the Lord must still be 21, not 14 -- pins entry 14's
       [not is_vigil] guard even after finding 2 dropped its layer test. *)
    ( "21 Universal II-class vigil of the Lord", mk 2026 6 23,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "ef-precious-blood-vigil",
      210 );
    (* Entry 22 -- RG 91 entry 22 (§4) (corrected: ends at Palm Sunday, not
       Passion Sunday). Both a Lent and a Passiontide feria, clear of Ash
       Wednesday, Holy Week and the Ember days. *)
    ("22 Lent feria", off (-41), cand ~rank:V.Class3 "ef-lent-1-mon", 220);
    ("22 Passiontide feria", off (-12), cand ~rank:V.Class3 "ef-passiontide-1-tue", 220);
    (* Entry 23 -- RG 91 entry 23 (§4). NOTE the table's own order here is the
       REVERSE of 11/12 and 14/16/19/20 above: entry 23 (particular
       calendars) is numbered BELOW entry 24 (universal), so a proper
       III-class feast outranks a universal one -- transcribed as the
       register states it, not "corrected" to match the other classes. *)
    ( "23 Proper III-class feast (non-base layer)", mk 2026 6 30,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:"diocese-warsaw" "ef-local-saint-3",
      230 );
    (* Entry 24 -- RG 91 entry 24 (§4). *)
    ( "24 Universal III-class feast", mk 2026 6 30,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-some-saint-3",
      240 );
    (* Entry 25 -- RG 91 entry 25 (§4). *)
    ("25 Advent feria to 16 Dec", mk 2026 12 1, cand ~rank:V.Class3 "ef-advent-1-tue", 250);
    (* Entry 26 -- RG 91 entry 26 (§4). *)
    ( "26 III-class vigil", mk 2026 8 9,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-lawrence-vigil",
      260 );
    (* Also worth doing: a NON-universal-layer Class3 vigil must still be 26,
       not 23 -- pins entry 23's [not is_vigil] guard. *)
    ( "26 III-class vigil (non-base layer)", mk 2026 8 10,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:"diocese-warsaw" "ef-local-patron-vigil",
      260 );
    (* Task 11, issue (a): the sanctoral bootstrap (data/ef/sanctoral.sexp)
       names its vigils with lectio's OWN "vigil-of-X" PREFIX convention, not
       [PE.vigil_suffix]'s "-vigil" SUFFIX every row above uses -- exactly
       the mismatch Task 7's review predicted. These two rows use the real
       bootstrapped slugs verbatim (data/ef/sanctoral.sexp: 28 Jun, 9 Aug),
       proving [band] recognises the prefix convention too: without it, both
       would misfile at 160/240 (an ordinary feast of the same rank) instead
       of 210/260. *)
    ( "21 II-class vigil via the sanctoral data's own \"vigil-of-X\" prefix",
      mk 2026 6 28,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "vigil-of-sts-peter-paul",
      210 );
    ( "26 III-class vigil via the sanctoral data's own \"vigil-of-X\" prefix",
      mk 2026 8 9,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "vigil-of-st-lawrence",
      260 );
    (* Entry 27 -- RG 91 entry 27 (§4): an otherwise-unoccupied IV-class
       Saturday. *)
    ( "27 Office of the BVM on Saturday", off 62,
      cand ~rank:V.Class4 "ef-time-after-pentecost-1-sat",
      270 );
    (* Entry 28 -- RG 91 entry 28 (§4): the unqualified IV-class catch-all. *)
    ("28 IV-class feria", off 65, cand ~rank:V.Class4 "ef-time-after-pentecost-1-tue", 280);
    (* Not an RG 91 row at all: a I-class candidate marked as a vigil, which
       is not the Nativity or Pentecost (entries 5/9, the only I-class
       vigils the table names) and so has no entry to fall into. Proves the
       documented fallback -- not entry 11/12/13, which the [not is_vigil]
       guard exists specifically to keep this out of. *)
    ( "unclassified: I-class vigil outside Nativity/Pentecost", mk 2026 3 10,
      cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-mystery-vigil",
      PE.unclassified );
    (* Also worth doing: a temporal-origin Class1 candidate on a date none of
       entries 1/2/3/5/6/7/9/10 name. 15 Jul 2026 is a Wednesday, off=101
       from Easter -- clear of every Easter-relative window this module
       checks, and not one of the fixed dates either. Without the
       [not is_temporal] guard on entries 11-13, this would wrongly reach 12
       (its default layer, "temporal", is not [universal_layer] and does not
       carry [indult_prefix], so it reads as "proper" by the layer test
       alone -- precisely the bug the guard exists to prevent; see the
       [not is_temporal] guard's role in the entry-25 mutation test recorded
       in the task report). *)
    ("unclassified: I-class temporal candidate on an unnamed date", mk 2026 7 15, cand "ef-unnamed-day", PE.unclassified);
    (* RG 91's own vigil list (§4, "Vigils / octaves / Rogations / Sunday classes") stops at III class --
       there is no IV-class vigil for entry 28's ferial catch-all to absorb. *)
    ( "unclassified: IV-class candidate marked as a vigil", mk 2026 6 20,
      cand ~rank:V.Class4 "ef-second-mystery-vigil", PE.unclassified );
    (* Task B fix round 1 (coordinator finding 1): RG 91's table enumerates
       only "dies liturgici" -- a [Cel.Commemoration_only] candidate is
       never a row in the table at all, regardless of what rank it would
       otherwise band to. [Class1]/universal-layer is deliberate: absent
       the status guard this would band to entry 11, the FIRST, highest-
       priority real entry any sanctoral candidate can reach, so this
       proves the guard is checked BEFORE and overrides every rank-keyed
       branch, not only the low-priority ones an accidental placement
       might happen to catch. Real-world witness: "maurice-and-companions-
       martyrs" (data/ef/sanctoral.sexp, 22 September, Class3,
       Commemoration_only) used to band to entry 24 alongside the genuine
       Class3 feast "thomas-of-villanova" -- test_oracle.ml's own former
       M17 allow-list entry, deleted once this fix made the divergence it
       named disappear. *)
    ( "unclassified: a Commemoration_only candidate has no row in RG91's \
       table at all, even at Class1", mk 2026 5 20,
      cand ~origin:P.Sanctoral ~status:Cel.Commemoration_only ~layer:PE.universal_layer
        "ef-some-commemoration-only",
      PE.unclassified )
  ]

(* Review finding 1's end-to-end proof: on a real Sunday landing on 2
   November, [Precedence.resolve] -- not just [band] in isolation -- observes
   the Sunday, not All Souls. This exercises the exact mechanism the finding
   named ("resolve observes the lowest band, so whenever 2 November falls on
   a Sunday, All Souls wins and the Sunday loses"), rather than only the
   integer [band] returns for the standalone row above. [disposition] and
   [admit] are stubs -- only [observed] is under test here. *)
let test_all_souls_yields_to_sunday () =
  let date = mk 2025 11 2 in
  let day_ctx = ctx date in
  let sunday =
    { P.cel =
        Cel.make ~slug:(S.of_string_exn "ef-time-after-pentecost-sunday-x") ~rank:V.Class2
          ~colour:Col.Green ~subject:Sub.Temporal ~layer:"temporal" ();
      origin = P.Temporal }
  in
  let all_souls = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-all-souls" in
  let rules =
    { P.band = (fun c cd -> PE.band c cd);
      disposition = (fun ~winner:_ ~loser:_ -> P.Omit);
      admit = (fun ~observed:_ ~temporal:_ cs -> List.map (fun (c, p, (_ : int)) -> (c, p)) cs) }
  in
  let resolution = P.resolve rules day_ctx ~temporal:sunday ~sanctoral:[ all_souls ] in
  Alcotest.(check string) "the Sunday is observed, not All Souls"
    "ef-time-after-pentecost-sunday-x"
    (S.to_string resolution.P.observed.P.cel.Cel.slug)

(* Task 8: [disposition] -- what happens to the day's LOSING candidate (RG
   92-95, 33, 94; §4). Table-driven like [band]'s
   own [cases] above, one row per rule, each checked against a description of
   which register clause it pins. [disposition] takes no context (see
   precedence.mli's [rules.disposition]), so "is the winner a Sunday" is read
   off the winner's own slug the same way [band] itself reads "is this a
   vigil" off the loser's -- see precedence_ef.ml's [sunday_marker]. *)

let string_of_disposition = function
  | P.Omit -> "Omit"
  | P.Commemorate P.Privileged -> "Commemorate(Privileged)"
  | P.Commemorate P.Ordinary -> "Commemorate(Ordinary)"
  | P.Transfer -> "Transfer"
  | P.Repose -> "Repose"

(* A II-class ordinary Sunday, built the same way [test_all_souls_yields_to_sunday]
   builds its Sunday -- a hand-typed slug matching temporal_ef.ml's own
   "ef-<season>-sunday-<n>" convention, since [disposition] only ever reads
   this string, never the real computed date. *)
let an_ordinary_sunday =
  cand ~rank:V.Class2 "ef-time-after-pentecost-sunday-11"

let disposition_cases =
  [ (* RG 95 -- §4, "Occurrence": only I-class feasts transfer; a
       II-class feast loses to a I-class day and is COMMEMORATED, not
       transferred. Paired with the next row (a I-class loser, same shape of
       winner) so the discriminating factor is provably the LOSER's own
       rank, not the winner's -- the brief's explicit "one without the other
       proves nothing" pairing. *)
    ( "RG95 II-class feast loses to I-class day -> Commemorate",
      cand "ef-nativity",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-saint",
      "Commemorate(Ordinary)" );
    ( "RG95 I-class feast loses to a higher I-class day -> Transfer",
      cand "ef-nativity",
      cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-local-i-class-feast",
      "Transfer" );
    (* Fix round 1 (post-Task-9 review): RG 95 (§4, "Occurrence" and "Transfer/translation")
       restricts the right of translation to I-class FEASTS -- RG 91's own
       table lists Sundays as a separate row (entry 6)
       from feasts (entries 11-13) -- so an impeded I-class
       Sunday must NOT transfer, unlike the plain I-class feast row above:
       same [Class1] rank, same kind of winner, the ONLY difference is that
       this loser's slug carries [PE.sunday_marker]. RG 109(a) (§4)
       confirms this from the other direction: "of a Sunday" is a
       privileged commemoration category, which presupposes an impeded
       Sunday stays put rather than moving to another day the way a feast
       does. Sourced from [Temporal_ef.temporal]'s own real output (Advent I
       Sunday 2026, Class1, "ef-advent-sunday-1"), the same coupling-safety
       reason [of_temporal]'s other callers use it -- this is also a
       realistic shape: 8 December falls on an Advent Sunday in 2024, 2030
       and 2041 (Immaculate Conception, RG 91 entry 4, outranking entry 6),
       and 24 December falls on Advent IV in 2023, 2028, 2034 and 2045 (the
       Nativity Vigil, also entry 5 outranking entry 6). *)
    ( "RG95/RG109(a): an impeded I-class SUNDAY does NOT transfer -- it is \
       Commemorated and Privileged",
      cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-immaculate-conception",
      of_temporal (T.advent_start 2026),
      "Commemorate(Privileged)" );
    (* RG 33 -- §4, "Vigils / octaves / Rogations / Sunday classes": a II- or
       III-class vigil impeded by any Sunday or a I-class feast is entirely
       OMITTED, not commemorated. The
       vigil is sourced from [Temporal_ef.temporal]'s own real output (as
       [of_temporal]'s existing callers above do), not a hand-typed
       "ef-ascension-vigil", so a drift in temporal_ef's vigil-slug
       convention cannot silently defeat this row the way a duplicated
       literal could. This is the row the brief singles out as most likely
       to pass vacuously if the fallback below happened to already be
       [Omit] -- it is not: the fallback is [Commemorate] (see the next two
       rows), so this genuinely exercises RG 33's own branch. *)
    ( "RG33 II-class vigil loses to an ordinary Sunday -> Omit",
      an_ordinary_sunday,
      of_temporal (off 38) (* Ascension Vigil *),
      "Omit" );
    ( "RG33 II-class vigil loses to a I-class feast (non-Sunday) -> Omit",
      cand "ef-immaculate-conception",
      of_temporal (off 38),
      "Omit" );
    (* RG 33's own boundary, proved from both sides so the rule is shown to
       gate on the WINNER too, not "any vigil is always omitted": winner is
       neither a Sunday nor I class, so RG 33's omission does not fire; a
       vigil, per RG 31's own text ("si impediuntur, commemorantur"), is
       ALWAYS commemorated once RG 33 does not omit it outright, regardless
       of RG 109's closed list.

       CORRECTED comment (final fix wave, item 2b): this row's own comment
       used to claim it "proves [the RG26 Class4-Omit branch's] own [not
       (is_vigil ...)] guard" -- that claim does NOT survive under the
       current [rank = Class4] gate (it may have been true under an earlier
       predicate). The vigil sourced here ({!of_temporal}, the real
       Ascension Vigil) is [Class2], not [Class4], so the RG26 branch's own
       [rank = Class4] test already excludes it on rank alone, with or
       without the [not (is_vigil ...)] guard -- verified: deleting that
       guard entirely leaves 257/257 green. This row still genuinely proves
       RG 31's own "if impeded, commemorated" vigil mandate (a vigil that
       RG 33 does not omit outright is commemorated, not omitted by some
       OTHER mechanism), which is real and worth keeping; it just does not
       discriminate the [not (is_vigil ...)] guard specifically. *)
    ( "RG33 boundary: vigil loses to an ordinary (non-Sunday, non-I-class) \
       II-class day -> Commemorate, NOT Omit (RG31's own vigil mandate)",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-other-feast",
      of_temporal (off 38),
      "Commemorate(Ordinary)" );
    (* The GENUINE witness for the RG26 branch's [not (is_vigil ...)] guard
       (final fix wave, item 2b): no REAL vigil this engine constructs can
       exercise it, since RG 91 has no IV-class vigil at all (this file's own
       entry-27/28 comments) -- every real vigil is Class2 or Class3, already
       excluded by [rank = Class4] alone, the exact shape the row above is.
       This row is therefore deliberately SYNTHETIC: a temporal-origin loser,
       [Class4], whose slug ALSO carries {!PE.vigil_suffix} -- a shape RG 91
       itself does not describe, but one {!Precedence.resolve} can still be
       asked to construct (see precedence_ef.ml's own comment on this guard).
       With the guard present, [is_vigil] excludes it from the RG26 branch,
       so it falls through to the ordinary [Commemorate] catch-all (RG 31's
       "if impeded, commemorated" mandate, the same rule the row above
       exercises for a realistic Class2 vigil). Deleting [not (is_vigil ...)]
       from that branch turns this row -- and only this row -- red: the
       branch would then fire on [rank = Class4] alone and return [Omit]. *)
    ( "SYNTHETIC (final fix wave, item 2b): a temporal-origin Class4 loser \
       ALSO vigil-shaped is Commemorate (RG31's vigil mandate via the \
       not-is_vigil guard), not Omit",
      an_ordinary_sunday,
      cand ~rank:V.Class4 "ef-synthetic-class4-vigil",
      "Commemorate(Ordinary)" );
    (* CORRECTED 2026-08-12 (Task 16, primary-source-verified): the register
       previously (mis-)transcribed RG 33 as covering only I/II-class
       vigils, so this row's own title used to read "outside RG33's
       I/II-class scope" and expect Commemorate. The primary text ("Vigilia
       II AUT III classis penitus omittitur...") covers II OR III class --
       St Lawrence's vigil (III class, RG 32) falling on ANY Sunday ("in
       dominica quavis") is entirely omitted, exactly like a II-class vigil.
       See {!PE.is_omissible_vigil}'s own comment for the full primary text
       and the register correction. *)
    ( "RG33 (corrected): a III-class vigil loses to a Sunday -> Omit, not \
       Commemorate",
      an_ordinary_sunday,
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-lawrence-vigil",
      "Omit" );
    (* Task 11, issue (a): [disposition]'s own [is_vigil] check (the RG 33
       omission test) is a SEPARATE call site from [band]'s -- both read the
       same private [is_vigil], but each needed its own witness, since a fix
       to one call site could in principle miss the other. Real bootstrapped
       slug (data/ef/sanctoral.sexp's "vigil-of-the-assumption", 14 Aug),
       not a hand-typed one, for the same coupling-safety reason [of_temporal]
       rows use real data elsewhere in this file. Before the fix this vigil
       was invisible to [is_vigil] entirely, so it would have fallen through
       to the ordinary Commemorate branch below instead of Omit -- the exact
       failure the task brief describes. *)
    ( "RG33 (prefix convention): a \"vigil-of-X\"-named II-class vigil loses \
       to an ordinary Sunday -> Omit",
      an_ordinary_sunday,
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "vigil-of-the-assumption",
      "Omit" );
    (* Brief: a Commemoration_only loser is ALWAYS Commemorate -- checked
       here with a loser that ALSO carries a Class1 rank and a vigil-suffixed
       slug losing to a Sunday, so this row only passes if the
       Commemoration_only check is checked BEFORE both RG 33's omission and
       RG 95's transfer, not after. Its expected privilege is [Privileged],
       not [Ordinary]: this loser's [rank] is [Class1] (the default [cand]
       leaves unless overridden, deliberately kept here for the
       branch-order proof above), and RG 109(b) (§4, "of
       a I-class day") makes any [Class1] commemoration privileged
       regardless of how it reached [Commemorate] -- Task 8's placeholder
       [interim_privilege] used to hide this (always [Ordinary]); Task 9's
       real [privilege_of] does not. This row is also this suite's ONLY
       witness for RG 109(b): a plain [Feast]-status [Class1] loser never
       reaches [Commemorate] at all (RG 95 sends it to [Transfer] instead,
       see the row above), so [Commemoration_only] is the only shape that
       can exercise it here (see the task report). *)
    ( "Commemoration_only loser is always Commemorate, even if I-class and \
       vigil-shaped, even losing to a Sunday -- and RG109(b) makes it \
       privileged",
      an_ordinary_sunday,
      cand ~origin:P.Sanctoral ~status:Cel.Commemoration_only ~layer:PE.universal_layer
        "ef-suppressed-vigil",
      "Commemorate(Privileged)" );
    (* Totality (SANCTORAL side): the lower ranks the RG 33/RG 95/Task-16
       branches never touch still reach the RG 95 "commemorated or omitted"
       branch's [Commemorate] side, not an unhandled/exceptional case -- RG
       111(c)/(d) admit an "ordinary" SAINT commemoration freely, with none
       of RG 109's closed-list restriction the temporal branch below has. *)
    ( "III-class feast loses to a I-class day -> Commemorate",
      cand "ef-nativity",
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-some-saint-3",
      "Commemorate(Ordinary)" );
    (* Task 16 (primary-source-verified: RG 95 + RG 109's closed list + RG
       113 -- see this branch's own comment in precedence_ef.ml for the full
       three-text argument): an ORDINARY, non-privileged TEMPORAL-cycle
       loser -- a bog-standard green-season feria of Time after Pentecost,
       none of RG 109(a)-(f) -- has NO standing to be commemorated at all
       when impeded; it is entirely omitted, not the "ordinary"
       commemoration a losing SAINT would get (contrast the SANCTORAL row
       immediately above, same rank, same kind of winner, opposite
       [Commemorate]/[Omit] outcome -- the discriminating factor is
       [origin], nothing else). Before this fix the engine wrongly
       commemorated the losing feria itself here; confirmed wrong against
       the missalemeum oracle (Task 16 report): every one of ~190
       structurally identical days (an ordinary sanctoral feast impeding an
       ordinary temporal feria, 2026-2027) shows zero commemorations in an
       independent published EF calendar. *)
    ( "TASK16: an ORDINARY temporal feria loses to a II-class Sunday -> \
       Omit, not Commemorate (RG109's closed list; contrast the sanctoral \
       row above)",
      an_ordinary_sunday,
      cand ~rank:V.Class4 "ef-time-after-pentecost-1-sat",
      "Omit" );
    (* Totality's other half: a SANCTORAL loser of the exact same rank as
       the row above still reaches [Commemorate], proving the branch above
       is gated on [origin] and not merely on rank -- without this row nothing
       here would distinguish "temporal losers are omitted" from "IV-class
       losers are omitted", which would be a much bigger (and wrong) claim. *)
    ( "TASK16 contrast: a SANCTORAL IV-class loser still reaches \
       Commemorate(Ordinary)",
      an_ordinary_sunday,
      cand ~origin:P.Sanctoral ~rank:V.Class4 ~layer:PE.universal_layer "ef-some-minor-saint",
      "Commemorate(Ordinary)" );
    (* Final fix wave, item 2(a): the RG 26 gate above is [rank = Class4],
       and until this row nothing in the suite discriminated that from the
       REFUTED predicate it replaced, [privilege_of loser = Ordinary]
       (precedence_ef.ml's own comment on this branch names that exact
       predicate as the wrong reading it corrects). The two predicates are
       extensionally identical over every candidate this engine actually
       constructs -- confirmed by enumerating the whole candidate space over
       1583-9999: substituting the old predicate back leaves 257/257 green
       and produces byte-identical `colitur day` output for every sampled
       year. No REALISTIC row can tell them apart, so this one is
       deliberately SYNTHETIC: a temporal-origin loser, [Class3] (not
       [Class4]), whose slug matches none of RG 109(a)-(f) -- [privilege_of]
       therefore returns [Ordinary] for it, the SAME privilege value as the
       Class4 row above. The two gates now diverge: RG 26 ([rank = Class4])
       says this loser is NOT a IV-class feria, so it falls through to the
       ordinary [Commemorate] branch (RG 24/25's mandate for a II/III-class
       feria); the refuted gate ([privilege_of loser = Ordinary]) tests only
       the privilege value, which is [Ordinary] here exactly as it is for
       the Class4 row, so it would wrongly return [Omit]. Reverting the
       branch's condition to [privilege_of loser = Ordinary] turns this row
       (and only this row, of the two) red -- see the final-fix-report for
       the exact Alcotest failure message this produces and its reversion. *)
    ( "SYNTHETIC (final fix wave, item 2a): a temporal-origin ORDINARY \
       Class3 loser is Commemorate under RG26 (rank=Class4 gate), would be \
       Omit under the refuted privilege_of=Ordinary gate",
      an_ordinary_sunday,
      cand ~rank:V.Class3 "ef-synthetic-ordinary-class3-feria",
      "Commemorate(Ordinary)" );
    (* RG 16(a) (register §6.0, Caput III "De Dominicis"): "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, leaves the Sunday with NO
       commemoration at all, unlike every other impeded II-class Sunday
       (contrast the very first row of this table, and [privilege_cases]'s
       own (a): both plain [Class1]/other-sanctoral winners still admit the
       Sunday, [Commemorate(Privileged)]). Two positive rows, I and II
       class, both real slugs/ranks/subjects this project's own audit
       confirmed against the calendarium (this task's own report): the
       Transfiguration (6 Aug, II class, "IN TRANSFIGURATIONE D. N. I. C.")
       and the Precious Blood (1 Jul, I class, "PRETIOSISSIMI SANGUINIS
       D. N. I. C."). *)
    ( "RG16(a): a Feast of the Lord, II class, impedes a II-class Sunday -> \
       Omit, no commemoration at all",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "transfiguration-of-our-lord",
      an_ordinary_sunday,
      "Omit" );
    ( "RG16(a): a Feast of the Lord, I class, impedes a II-class Sunday -> \
       Omit, no commemoration at all",
      cand ~origin:P.Sanctoral ~rank:V.Class1 ~subject:Sub.Lord ~layer:PE.universal_layer
        "precious-blood-of-our-lord-jesus-christ",
      an_ordinary_sunday,
      "Omit" );
    (* Conjunct 1/4, WINNER subject: RG 16(a) names a feast "Domini", not any
       I-class feast. {!band} entries 11-13 admit any I-class feast ahead of
       a II-class Sunday with no subject test (unlike entry 14, see that
       entry's own comment) -- so a real I-class SAINT feast can win the day
       exactly as a Lord feast would, and must NOT trigger RG 16(a). Real
       slug/rank/subject from data/ef/sanctoral.sexp (24 June, I class,
       "NATIVITAS S. IOANNIS BAPTISTAE", subject Saint): 24 June falls on a
       Time-after-Pentecost Sunday in real years (e.g. 2029), so this is a
       live, not merely synthetic, shape -- {!band} entries 11-13 rank it
       ahead of the ordinary Sunday's own entry 15 regardless of subject.
       Dropping the [subject = Lord] conjunct would wrongly Omit the Sunday
       here too. *)
    ( "RG16(a) does NOT fire for a I-class feast of a SAINT (not the Lord) \
       impeding a II-class Sunday -- stays Commemorate(Privileged)",
      cand ~origin:P.Sanctoral ~rank:V.Class1 ~subject:Sub.Saint ~layer:PE.universal_layer
        "nativity-of-st-john-the-baptist",
      an_ordinary_sunday,
      "Commemorate(Privileged)" );
    (* Conjunct 2/4, LOSER rank: RG 16(a) names "Dominica II classis", not
       any Sunday. A I-class Sunday (Advent/Lent/Passiontide, Low Sunday,
       {!band} entry 6) can never actually reach here as a loser against a
       Class1-or-2 Lord winner in real banding -- CORRECTED (fix round 1):
       not because "entry 6's own band value (6) beats every entry such a
       winner could occupy, 3/11-14" (WRONG: 6 is not lower than 3, and the
       claim proves the opposite of what it was cited for anyway -- see
       precedence_ef.ml's own corrected comment on this same conjunct for
       the full argument). Against entries 11-14 the numeric claim genuinely
       holds (6 < 11-14); against entry 3 (Epiphany/Ascension/Trinity/
       Corpus Christi/Sacred Heart/Christ the King) it is a STRUCTURAL fact
       instead -- every band-3 celebration is temporal-origin, and
       [Precedence.resolve] takes exactly one temporal candidate per day, so
       a band-3 Lord feast IS that date's own single temporal candidate,
       never a second one contesting a separately-produced Sunday. Either
       way, this is exactly why the primary text restricts itself to II
       class -- but [disposition] takes no [context] and cannot itself
       re-derive that fact, so the rank check is what actually enforces it
       here. Sourced from [Temporal_ef.temporal]'s own real output (Advent I
       Sunday 2026, Class1, "ef-advent-sunday-1"), the same row
       [disposition_cases]'s own RG95/RG109(a) entry above already uses.
       Dropping the [rank = Class2] conjunct on the loser would wrongly Omit
       this Sunday's commemoration too. *)
    ( "RG16(a) does NOT fire for a I-class Sunday, even losing to a Feast of \
       the Lord -- stays Commemorate(Privileged)",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "transfiguration-of-our-lord",
      of_temporal (T.advent_start 2026),
      "Commemorate(Privileged)" );
    (* Conjunct 3/4, LOSER is a Sunday at all: RG 16(a) protects "de
       dominica" specifically, not every ordinary office a Feast of the Lord
       happens to impede. An ordinary sanctoral SAINT losing to the same
       Lord-subject Class2 winner as the row above must still reach the
       ordinary RG 95/111(c) commemorate-or-omit flow, not RG 16(a)'s
       special no-commemoration rule. Dropping [is_sunday_slug] (or its
       [is_temporal] companion) here would wrongly Omit this ordinary
       saint's commemoration too. *)
    ( "RG16(a) does NOT fire for an ordinary (non-Sunday) SAINT losing to a \
       Feast of the Lord -- stays Commemorate(Ordinary)",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "transfiguration-of-our-lord",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-other-saint",
      "Commemorate(Ordinary)" );
    (* Conjunct 4/4, WINNER rank: RG 16(a) says "I aut II classis", not any
       class. Every real [subject = Lord] entry this project's own sanctoral
       audit found is Class1 or Class2 (this task's own report), so this
       conjunct is not independently reachable on real data -- SYNTHETIC,
       the same defensive-but-not-load-bearing status this file already
       gives the RG26 branch's own [not is_vigil] guard above. Without the
       rank conjunct, [subject = Lord] alone would wrongly Omit this
       Sunday's commemoration under a hypothetical Class3 "feast of the
       Lord" too, which RG 16(a)'s own "I aut II classis" text does not
       cover. *)
    ( "SYNTHETIC: RG16(a) does NOT fire for a Class3 feast of the Lord \
       (outside \"I aut II classis\") impeding a II-class Sunday -- stays \
       Commemorate(Privileged)",
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Lord ~layer:PE.universal_layer
        "ef-synthetic-class3-lord-feast",
      an_ordinary_sunday,
      "Commemorate(Privileged)" );
    (* PRIMARY authority -- CORRECTED, fix round 1 (coordinator finding 7):
       RG 95's own second paragraph, present in ALL THREE documents
       including the electronic transcription: "Si vero duo festa eiusdem
       Divinae Personae... simul occurrunt, fit de festo, quod in tabella
       praecedentiae superiorem obtinet locum et aliud omittitur" -- if two
       feasts of the SAME DIVINE PERSON occur together, the one higher in
       the table is kept, the OTHER OMITTED -- an occurrence-level rule
       closer to this exact collision than RG 112(a) below.
       CORROBORATION, RG 112(a) (docs/research/rules-register.md §4/§6.0,
       primary text, verified against all three documents): "112. Ad
       commemorationes et orationes quod attinet, haec insuper serventur:
       a) Officium, Missa aut commemoratio de aliquo festo vel mysterio
       unius Divinae Personae excludit commemorationem aut orationem de
       alio festo vel mysterio eiusdem Divinae personae" -- the Office,
       Mass or commemoration of some feast or MYSTERY of ONE Divine Person
       excludes a commemoration or oration of ANOTHER feast or mystery of
       the SAME Divine Person. Real witness, the ONE date these two
       candidates can ever coincide: Holy Family (RG 17(b)) as winner, the
       Commemoration of the Baptism of the Lord (13 January, data/ef/
       sanctoral.sexp: Class2, subject Lord, status Feast) as loser, on the
       seven years 13 January is itself Holy Family's own date (2008, 2013,
       2019, 2030, 2036, 2041, 2047).
       CORROBORATION 2, the Holy Family Mass propers' own more specific
       rubric, both photographic scans, word for word (real, and settles 13
       January on its own regardless of RG 95/112(a)): "Si festum S.
       Familiae occurrerit die 13 ianuarii, Missa dicitur de festo S.
       Familiae, SINE COMMEMORATIONE BAPTISMATIS D.N.I.C., et sine
       commemoratione dominicae" -- if the feast of the Holy Family occurs
       on 13 January, the Mass is said of the Holy Family, WITHOUT
       commemoration of the Baptism, and without commemoration of the
       Sunday (RG 17(b)'s own general rule, restated).
       CORRECTED, fix round 1 (coordinator finding 2): a previous version of
       this comment claimed the electronic transcription's absence of this
       specific rubric was "exactly the transcription's own documented
       defect" -- WRONG, struck. That transcription (a 2006 web capture)
       carries almost no Mass-propers text of any kind (5 Introit
       occurrences in 26,322 lines against 61/402 in the two photographic
       scans; its own "Proprium de Tempore" page is a table of contents
       linking to PDFs the capture never pulled in) -- a coverage gap, not
       a silent drop from content it otherwise has. RG 112 itself, unlike
       this rubric, IS present in full in the transcription (all four
       sub-clauses, verbatim) -- checked directly, not assumed. *)
    ( "RG112(a): a feast/mystery of the Lord excludes a commemoration of \
       ANOTHER feast/mystery of the Lord -- Omit, not Commemorate (Holy \
       Family vs the Baptism of the Lord, 13 January)",
      of_temporal (T.holy_family_sunday 2019),
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "commemoration-of-the-baptism-of-the-lord",
      "Omit" );
    (* Conjunct 1/2, LOSER subject: an ORDINARY saint losing to the SAME
       Lord-subject winner must still reach the ordinary commemorate flow,
       not RG 112(a)'s exclusion -- RG 112(a) excludes another mystery of
       the SAME Divine Person, not every commemoration a Lord feast happens
       to outrank. Dropping the loser's own [subject = Lord] test would
       wrongly Omit this ordinary saint's commemoration too. *)
    ( "RG112(a) does NOT fire when only the WINNER is a mystery of the Lord \
       -- an ordinary saint stays Commemorate(Ordinary)",
      of_temporal (T.holy_family_sunday 2019),
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Saint ~layer:PE.universal_layer
        "ef-synthetic-ordinary-saint",
      "Commemorate(Ordinary)" );
    (* Conjunct 2/2, WINNER subject: a Class2 SAINT'S feast (not of the
       Lord) impeding an ordinary temporal candidate must not exclude an
       otherwise-due Lord commemoration either -- RG 112(a) is keyed on the
       WINNER's own subject too, not just the loser's. Real slug/rank from
       data/ef/sanctoral.sexp (25 January, Class3, "Conversion of St
       Paul") standing in for a same-rank saint winner; only the [subject]
       distinction is under test here, not a real-date collision (Holy
       Family's own subject is what makes this branch reachable at all --
       an ordinary sanctoral saint never wins against Holy Family in
       practice, {!band}'s [entry_14_movable_band] sees to that, so this
       row is SYNTHETIC in the same sense the RG16(a) block's own Class3
       row above is: defensive, not a live witness). *)
    ( "SYNTHETIC: RG112(a) does NOT fire when only the LOSER is a mystery \
       of the Lord -- stays Commemorate(Ordinary)",
      cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Saint ~layer:PE.universal_layer
        "conversion-of-st-paul",
      cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
        "commemoration-of-the-baptism-of-the-lord",
      "Commemorate(Ordinary)" )
  ]

(* Task 9: [privilege_of]'s RG 109 categories (§4, "Commemorations"),
   exercised through [PE.disposition]'s [Commemorate] payload -- [privilege_of]
   itself is private, so this is the only vantage point a test outside
   precedence_ef.ml has on it. Each row below is built to match ONLY the one
   category it names (see each row's own comment for why), closing the
   hazard flagged in the task brief ("a test day that is both a Sunday and a
   I-class day proves nothing about either"). Category (b), "of a I-class
   day", already has its sole witness above (the Commemoration_only row):
   a plain [Feast]-status [Class1] loser can never reach [Commemorate] at
   all in this ruleset (RG 95 routes it to [Transfer] instead), so no
   further row for (b) is added here -- see the task report. Category (f),
   "of the Major Rogations, in Mass", has no row at all: no candidate this
   codebase can currently construct represents one (see [privilege_of]'s own
   comment on (f)) -- the negative row below proves the one slug this engine
   DOES compute that could be mistaken for it (the Minor Rogations) is
   correctly NOT conflated with it, which is the strongest claim available
   without inventing an unfounded slug convention. *)
let privilege_cases =
  [ (* (a) RG 109(a) (§4): "of a Sunday". [an_ordinary_sunday] is Class2,
       not Class1, not within the Nativity octave, not an Ember day, not a
       feria of Advent/Lent/Passiontide -- matches (a) alone. *)
    ( "(a) an ordinary Sunday commemoration is privileged",
      cand "ef-nativity",
      an_ordinary_sunday,
      "Commemorate(Privileged)" );
    (* (c) RG 109(c) (§4): "of days within the Octave of the Nativity" --
       sourced from [Temporal_ef.temporal]'s own output (29 Dec 2026, Class2,
       "ef-nativity-octave-day-5"), not a hand-typed slug, for the same
       coupling-safety reason the file's own [of_temporal] rows use it
       elsewhere. Not a Sunday, not Class1, not an Ember day, not an
       Advent/Lent/Passiontide feria slug. *)
    ( "(c) a day within the Nativity octave is privileged",
      cand "ef-nativity",
      of_temporal (mk 2026 12 29),
      "Commemorate(Privileged)" );
    (* (d) RG 109(d) (§4): "of September Ember days" -- 23 Sep 2026 is
       the September Ember Wednesday (independently derived from
       [Temporal_ef]'s own third-Sunday-of-September rule: first Sunday of
       September 2026 is the 6th, +14 days = 20th, +3 = 23rd), sourced from
       [Temporal_ef.temporal] itself, Class2. Not a Sunday, not Class1, not
       within the Nativity octave, not a plain Advent/Lent/Passiontide feria
       slug either -- and, unlike the Advent/Lent Ember rows below, its own
       slug ("ef-september-ember-wed") never starts with any of (e)'s own
       [alp_feria_prefixes] ("ef-advent-"/"ef-lent-"/"ef-passiontide-"), so
       (d) is this candidate's ONLY route to [Privileged] -- a genuine,
       still-necessary distinction from (e), unlike the Advent/Lent Ember
       case below (fix round 1). *)
    ( "(d) a September Ember day is privileged",
      cand "ef-nativity",
      of_temporal (mk 2026 9 23),
      "Commemorate(Privileged)" );
    (* (e) RG 109(e) (§4): "of ferias of Advent, Lent and Passiontide" --
       two rows, one per season named, both from [Temporal_ef.temporal]'s
       own generic ferial fallback, neither a Sunday, Ember day, or within
       the Nativity octave. *)
    ( "(e) an Advent feria is privileged",
      cand "ef-nativity",
      of_temporal (mk 2026 12 1),
      "Commemorate(Privileged)" );
    ( "(e) a Lent feria is privileged",
      cand "ef-nativity",
      of_temporal (off (-41)),
      "Commemorate(Privileged)" );
    (* CHANGED, fix round 1 (F1/F2/F6): these two rows used to be titled
       "boundary: ... is NOT privileged (only September is, RG109(d))" and
       expected [Omit] (a Task-16-pass reading that treated RG 109(e)'s bare
       "feriis Adventus, Quadragesimae" as tacitly excluding the Advent and
       Lent Ember sub-days, by analogy with (d)'s own separate, explicit
       September carve-out). Review round 1 (F1) reproduced the real
       consequence directly -- 1900-12-21, an Advent Ember Friday, lost its
       own commemoration entirely, while an ordinary (lower-solemnity,
       non-Ember) Advent feria the same week kept its commemoration --
       backwards on any reading, and traced it to this exact
       misclassification (F2).

       Corrected reading (precedence_ef.ml's own [privilege_of], (e)
       branch, carries the full argument): RG 91's TABLE needs an explicit
       "exceptis feriis Quatuor Temporum" at its own entries 22 and 25 to
       keep Ember days from being double-listed against their own entry 18
       -- an exception that would be unnecessary drafting if "feriae
       Adventus"/"feriae Quadragesimae" did not ALREADY include their Ember
       sub-days by default. RG 109(e) carries no such "exceptis" clause, so
       its bare text is read at that same default, INCLUSIVE scope: the
       Advent and Lent Ember ferias ARE privileged under (e), not merely
       commemorable-but-ordinary. (d)'s own separate existence survives
       this reading intact -- September Ember days sit outside Advent/Lent/
       Passiontide under ANY reading, so (d) remains the ONLY way they
       reach [Privileged], the point the row immediately above this one
       makes explicit.

       16 Dec 2026 is the Advent Ember Wednesday (independently derived:
       Advent I 2026 is 29 Nov, +14 days = 13 Dec, +3 = 16 Dec); the Lent
       Ember Wednesday is the same date [off (-39)] already used by the
       entry-18 [band] row above. Both sourced from [Temporal_ef.temporal],
       not hand-typed, for the same coupling-safety reason every
       [of_temporal] row in this file uses it. *)
    ( "(e), corrected fix round 1: an Advent Ember day is ALSO privileged, \
       not excluded from (e)",
      cand "ef-nativity",
      of_temporal (mk 2026 12 16),
      "Commemorate(Privileged)" );
    ( "(e), corrected fix round 1: a Lent Ember day is ALSO privileged, \
       not excluded from (e)",
      cand "ef-nativity",
      of_temporal (off (-39)),
      "Commemorate(Privileged)" );
    (* Negative, RG 109(f)'s own boundary: the Minor Litanies/Rogations
       (Monday/Tuesday before Ascension, RG 87 -- [Temporal_ef.temporal]
       DOES compute these, unlike the Major Litanies RG 109(f) actually
       names, see [privilege_of]'s own comment) must NOT be mistaken for the
       Major Rogations RG 109(f) privileges: RG 88 says the Minor Rogations
       change nothing in the Office at all, so nothing about them is
       privileged either -- and (Task 16) being temporal+ordinary, a Minor
       Rogation day impeded by a saint is now omitted outright, matching the
       missalemeum oracle exactly (Task 16 report: 11 May 2026 and 12 May
       2026, both Minor Rogation days impeded by a saint, show zero
       commemoration of the Rogation in the independent oracle). *)
    ( "boundary: a Minor Rogation day is NOT privileged (RG109(f) names \
       the Major Litanies, not these) -- TASK16 omits it entirely",
      cand "ef-nativity",
      of_temporal (off 36),
      "Omit" )
  ]

(* Task 9: [PE.admit] -- RG 111's admission counts (§4, "Commemorations"),
   given commemorations ALREADY tagged with their real privilege (as
   [PE.disposition] now tags them -- see [privilege_cases] above). Every
   candidate/privilege pair here is built directly, not routed through
   [PE.disposition], so these rows isolate [admit]'s own selection logic
   from [privilege_of]'s classification -- the two are proved separately by
   design (unlike a test that only proves [admit] admits SOME correct-looking
   set without knowing whether it or [privilege_of] supplied the "correct"
   part). Checked on slug IDENTITY, not count (the brief: "'two admitted'
   proves nothing about *which* two"). *)

(* Class2 dignity, tagged [Ordinary] explicitly (not via [privilege_of]) --
   used as the higher-dignity, non-privileged half of every asymmetry pair
   below. *)
let ordinary_hi = cand ~rank:V.Class2 "ef-ordinary-hi"

(* Class3 dignity (LOWER than [ordinary_hi]), tagged [Privileged] explicitly
   -- pairing a lower-dignity privileged candidate against a higher-dignity
   ordinary one is what makes the II-class-Sunday-vs-other-II-class
   asymmetry observable: pure dignity and "privilege wins the slot" pick
   DIFFERENT winners from this exact pair. *)
let privileged_lo = cand ~rank:V.Class3 "ef-privileged-lo"

(* Class2 dignity (tied with [ordinary_hi], distinguishing rank from
   privilege alone), tagged [Privileged] -- the higher-dignity privileged
   candidate for the "two privileged due" row. *)
let privileged_hi = cand ~rank:V.Class2 "ef-privileged-hi"

(* Class4, the lowest dignity in play -- the third candidate for the
   III/IV-class "at most two" row, so which TWO of three survive is the
   thing under test, not merely how many. *)
let ordinary_lowest = cand ~rank:V.Class4 "ef-ordinary-lowest"

(* Class3, tagged [Ordinary] -- fix round 1, F7: the RG 111(b) rank-floor
   witness [admit_cases] was missing. [ordinary_hi] above is already Class2,
   so every existing II-class-Sunday row here passes whether or not
   [admit]'s "de festo II classis" filter is even present -- reverting that
   filter would only redden [test_oracle.ml], not this file, which is
   exactly the coverage gap the review round found. This candidate is the
   ONLY thing due on the Sunday row below, so a version of [admit] without
   the rank floor would (wrongly) admit it on pure "best available"
   dignity, same as it would have admitted [ordinary_hi]. *)
let ordinary_class3 = cand ~rank:V.Class3 "ef-ordinary-class3"

let observed_class1 = cand "ef-nativity" (* Class1 by [cand]'s own default. *)
let observed_class2_sunday = an_ordinary_sunday (* Class2, slug carries "-sunday". *)
let observed_class2_other = cand ~rank:V.Class2 "ef-other-class2-day" (* Class2, no "-sunday". *)
let observed_class3 = cand ~rank:V.Class3 "ef-some-class3-day"

let slugs_of admitted =
  List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) admitted

(* [admit_cases] below (Task B, ef-rg16a) needs a precedence-order [int] on
   every comms triple -- {!Precedence.rules.admit}'s new parameter, RG 113
   (docs/research/rules-register.md §4, "RG 113"). This table's own header
   comment states its purpose: isolate [admit]'s own selection/count logic
   from {!PE.band}'s occurrence-table classification, so every candidate
   here is built from a synthetic slug ("ef-ordinary-hi" etc.) that matches
   none of [band]'s 28 real branches -- calling the REAL [PE.band] on them
   would collapse every one to {!PE.unclassified} (the same tied value),
   destroying the table's ability to distinguish rows at all. [order_of_rank]
   is a TEST-ONLY stand-in, reproducing exactly the relative order the
   previous [dignity] function gave (RG 8's rank cardinal, lower first) --
   so every existing expectation below still holds unchanged; only the
   tuple shape gained this third field. *)
let order_of_rank = function V.Class1 -> 10 | V.Class2 -> 20 | V.Class3 -> 30 | V.Class4 -> 40
let po c p = (c, p, order_of_rank c.P.cel.Cel.rank)

(* Real-data shapes for the fix round's own RG16(a)/[admit] interaction
   (below): a Feast of the Lord (subject Lord, Class2, sanctoral) as
   [observed], oracle-confirmed real slugs/ranks rather than hand-typed
   ones, the same coupling-safety reason [of_temporal] rows elsewhere in
   this file use real data. *)
let lord_winner =
  cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
    "transfiguration-of-our-lord"

(* Pope Sixtus II et al. (6 August, real slug/rank/status from
   data/ef/sanctoral.sexp): Class3, Commemoration_only, tagged [Ordinary]
   here directly (as every other [admit_cases] row does, isolating
   [admit]'s own selection logic from [privilege_of], per this table's own
   header comment). *)
let sixtus =
  cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only
    ~layer:PE.universal_layer "pope-sixtus-ii-felicissimus-and-agapitus-martyrs"

(* (description, observed, temporal, comms, expected). [temporal] --
   {!Precedence.rules.admit}'s own new parameter, fix round 1 (RG16(a)
   task): the day's temporal-cycle candidate, independent of who is
   [observed]. Every row below except the last two repeats [observed] as
   [temporal] -- no RG16(a) displacement occurs in those rows (the pattern
   [PE.admit]'s pre-fix code implicitly, and wrongly, assumed held
   UNIVERSALLY), so the two values coinciding is the faithful shape, not a
   simplification that dodges the new parameter. The last two rows are
   where they genuinely differ -- see their own comment. *)
let admit_cases =
  [ (* RG 111 (§4): "I class: none save one privileged." *)
    ( "I-class day, only an ordinary commemoration due -> none admitted",
      observed_class1, observed_class1,
      [ po ordinary_hi P.Ordinary ],
      [] );
    ( "I-class day, ordinary + privileged both due -> only the privileged \
       one, regardless of the ordinary one's higher dignity",
      observed_class1, observed_class1,
      [ po ordinary_hi P.Ordinary; po privileged_lo P.Privileged ],
      [ "ef-privileged-lo" ] );
    ( "I-class day, two privileged due -> only the higher-dignity one (still \
       just \"one\")",
      observed_class1, observed_class1,
      [ po privileged_lo P.Privileged; po privileged_hi P.Privileged ],
      [ "ef-privileged-hi" ] );
    (* RG 111: "II-class Sundays: one (dropped if a privileged one is due)." *)
    ( "II-class Sunday, only an ordinary commemoration due -> it is admitted",
      observed_class2_sunday, observed_class2_sunday,
      [ po ordinary_hi P.Ordinary ],
      [ "ef-ordinary-hi" ] );
    ( "II-class Sunday, ordinary (higher dignity) + privileged (lower \
       dignity) both due -> the PRIVILEGED one is admitted, the ordinary \
       one dropped despite outranking it",
      observed_class2_sunday, observed_class2_sunday,
      [ po ordinary_hi P.Ordinary; po privileged_lo P.Privileged ],
      [ "ef-privileged-lo" ] );
    (* RG 111(b)'s own rank floor ("scilicet DE FESTO II CLASSIS"), fix
       round 1 F7: a Class3 ORDINARY candidate -- no privileged rival due,
       so the pre-fix-round code's "no privileged? take the best of what's
       left" fallback would (wrongly) admit it -- is admitted NOTHING. The
       slot is reserved for a II-class candidate specifically; a III-class
       ordinary one has no standing for it at all, unlike "other II class"
       below, which has no such restriction. *)
    ( "II-class Sunday, sole candidate is an ORDINARY Class3 (not \
       \"de festo II classis\") -> admitted nothing, not the best available",
      observed_class2_sunday, observed_class2_sunday,
      [ po ordinary_class3 P.Ordinary ],
      [] );
    (* RG 111: "other II class: one" -- no privilege override, the exact
       asymmetry the brief and precedence_ef.ml's own [admit] comment flag:
       same candidate pair as the II-class-Sunday row above, OPPOSITE
       observed day, OPPOSITE winner. *)
    ( "other II-class day, only an ordinary commemoration due -> it is \
       admitted",
      observed_class2_other, observed_class2_other,
      [ po ordinary_hi P.Ordinary ],
      [ "ef-ordinary-hi" ] );
    ( "other II-class day, same ordinary+privileged pair as the Sunday row \
       above -> the ORDINARY one wins on pure dignity this time, the \
       privileged one dropped",
      observed_class2_other, observed_class2_other,
      [ po ordinary_hi P.Ordinary; po privileged_lo P.Privileged ],
      [ "ef-ordinary-hi" ] );
    (* RG 111: "III-IV class: at most two" -- three candidates due, top two
       by dignity admitted, the third (lowest dignity) dropped. *)
    ( "III-class day, three commemorations due -> the top two by dignity, \
       not merely \"two of them\"",
      observed_class3, observed_class3,
      [ po ordinary_hi P.Ordinary; po privileged_lo P.Privileged;
        po ordinary_lowest P.Ordinary ],
      [ "ef-ordinary-hi"; "ef-privileged-lo" ] );
    (* Fix round 1, item 2 (RG16(a) task review): the CRITICAL witness for
       [~temporal]. RG 16(a)'s own text -- the winning Feast of the Lord
       holds the Sunday's place "cum omnibus iuribus et privilegiis" -- means
       the day is STILL a "dominica II classis" for RG 111(b)'s own rank
       floor, even though [observed] is now the FEAST (Transfiguration, real
       slug/rank/subject), not the Sunday. [temporal] (a real Sunday
       candidate, {!an_ordinary_sunday}) is what reveals that; nothing in
       [observed] does, since its own slug carries no Sunday marker at all.
       Oracle-confirmed (missalemeum, 2023-08-06, a Sunday):
       "commemorations": [], Sixtus "displaced". Before this fix
       [observed_is_sunday] read [observed]'s own slug and got [false] here,
       wrongly taking the "other II class: one" branch below and admitting
       Sixtus (Class3) despite RG 111(b)'s rank floor. *)
    ( "RG16(a) interaction: a Feast of the Lord observed on a day whose \
       TEMPORAL candidate is a II-class Sunday -> the RG111(b) rank floor \
       still applies, admits nothing (Sixtus, Class3, has no standing)",
      lord_winner, an_ordinary_sunday,
      [ po sixtus P.Ordinary ],
      [] );
    (* The control, same pair as the row above with [observed] = [temporal]
       (no displacement -- an ordinary WEEKDAY, not a Sunday): "other II
       class: one", no rank floor, Sixtus IS admitted. Oracle-confirmed
       (missalemeum, 2026-08-06, a Thursday): Sixtus in "commemorations".
       Proves the DISCRIMINATING factor is [temporal]'s own Sunday-ness, not
       merely "was the winner a Feast of the Lord" -- that alone is true of
       BOTH rows here, and only one of them excludes Sixtus. *)
    ( "control: the same Feast of the Lord on an ORDINARY weekday (temporal \
       = observed, not a Sunday) -> \"other II class: one\", Sixtus IS \
       admitted",
      lord_winner, lord_winner,
      [ po sixtus P.Ordinary ],
      [ "pope-sixtus-ii-felicissimus-and-agapitus-martyrs" ] )
  ]

(* Order independence (brief: "the admitted set must not depend on input
   order"): the SAME three candidates as the III-class row above, passed in
   the reverse order, must still admit the same top two -- exercised on this
   row specifically because it is the one where the sort actually has work
   to do (three distinct dignities, a real top-2 cut), unlike a
   two-candidate row where either order already happens to be sorted. *)
let test_admit_order_independent () =
  let comms =
    [ po ordinary_hi P.Ordinary; po privileged_lo P.Privileged; po ordinary_lowest P.Ordinary ]
  in
  let forward = slugs_of (PE.admit ~observed:observed_class3 ~temporal:observed_class3 comms) in
  let reversed =
    slugs_of (PE.admit ~observed:observed_class3 ~temporal:observed_class3 (List.rev comms))
  in
  Alcotest.(check (list string)) "reversed input admits the same candidates"
    forward reversed

(* RG 113 teeth (Task B, ef-rg16a): two SAME-rank candidates whose slug order
   DISAGREES with their band/precedence order -- "aa-worse-precedence" sorts
   first alphabetically but is given the WORSE (higher) precedence int,
   "zz-better-precedence" sorts last alphabetically but the BETTER (lower)
   one. Both are [Class3] (same RG 8 "dignity", so a version of [admit] that
   silently reverted to sorting by [rank] then slug -- the exact pre-fix
   shape this task replaced -- would pick "aa-worse-precedence" here, the
   OPPOSITE of what this asserts. A real RG 91 [band] value never actually
   produces this exact pairing against these two synthetic slugs (see
   [admit_cases]'s own header on why this table uses synthetic, non-band-
   classifiable slugs) -- the precedence ints are supplied directly, the
   same [po]/explicit-order convention this file already uses, standing in
   for whatever real [band] would compute. This is what proves [admit]
   actually consults the passed-in precedence value rather than merely
   accepting one as a matter of new type-checking. *)
let order_tiebreak_alpha_first_worse = cand ~rank:V.Class2 "aa-worse-precedence"
let order_tiebreak_alpha_last_better = cand ~rank:V.Class2 "zz-better-precedence"

(* [observed_class2_other]: "other II class: one" admits exactly ONE, by
   precedence order alone (no privilege override, no rank floor) -- unlike
   the III-class row [test_admit_order_independent] reuses, which admits
   TWO out of two offered here and so cannot distinguish "picked by
   precedence" from "picked both anyway". *)
let test_admit_uses_precedence_not_slug_when_they_disagree () =
  let comms =
    [ (order_tiebreak_alpha_first_worse, P.Ordinary, 90);
      (order_tiebreak_alpha_last_better, P.Ordinary, 10) ]
  in
  Alcotest.(check (list string))
    "the BETTER-precedence candidate is admitted even though it sorts LAST alphabetically"
    [ "zz-better-precedence" ]
    (slugs_of (PE.admit ~observed:observed_class2_other ~temporal:observed_class2_other comms))

(* The brief: "a case proving that what the limit drops is reported in
   omitted rather than vanishing" -- three end-to-end proofs, wired with the
   REAL [PE.band], [PE.disposition] and [PE.admit] together (not a stub, so
   [privilege_of]'s real classification is exercised too, not just [admit]'s
   selection logic in isolation as above).

   [rules] deliberately reused, not rebuilt per test, since it is always the
   same three real functions. *)
let real_rules = { P.band = PE.band; disposition = PE.disposition; admit = PE.admit }

(* I-class day, zero admitted: the strongest form of "does not vanish" --
   EVERY commemoration due is dropped (RG 111: "none save one privileged",
   and the one loser here is ordinary), yet it must still appear in
   [omitted], not merely be absent from [commemorations]. *)
let test_i_class_day_drops_into_omitted () =
  let date = mk 2026 12 25 in
  let day_ctx = ctx date in
  let nativity = of_temporal date in
  let saint = cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-some-saint-3" in
  let resolution = P.resolve real_rules day_ctx ~temporal:nativity ~sanctoral:[ saint ] in
  Alcotest.(check (list string)) "nothing admitted on a I-class day with only an ordinary loser due"
    [] (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.commemorations);
  Alcotest.(check (list (pair string string))) "the ordinary loser is reported omitted, not vanished"
    [ ("ef-some-saint-3", "omitted: admission limit reached") ]
    (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted)

(* II-class Sunday, two ordinary losers due, RG 111's "one" admits the
   higher-dignity one and drops the other into [omitted]. *)
let test_ii_class_sunday_drops_second_loser_into_omitted () =
  let date = mk 2025 11 9 (* an ordinary Time-after-Pentecost Sunday, not All Souls-adjacent. *) in
  let day_ctx = ctx date in
  let sunday =
    { P.cel =
        Cel.make ~slug:(S.of_string_exn "ef-time-after-pentecost-sunday-x") ~rank:V.Class2
          ~colour:Col.Green ~subject:Sub.Temporal ~layer:"temporal" ();
      origin = P.Temporal }
  in
  let saint_a = cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-saint" in
  let saint_b = cand ~origin:P.Sanctoral ~rank:V.Class3 ~layer:PE.universal_layer "ef-some-saint-3" in
  let resolution = P.resolve real_rules day_ctx ~temporal:sunday ~sanctoral:[ saint_a; saint_b ] in
  Alcotest.(check (list string)) "only the higher-dignity (Class2) loser is admitted"
    [ "ef-some-saint" ]
    (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.commemorations);
  Alcotest.(check (list (pair string string))) "the lower-dignity loser is reported omitted, not vanished"
    [ ("ef-some-saint-3", "omitted: admission limit reached") ]
    (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted)

(* A genuinely privileged commemoration reaching [admit] through the REAL
   pipeline (register RG 109(e)): a Lent feria (Class3, temporal-origin)
   loses to a universal Class2 sanctoral feast on the same date, and
   [PE.disposition] tags it [Privileged] via [privilege_of] -- proving
   [privilege_of] and [admit] cooperate correctly end-to-end, not merely in
   the hand-tagged unit tests above. *)
let test_privileged_lent_feria_admitted_end_to_end () =
  let date = off (-41) (* Lent I Monday, the same date the entry-22 [band] row uses. *) in
  let day_ctx = ctx date in
  let lent_feria = of_temporal date in
  let saint = cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-saint" in
  let resolution = P.resolve real_rules day_ctx ~temporal:lent_feria ~sanctoral:[ saint ] in
  Alcotest.(check string) "the Lent feast wins the day, not the sanctoral feast's own commemoration"
    "ef-some-saint" (S.to_string resolution.P.observed.P.cel.Cel.slug);
  Alcotest.(check (list (pair string string))) "the Lent feria is admitted, tagged Privileged"
    [ ("ef-lent-1-monday", "Privileged") ]
    (List.map
       (fun (c, p) -> (S.to_string c.P.cel.Cel.slug, match p with P.Privileged -> "Privileged" | P.Ordinary -> "Ordinary"))
       resolution.P.commemorations);
  Alcotest.(check int) "nothing omitted" 0 (List.length resolution.P.omitted)

(* Fix round 1 (post-Task-9 review): the II-class-Sunday override (RG 111
   "one, dropped if a privileged one is due") reached through the REAL
   pipeline, with both a privileged AND an ordinary commemoration due on the
   same day -- previously only proven at the isolated [admit] level
   ([admit_cases]'s hand-tagged rows above). RG 109(a)-(f)'s five reachable
   categories are all properties of a TEMPORAL-origin office, and only one
   temporal candidate exists per date, so a privileged AND an ordinary
   commemoration cannot both be due from the temporal side alone -- but
   category (b) is the exception: it is sanctoral and rank-based (a
   [Commemoration_only] entry carrying [Class1], the exact shape
   [disposition_cases]'s own (b) witness row uses), and a [Commemoration_only]
   entry is held out of the band contest entirely ({!Precedence.resolve}),
   so it is a loser regardless of what [observed] turns out to be. Paired
   with an ordinary sanctoral saint, both lose to an ordinary Class2 Sunday,
   giving [observed_is_sunday = true] with one [Privileged] and one
   [Ordinary] loser due at once -- no synthetic fixture needed.

   NOTE on what this test does and does not prove: the only reachable
   witness for RG 109(b) is [Class1] (the highest dignity), and any
   sanctoral loser that could ALSO beat this same Sunday by pure dignity
   would win the day outright instead of losing to it (any [Feast]-status
   [Class1] sanctoral candidate bands at entry 11-13, ahead of an ordinary
   Sunday's entry 15) -- so within this specific pipeline shape the
   privileged loser is unavoidably also the higher-dignity one, and this
   test cannot by itself distinguish "privilege overrides dignity" from
   "dignity alone happened to pick the same winner". That distinction is
   what [admit_cases]'s hand-tagged rows above prove (a LOWER-dignity
   privileged candidate still beats a HIGHER-dignity ordinary one on a
   II-class Sunday, the opposite of "other II class"'s own row). This test's
   job is narrower and complementary: proving the real pipeline
   ([PE.band], [PE.disposition], [PE.privilege_of] via [disposition],
   [PE.admit] together) actually reaches and exercises the override branch
   end-to-end, not merely in isolation. *)
let test_ii_class_sunday_privileged_witness_admitted_end_to_end () =
  let date = mk 2026 7 5 (* an ordinary Time-after-Pentecost Sunday. *) in
  let day_ctx = ctx date in
  let sunday = of_temporal date in
  let privileged_witness =
    cand ~origin:P.Sanctoral ~status:Cel.Commemoration_only ~layer:PE.universal_layer
      "ef-commemoration-only-b-witness"
    (* Class1 by [cand]'s own default -- RG 109(b). *)
  in
  let ordinary_saint = cand ~origin:P.Sanctoral ~rank:V.Class2 ~layer:PE.universal_layer "ef-some-saint" in
  let resolution =
    P.resolve real_rules day_ctx ~temporal:sunday ~sanctoral:[ privileged_witness; ordinary_saint ]
  in
  (* Checked against [sunday]'s own slug, not a hand-typed/guessed literal
     (its exact week number is not worth independently re-deriving here):
     this asserts identity with the real [Temporal_ef.temporal] candidate,
     proving the SUNDAY -- not either sanctoral loser -- is what wins the
     day, which the privilege assertions below presuppose. *)
  Alcotest.(check string) "the Sunday wins the day, not either sanctoral loser"
    (S.to_string sunday.P.cel.Cel.slug) (S.to_string resolution.P.observed.P.cel.Cel.slug);
  Alcotest.(check (list (pair string string)))
    "only the privileged (Commemoration_only, Class1) witness is admitted"
    [ ("ef-commemoration-only-b-witness", "Privileged") ]
    (List.map
       (fun (c, p) -> (S.to_string c.P.cel.Cel.slug, match p with P.Privileged -> "Privileged" | P.Ordinary -> "Ordinary"))
       resolution.P.commemorations);
  Alcotest.(check (list (pair string string))) "the ordinary saint is dropped into omitted, not vanished"
    [ ("ef-some-saint", "omitted: admission limit reached") ]
    (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted)

(* RG 16(a) (register §6.0) reached end-to-end through the REAL pipeline
   ([PE.band], [PE.disposition] AND [PE.admit] together, not any one of
   them in isolation as this file's own hand-tagged rows test them
   separately): a real civil date this task's own blast-radius measurement
   names as a live instance of the bug this branch fixes -- 6 August 2028
   is a Sunday (independently checked with `date -d 2028-08-06 +%A`, the
   same cross-check discipline [test_golden.ml]'s own header requires),
   Time after Pentecost week 9, and the Transfiguration (6 Aug, II class,
   "IN TRANSFIGURATIONE D. N. I. C.") falls on it. Before this fix
   `colitur day` emitted this Sunday as an admitted
   [+ef-time-after-pentecost-sunday-9] commemoration on that date (register
   §6.0's own reproduction).

   Fix round 1 (item 2): a THIRD candidate, Pope Sixtus II et al. (real
   slug/rank/status, 6 August, unrelated to the Sunday), is now also
   offered, so this test exercises BOTH fixes at once, the same real shape
   [test_golden.ml]'s own 2028-08-06 pin does: [observed] is the
   Transfiguration ({!band} entry 14 already did this correctly before this
   task); the Sunday is [disposition]-level [Omit] (RG 16(a)); Sixtus is
   [disposition]-level [Commemorate], but [admit] then excludes him too --
   RG 111(b)'s rank floor, reached only because [admit] reads the day's
   Sunday-ness off [~temporal] (the [sunday] candidate passed to
   [P.resolve]), not off [observed] (the Transfiguration, whose own slug
   carries no Sunday marker). [commemorations] is EMPTY, and BOTH losers
   land in [omitted] with their own distinct reasons -- the Sunday via
   [disposition]'s own Omit ("omitted: yielded to a higher day"), Sixtus via
   [admit]'s cut ("omitted: admission limit reached") -- proving the two
   fixes operate through genuinely different mechanisms, not the same one
   coincidentally producing the same string. *)
let test_rg16a_lord_feast_suppresses_sunday_end_to_end () =
  let date = mk 2028 8 6 in
  let day_ctx = ctx date in
  let sunday = of_temporal date in
  let transfiguration =
    cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
      "transfiguration-of-our-lord"
  in
  let sixtus =
    cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only ~layer:PE.universal_layer
      "pope-sixtus-ii-felicissimus-and-agapitus-martyrs"
  in
  let resolution =
    P.resolve real_rules day_ctx ~temporal:sunday ~sanctoral:[ transfiguration; sixtus ]
  in
  Alcotest.(check string) "the Transfiguration is observed, not the Sunday"
    "transfiguration-of-our-lord" (S.to_string resolution.P.observed.P.cel.Cel.slug);
  Alcotest.(check int)
    "no commemorations at all -- RG16(a)'s own \"nulla fit commemoratio\" AND RG111(b)'s rank floor, both live"
    0 (List.length resolution.P.commemorations);
  Alcotest.(check (list (pair string string)))
    "both losers omitted, via two DIFFERENT mechanisms: the Sunday at disposition-level (RG16a), Sixtus at \
     admit's own cut (RG111b)"
    [ ("ef-time-after-pentecost-sunday-9", "omitted: yielded to a higher day");
      ("pope-sixtus-ii-felicissimus-and-agapitus-martyrs", "omitted: admission limit reached") ]
    (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted
    |> List.sort compare)

(* End-to-end proof of BOTH fixes this task adds together, {!P.resolve} in
   full (band picks the winner, disposition and admit decide the loser's
   fate), not just the two isolated functions [cases]/[disposition_cases]
   already prove: 13 January 2019, the latest possible date Holy Family can
   fall on (Epiphany itself a Sunday) and one of the seven years it
   coincides with the fixed Commemoration of the Baptism of the Lord.
   {!band}'s [entry_14_movable_band] (RG 91 entry 14, "primum mobilia") must
   pick Holy Family as [observed], not the fixed Baptism candidate (which,
   absent that fix, would win outright via {!compare_by}'s own alphabetical
   tie-break at the shared literal 14 -- backwards from the primary text);
   {!PE.disposition}'s new RG 112(a) branch must then Omit the Baptism
   entirely, not merely demote it to a commemoration -- confirmed against
   BOTH the general rubric (RG 112(a)) and the Holy Family Mass propers' own
   more specific 13-January note (this file's own citation on the
   RG112(a) [disposition_cases] rows above), and independently against
   missalemeum's own JSON for this exact date (register §6.0): title "The
   Holy Family: Jesus, Mary & Joseph", commemorations [], the Baptism
   listed only under "displaced". *)
let test_holy_family_excludes_baptism_end_to_end () =
  let date = T.holy_family_sunday 2019 in
  Alcotest.(check string) "sanity: this really is 13 January 2019" "2019-01-13" (D.to_iso8601 date);
  let day_ctx = ctx date in
  let holy_family = of_temporal date in
  let baptism =
    cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
      "commemoration-of-the-baptism-of-the-lord"
  in
  let resolution = P.resolve real_rules day_ctx ~temporal:holy_family ~sanctoral:[ baptism ] in
  Alcotest.(check string) "Holy Family is observed, not the fixed Baptism (RG91 e14: movable first)"
    "ef-time-after-epiphany-sunday-1" (S.to_string resolution.P.observed.P.cel.Cel.slug);
  Alcotest.(check string) "II class" "class-2" (V.rank_to_string resolution.P.observed.P.cel.Cel.rank);
  Alcotest.(check int)
    "no commemorations at all -- RG112(a)'s own exclusion, not merely dropped by an admission count"
    0 (List.length resolution.P.commemorations);
  Alcotest.(check (list string)) "the Baptism is OMITTED, not merely uncommemorated"
    [ "commemoration-of-the-baptism-of-the-lord" ]
    (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.omitted)

(* Fix round 1 (coordinator finding 1), end-to-end proof of the
   [entry_14_movable_band] fix itself: a genuine I-class candidate landing
   inside Holy Family's own 7-13 January window (a synthetic proper/indult
   feast, standing in for the diocesan-overlay shape {!band}'s own new
   comment names as the live risk) must be OBSERVED and must NOT transfer
   -- reproducing, at the [Precedence.resolve] level, the exact regression
   the coordinator demonstrated against the pre-fix [-14] sentinel ("the
   I-class feast displaced and transferred to the next day"). With the fix,
   {!band}'s entry 11 (110) beats {!PE.entry_14_movable_band} (139)
   outright, so the I-class candidate wins the day, and Holy Family itself
   -- losing, is_temporal, its own slug carrying the "-sunday" marker --
   falls to RG 109(a)'s ordinary privileged-Sunday-commemoration path
   (disposition's final catch-all, [privilege_of]'s (a) branch), the same
   treatment ANY genuine feast outranking an ordinary Sunday gets: neither
   omitted nor transferred, a Privileged commemoration. *)
let test_class1_feast_inside_holy_familys_window_end_to_end () =
  let date = mk 2026 1 11 in
  Alcotest.(check string) "sanity: this is Holy Family's own date in 2026" "2026-01-11" (D.to_iso8601 date);
  let day_ctx = ctx date in
  let holy_family = of_temporal date in
  let synthetic_class1 =
    cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:PE.universal_layer "ef-synthetic-class1-probe"
  in
  let resolution = P.resolve real_rules day_ctx ~temporal:holy_family ~sanctoral:[ synthetic_class1 ] in
  Alcotest.(check string) "the I-class candidate is observed, NOT Holy Family (RG91 e11 beats e14-movable)"
    "ef-synthetic-class1-probe" (S.to_string resolution.P.observed.P.cel.Cel.slug);
  Alcotest.(check (list string)) "nothing transfers -- the pre-fix bug moved the I-class feast to the next day"
    [] (List.map (fun c -> S.to_string c.P.cel.Cel.slug) resolution.P.deferred);
  (match resolution.P.commemorations with
  | [ (c, P.Privileged) ] ->
      Alcotest.(check string)
        "Holy Family itself is privileged-commemorated (RG109(a), a Sunday-shaped office \
         outranked by a genuine feast), neither omitted nor transferred"
        "ef-time-after-epiphany-sunday-1" (S.to_string c.P.cel.Cel.slug)
  | other ->
      Alcotest.failf "expected exactly one Privileged commemoration (Holy Family), got %d"
        (List.length other))

(* Most Holy Name of Jesus (RG 17(a)) -- the SAME two proofs immediately
   above, restated for Holy Name instead of Holy Family. UNLIKE Holy Family,
   there is no LIVE collision to exercise here (this file's own [cases]/
   [disposition_cases] rows never construct one, because
   data/ef/sanctoral.sexp has nothing fixed-date 2-5 January besides
   [telesphorus-pope-and-martyr] (5 Jan, Commemoration_only, so it can never
   contest [observed] at all -- {!P.resolve}'s own partition holds it out of
   the band contest entirely) -- register §6.2's own account: "no live
   collision exists for it in this codebase's current data". These two
   tests are the SYNTHETIC proof the task brief asked for instead: that
   {!band}'s [entry_14_movable_band]/RG 112(a) mechanism, ALREADY proved
   above for Holy Family, governs Holy Name identically, since neither
   {!band} nor {!disposition} key on anything Holy-Family-specific (only on
   [subject]/[rank]/[origin], which [temporal_ef.ml]'s fix now sets
   identically for both feasts) -- not a live-data regression test, a
   mechanism-generalises-correctly one, the same distinction this file's own
   header draws between a table row and an end-to-end proof.

   Run against BOTH of RG 17(a)'s own shapes -- the Sunday (2026-01-04) and
   the 2 January fallback (2029-01-02, {!T.holy_name_fallback_date}) --
   because [temporal_ef.ml]'s own fallback-branch comment claims both band
   identically at {!PE.entry_14_movable_band} (ONE feast, not two
   differently-classified ones); a table proving the SAME two outcomes hold
   for both dates is direct evidence for that specific claim, not merely
   for the Sunday shape colitur already had a name for before this task. *)
let holy_name_probe_dates =
  [ ("Sunday shape (2026-01-04)", mk 2026 1 4); ("fallback shape (2029-01-02)", T.holy_name_fallback_date 2029) ]

let test_holy_name_excludes_synthetic_lord_end_to_end () =
  List.iter
    (fun (label, date) ->
      let holy_name = of_temporal date in
      Alcotest.(check bool) (label ^ ": sanity -- temporal_ef really did tag this Lord") true
        (holy_name.P.cel.Cel.subject = Sub.Lord);
      let day_ctx = ctx date in
      let synthetic_lord =
        cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer
          "ef-synthetic-fixed-lord-probe"
      in
      let resolution = P.resolve real_rules day_ctx ~temporal:holy_name ~sanctoral:[ synthetic_lord ] in
      Alcotest.(check string) (label ^ ": Holy Name is observed (RG91 e14: movable first)")
        (S.to_string holy_name.P.cel.Cel.slug) (S.to_string resolution.P.observed.P.cel.Cel.slug);
      Alcotest.(check int) (label ^ ": no commemorations at all -- RG112(a)'s own exclusion") 0
        (List.length resolution.P.commemorations);
      Alcotest.(check (list string)) (label ^ ": the synthetic Lord candidate is OMITTED, not commemorated")
        [ "ef-synthetic-fixed-lord-probe" ]
        (List.map (fun (c, _) -> S.to_string c.P.cel.Cel.slug) resolution.P.omitted))
    holy_name_probe_dates

(* A REAL asymmetry this test found between the two shapes, kept explicit
   rather than papered over by forcing one uniform expectation on both --
   RG 109(a)'s privilege is textually "of a SUNDAY" (docs/research/rules-
   register.md §4), read here via {!PE.is_sunday_slug}'s own substring test:
   the Sunday shape's slug ("ef-holy-name-sunday") carries the marker and
   the fallback shape's ("ef-holy-name") genuinely does not, because 2
   January genuinely is not a Sunday. So a losing Holy-Name-Sunday is
   PRIVILEGED (RG109(a)) and survives RG111(a)'s "I class: none save one
   privileged" cap when it loses to a higher day; a losing Holy-Name-
   fallback has no RG109(a)-(f) category to claim (not a Sunday, not I
   class, none of (c)-(f) either) and so is only ORDINARY -- which
   RG111(a)'s own cap admits NONE of on a I-class day. Both outcomes are
   individually correct readings of RG 109/111's own closed lists, not a
   defect in either shape -- first observed as a test failure while writing
   this pair (the original version of this test asserted one Privileged
   commemoration for BOTH shapes uniformly and reddened here), traced to
   this citation rather than forced to pass. *)
let test_class1_feast_inside_holy_name_window_end_to_end () =
  List.iter
    (fun (label, date, expect_privileged_commemoration) ->
      let holy_name = of_temporal date in
      let day_ctx = ctx date in
      let synthetic_class1 =
        cand ~origin:P.Sanctoral ~subject:Sub.Saint ~layer:PE.universal_layer "ef-synthetic-class1-probe-hn"
      in
      let resolution = P.resolve real_rules day_ctx ~temporal:holy_name ~sanctoral:[ synthetic_class1 ] in
      Alcotest.(check string) (label ^ ": the I-class candidate is observed, NOT Holy Name (RG91 e11 beats e14-movable)")
        "ef-synthetic-class1-probe-hn" (S.to_string resolution.P.observed.P.cel.Cel.slug);
      Alcotest.(check (list string)) (label ^ ": nothing transfers") []
        (List.map (fun c -> S.to_string c.P.cel.Cel.slug) resolution.P.deferred);
      if expect_privileged_commemoration then
        match resolution.P.commemorations with
        | [ (c, P.Privileged) ] ->
            Alcotest.(check string)
              (label ^ ": Holy Name itself is privileged-commemorated (RG109(a)), neither omitted nor transferred")
              (S.to_string holy_name.P.cel.Cel.slug) (S.to_string c.P.cel.Cel.slug)
        | other ->
            Alcotest.failf "%s: expected exactly one Privileged commemoration (Holy Name), got %d" label
              (List.length other)
      else
        Alcotest.(check int)
          (label ^ ": RG111(a) admits none -- Holy Name's own commemoration here is Ordinary \
                    (no RG109(a)-(f) category applies to a non-Sunday loser), and a I-class day admits \
                    only a Privileged one")
          0 (List.length resolution.P.commemorations))
    [ ("Sunday shape (2026-01-04)", mk 2026 1 4, true);
      ("fallback shape (2029-01-02)", T.holy_name_fallback_date 2029, false) ]

(* Completes Task 7's carried fix (RG 91 entry 8, §4): on a real Sunday
   landing on 2 November, All Souls does not merely lose (that was Task 7's
   [band] fix, proved by [test_all_souls_yields_to_sunday] above) -- it must
   be TRANSFERRED, not commemorated and not omitted. All Souls is I class
   (RG 91 entry 8's own [rank] field, untouched by the Sunday-exception band
   bump -- see precedence_ef.ml's comment on entry 8), so RG 95's rank
   condition alone should route it to [Transfer]. *)
let test_all_souls_disposition_is_transfer () =
  let sunday = an_ordinary_sunday in
  let all_souls = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-all-souls" in
  Alcotest.(check string) "All Souls loses to a Sunday and transfers"
    "Transfer"
    (string_of_disposition (PE.disposition ~winner:sunday ~loser:all_souls))

(* The same fact, proved end-to-end through [Precedence.resolve] with the
   REAL [PE.band] and REAL [PE.disposition] wired together (Task 7's own
   integration test above still stubs [disposition] to a constant [Omit],
   which is exactly what this task must not leave true) -- All Souls must
   land in [deferred], not [commemorations] or [omitted]. WHERE it is placed
   (3 November, RG 96) is [Rite.transfer_target]'s job, out of this task's
   scope; this only proves [resolve] hands it to the transfer path at all. *)
let test_all_souls_transfers_end_to_end () =
  let date = mk 2025 11 2 in
  let day_ctx = ctx date in
  let sunday =
    { P.cel =
        Cel.make ~slug:(S.of_string_exn "ef-time-after-pentecost-sunday-x") ~rank:V.Class2
          ~colour:Col.Green ~subject:Sub.Temporal ~layer:"temporal" ();
      origin = P.Temporal }
  in
  let all_souls = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-all-souls" in
  let rules =
    { P.band = PE.band; disposition = PE.disposition;
      admit = (fun ~observed:_ ~temporal:_ cs -> List.map (fun (c, p, (_ : int)) -> (c, p)) cs) }
  in
  let resolution = P.resolve rules day_ctx ~temporal:sunday ~sanctoral:[ all_souls ] in
  Alcotest.(check (list string)) "All Souls is deferred (transferred), not omitted or commemorated"
    [ "ef-all-souls" ]
    (List.map (fun c -> S.to_string c.P.cel.Cel.slug) resolution.P.deferred);
  Alcotest.(check int) "nothing commemorated" 0 (List.length resolution.P.commemorations);
  Alcotest.(check int) "nothing omitted" 0 (List.length resolution.P.omitted)

(* Task 11: [PE.transfer_target] -- RG 96 ("the next following day that is
   not I or II class") plus its Annunciation exception. [occupant] is a
   synthetic callback ({!Colitur_kernel.Rite.t.transfer_target}'s own
   [occupant] parameter), not a real [Calendar]-driven one -- the CLI's own
   end-to-end proof (colitur day, All Souls landing on 3 Nov 2025 and the
   Annunciation landing on 5 Apr 2027, see test/cli.t and the task report)
   is what wires this against real data; these rows isolate the search
   function itself. *)

(* [blocked] returns Class1 (blocking) for exactly the listed dates, Class4
   (not blocking) everywhere else -- enough to exercise [is_blocking]'s own
   two-way test (RG 96 speaks of I OR II class; Class1 alone is enough to
   prove the blocking side, [test_transfer_target_terminates...] below adds
   nothing by varying it further). *)
let occupant_blocking_on blocked_dates (d : D.t) : V.rank Cel.t =
  let blocking = List.exists (fun bd -> D.compare bd d = 0) blocked_dates in
  Cel.make ~slug:(S.of_string_exn "occupant") ~rank:(if blocking then V.Class1 else V.Class4)
    ~colour:Col.Green ~layer:"synthetic" ()

let occupant_always_blocking (_ : D.t) : V.rank Cel.t =
  Cel.make ~slug:(S.of_string_exn "occupant") ~rank:V.Class1 ~colour:Col.Green ~layer:"synthetic" ()

(* General RG 96 search: two consecutive blocked days past [origin], proving
   the search walks past MORE than one ineligible day rather than only
   trying [origin + 1] and stopping (the same shape Calendar's own
   synthetic fixture pins for the abstraction -- this pins it for the real
   EF search function). *)
let test_transfer_target_general_multi_step_search () =
  let origin = mk 2026 1 10 in
  let occupant = occupant_blocking_on [ mk 2026 1 11; mk 2026 1 12 ] in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-some-i-class-feast" in
  let target = PE.transfer_target c origin occupant in
  Alcotest.(check string) "lands on the first day past the blocked run"
    "2026-01-13" (D.to_iso8601 target)

(* Coordinator review (fix round 1): RG 96 Attamen (a) (register-transcribed,
   primary-source-verified) makes the Annunciation exception CONDITIONAL on
   the general RG 96 walk carrying the feast past Easter Sunday -- NOT
   unconditional as the first transcription had it. The occupant here blocks
   every day from [origin + 1] through the day after Easter (26 March - 6
   April 2026 inclusive), so the GENERAL target itself would land at 7
   April -- after Easter (5 April) -- which is exactly the trigger
   condition, not merely "the Annunciation is impeded at all". *)
let test_transfer_target_annunciation_starts_at_monday_after_low_sunday () =
  let origin = mk 2026 3 25 in
  let easter_2026 = Comp.gregorian_easter 2026 in
  let blocked_through_day_after_easter =
    let rec range a b = if D.compare a b > 0 then [] else a :: range (D.add_days a 1) b in
    range (D.add_days origin 1) (D.add_days easter_2026 1)
  in
  let occupant = occupant_blocking_on blocked_through_day_after_easter in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer PE.annunciation_slug in
  let target = PE.transfer_target c origin occupant in
  let monday_after_low_sunday = D.add_days easter_2026 8 in
  Alcotest.(check string) "lands on the Monday after Low Sunday (Easter + 8), the general \
                           walk having crossed Easter itself"
    (D.to_iso8601 monday_after_low_sunday) (D.to_iso8601 target);
  Alcotest.(check bool) "NOT the general target (2 days after Easter, discriminates the branch)"
    true
    (D.compare target (D.add_days easter_2026 2) <> 0)

(* RG 96's own qualifier on the exception -- "searching onward from there
   only if that day is itself blocked" (rite.mli) -- is [search_from]'s
   ORDINARY behaviour, not a second mechanism: same blocked run as above
   (forcing the general target past Easter, so the exception fires), PLUS
   the Monday after Low Sunday itself blocked, confirming the search
   continues exactly one more day from there. *)
let test_transfer_target_annunciation_searches_onward_if_blocked () =
  let origin = mk 2026 3 25 in
  let easter_2026 = Comp.gregorian_easter 2026 in
  let monday_after_low_sunday = D.add_days easter_2026 8 in
  let blocked =
    let rec range a b = if D.compare a b > 0 then [] else a :: range (D.add_days a 1) b in
    range (D.add_days origin 1) (D.add_days easter_2026 1) @ [ monday_after_low_sunday ]
  in
  let occupant = occupant_blocking_on blocked in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer PE.annunciation_slug in
  let target = PE.transfer_target c origin occupant in
  Alcotest.(check string) "searches onward one more day when that Monday is itself blocked"
    (D.to_iso8601 (D.add_days monday_after_low_sunday 1)) (D.to_iso8601 target)

(* THE REGRESSION PIN (coordinator review): the bug an unconditional
   exception produced. 25 March 2057 is Lent III Sunday (I class, RG 91
   entry 6), impeding the Annunciation; 26 March 2057 is an ordinary Lent
   feria (III class, well before Easter, 22 April 2057) -- the general RG
   96 target. The general target does NOT fall after Easter, so the
   exception must NOT fire: the Annunciation lands on 26 March, not 13
   April (Easter + 8), which is what the unconditional reading produced
   (verified by reverting the fix and re-running this exact test -- see the
   task report's mutation record). Uses the REAL [Temporal_ef.temporal] as
   [occupant] (not a synthetic stand-in), the same coupling-safety
   convention [of_temporal]'s callers use elsewhere in this file, so this
   is also effectively an end-to-end check of the real 2057 calendar
   shape, not just the search's own logic in isolation. *)
let test_transfer_target_annunciation_not_overridden_when_general_target_precedes_easter () =
  let origin = mk 2057 3 25 in
  Alcotest.(check string) "25 March 2057 is a Sunday (Lent III)" "sunday"
    (D.weekday_to_string (D.weekday origin));
  let occupant d = (T.temporal d).Colitur_kernel.Temporal.office in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer PE.annunciation_slug in
  let target = PE.transfer_target c origin occupant in
  Alcotest.(check string) "lands on 26 March 2057 (the general RG96 target), NOT the \
                           Annunciation exception's Monday after Low Sunday"
    "2057-03-26" (D.to_iso8601 target)

(* rite.mli's own obligations on [transfer_target] (Task 11 brief): the call
   must TERMINATE and its result must be STRICTLY AFTER [origin], even for a
   rite/data shape this function cannot have anticipated -- an occupant that
   reports every single day as blocking, forever. Calendar's own round guard
   (max_transfer_rounds) does not cover this: it bounds ROUNDS across a
   whole year, not the internal walk one call to this function makes (see
   precedence_ef.ml's own comment on [search_from] and [max_search_days]).
   Deliberately NOT pinning the exact returned date against the private
   [max_search_days] constant -- that would coalesce a behavioural contract
   (terminates, makes forward progress) with an internal tuning value this
   function is free to change; a generous, test-owned ceiling (1000 days,
   comfortably past any realistic bound) is enough to prove termination is
   genuine and not merely "didn't hang during this particular run". *)
let test_transfer_target_terminates_under_pathological_occupant () =
  let origin = mk 2026 1 1 in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-pathological-case" in
  let target = PE.transfer_target c origin occupant_always_blocking in
  Alcotest.(check bool) "strictly after origin" true (D.compare target origin > 0);
  Alcotest.(check bool)
    "terminates within a generous bound (proves the internal search is bounded, not merely lucky)"
    true
    (D.compare target (D.add_days origin 1000) <= 0)

(* Coordinator review: [search_from] must not probe [occupant] past
   {!Date}'s own domain ceiling (31 December 9999). A SYNTHETIC occupant
   (like [occupant_always_blocking] above) can never actually discriminate
   this: it never calls [Computus.gregorian_easter] itself, so it cannot
   raise regardless of whether the domain guard exists -- a test built on
   one would only prove [search_from]'s unrelated step bound, not this fix.
   [occupant] here is instead the REAL [Temporal_ef.temporal] (no sanctoral
   layer needed: 29-31 Dec are ALREADY II class via [named]'s own Nativity-
   octave-day entries, so three real, unbroken blocking days already sit at
   the very end of the domain) -- exactly the shape that raises without the
   fix: 1 January of civil year 10000 is next, and [Computus.gregorian_easter
   10000] does [Date.make ~year:10000 ...] and [failwith]s (the .ml's own
   [domain_max_date] comment; also how the reviewer reproduced the bug
   through the project's own overlay mechanism -- see the task report for
   that end-to-end reproduction). Mutation-verified: reverting the domain
   guard makes this test error with exactly that uncaught [Failure], not
   merely fail an assertion (see the task report). *)
let test_transfer_target_does_not_raise_at_domain_ceiling () =
  let origin = mk 9999 12 28 in
  let occupant d = (T.temporal d).Colitur_kernel.Temporal.office in
  let c = cand ~origin:P.Sanctoral ~layer:PE.universal_layer "ef-domain-ceiling-case" in
  let target = PE.transfer_target c origin occupant in
  Alcotest.(check bool) "past 31 December 9999 (the guard engaged; nothing admissible remained \
                         in-domain, so the search gave up at the ceiling rather than crashing)"
    true
    (D.compare target (mk 9999 12 31) > 0)


let suite =
  ( "Precedence_ef",
    List.map
      (fun (desc, date, c, expect) ->
        Alcotest.test_case desc `Quick (fun () ->
            Alcotest.(check int) desc expect (PE.band (ctx date) c)))
      cases
    @ [ Alcotest.test_case "8 All Souls yields to a Sunday (resolve-level)" `Quick
          test_all_souls_yields_to_sunday ]
    @ List.map
        (fun (desc, winner, loser, expect) ->
          Alcotest.test_case desc `Quick (fun () ->
              Alcotest.(check string) desc expect
                (string_of_disposition (PE.disposition ~winner ~loser))))
        disposition_cases
    @ List.map
        (fun (desc, winner, loser, expect) ->
          Alcotest.test_case desc `Quick (fun () ->
              Alcotest.(check string) desc expect
                (string_of_disposition (PE.disposition ~winner ~loser))))
        privilege_cases
    @ [ Alcotest.test_case "All Souls disposition is Transfer" `Quick
          test_all_souls_disposition_is_transfer;
        Alcotest.test_case "All Souls transfers end-to-end (resolve, real rules)" `Quick
          test_all_souls_transfers_end_to_end ]
    @ List.map
        (fun (desc, observed, temporal, comms, expect) ->
          Alcotest.test_case desc `Quick (fun () ->
              Alcotest.(check (list string)) desc expect
                (slugs_of (PE.admit ~observed ~temporal comms))))
        admit_cases
    @ [ Alcotest.test_case "admit is order-independent (III-class, 3 candidates)" `Quick
          test_admit_order_independent;
        Alcotest.test_case
          "RG113: admit picks by precedence order, not slug, when they disagree" `Quick
          test_admit_uses_precedence_not_slug_when_they_disagree;
        Alcotest.test_case "I-class day: full drop reported in omitted, not vanished" `Quick
          test_i_class_day_drops_into_omitted;
        Alcotest.test_case "II-class Sunday: second loser dropped into omitted" `Quick
          test_ii_class_sunday_drops_second_loser_into_omitted;
        Alcotest.test_case "RG109(e) Lent feria privileged end-to-end" `Quick
          test_privileged_lent_feria_admitted_end_to_end;
        Alcotest.test_case
          "II-class Sunday override: RG109(b) witness admitted over an ordinary saint, end-to-end"
          `Quick test_ii_class_sunday_privileged_witness_admitted_end_to_end;
        Alcotest.test_case
          "RG16(a): a Feast of the Lord suppresses the Sunday's own commemoration entirely, \
           end-to-end (2028-08-06)"
          `Quick test_rg16a_lord_feast_suppresses_sunday_end_to_end;
        Alcotest.test_case
          "RG112(a) + RG91 e14 \"primum mobilia\": Holy Family excludes the Baptism of the Lord \
           entirely, end-to-end (13 Jan 2019)"
          `Quick test_holy_family_excludes_baptism_end_to_end;
        Alcotest.test_case
          "fix round 1, finding 1: a synthetic I-class candidate inside Holy Family's own window is \
           observed and does not transfer (2026-01-11)"
          `Quick test_class1_feast_inside_holy_familys_window_end_to_end;
        Alcotest.test_case
          "RG112(a) + RG91 e14 \"primum mobilia\": Holy Name of Jesus excludes a synthetic fixed Lord \
           candidate entirely, both shapes (Sunday + fallback)"
          `Quick test_holy_name_excludes_synthetic_lord_end_to_end;
        Alcotest.test_case
          "a synthetic I-class candidate inside Holy Name's own window is observed and does not \
           transfer, both shapes (Sunday + fallback)"
          `Quick test_class1_feast_inside_holy_name_window_end_to_end;
        Alcotest.test_case "transfer_target: general RG96 search walks past more than one blocked day"
          `Quick test_transfer_target_general_multi_step_search;
        Alcotest.test_case "transfer_target: Annunciation exception starts at Monday after Low Sunday"
          `Quick test_transfer_target_annunciation_starts_at_monday_after_low_sunday;
        Alcotest.test_case
          "transfer_target: Annunciation exception searches onward if that Monday is blocked" `Quick
          test_transfer_target_annunciation_searches_onward_if_blocked;
        Alcotest.test_case
          "transfer_target: Annunciation NOT overridden when the general target precedes Easter \
           (2057 regression)"
          `Quick test_transfer_target_annunciation_not_overridden_when_general_target_precedes_easter;
        Alcotest.test_case "transfer_target: terminates and stays forward under a pathological occupant"
          `Quick test_transfer_target_terminates_under_pathological_occupant;
        Alcotest.test_case "transfer_target: does not raise probing past the domain ceiling" `Quick
          test_transfer_target_does_not_raise_at_domain_ceiling ] )