aboutsummaryrefslogtreecommitdiff
path: root/test/test_precedence_ef.ml
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_precedence_ef.ml')
-rw-r--r--test/test_precedence_ef.ml357
1 files changed, 328 insertions, 29 deletions
diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml
index a0e1db1..2935e40 100644
--- a/test/test_precedence_ef.ml
+++ b/test/test_precedence_ef.ml
@@ -267,7 +267,26 @@ let cases =
(* 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 )
+ 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
@@ -290,7 +309,7 @@ let test_all_souls_yields_to_sunday () =
let rules =
{ P.band = (fun c cd -> PE.band c cd);
disposition = (fun ~winner:_ ~loser:_ -> P.Omit);
- admit = (fun ~observed:_ cs -> cs) }
+ 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"
@@ -540,7 +559,110 @@ let disposition_cases =
Omit under the refuted privilege_of=Ordinary gate",
an_ordinary_sunday,
cand ~rank:V.Class3 "ef-synthetic-ordinary-class3-feria",
- "Commemorate(Ordinary)" )
+ "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)" )
]
(* Task 9: [privilege_of]'s RG 109 categories (§4, "Commemorations"),
@@ -719,32 +841,75 @@ 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,
- [ (ordinary_hi, P.Ordinary) ],
+ 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,
- [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ],
+ 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,
- [ (privileged_lo, P.Privileged); (privileged_hi, P.Privileged) ],
+ 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,
- [ (ordinary_hi, P.Ordinary) ],
+ 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,
- [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ],
+ 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,
@@ -755,8 +920,8 @@ let admit_cases =
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,
- [ (ordinary_class3, P.Ordinary) ],
+ 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:
@@ -764,23 +929,55 @@ let admit_cases =
observed day, OPPOSITE winner. *)
( "other II-class day, only an ordinary commemoration due -> it is \
admitted",
- observed_class2_other,
- [ (ordinary_hi, P.Ordinary) ],
+ 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,
- [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ],
+ 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,
- [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged);
- (ordinary_lowest, P.Ordinary) ],
- [ "ef-ordinary-hi"; "ef-privileged-lo" ] )
+ 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
@@ -791,13 +988,48 @@ let admit_cases =
two-candidate row where either order already happens to be sorted. *)
let test_admit_order_independent () =
let comms =
- [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged); (ordinary_lowest, P.Ordinary) ]
+ [ 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
- let forward = slugs_of (PE.admit ~observed:observed_class3 comms) in
- let reversed = slugs_of (PE.admit ~observed: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
@@ -929,6 +1161,63 @@ let test_ii_class_sunday_privileged_witness_admitted_end_to_end () =
[ ("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)
+
(* 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
@@ -960,7 +1249,10 @@ let test_all_souls_transfers_end_to_end () =
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:_ cs -> cs) } 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" ]
@@ -1150,13 +1442,16 @@ let suite =
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, comms, expect) ->
+ (fun (desc, observed, temporal, comms, expect) ->
Alcotest.test_case desc `Quick (fun () ->
Alcotest.(check (list string)) desc expect
- (slugs_of (PE.admit ~observed comms))))
+ (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
@@ -1166,6 +1461,10 @@ let suite =
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 "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"