diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 17:13:06 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 17:13:06 +0200 |
| commit | a2654a1e4d8987a251bcefbad2b0f0b867a6c6e6 (patch) | |
| tree | 39360eccd3fa8ee5379fc46276d1e0ef0e6ff2b5 /test/test_precedence_ef.ml | |
| parent | e19adb9c7ea369cafe76e7eff50e9248a4a954d0 (diff) | |
| parent | e5b368dec8fdc9ab983ee0f1dee69c37883cbe12 (diff) | |
| download | colitur-a2654a1e4d8987a251bcefbad2b0f0b867a6c6e6.tar.gz colitur-a2654a1e4d8987a251bcefbad2b0f0b867a6c6e6.zip | |
Merge branch 'ef-rg16a': RG 16(a), RG 111(b), RG 113, and commemoration identity
Closes the largest known-wrong-output item on record (RG 16(a): a Feast
of the Lord occurring on a II-class Sunday leaves the Sunday
uncommemorated -- 5996 wrong days over 1583-9999) together with RG
111(b)'s rank floor on the day it holds, and gives the oracle layer the
ability to compare commemoration identity rather than only presence and
count.
Also corrects a methodological defect that had produced wrong rulings in
both this project and its sibling: docs/research/ holds an electronic
transcription alongside two photographic scans, and the transcription
silently drops commemoration lines -- seven across the year. Reasoning
from its silence had convicted the oracle wrongly on 14 August and
ruled a genuine 9 August commemoration spurious. The scans are the
primary source; the rule is now recorded at the top of the register.
RG 113 replaces an alphabetical same-rank tie-break that had no
rubrical warrant. A bare Commemoratio has no row in RG 91's table at
all, so band no longer hands one the entry of a III-class universal
feast: 4451 days, all four reordered pairs verified against a
photographic scan, zero observed-day changes.
Diffstat (limited to 'test/test_precedence_ef.ml')
| -rw-r--r-- | test/test_precedence_ef.ml | 357 |
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" |
