summaryrefslogtreecommitdiff
path: root/test/test_precedence_ef.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 10:04:21 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 10:04:21 +0200
commit59fbda87d565ad9b98c2493e9703390cd988609d (patch)
tree71ebfb8e9568cd5003c10dabc8804fba4ca66e76 /test/test_precedence_ef.ml
parent254cfd7d9111b0f23f9c44e82d3e31d5817dc5a4 (diff)
parente30a4be5a2594fb3b6c4b51345d138cd89ba78ad (diff)
downloadcolitur-59fbda87d565ad9b98c2493e9703390cd988609d.tar.gz
colitur-59fbda87d565ad9b98c2493e9703390cd988609d.zip
Merge branch 'ef-rg112-rg110': the Holy Family, RG 112(a), observed identity
Builds the Feast of the Holy Family (RG 17(b), first Sunday after Epiphany, II class), which did not exist in colitur at all -- it emitted an ordinary Sunday on all 8417 days the feast occurs. Adds RG 112(a)'s exclusion, on RG 95 paragraph 2's authority ('Si vero duo festa eiusdem Divinae Personae ... fit de festo, quod in tabella praecedentiae superiorem obtinet locum, et aliud omittitur'), corroborated by RG 112(a) and by the Holy Family Mass propers' own 13-January rubric. Three validation layers had missed the missing feast, each for its own reason: the differential because lectio lacks it too and shares colitur's data lineage; the golden layer for want of a pin; and the oracle because it compared the observed day's rank and colour but never its identity. That last gap is the more important half of this change and is now closed -- the oracle compares observed identity, with the unresolvable population counted and pinned rather than silently skipped. Also rescales RG 91's band table x10. entry_14_movable_band had been a negative sentinel, chosen to avoid collision with a real table position; it also inverted ordering, making a movable II-class feast of the Lord outrank every I-class day. Unreachable on universal data, live the moment a diocesan overlay places a I-class proper or indult feast in the 7-13 January window. A half-row is now an ordinary position between its neighbours. The rescale was verified entry-by-entry against a photographic scan and moves zero days across the domain. RG 110 is NOT built despite the branch name; it remains open, as do the Holy Name of Jesus office (missing in 3619 of 8417 years) and RG 98's transfer-ordering mechanism.
Diffstat (limited to 'test/test_precedence_ef.ml')
-rw-r--r--test/test_precedence_ef.ml354
1 files changed, 285 insertions, 69 deletions
diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml
index 2935e40..4ee2fd8 100644
--- a/test/test_precedence_ef.ml
+++ b/test/test_precedence_ef.ml
@@ -48,110 +48,162 @@ 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", 1);
- ("1 Easter Sunday", off 0, cand "ef-easter-sunday", 1);
- ("1 Pentecost Sunday", off 49, cand "ef-pentecost", 1);
+ ("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", 2);
- ("2 Good Friday", off (-2), cand "ef-good-friday", 2);
- ("2 Holy Saturday", off (-1), cand "ef-holy-saturday", 2);
+ ("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", 3);
- ("3 Ascension", off 39, cand "ef-ascension", 3);
- ("3 Trinity", off 56, cand "ef-trinity", 3);
- ("3 Corpus Christi", off 60, cand "ef-corpus-christi", 3);
- ("3 Sacred Heart", off 68, cand "ef-sacred-heart", 3);
- ("3 Christ the King", T.christ_the_king 2026, cand "ef-christ-the-king", 3);
+ ("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",
- 4 );
- ("4 Assumption", mk 2026 8 15, cand ~origin:P.Sanctoral ~subject:Sub.Bvm ~layer:PE.universal_layer "ef-assumption", 4);
+ 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", 5);
- ("5 Octave day (Circumcision)", mk 2026 1 1, cand "ef-circumcision", 5);
+ ("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", 6);
- ("6 Lent Sunday", off (-42), cand "ef-lent-sunday-1", 6);
- ("6 Passion Sunday (I Passiontide)", off (-14), cand "ef-passion-sunday", 6);
- ("6 Palm Sunday (II Passiontide)", off (-7), cand "ef-palm-sunday", 6);
- ("6 Low Sunday", off 7, cand "ef-low-sunday", 6);
+ ("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", 7);
- ("7 Monday of Holy Week", off (-6), cand "ef-holy-monday", 7);
- ("7 Tuesday of Holy Week", off (-5), cand "ef-holy-tuesday", 7);
- ("7 Wednesday of Holy Week", off (-4), cand "ef-holy-wednesday", 7);
+ ("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", 8);
+ ("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 8 -- it must lose to entry 15 (16 =
- entry 15's own value + 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
+ 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", 16);
+ ("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", 9);
+ ("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", 10);
- ("10 Easter octave, day+6", off 6, cand "ef-easter-1-sat", 10);
- ("10 Pentecost octave, day+50", off 50, cand "ef-pentecost-1-mon", 10);
- ("10 Pentecost octave, day+55", off 55, cand "ef-pentecost-1-sat", 10);
+ ("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",
- 11 );
+ 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",
- 12 );
+ 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",
- 13 );
+ 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",
- 14 );
+ 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 14, not
- 19 -- the exact restriction review finding 2 flagged and this row
- exists to keep from silently coming back. *)
+ 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",
- 14 );
+ 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", 15);
+ ("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",
- 16 );
+ 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", 17);
- ("17 Nativity octave, 31 Dec", mk 2026 12 31, cand ~rank:V.Class2 "ef-nativity-octave-day-7", 17);
+ ("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
@@ -159,21 +211,21 @@ let cases =
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", 18);
+ ("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)), 18);
+ ("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",
- 19 );
+ 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",
- 20 );
+ 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
@@ -183,22 +235,22 @@ let cases =
(* 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), 21);
+ ("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",
- 21 );
+ 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",
- 21 );
+ 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", 22);
- ("22 Passiontide feria", off (-12), cand ~rank:V.Class3 "ef-passiontide-1-tue", 22);
+ ("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
@@ -206,45 +258,45 @@ let cases =
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",
- 23 );
+ 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",
- 24 );
+ 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", 25);
+ ("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",
- 26 );
+ 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",
- 26 );
+ 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 16/24 (an ordinary feast of the same rank) instead
- of 21/26. *)
+ 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",
- 21 );
+ 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",
- 26 );
+ 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",
- 27 );
+ 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", 28);
+ ("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
@@ -662,7 +714,87 @@ let disposition_cases =
cand ~origin:P.Sanctoral ~rank:V.Class3 ~subject:Sub.Lord ~layer:PE.universal_layer
"ef-synthetic-class3-lord-feast",
an_ordinary_sunday,
- "Commemorate(Privileged)" )
+ "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"),
@@ -1218,6 +1350,82 @@ let test_rg16a_lord_feast_suppresses_sunday_end_to_end () =
(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))
+
(* 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
@@ -1465,6 +1673,14 @@ let suite =
"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 "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"