From b23020c7acc6b22d99ffeeac5d65e9ad4a58c82c Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 09:11:21 +0200 Subject: precedence(ef): fix round 1 (F1) -- band's -14 sentinel inverted the table entry_14_movable_band = -14 was justified only by "cannot collide with a real table position (1..28) or unclassified" -- a collision argument, not an ordering one. band's own value is compared by plain <, so a negative number beats every I-class entry (1-13), not merely the fixed Baptism it was built to beat. Not live against the shipped universal sanctoral data (no I-class universal feast falls 7-13 January, which is why the original 1583-9999 sweep showed only the intended shape), but entries 12/13 admit PROPER and INDULT I-class feasts too, and those arrive via adjustments.sexp-style overlays -- this architecture's own advertised extension path. A diocesan patronal feast landing in Holy Family's window would have made the inversion live immediately: the I-class feast displaced and transferred to the next day, reproduced and confirmed before this fix, reverted after. Fixed by rescaling every band branch to the real RG 91 entry number times ten (unclassified untouched at max_int) instead of the bare 1..28, reserving genuine headroom immediately before each entry for a movable sub-rank -- not an arbitrary rescale: RG 91's own text records a "primum mobilia, deinde fixa" split at three rows, not only entry 14 (entry 12's own ninth sub-item, entry 14, and entry 20), so this leaves room for the other two without a second rescale if either ever gets a live witness. entry_14_movable_band now derives from entry_14_fixed_band - 1, tied together so they cannot drift apart. Added a synthetic I-class end-to-end test reproducing the exact counter-example that found this: a universal I-class candidate inside Holy Family's own 7-13 January window is now observed and does not transfer, with Holy Family itself falling to RG109(a)'s ordinary privileged-Sunday-commemoration path, the same treatment any genuine feast outranking an ordinary Sunday gets. --- lib/rites/rite_ef/precedence_ef.ml | 149 +++++++++++++++-------- lib/rites/rite_ef/precedence_ef.mli | 41 +++++-- test/test_precedence_ef.ml | 232 +++++++++++++++++++++++------------- 3 files changed, 276 insertions(+), 146 deletions(-) diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 87cdeeb..39251a7 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -48,33 +48,72 @@ let universal_layer = "ef-universal" let indult_prefix = "indult:" let unclassified = max_int -(* Not an RG citation -- an ordering device for RG 91 entry 14's own text - (scan-verified, docs/research/rules-register.md §4): "14. Festa Domini II - classis, PRIMUM MOBILIA, DEINDE FIXA" -- feasts of the Lord, II class, - MOVABLE FIRST, then fixed. Both halves are the SAME numbered table row, - but the primary text states a real priority between them, not merely two - disjoint categories -- [band]'s own entry-14 branches below must not - literally TIE at the shared integer 14, because a tie would fall to +(* CORRECTED, fix round 1 (coordinator finding 1) -- the ORIGINAL version of + this constant was [-14], justified only by "cannot collide with a real + table position (1..28) or [unclassified]". That is a COLLISION argument, + not an ORDERING one, and [band]'s own return value is compared by + {!Precedence.compare_by}/{!compare_precedence} as a plain [<] -- a + negative value is not merely "distinct from 1..28", it is LOWER than + every one of them, so a movable Class2 Lord feast (Holy Family) would + have beaten every I-class day (entries 1-13) outright, not merely the + fixed Baptism it was built to beat. Not live against the shipped + UNIVERSAL sanctoral data (no I-class universal feast falls 7-13 January), + which is why the original 1583-9999 blast radius (§6.2) showed only the + one intended shape -- but entries 12/13 admit PROPER and INDULT I-class + feasts too (patron/titular/dedication days, {!is_universal}/{!is_indult} + below), which arrive via `data/ef/adjustments.sexp`-style overlays, this + architecture's own advertised extension point -- a diocesan I-class + patronal feast landing 7-13 January would have made the inversion live + immediately, transferring the I-class feast as though a II-class Sunday + had impeded it. Demonstrated directly (fix round 1, reproduced then + reverted): a synthetic Class1 sanctoral candidate on 11 January was + OBSERVED as Holy Family (band -14) and the synthetic candidate itself + TRANSFERRED to the next day -- backwards on every count. + + FIXED: every {!band} branch now returns the real RG 91 entry number + TIMES TEN (entry 1 -> 10, entry 28 -> 280, {!unclassified} untouched at + [max_int]) instead of the bare 1..28 -- not an arbitrary rescale, a + deliberate one: RG 91's own text records a "primum mobilia, deinde + fixa" (movable-then-fixed) split at THREE rows, not only this one -- + entry 12's own ninth sub-item ("Festa indulta I classis, primum + mobilia, deinde fixa"), entry 14 (this one), and entry 20 ("indult + II-class feasts (movable then fixed)", register §4's own summary) -- + and only entry 14 has a live witness in this codebase's data today. The + ×10 scale reserves nine integers of genuine headroom immediately BEFORE + each real entry's own value for exactly this kind of sub-rank, without + requiring a second rescale if entry 12's or entry 20's own movable half + ever needs one too -- a value DERIVABLE from RG 91's own table structure, + not an arithmetic convenience chosen to dodge one collision. [band]'s own + entry-14 comments (below) restate this scale at each branch it touches. *) +let entry_14_fixed_band = 140 + +(* RG 91 entry 14's own text (scan-verified, docs/research/rules-register.md + §4): "14. Festa Domini II classis, PRIMUM MOBILIA, DEINDE FIXA" -- feasts + of the Lord, II class, MOVABLE FIRST, then fixed. Both halves are the + SAME numbered table row, but the primary text states a real priority + between them, not merely two disjoint categories -- [band]'s own + entry-14 branches below must not literally TIE at + {!entry_14_fixed_band}, because a tie would fall to {!Precedence.resolve}'s own kernel-level fallback ([compare_by], Slug.compare), which is deliberately NOT rubric-authoritative (the exact anti-pattern RG 113's own fix already corrected once for commemoration ordering, this file's own header) and would in fact pick the WRONG side here: "commemoration-of-the-baptism-of-the-lord" sorts before "ef-time-after-epiphany-sunday-1" alphabetically, backwards from "primum - mobilia". Negative, so it can never collide with any real table position - (1..28) or [unclassified] (positive [max_int]) by construction -- not a - citation to some entry "13.5" that does not exist in the primary text, - just an ordering fact this integer has to carry because RG 91's own two - half-rows must compare unequal here. The one real witness today: Holy - Family (RG 17(b), temporal_ef.ml's own [holy_family_sunday]) against the - fixed Commemoration of the Baptism of the Lord (13 January) -- the only - date they can ever coincide, since Holy Family only ever falls 7-13 - January and no OTHER fixed Class2-Lord sanctoral entry shares that - window (register §6.0's own subject audit: Transfiguration 6 Aug, - Exaltation of the Cross 14 Sep, Dedication of the Archbasilica 9 Nov, - the Purification 2 Feb, the Baptism 13 Jan -- the Baptism alone falls in - Holy Family's own window). *) -let entry_14_movable_band = -14 + mobilia". Derived from {!entry_14_fixed_band}, one better (lower) than + it, using the one integer of headroom the ×10 scale reserves immediately + before every real entry -- not a citation to some entry "13.5" that does + not exist in the primary text, just the ordering fact RG 91's own two + half-rows require, expressed the same way the whole table now is. The + one real witness today: Holy Family (RG 17(b), temporal_ef.ml's own + [holy_family_sunday]) against the fixed Commemoration of the Baptism of + the Lord (13 January) -- the only date they can ever coincide, since + Holy Family only ever falls 7-13 January and no OTHER fixed Class2-Lord + sanctoral entry shares that window (register §6.0's own subject audit: + Transfiguration 6 Aug, Exaltation of the Cross 14 Sep, Dedication of the + Archbasilica 9 Nov, the Purification 2 Feb, the Baptism 13 Jan -- the + Baptism alone falls in Holy Family's own window). *) +let entry_14_movable_band = entry_14_fixed_band - 1 let is_indult layer = String.starts_with ~prefix:indult_prefix layer let is_universal layer = String.equal layer universal_layer @@ -155,8 +194,12 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc (* Named so entry 8's Sunday exception below can read "one worse than the Sunday it must yield to" rather than a bare integer that happens to equal entry 15's own value; entry 15's own branch returns this same - binding, not a second literal, so the two can never drift apart. *) - let entry_15_band = 15 in + binding, not a second literal, so the two can never drift apart. + CORRECTED, fix round 1 (coordinator finding 1): every branch below now + returns the real RG 91 entry number TIMES TEN, not the bare 1..28 -- + see {!entry_14_fixed_band}'s own comment for why (a genuine ordering + bug the bare scale could not express, not a cosmetic rename). *) + let entry_15_band = 150 in let open Vocab_ef in (* CORRECTED (Task B fix round 1, ef-rg16a): a [Commemoration_only] celebration has NO row in RG 91's table at all, checked FIRST, ahead of @@ -199,9 +242,9 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc ea22ad2. The guard is right; its former justification was not. *) if status = Celebration.Commemoration_only then unclassified (* 1: Nativity, Easter Sunday, Pentecost Sunday (I class w/ octave). *) - else if is_temporal && rank = Class1 && ((m = 12 && d = 25) || off = 0 || off = 49) then 1 + else if is_temporal && rank = Class1 && ((m = 12 && d = 25) || off = 0 || off = 49) then 10 (* 2: Sacred Triduum (Thu-Sat of Holy Week). *) - else if is_temporal && rank = Class1 && off >= -3 && off <= -1 then 2 + else if is_temporal && rank = Class1 && off >= -3 && off <= -1 then 20 (* 3: Epiphany, Ascension, Holy Trinity, Corpus Christi, Sacred Heart, Christ the King. *) else if is_temporal && rank = Class1 @@ -209,25 +252,25 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc || off = 39 (* Ascension *) || off = 56 (* Trinity *) || off = 60 (* Corpus Christi *) || off = 68 (* Sacred Heart *) || Date.compare date (Temporal_ef.christ_the_king (Date.year date)) = 0) - then 3 + then 30 (* 4: Immaculate Conception, Assumption BVM. *) - else if (not is_temporal) && rank = Class1 && ((m = 12 && d = 8) || (m = 8 && d = 15)) then 4 + else if (not is_temporal) && rank = Class1 && ((m = 12 && d = 8) || (m = 8 && d = 15)) then 40 (* 5: Vigil & Octave day of the Nativity. *) - else if is_temporal && rank = Class1 && ((m = 12 && d = 24) || (m = 1 && d = 1)) then 5 + else if is_temporal && rank = Class1 && ((m = 12 && d = 24) || (m = 1 && d = 1)) then 50 (* 6: Sundays of Advent, Lent, Passiontide, and Low Sunday. *) else if is_temporal && rank = Class1 && is_sunday && (season = Advent || season = Lent || season = Passiontide || off = 7) - then 6 + then 60 (* 7: I-class ferias not above -- Ash Wednesday; Mon/Tue/Wed of Holy Week. Thu-Sat of Holy Week are the Triduum, entry 2 above, not this entry. *) - else if is_temporal && rank = Class1 && (off = -46 || (off >= -6 && off <= -4)) then 7 + else if is_temporal && rank = Class1 && (off = -46 || (off >= -6 && off <= -4)) then 70 (* 8: All Souls -- RG 91 entry 8's own text (§4) carries a qualifier this transcription must honour: "yields to an occurring Sunday". 2 November is always Time_after_pentecost (well clear of Advent/Lent/Passiontide and of every other entry's own Easter-relative or fixed date), so a Sunday landing on it is always an ordinary entry-15 II-class Sunday -- the one and only rival this exception ever has to lose to. On such a - Sunday this returns [entry_15_band + 1]: strictly worse than 15 (an + Sunday this returns [entry_15_band + 1]: strictly worse than 150 (an exact tie would fall to Precedence.resolve's slug tie-break, which for "ef-all-souls" against a "ef-time-after-pentecost-sunday-*" slug would make All Souls WIN -- the precise bug this guards against), but @@ -237,20 +280,20 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc feasts transfer) still sees the true I-class candidate it needs to move to 3 November. *) else if (not is_temporal) && rank = Class1 && m = 11 && d = 2 then - if is_sunday then entry_15_band + 1 else 8 + if is_sunday then entry_15_band + 1 else 80 (* 9: Vigil of Pentecost. *) - else if is_temporal && rank = Class1 && off = 48 then 9 + else if is_temporal && rank = Class1 && off = 48 then 90 (* 10: Days within the Octaves of Easter and Pentecost. *) else if is_temporal && rank = Class1 && ((off >= 1 && off <= 6) || (off >= 50 && off <= 55)) - then 10 + then 100 (* 11: I-class feasts of the universal Church not above. *) - else if (not is_temporal) && (not is_vigil) && rank = Class1 && is_universal layer then 11 + else if (not is_temporal) && (not is_vigil) && rank = Class1 && is_universal layer then 110 (* 12: Proper I-class feasts. *) - else if (not is_temporal) && (not is_vigil) && rank = Class1 && not (is_indult layer) then 12 + else if (not is_temporal) && (not is_vigil) && rank = Class1 && not (is_indult layer) then 120 (* 13: Indult I-class feasts. By elimination once 11 and 12 have failed: not the universal layer (11), and marked as an indult overlay (12's "not indult" test having just failed). *) - else if (not is_temporal) && (not is_vigil) && rank = Class1 then 13 + else if (not is_temporal) && (not is_vigil) && rank = Class1 then 130 (* 14, MOVABLE half -- RG 91 entry 14's own "primum mobilia, deinde fixa" ({!entry_14_movable_band}'s own comment has the full citation and reasoning). Checked before the FIXED half immediately below, @@ -272,30 +315,32 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc occurring II-class Sunday with no universal qualifier either). No layer test here, unlike 11/12/13 and 16/19/20: the register does not split this entry into universal/proper/indult, so a proper or - indult feast of the Lord still bands 14, not 19/20. *) - else if (not is_temporal) && (not is_vigil) && rank = Class2 && subject = Subject.Lord then 14 + indult feast of the Lord still bands {!entry_14_fixed_band}, not + 19/20. *) + else if (not is_temporal) && (not is_vigil) && rank = Class2 && subject = Subject.Lord then + entry_14_fixed_band (* 15: Sundays, II class (every Sunday not already named at 6). *) else if is_temporal && rank = Class2 && is_sunday then entry_15_band (* 16: II-class feasts of the universal Church, not of the Lord. *) - else if (not is_temporal) && (not is_vigil) && rank = Class2 && is_universal layer then 16 + else if (not is_temporal) && (not is_vigil) && rank = Class2 && is_universal layer then 160 (* 17: Days within the Octave of the Nativity (26-28 Dec are Stephen, John, the Innocents -- sanctoral, not this entry). *) - else if is_temporal && rank = Class2 && m = 12 && (d = 29 || d = 30 || d = 31) then 17 + else if is_temporal && rank = Class2 && m = 12 && (d = 29 || d = 30 || d = 31) then 170 (* 18: II-class ferias -- Advent 17-23 Dec; Ember days of Advent, Lent, September. *) else if is_temporal && rank = Class2 && ((season = Advent && m = 12 && d >= 17 && d <= 23) || is_ember_18 slug) - then 18 + then 180 (* 19: Proper II-class feasts. *) - else if (not is_temporal) && (not is_vigil) && rank = Class2 && not (is_indult layer) then 19 + else if (not is_temporal) && (not is_vigil) && rank = Class2 && not (is_indult layer) then 190 (* 20: Indult II-class feasts. By elimination, as at 13. *) - else if (not is_temporal) && (not is_vigil) && rank = Class2 then 20 + else if (not is_temporal) && (not is_vigil) && rank = Class2 then 200 (* 21: II-class vigils (Ascension, Assumption, John Baptist, Peter & Paul -- can be temporal- or sanctoral-origin, see the file comment above). *) - else if rank = Class2 && is_vigil then 21 + else if rank = Class2 && is_vigil then 210 (* 22: Ferias of Lent and Passiontide (Thursday after Ash Wednesday to the Saturday before Palm Sunday), except the Ember days (18 above). *) - else if is_temporal && rank = Class3 && (season = Lent || season = Passiontide) then 22 + else if is_temporal && rank = Class3 && (season = Lent || season = Passiontide) then 220 (* 23: III-class feasts in particular calendars. Unlike 11/12 and 14/16 above, the universal entry (24) is the HIGHER number here -- RG 91's own table ranks a particular-calendar III-class feast ahead of a @@ -303,13 +348,13 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc the register states it, not "corrected" into the other classes' pattern. RG 91 has no indult sub-rank at III class, so every non-base layer lands here, not split further. *) - else if (not is_temporal) && (not is_vigil) && rank = Class3 && not (is_universal layer) then 23 + else if (not is_temporal) && (not is_vigil) && rank = Class3 && not (is_universal layer) then 230 (* 24: III-class feasts in the universal calendar. *) - else if (not is_temporal) && (not is_vigil) && rank = Class3 then 24 + else if (not is_temporal) && (not is_vigil) && rank = Class3 then 240 (* 25: Ferias of Advent to 16 Dec, except the Ember days (18 above). *) - else if is_temporal && rank = Class3 && season = Advent then 25 + else if is_temporal && rank = Class3 && season = Advent then 250 (* 26: III-class vigils (St Lawrence). *) - else if rank = Class3 && is_vigil then 26 + else if rank = Class3 && is_vigil then 260 (* 27: Office of the BVM on Saturday -- every otherwise-unoccupied IV-class Saturday, per the historical default that fills it; ordinary Mass propers still make Rogation Mon/Tue/Wed proper without changing the @@ -318,12 +363,12 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc vigils for the same reason 11-13/14/16/19/20/23/24 do: RG 91 has no IV-class vigil at all (RG 91's own vigil list, §4 "Vigils", stops at III class), so one would be an anomaly, not this entry. *) - else if is_temporal && (not is_vigil) && rank = Class4 && weekday = Date.Sat then 27 + else if is_temporal && (not is_vigil) && rank = Class4 && weekday = Date.Sat then 270 (* 28: IV-class ferias -- the unqualified catch-all (temporal_ef.ml's own comment on [ferial_rank] cites the same primary text, "Feriae IV classis"). Excludes vigils for the same reason as 27 above: a IV-class "feria" that is also a vigil is not a feria RG 91 describes. *) - else if (not is_vigil) && rank = Class4 then 28 + else if (not is_vigil) && rank = Class4 then 280 else unclassified (* Task 8: what happens to the day's LOSING candidate (docs/research/ diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 86a0ead..239a254 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -62,25 +62,40 @@ val ember_prefixes : string list new rule, not a silently wrong one. *) val unclassified : int +(** {!band}'s value for RG 91 entry 14's FIXED half (a sanctoral, i.e. + fixed-date, Class2 feast of the Lord) -- see {!entry_14_movable_band}'s + own comment for why entry 14 needs two distinct values at all. *) +val entry_14_fixed_band : int + (** RG 91 entry 14's own text (§4): "Festa Domini II classis, primum mobilia, deinde fixa" -- feasts of the Lord, II class, MOVABLE first, then fixed. {!band}'s value for the movable half (Holy Family, RG - 17(b)) -- strictly better than the fixed half's own literal [14], - negative so it can never collide with a real table position or - {!unclassified}. See precedence_ef.ml's own comment for the full - citation and why the two halves must not literally tie. *) + 17(b)) -- [entry_14_fixed_band - 1], strictly better than the fixed + half without colliding with entry 13 or any other real table position. + CORRECTED, fix round 1 (coordinator finding 1): this used to be a + negative sentinel, justified only by "cannot collide with a real table + position" -- a COLLISION argument, not an ORDERING one; since {!band} + values are compared by plain [<], a negative value beats every I-class + entry (1-13) as well as the fixed half, not merely the fixed half it + was built to beat. {!band}'s own entries now use the real RG 91 number + TIMES TEN throughout, reserving genuine headroom before every entry -- + see precedence_ef.ml's own comment for the full citation, the counter- + example that found this, and why entries 12 and 20 may need the same + treatment if their own "movable then fixed" halves ever get a witness. *) val entry_14_movable_band : int (** [band ctx c]: RG 91's Table of Precedence. Returns the table's own entry - number -- I class 1-13, II class 14-21, III class 22-26, IV class 27-28; - lower wins (see {!Precedence.rules.band}) -- EXCEPT where the table's own - text states an exception: entry 8 (All Souls) reads "yields to an - occurring Sunday", so on a Sunday this returns a value that - loses to entry 15 rather than the literal integer 8 (see the comment on - entry 8 in precedence_ef.ml for the exact value and why). ALSO - {!unclassified} for any [Cel.Commemoration_only] candidate, checked - first, ahead of every rank-keyed branch -- CORRECTED, Task B fix round - 1 (coordinator finding 1): RG 91's own table enumerates only "dies + number TIMES TEN -- I class 10-130, II class 140-210, III class 220-260, + IV class 270-280; lower wins (see {!Precedence.rules.band}) -- EXCEPT + where the table's own text states an exception: entry 8 (All Souls) + reads "yields to an occurring Sunday", so on a Sunday this returns a + value that loses to entry 15 (150) rather than 80 (see the comment on + entry 8 in precedence_ef.ml for the exact value and why); entry 14 + (Feasts of the Lord, II class) splits into {!entry_14_movable_band} and + {!entry_14_fixed_band} per the table's own "primum mobilia, deinde + fixa". ALSO {!unclassified} for any [Cel.Commemoration_only] candidate, + checked first, ahead of every rank-keyed branch -- CORRECTED, Task B fix + round 1 (coordinator finding 1): RG 91's own table enumerates only "dies liturgici" (entry 24's own text, "Festa III classis..." -- FEASTS), so a celebration reduced to a bare commemoration was never a row in the table to begin with, confirmed by the calendarium's own notation (a diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml index 01a54c0..98ae0c8 100644 --- a/test/test_precedence_ef.ml +++ b/test/test_precedence_ef.ml @@ -48,96 +48,121 @@ 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 @@ -147,35 +172,38 @@ let cases = 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's own literal 14 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. See [PE.entry_14_movable_band]'s own comment - in precedence_ef.ml for why the value is negative rather than some - other distinct integer. *) + 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 @@ -183,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 @@ -207,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 @@ -230,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 @@ -1343,6 +1371,44 @@ let test_holy_family_excludes_baptism_end_to_end () = [ "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 @@ -1594,6 +1660,10 @@ let suite = "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" -- cgit v1.3