diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 13:32:13 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 13:32:13 +0200 |
| commit | 7d3b5ec831a60e8b63466251d63b2bd564acba2b (patch) | |
| tree | f9e5015704ff74f42b777d7aac207ffda0d55673 | |
| parent | 5c9f2cdbffdff67ec8a1939ed6958767acf406fd (diff) | |
| download | colitur-7d3b5ec831a60e8b63466251d63b2bd564acba2b.tar.gz colitur-7d3b5ec831a60e8b63466251d63b2bd564acba2b.zip | |
fix(rite-ef,kernel): follow the oracle on the Purification; admit reads the day's own Sunday-ness, not observed
Fix round 1 review, two findings landed together because each golden pin's
correctness depends on both.
CRITICAL (item 1): reverts this branch's own first-pass retag of the
Purification (2 February) from Lord back to Bvm -> Lord. The calendarium
titles it 'B. Mariae Virg.', but missalemeum -- this project's designated
EF oracle -- treats it as taking an occurring II-class Sunday's place
outright, commemorations empty, exactly RG16(a)'s own festum Domini
treatment (2020-02-02, 2014-02-02, independently fetched). Control,
2019-09-08 (the Nativity of the BVM, an ordinary Marian feast, on a
Sunday): the Sunday observed, the feast merely commemorated -- the
opposite pattern, proving the Purification's own oracle treatment is
deliberate, not a gap. RG16(a) alone, on the untouched data, would have
matched the oracle character for character; retagging Bvm is what moved
colitur away from it on 1200 domain-wide days. The user has ruled: follow
the oracle. Genuine primary-text counter-evidence (RG120(b), the colour
rule grouping 2 February under 'B. Mariae Virg.', a category separate
from 'Domini') is recorded in the register, not discarded -- a different
project could reasonably rule the other way on the same evidence.
most-holy-name-of-mary stays retagged Bvm; that one was never disputed.
Removes the now-unneeded C12 lectio allow-list entry (data/ef/
expected-divergences.sexp, test_differential.ml): with the Purification
Lord again, colitur matches lectio's own class = lord on every affected
date, no divergence to allow-list.
IMPORTANT (item 2): Precedence.rules.admit gains a ~temporal parameter --
a genuine kernel signature change, unlike RG16(a) itself. RG111(b)'s own
Sunday rank floor ('one commemoration, namely of a II-class feast') used
to be detected by reading observed's own slug for a Sunday marker, which
silently assumed observed IS the day's temporal-cycle office. RG16(a)
breaks that assumption on purpose: a Feast of the Lord standing in for an
impeded Sunday 'cum omnibus iuribus et privilegiis' keeps the day a
dominica II classis for RG111(b) too, even though the FEAST, not the
Sunday, is observed. Before this fix an unrelated saint (Pope Sixtus II
et al., 6 August, Class3) was wrongly admitted into the freed slot on
every one of 1178 domain-wide days. Oracle-confirmed directly: 2023-08-06
(a Sunday) shows him displaced, commemorations empty; 2026-08-06 (an
ordinary Thursday) shows him admitted -- being a Sunday is exactly what
excludes him. admit now reads Sunday-ness off ~temporal (the day's own
temporal-cycle candidate, passed through from Precedence.resolve
unchanged) instead of observed. Validate's own admission fixed-point
invariant updated to pass the same real temporal candidate through.
Both golden pins in test_golden.ml were promoted from actual colitur day
output without independently consulting the rubric or the oracle first --
a real defect the review caught. Re-derived from both before re-pinning:
2028-08-06 and 2025-02-02 now pin comms=[], no commemoration at all,
matching missalemeum exactly in both cases.
| -rw-r--r-- | data/ef/adjustments.sexp | 71 | ||||
| -rw-r--r-- | data/ef/expected-divergences.sexp | 5 | ||||
| -rw-r--r-- | lib/kernel/precedence.ml | 3 | ||||
| -rw-r--r-- | lib/kernel/precedence.mli | 16 | ||||
| -rw-r--r-- | lib/kernel/validate.ml | 17 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.ml | 46 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.mli | 45 | ||||
| -rw-r--r-- | test/test_calendar.ml | 2 | ||||
| -rw-r--r-- | test/test_differential.ml | 23 | ||||
| -rw-r--r-- | test/test_golden.ml | 95 | ||||
| -rw-r--r-- | test/test_precedence.ml | 2 | ||||
| -rw-r--r-- | test/test_precedence_ef.ml | 158 | ||||
| -rw-r--r-- | test/test_rite_ef.ml | 45 | ||||
| -rw-r--r-- | test/test_validate.ml | 12 |
14 files changed, 370 insertions, 170 deletions
diff --git a/data/ef/adjustments.sexp b/data/ef/adjustments.sexp index 7d61170..05ee401 100644 --- a/data/ef/adjustments.sexp +++ b/data/ef/adjustments.sexp @@ -11,41 +11,46 @@ ; removal (Overlay.apply's own diagnostic fires if this slug is ever absent, ; e.g. after a re-bootstrap that renames it), not a silent drop. ; -; RG16(a) task (docs/research/rules-register.md §6.0): corrects two of -; data/ef/sanctoral.sexp's six `(subject Lord)` entries, both inherited -; unchecked from lectio's own `class = lord` field (tridentine-calendar.ini) -; -- confirmed WRONG against the calendarium itself, which names both a -; feast of the BLESSED VIRGIN, not of the Lord: -; - `purification-of-the-blessed-virgin-mary` (2 Feb): calendarium line -; "IN PURIFICATIONE B. MARIAE VIRG., II classis" -- "B. Mariae Virg." is -; the Missal's own Purification-of-Mary title, not "D. N. I. C." (the -; formula the genuinely Lord-tagged entries carry, e.g. 6 Aug -; "IN TRANSFIGURATIONE D. N. I. C." or 1 Jul "PRETIOSISSIMI SANGUINIS -; D. N. I. C."). -; - `most-holy-name-of-mary` (12 Sep): calendarium line "Sanctissimi -; Nominis Mariae, III classis" -- "Nominis Mariae", the Name of MARY, -; not of the Lord. -; Corrected as an Edit directive, not a hand-edit of data/ef/sanctoral.sexp -; itself: that file's own header says "do not hand-edit... re-run the -; generator and commit the diff instead", and its provenance SHA-256 covers -; a straight, byte-faithful conversion of lectio's INI -- an edit belongs in -; THIS file, the project's own existing mechanism for a correction over the -; bootstrap (the [Suppress] directive above sets exactly this precedent). -; The base sanctoral.sexp stays a faithful mirror of lectio (so a future -; re-bootstrap cannot silently re-introduce the error unnoticed: this Edit -; keeps applying regardless, and [Overlay.apply]'s own diagnostic fires -; loudly if the slug it targets is ever renamed or removed upstream). +; RG16(a) task (docs/research/rules-register.md §6.0): corrects one of +; data/ef/sanctoral.sexp's six `(subject Lord)` entries, inherited unchecked +; from lectio's own `class = lord` field (tridentine-calendar.ini) -- +; `most-holy-name-of-mary` (12 Sep): calendarium line "Sanctissimi Nominis +; Mariae, III classis" -- "Nominis Mariae", the Name of MARY, not of the +; Lord, with no counter-evidence found anywhere (the "D. N. I. C." formula +; the genuinely Lord-tagged entries carry, e.g. 6 Aug "IN TRANSFIGURATIONE +; D. N. I. C.", is simply absent here, and nothing else in the calendarium +; suggests otherwise). This tag has no behavioural effect either way today +; ({!Precedence_ef.band}'s entry 14, the only place [subject] is tested for +; a II-class candidate, requires [Class2]; this entry is [Class3]) -- kept +; for the data's own accuracy, not because anything currently reads it. ; -; The other four `(subject Lord)` entries were verified against the same -; calendarium and left untouched (all task-report-cited): 1 Jul "PRETIOSISSIMI -; SANGUINIS D. N. I. C., I classis"; 6 Aug "IN TRANSFIGURATIONE D. N. I. C., -; II classis"; 14 Sep "IN EXALTATIONE S. CRUCIS, II classis" (the Cross as -; the instrument of Christ's own Passion, not a canonised person -- the -; traditional "feast of the Lord" reading, {!Subject.t} having no separate -; category for it); 9 Nov "IN DEDICATIONE ARCHIBASILICAE SANCTISSIMI -; SALVATORIS, II classis" ("of the Most Holy SAVIOUR", i.e. Christ). +; The tag [Bvm] itself carries no further systematic meaning in this +; codebase beyond "not Lord" -- after this correction it exists on exactly +; this one entry, while every other Marian feast (the Assumption, the +; Immaculate Conception, the Nativity of the BVM, ...) stays `(subject +; Saint)`. Nothing reads [Bvm] specifically; {!Subject.t}'s four-way split +; is not fully exercised by this codebase's own logic, only [Lord] is. +; +; RG16(a) task, fix round 1 (CRITICAL finding, reverted): a companion Edit +; here previously also retagged `purification-of-the-blessed-virgin-mary` +; (2 Feb) to [Bvm], on the same calendarium-title argument ("B. Mariae +; Virg.", not "D. N. I. C."). REVERTED -- the user has ruled: follow the +; oracle. missalemeum (this project's designated EF oracle) treats the +; Purification as taking an occurring II-class Sunday's place OUTRIGHT, with +; NO commemoration of the Sunday (`2020-02-02`, `2014-02-02`: title +; "Purification of the Blessed Virgin Mary", `"commemorations": []`), +; exactly RG 16(a)'s own "festum Domini" treatment -- and NOT the treatment +; it gives an ordinary Marian feast on a Sunday (`2019-09-08`, the Nativity +; of the BVM: title "XIII Sunday after Pentecost", the FEAST demoted to a +; commemoration, the Sunday observed -- the opposite pattern). Genuine +; primary-text counter-evidence for the Marian reading remains on record +; (RG 120(b): the white-colour rule groups 2 February under "B. Mariae +; Virg.", a SEPARATE category from "Domini" -- register §6.0 quotes it in +; full), so the calendarium TITLE and the oracle's BEHAVIOUR disagree here; +; the user's ruling resolves that disagreement in the oracle's favour for +; this codebase's own purposes, not by declaring the calendarium argument +; wrong. See register §6.0 for the full account of both sides. ((id ef-adjustments) (directives ((Suppress vigil-of-christmas) - (Edit purification-of-the-blessed-virgin-mary ((Set_subject Bvm))) (Edit most-holy-name-of-mary ((Set_subject Bvm)))))) diff --git a/data/ef/expected-divergences.sexp b/data/ef/expected-divergences.sexp index d6161c2..c8d863d 100644 --- a/data/ef/expected-divergences.sexp +++ b/data/ef/expected-divergences.sexp @@ -72,8 +72,3 @@ (verdict colitur) (note "Holy Thursday (\"ef-passiontide-2-thursday\") is white in colitur, per RG 128(b)'s explicit exception -- independently confirmed by the missalemeum oracle (Task 16 report), whose colour set for that day is white alone, unlike the surrounding Triduum days which include violet as an option. lectio has no such exception and prints Passiontide's base violet straight through Holy Thursday. Found via Task 16's oracle comparison, not the original lectio differential (lectio's own colour did not previously disagree, because colitur's own bug matched it) -- fixed in colitur, so this row records a NEW divergence from lectio's still-uncorrected violet, not a pre-existing one.") (expected_rows 46)) - ((id C12) - (citation "Calendarium (docs/research/1962-06-23,_SS_Ioannes_XXIII,_Missale_Romanum,_LT.pdf), 2 February row: \"IN PURIFICATIONE B. MARIAE VIRG., II classis\" -- \"of the Purification of the Blessed VIRGIN MARY\", not \"D. N. I. C.\" (the formula the calendarium uses for the project's other five genuine Lord-subject entries, e.g. 6 Aug \"IN TRANSFIGURATIONE D. N. I. C.\"); combined with RG 16(a) (register §6.0, Caput III \"De Dominicis\": a Feast of the LORD, I or II class, takes an occurring II-class Sunday's own place -- a BVM feast does not)") - (verdict colitur) - (note "The Purification (2 February) is a feast of the Blessed Virgin Mary, not of the Lord -- data/ef/sanctoral.sexp inherited lectio's own unchecked `class = lord` field; data/ef/adjustments.sexp retags it `Bvm` (RG16(a) task, this task's own report), so it no longer bands at RG 91 entry 14 (Feasts of the Lord, II class) and no longer outranks an occurring II-class Sunday. On the six 2 Februaries in this fixture's 2005-2050 span that fall on a Sunday, colitur now observes the Sunday and commemorates the Purification (RG 109(a)/RG 111(b), an ordinary II-class-Sunday commemoration); lectio still tags the entry `lord` and so still observes the Purification outright, displacing the Sunday. expected_rows is not transcribed from a comparator run: 2 February falls on a Sunday in exactly six years of 2005-2050 -- 2014, 2020, 2025, 2031, 2042, 2048 -- independently verified with `date -d <year>-02-02 +%A` (glibc, the same cross-check discipline test_golden.ml's own header requires), one row each.") - (expected_rows 6)) diff --git a/lib/kernel/precedence.ml b/lib/kernel/precedence.ml index 05ad69f..d48203e 100644 --- a/lib/kernel/precedence.ml +++ b/lib/kernel/precedence.ml @@ -17,6 +17,7 @@ type ('s, 'r) rules = { disposition : winner:'r candidate -> loser:'r candidate -> disposition; admit : observed:'r candidate -> + temporal:'r candidate -> ('r candidate * privilege) list -> ('r candidate * privilege) list; } @@ -55,7 +56,7 @@ let resolve rules ctx ~temporal ~sanctoral = ([], [], []) losers in let comms = List.rev comms and deferred = List.rev deferred in - let admitted = rules.admit ~observed comms in + let admitted = rules.admit ~observed ~temporal comms in let dropped = List.filter (fun c -> not (List.exists (fun a -> fst a == fst c) admitted)) comms in diff --git a/lib/kernel/precedence.mli b/lib/kernel/precedence.mli index ae054dd..d394cb1 100644 --- a/lib/kernel/precedence.mli +++ b/lib/kernel/precedence.mli @@ -34,12 +34,28 @@ type ('s, 'r) rules = { (** RG 92-95: the loser's fate, which depends on the loser's own rank. *) admit : observed:'r candidate -> + temporal:'r candidate -> ('r candidate * privilege) list -> ('r candidate * privilege) list; (** RG 108-111: how many commemorations are admitted, and in what order; anything filtered out here is recorded in {!resolution.omitted}, not dropped. + [temporal] is {!resolve}'s own [~temporal] argument, passed through + unchanged -- the day's temporal-cycle candidate, regardless of + whether it won. Fix round 1 (RG16(a) task): before this, a rite's + [admit] could only infer properties of the CIVIL DAY (chiefly "is + this a Sunday", RG 111(b)'s own two-tier admission rule) from + [observed]'s own fields -- a proxy that breaks the moment something + OTHER than the day's own temporal candidate can be [observed], the + exact shape RG 16(a) introduces (a Feast of the Lord standing in + the impeded Sunday's place "cum omnibus iuribus et privilegiis", + RG 91 entry 14). This is NOT a kernel definition of "Sunday" -- + the kernel does not gain any rite-specific knowledge by this + parameter, it only threads through a value {!resolve} already + holds; a rite's own [admit] is free to ignore it entirely, the + same as [observed]. + OBLIGATION ON THE IMPLEMENTATION, not enforced by this type: every candidate this function returns must be a value taken UNCHANGED from its input list, never rebuilt (e.g. via a [{ c with ... }] diff --git a/lib/kernel/validate.ml b/lib/kernel/validate.ml index cc8bdce..a3208dd 100644 --- a/lib/kernel/validate.ml +++ b/lib/kernel/validate.ml @@ -303,11 +303,26 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year = let observed_candidate : 'r Precedence.candidate = { Precedence.cel = d.Liturgical_day.observed; origin = Precedence.Sanctoral } in + (* [~temporal] (fix round 1, RG16(a) task): {!Precedence.rules.admit} + now also takes the day's own temporal-cycle candidate, reused + here from {!Liturgical_day.t}'s own embedded [Temporal.t] -- + the exact same value {!Calendar} passed to {!Precedence.resolve} + in the first place, so re-offering is against the SAME inputs, + not a reconstruction that could itself introduce a false + negative. [origin] is [Precedence.Temporal], genuinely (this IS + the temporal candidate, not a reconstructed sanctoral one). *) + let temporal_candidate : 'r Precedence.candidate = + { Precedence.cel = d.Liturgical_day.temporal.Temporal.office; + origin = Precedence.Temporal } + in let as_candidates comms = List.map (fun (c, p) -> ({ Precedence.cel = c; origin = Precedence.Sanctoral }, p)) comms in let offered = as_candidates d.Liturgical_day.commemorations in - let readmitted = rite.Rite.rules.Precedence.admit ~observed:observed_candidate offered in + let readmitted = + rite.Rite.rules.Precedence.admit ~observed:observed_candidate + ~temporal:temporal_candidate offered + in let norm l = List.map (fun (c, p) -> (Slug.to_string c.Precedence.cel.Celebration.slug, p)) l |> List.sort compare diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 179fa13..2987312 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -707,10 +707,35 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate) classis", "dominicis II classis", "aliis diebus II classis", "diebus III et IV classis") -- read here off [observed]'s own [rank] and, for the Sunday/non-Sunday II-class split, the same slug marker [privilege_of] and - RG 33's [impedes_vigil] already use ([is_sunday_slug]). No [context] - (date/season/weekday) is available to [admit] (see precedence.mli's - [rules.admit]) or needed: [observed] IS the day's own celebration, so its - rank and slug already carry everything RG 111's own four categories test. *) + RG 33's [impedes_vigil] already use ([is_sunday_slug]). + + CORRECTED (fix round 1, RG16(a) task): the previous version of this + comment claimed "[observed] IS the day's own celebration, so its rank and + slug already carry everything RG 111's own four categories test" -- + WRONG once RG 16(a) exists. RG 16(a)'s own text says the winning Feast of + the Lord holds the Sunday's place "cum omnibus iuribus et privilegiis" + (with ALL its rights and privileges) -- the day remains a "dominica II + classis" for RG 111(b)'s own purposes even though [observed] is now the + FEAST, not the Sunday, so [observed]'s own slug is no longer a reliable + signal of "is this a Sunday" once something can legitimately observe in + the Sunday's place. Confirmed wrong for real data by the oracle: 6 August + falling on a Sunday shows the Transfiguration observed and NO + commemoration (missalemeum: `commemorations: []`, Pope Sixtus II et al. + `displaced`); the pre-fix code, reading Sunday-ness off [observed]'s own + slug ("transfiguration-of-our-lord", no Sunday marker), wrongly took the + [Class2, false] "other II class: one" branch below and admitted Sixtus + (Class3) regardless of RG 111(b)'s own "de festo II classis" rank floor. + Control, also oracle-confirmed: 6 August on an ordinary WEEKDAY (no + Sunday collision) correctly admits Sixtus -- being a Sunday is exactly + what excludes him, and [observed]'s own identity cannot tell the two + cases apart on its own. + + Fixed by reading Sunday-ness off [temporal] instead -- {!Precedence.rules.admit}'s + own [~temporal] parameter, {!Precedence.resolve}'s [~temporal] argument + passed straight through, unaffected by whether it won the day. [context] + (date/season/weekday) is still not needed: [temporal]'s own slug already + carries everything this split needs, the same way [observed]'s used to + before a competing office could occupy the Sunday's place. *) (* RG 8's four-class dignity order, Class1 highest. Deliberately NOT [band] (RG 91's much finer 28-entry table): [band] needs a [context] [admit] @@ -738,6 +763,7 @@ let rec take n = function | x :: xs -> if n <= 0 then [] else x :: take (n - 1) xs let admit ~(observed : Vocab_ef.rank Precedence.candidate) + ~(temporal : Vocab_ef.rank Precedence.candidate) (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list = (* Sorted once, by dignity then slug (see [compare_dignity]); every branch @@ -758,11 +784,17 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) let sorted = List.stable_sort compare_dignity comms in let is_privileged (_, p) = p = Precedence.Privileged in let observed_rank = observed.Precedence.cel.Celebration.rank in - let observed_is_sunday = - is_sunday_slug (Slug.to_string observed.Precedence.cel.Celebration.slug) + (* CORRECTED (fix round 1, RG16(a) task): read off [temporal], not + [observed] -- see this function's own doc comment above for the full + argument and the oracle evidence. [temporal] is the day's own + temporal-cycle candidate regardless of who is [observed]; RG 16(a) + makes that distinction live for the first time (a Feast of the Lord + can now be [observed] on a day whose [temporal] candidate is a Sunday). *) + let day_is_sunday = + is_sunday_slug (Slug.to_string temporal.Precedence.cel.Celebration.slug) in let open Vocab_ef in - match (observed_rank, observed_is_sunday) with + match (observed_rank, day_is_sunday) with | Class1, _ -> (* RG 111: "I class: none save one privileged." Ordinary commemorations never get a slot at all on a I-class day, no matter how many are diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 55f947e..ee7667d 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -164,24 +164,26 @@ val nativity_octave_prefix : string so the two cannot silently drift apart. *) val september_ember_prefix : string -(** [admit ~observed comms]: RG 108-111 (docs/research/rules-register.md §4, - "Commemorations"). How many of [comms] -- each already tagged with its - real RG 109 privilege by {!disposition} -- RG 111 admits, and which, +(** [admit ~observed ~temporal comms]: RG 108-111 (docs/research/rules-register.md + §4, "Commemorations"). How many of [comms] -- each already tagged with + its real RG 109 privilege by {!disposition} -- RG 111 admits, and which, given the day actually observed: - [observed] a [Class1] day: none, except at most one privileged commemoration (the highest-dignity one, if several are due) -- an ordinary one is never admitted here, no matter how many are due; - - [observed] a [Class2] Sunday (its slug carries {!sunday_marker}): one, - subject to TWO conditions, not one -- (i) a privileged commemoration, - whenever due, categorically takes the day's one slot over any - ordinary one, not by comparing dignity, so an ordinary commemoration - that would otherwise win on dignity is still dropped; (ii) failing - that, the slot is reserved for a [Class2] candidate SPECIFICALLY - ("de festo II classis", RG 111(b)'s own wording -- a RANK FLOOR, not - "whichever ordinary candidate has the best dignity"): a III- or - IV-class ordinary loser has no standing for this slot at all and is - admitted nothing, even when it is the only candidate due; - - [observed] any other [Class2] day: one, by dignity alone -- no + - the CIVIL DAY is a [Class2] Sunday ([temporal]'s slug carries + {!sunday_marker} -- CORRECTED, fix round 1, RG16(a) task: read off + [temporal], not [observed]; see below): one, subject to TWO + conditions, not one -- (i) a privileged commemoration, whenever due, + categorically takes the day's one slot over any ordinary one, not by + comparing dignity, so an ordinary commemoration that would otherwise + win on dignity is still dropped; (ii) failing that, the slot is + reserved for a [Class2] candidate SPECIFICALLY ("de festo II classis", + RG 111(b)'s own wording -- a RANK FLOOR, not "whichever ordinary + candidate has the best dignity"): a III- or IV-class ordinary loser + has no standing for this slot at all and is admitted nothing, even + when it is the only candidate due; + - the civil day is any other [Class2] day: one, by dignity alone -- no privilege override and no rank floor, unlike the Sunday case immediately above; - [observed] a [Class3] or [Class4] day: at most two, by dignity alone. @@ -192,6 +194,20 @@ val september_ember_prefix : string Ties break on slug, matching {!Precedence.compare_by}, so the result never depends on the order [comms] arrives in. + [temporal] -- CORRECTED, fix round 1 (RG16(a) task): this function used + to read the Sunday/non-Sunday split off [observed]'s own slug, which + silently assumed [observed] IS the day's temporal-cycle office. RG 16(a) + breaks that assumption on purpose: a Feast of the Lord holds an impeded + II-class Sunday's own place "cum omnibus iuribus et privilegiis" + (RG 16(a)'s own text), so the day still counts as a "dominica II + classis" for RG 111(b) even though [observed] is now the FEAST. + [temporal] -- {!Precedence.rules.admit}'s own parameter, the day's + temporal-cycle candidate regardless of who won -- is the reliable + signal; oracle-confirmed (6 August: Sunday excludes a Class3 saint + Sixtus II from the freed slot, an ordinary weekday admits him). Every + row of the previous behaviour where [observed] and [temporal] coincide + (no RG 16(a) displacement occurred) is unaffected. + Every candidate this returns is a value taken unchanged from [comms], never rebuilt: {!Precedence.resolve}'s own [dropped]/[omitted] accounting tells an admitted candidate from a dropped one by physical @@ -202,6 +218,7 @@ val september_ember_prefix : string itself total over an empty or arbitrarily long [comms]. *) val admit : observed:Vocab_ef.rank Precedence.candidate -> + temporal:Vocab_ef.rank Precedence.candidate -> (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list -> (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list diff --git a/test/test_calendar.ml b/test/test_calendar.ml index 505994e..8770017 100644 --- a/test/test_calendar.ml +++ b/test/test_calendar.ml @@ -82,7 +82,7 @@ module Fixture = struct test below a genuine Precedence-native omission (distinct from a deferred one) to exercise. *) let rules : (season, rank) P.rules = - { P.band; disposition; admit = (fun ~observed:_ cs -> List.filteri (fun i _ -> i < 1) cs) } + { P.band; disposition; admit = (fun ~observed:_ ~temporal:_ cs -> List.filteri (fun i _ -> i < 1) cs) } (* RG 96, generic form: search forward from the day after [origin] for the first day whose occupant is not "blocking" -- in this synthetic diff --git a/test/test_differential.ml b/test/test_differential.ml index aa75737..e12cd9a 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -450,19 +450,16 @@ let layer_c_reason (l : row) (c : row) diffs = && subset diffs [ Slug_f; Rank; Colour_f ] then Some "C10" else if String.equal c.slug "ef-passiontide-2-thursday" && diffs = [ Colour_f ] then Some "C11" - (* RG16(a) task (register §6.0 + the sanctoral subject audit, this task's - own report): 2 February on a Sunday. colitur now observes the Sunday - (data/ef/adjustments.sexp retags the Purification `Bvm`, so it no - longer bands at RG 91 entry 14 -- only a Feast of the LORD does); the - Purification survives as its own commemoration, matching the register's - ruling, just not as [c]'s OBSERVED slug here. lectio still tags it - `class = lord` (unretagged upstream) and so still observes it outright. - Matched on [l.slug] (lectio's side), the only side that can show this - literal now -- colitur's own [c.slug] is whichever Sunday family the - date falls in (time-after-epiphany or Septuagesima), not one fixed - literal, unlike [c7]/[c11]'s single-slug tests above. *) - else if String.equal l.slug "purification-of-the-blessed-virgin-mary" && subset diffs [ Slug_f; Colour_f ] - then Some "C12" + (* RG16(a) task, fix round 1 (CRITICAL finding): a C12 entry briefly lived + here, allow-listing 2 February on a Sunday against lectio, when + `data/ef/adjustments.sexp` retagged the Purification `Bvm`. REVERTED -- + the user has ruled: follow the oracle (register §6.0 has the full + account). The Purification is `Lord` again, colitur observes it + outright on those six 2-February Sundays, matching lectio's own + `class = lord` exactly -- no divergence to allow-list. Left + unimplemented here deliberately, not merely absent by oversight: this + comment is the record that C12 existed and was removed, should a + future reader wonder why the numbering skips it. *) else None (* ---------------------------------------------------------------------- *) diff --git a/test/test_golden.ml b/test/test_golden.ml index 85a129b..fb3cff4 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -501,16 +501,38 @@ let test_ii_class_sunday_admits_only_ii_class_commemoration () = of the bug this pin closes -- before this fix `colitur day` admitted "+ef-time-after-pentecost-sunday-9" here. + CORRECTED, fix round 1 (IMPORTANT finding, item 2): this pin used to + expect Pope Sixtus II et al. (6 August's own unrelated Class3 saint, + Commemoration_only) admitted as an ordinary commemoration in the freed + slot -- promoted from `colitur day`'s own actual output without + consulting either the primary text or the oracle first (the exact + vacuous-pin flavour the review caught, and this file's own header warns + against). Re-derived properly this time: + - PRIMARY TEXT: RG 16(a) itself says the winning Feast of the Lord holds + the Sunday's place cum omnibus iuribus et privilegiis -- WITH ALL the + Sunday's own rights and privileges. RG 111(b) is one of those: in + dominicis II classis, una tantum admittitur commemoratio, SCILICET DE + FESTO II CLASSIS -- a Sunday's own slot is reserved for a Class2 + candidate specifically. The day is still a dominica II classis for + this purpose even though the Transfiguration, not the Sunday, is + [observed] -- so Sixtus (Class3) has no standing for the slot at all, + the same as on any other II-class Sunday (contrast + [test_ii_class_sunday_admits_only_ii_class_commemoration] above, + romanus, Class3, excluded the identical way on an ORDINARY II-class + Sunday). + - ORACLE, independently fetched (missalemeum's own /api/v5/calendar + JSON, not the small-model webpage summary, which proved unreliable for + this exact question): 2023-08-06 (a Sunday) -- title Transfiguration of + Our Lord, commemorations empty, displaced Pope Sixtus II, Felicissimus + and Agapitus, Martyrs -- Sixtus displaced, not commemorated. Control, + 2026-08-06 (a Thursday, no Sunday collision) -- Sixtus present in + commemorations. Being a Sunday is exactly what excludes him. + Deliberately NOT a bare "comms=[]" pin (this project's own catalogued vacuity flavour: a count/emptiness assertion that would pass even if nothing had ever been offered): [omitted_has] first proves the Sunday really WAS a candidate that reached [disposition] and was actively - excluded, not merely never constructed. And the Sunday is not the day's - ONLY loser -- Pope Sixtus II, Felicissimus and Agapitus (Martyrs, - Commemoration_only, III class, also fixed 6 August, unrelated to the - Sunday) still receive their own ORDINARY commemoration exactly as RG - 111(c)/(d) require: proof that RG 16(a) suppresses the SUNDAY - specifically, not "every commemoration due that day". *) + excluded, not merely never constructed. *) let test_rg16a_lord_feast_no_sunday_commemoration_2028 () = let d = fetch 2028 8 6 in Alcotest.(check bool) @@ -518,35 +540,45 @@ let test_rg16a_lord_feast_no_sunday_commemoration_2028 () = true (omitted_has d "ef-time-after-pentecost-sunday-9"); Alcotest.(check string) - "2028-08-06: the Transfiguration observed; the Sunday has NO commemoration (RG16a); an UNRELATED III-class \ - saint the same day still gets its own ordinary one" + "2028-08-06: the Transfiguration observed; NO commemoration at all -- RG16(a)'s own \"cum omnibus iuribus \ + et privilegiis\" keeps the day a dominica II classis for RG111(b), so Sixtus (Class3) has no standing \ + either, oracle-confirmed" "2028-08-06 sunday season=time-after-pentecost week=9 slug=transfiguration-of-our-lord rank=class-2 \ - colour=white comms=[pope-sixtus-ii-felicissimus-and-agapitus-martyrs:ordinary] in=- out=[]" + colour=white comms=[] in=- out=[]" (describe d) -(* 2025-02-02: the other direction, and the change that made this task's - two halves inseparable (this task's own report). The Purification (2 - Feb, calendarium "IN PURIFICATIONE B. MARIAE VIRG.") is a feast of the - BLESSED VIRGIN, not of the Lord -- data/ef/adjustments.sexp retags it - [Bvm] (the sanctoral audit, this task's report), so it no longer bands - at RG 91 entry 14 and no longer outranks an occurring II-class Sunday - (entry 16 > entry 15). `date -d 2025-02-02 +%A` = Sunday, independently - confirmed. Register §6.0's own text: "Applying RG 16(a) to today's - [pre-fix] data would then also suppress the Sunday's commemoration - there" -- exactly the wrong outcome the data fix (not RG 16(a) itself) - prevents: with the Purification correctly Bvm, RG 16(a)'s own winner-side - [subject = Lord] conjunct never matches here, so the Sunday is simply the - observed day and the Purification survives as its ordinary commemoration - (RG 109/RG 111(b): a plain II-class candidate on a II-class Sunday, no - privileged rival due). *) +(* 2025-02-02, the other real instance of the SAME pattern -- not a + contrasting second direction, per this task's fix round 1 (CRITICAL + finding, item 1, reverted; see register §6.0 for the full account). + + The Purification (2 Feb, calendarium "IN PURIFICATIONE B. MARIAE VIRG.") + stays [subject = Lord] -- the user has ruled: follow the oracle. + missalemeum treats it exactly as RG 16(a)'s own "festum Domini" case + (independently fetched, `/api/v5/calendar` JSON): 2020-02-02 (a Sunday) + -- `{"title":"Purification of the Blessed Virgin Mary","tags":["IV + Sunday after Epiphany"],...,"commemorations":[],"displaced":[]}` -- the + Sunday named only as a TAG (the underlying temporal placement), not + commemorated at all; matches 2014-02-02 exactly. Contrast, the SAME + oracle on an ordinary Marian feast (2019-09-08, the Nativity of the + BVM): `{"title":"XIII Sunday after Pentecost",...,"commemorations": + [{"title":"Nativity of the Blessed Virgin Mary",...}],...}` -- the + Sunday observed, the feast merely commemorated, the OPPOSITE pattern -- + proving missalemeum does not treat every Marian feast this way, only the + Purification specifically. `date -d 2025-02-02 +%A` = Sunday, + independently confirmed. + + Unlike 2028-08-06, no second sanctoral candidate exists on 2 February + (data/ef/sanctoral.sexp has exactly one 2 February entry) -- a genuinely + clean "comms=[]" case, complementing the pair above rather than + duplicating it. *) let test_purification_on_a_sunday_2025 () = check ~msg: - "2025-02-02: the Sunday observed (Purification retagged Bvm, no longer a Feast of the Lord); the \ - Purification survives as an ordinary commemoration, not suppressed" + "2025-02-02: the Purification observed (subject Lord, unretagged -- the oracle's own treatment); NO \ + commemoration of the Sunday, oracle-confirmed" 2025 2 2 - "2025-02-02 sunday season=time-after-epiphany week=4 slug=ef-time-after-epiphany-sunday-4 rank=class-2 \ - colour=green comms=[purification-of-the-blessed-virgin-mary:ordinary] in=- out=[]" + "2025-02-02 sunday season=time-after-epiphany week=4 slug=purification-of-the-blessed-virgin-mary \ + rank=class-2 colour=white comms=[] in=- out=[]" let suite = ( "golden pins (known-tricky years)", @@ -574,10 +606,11 @@ let suite = Alcotest.test_case "IV-class ferias never commemorated (2026)" `Quick test_iv_class_ferias_never_commemorated; Alcotest.test_case "RG111(b): a II-class Sunday admits only a de-festo-II-classis commemoration (2009, 2026)" `Quick test_ii_class_sunday_admits_only_ii_class_commemoration; - Alcotest.test_case "RG16(a): a Feast of the Lord leaves the impeded Sunday with no commemoration (2028-08-06)" + Alcotest.test_case + "RG16(a): a Feast of the Lord leaves the impeded Sunday with NO commemoration at all, not even an \ + unrelated saint's own (2028-08-06, rubric + oracle)" `Quick test_rg16a_lord_feast_no_sunday_commemoration_2028; Alcotest.test_case - "RG16(a) does not fire for the (corrected) Purification: the Sunday observed, the Purification \ - commemorated (2025-02-02)" + "RG16(a), the Purification (oracle-ruled): observed outright, the Sunday not commemorated (2025-02-02)" `Quick test_purification_on_a_sunday_2025 ] ) diff --git a/test/test_precedence.ml b/test/test_precedence.ml index 369c490..ff75baa 100644 --- a/test/test_precedence.ml +++ b/test/test_precedence.ml @@ -26,7 +26,7 @@ let rules = | Cel.Feast -> (match loser.P.cel.Cel.rank with | Hi -> P.Transfer | Lo -> P.Commemorate P.Ordinary)); - admit = (fun ~observed:_ cs -> List.filteri (fun i _ -> i < 2) cs) } + admit = (fun ~observed:_ ~temporal:_ cs -> List.filteri (fun i _ -> i < 2) cs) } let slug_of c = S.to_string c.P.cel.Cel.slug diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml index facf3eb..52de989 100644 --- a/test/test_precedence_ef.ml +++ b/test/test_precedence_ef.ml @@ -290,7 +290,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 -> 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" @@ -812,31 +812,58 @@ 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 +(* Real-data shapes for the fix round's own RG16(a)/[admit] interaction + (below): a Feast of the Lord (subject Lord, Class2, sanctoral) as + [observed], oracle-confirmed real slugs/ranks rather than hand-typed + ones, the same coupling-safety reason [of_temporal] rows elsewhere in + this file use real data. *) +let lord_winner = + cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer + "transfiguration-of-our-lord" + +(* Pope Sixtus II et al. (6 August, real slug/rank/status from + data/ef/sanctoral.sexp): Class3, Commemoration_only, tagged [Ordinary] + here directly (as every other [admit_cases] row does, isolating + [admit]'s own selection logic from [privilege_of], per this table's own + header comment). *) +let sixtus = + cand ~origin:P.Sanctoral ~rank:V.Class3 ~status:Cel.Commemoration_only + ~layer:PE.universal_layer "pope-sixtus-ii-felicissimus-and-agapitus-martyrs" + +(* (description, observed, temporal, comms, expected). [temporal] -- + {!Precedence.rules.admit}'s own new parameter, fix round 1 (RG16(a) + task): the day's temporal-cycle candidate, independent of who is + [observed]. Every row below except the last two repeats [observed] as + [temporal] -- no RG16(a) displacement occurs in those rows (the pattern + [PE.admit]'s pre-fix code implicitly, and wrongly, assumed held + UNIVERSALLY), so the two values coinciding is the faithful shape, not a + simplification that dodges the new parameter. The last two rows are + where they genuinely differ -- see their own comment. *) let admit_cases = [ (* RG 111 (§4): "I class: none save one privileged." *) ( "I-class day, only an ordinary commemoration due -> none admitted", - observed_class1, + observed_class1, observed_class1, [ (ordinary_hi, P.Ordinary) ], [] ); ( "I-class day, ordinary + privileged both due -> only the privileged \ one, regardless of the ordinary one's higher dignity", - observed_class1, + observed_class1, observed_class1, [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ], [ "ef-privileged-lo" ] ); ( "I-class day, two privileged due -> only the higher-dignity one (still \ just \"one\")", - observed_class1, + observed_class1, observed_class1, [ (privileged_lo, P.Privileged); (privileged_hi, P.Privileged) ], [ "ef-privileged-hi" ] ); (* RG 111: "II-class Sundays: one (dropped if a privileged one is due)." *) ( "II-class Sunday, only an ordinary commemoration due -> it is admitted", - observed_class2_sunday, + observed_class2_sunday, observed_class2_sunday, [ (ordinary_hi, P.Ordinary) ], [ "ef-ordinary-hi" ] ); ( "II-class Sunday, ordinary (higher dignity) + privileged (lower \ dignity) both due -> the PRIVILEGED one is admitted, the ordinary \ one dropped despite outranking it", - observed_class2_sunday, + observed_class2_sunday, observed_class2_sunday, [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ], [ "ef-privileged-lo" ] ); (* RG 111(b)'s own rank floor ("scilicet DE FESTO II CLASSIS"), fix @@ -848,7 +875,7 @@ 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, + observed_class2_sunday, observed_class2_sunday, [ (ordinary_class3, P.Ordinary) ], [] ); (* RG 111: "other II class: one" -- no privilege override, the exact @@ -857,23 +884,55 @@ let admit_cases = observed day, OPPOSITE winner. *) ( "other II-class day, only an ordinary commemoration due -> it is \ admitted", - observed_class2_other, + observed_class2_other, observed_class2_other, [ (ordinary_hi, P.Ordinary) ], [ "ef-ordinary-hi" ] ); ( "other II-class day, same ordinary+privileged pair as the Sunday row \ above -> the ORDINARY one wins on pure dignity this time, the \ privileged one dropped", - observed_class2_other, + observed_class2_other, observed_class2_other, [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged) ], [ "ef-ordinary-hi" ] ); (* RG 111: "III-IV class: at most two" -- three candidates due, top two by dignity admitted, the third (lowest dignity) dropped. *) ( "III-class day, three commemorations due -> the top two by dignity, \ not merely \"two of them\"", - observed_class3, + observed_class3, observed_class3, [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged); (ordinary_lowest, P.Ordinary) ], - [ "ef-ordinary-hi"; "ef-privileged-lo" ] ) + [ "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, + [ (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, + [ (sixtus, P.Ordinary) ], + [ "pope-sixtus-ii-felicissimus-and-agapitus-martyrs" ] ) ] (* Order independence (brief: "the admitted set must not depend on input @@ -886,8 +945,10 @@ let test_admit_order_independent () = let comms = [ (ordinary_hi, P.Ordinary); (privileged_lo, P.Privileged); (ordinary_lowest, P.Ordinary) ] 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 + let forward = slugs_of (PE.admit ~observed:observed_class3 ~temporal:observed_class3 comms) in + let reversed = + slugs_of (PE.admit ~observed:observed_class3 ~temporal:observed_class3 (List.rev comms)) + in Alcotest.(check (list string)) "reversed input admits the same candidates" forward reversed @@ -1023,25 +1084,34 @@ let test_ii_class_sunday_privileged_witness_admitted_end_to_end () = (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] AND [PE.disposition] together, not [disposition] in - isolation as [disposition_cases]'s own rows above test it): 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); this proves the full pipeline now - agrees with the isolated [disposition] rows above: [observed] is the + ([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), [commemorations] is EMPTY (not merely missing the Sunday from - [admit]'s cut -- RG 16(a) is a [disposition]-level Omit, before [admit] - ever runs), and the Sunday appears in [omitted] with - ["omitted: yielded to a higher day"] ({!Precedence.resolve}'s own Omit - reason), never ["omitted: admission limit reached"] (that reason would - mean the Sunday was tagged [Commemorate] and then cut by [admit] -- a - different, wrong mechanism for the same wrong end state). *) + 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 @@ -1050,15 +1120,25 @@ let test_rg16a_lord_feast_suppresses_sunday_end_to_end () = cand ~origin:P.Sanctoral ~rank:V.Class2 ~subject:Sub.Lord ~layer:PE.universal_layer "transfiguration-of-our-lord" in - let resolution = P.resolve real_rules day_ctx ~temporal:sunday ~sanctoral:[ transfiguration ] 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\"" + 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))) - "the Sunday is omitted with RG16(a)'s own disposition-level reason, not admit's cut" - [ ("ef-time-after-pentecost-sunday-9", "omitted: yielded to a higher day") ] - (List.map (fun (c, reason) -> (S.to_string c.P.cel.Cel.slug, reason)) resolution.P.omitted) + "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 @@ -1091,7 +1171,7 @@ 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 -> 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" ] @@ -1281,10 +1361,10 @@ 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; diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml index ad9057b..6fcb25d 100644 --- a/test/test_rite_ef.ml +++ b/test/test_rite_ef.ml @@ -88,22 +88,30 @@ let test_vigil_of_christmas_suppressed () = appears_anywhere (* RG16(a) task (register §6.0 + the sanctoral subject audit, this task's - own report): data/ef/sanctoral.sexp is left untouched (still a faithful - mirror of lectio, subject `Lord` for both entries below -- see - test_sanctoral_ef.ml's own "explicit class = lord" spot-check, which - loads that file WITHOUT applying this overlay and still passes); the - correction lives entirely in data/ef/adjustments.sexp's two [Edit] - directives, the same mechanism [test_vigil_of_christmas_suppressed] - above already proves for a sibling bootstrap defect. This is the ONE - test in the suite that observes the two retags actually take effect - through the real pipeline -- without it, a typo in either directive's - slug (silently a no-op diagnostic, not a hard failure -- Overlay.apply's - own contract) or a reverted adjustments.sexp would leave every other - test green (2 February isn't a Sunday in any golden-pinned year, and - [test_sanctoral_ef.ml] deliberately never applies the overlay). Also - re-verifies the four entries the audit confirmed correct and left - untouched, so a future accidental retag of one of THEM would be caught - here too, not just the two that changed. *) + own report). data/ef/sanctoral.sexp is left untouched (still a faithful + mirror of lectio, subject `Lord` for every entry below except + `most-holy-name-of-mary` -- see test_sanctoral_ef.ml's own "explicit + class = lord" spot-check, which loads that file WITHOUT applying this + overlay and still passes); the one real correction lives entirely in + data/ef/adjustments.sexp's single remaining [Edit] directive, the same + mechanism [test_vigil_of_christmas_suppressed] above already proves for + a sibling bootstrap defect. + + CORRECTED, fix round 1 (CRITICAL finding, item 1): this test used to + also assert `purification-of-the-blessed-virgin-mary` was retagged + `Bvm`. REVERTED -- the user has ruled: follow the oracle (register §6.0 + has the full account, both the calendarium argument for `Bvm` and the + oracle evidence against it). The Purification is `Lord` again, one of + FIVE untouched entries this test now re-verifies, not four. + + This is the ONE test in the suite that observes the one live retag + actually takes effect through the real pipeline -- without it, a typo in + the directive's slug (silently a no-op diagnostic, not a hard failure -- + Overlay.apply's own contract) or a reverted adjustments.sexp would leave + every other test green ([test_sanctoral_ef.ml] deliberately never + applies the overlay). Also re-verifies the five entries the audit + confirmed correct and left untouched, so a future accidental retag of + one of THEM would be caught here too, not just the one that changed. *) let test_rg16a_subject_retag_in_effect () = let layer = real_layer () in let subject_of slug = @@ -111,10 +119,11 @@ let test_rg16a_subject_retag_in_effect () = | Some e -> e.Layer.cel.Cel.subject | None -> Alcotest.failf "slug %s not found in the overlaid layer" slug in - Alcotest.(check bool) "Purification retagged Bvm (calendarium: \"B. MARIAE VIRG.\")" true - (subject_of "purification-of-the-blessed-virgin-mary" = Subject.Bvm); Alcotest.(check bool) "Most Holy Name of Mary retagged Bvm (calendarium: \"Nominis Mariae\")" true (subject_of "most-holy-name-of-mary" = Subject.Bvm); + Alcotest.(check bool) + "Purification stays Lord (reverted, fix round 1 -- the user's ruling: follow the oracle)" true + (subject_of "purification-of-the-blessed-virgin-mary" = Subject.Lord); Alcotest.(check bool) "Precious Blood stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true (subject_of "precious-blood-of-our-lord-jesus-christ" = Subject.Lord); Alcotest.(check bool) "Transfiguration stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true diff --git a/test/test_validate.ml b/test/test_validate.ml index 89053c1..42c58f5 100644 --- a/test/test_validate.ml +++ b/test/test_validate.ml @@ -225,7 +225,7 @@ module Synthetic = struct let rules : (season, rank) P.rules = { P.band = (fun _ _ -> 0); disposition = (fun ~winner:_ ~loser:_ -> P.Omit); - admit = (fun ~observed:_ _ -> []) } + admit = (fun ~observed:_ ~temporal:_ _ -> []) } let year_start y = match D.make ~year:y ~month:1 ~day:1 with Ok d -> d | Error e -> failwith e @@ -377,7 +377,7 @@ module Synthetic = struct let dup_rules : (season, rank) P.rules = { P.band = (fun _ c -> match c.P.origin with P.Temporal -> 0 | P.Sanctoral -> 10); disposition = (fun ~winner:_ ~loser:_ -> P.Commemorate P.Ordinary); - admit = (fun ~observed:_ cs -> List.map (fun (c, p) -> ({ c with P.origin = c.P.origin }, p)) cs) } + admit = (fun ~observed:_ ~temporal:_ cs -> List.map (fun (c, p) -> ({ c with P.origin = c.P.origin }, p)) cs) } (* "unconverged": two entries collide on one date (6 June), both beating the temporal office and tied with each other, so slug decides: @@ -416,7 +416,7 @@ module Synthetic = struct disposition = (fun ~winner:_ ~loser -> match loser.P.cel.Cel.rank with R1 -> P.Transfer | R2 -> P.Commemorate P.Ordinary); - admit = (fun ~observed:_ cs -> cs) } + admit = (fun ~observed:_ ~temporal:_ cs -> cs) } let guard_transfer_target (_ : rank P.candidate) (origin : D.t) (_ : D.t -> rank Cel.t) = origin @@ -444,7 +444,7 @@ module Synthetic = struct { P.band = (fun _ c -> match c.P.origin with P.Temporal -> 0 | P.Sanctoral -> 10); disposition = (fun ~winner:_ ~loser:_ -> P.Commemorate P.Ordinary); admit = - (fun ~observed:_ cs -> + (fun ~observed:_ ~temporal:_ cs -> let sorted = List.stable_sort adm_compare_slug cs in if List.length sorted mod 2 = 1 then adm_take 2 sorted else adm_take 1 sorted) } @@ -475,7 +475,7 @@ module Synthetic = struct disposition = (fun ~winner:_ ~loser -> match loser.P.cel.Cel.rank with R1 -> P.Transfer | R2 -> P.Commemorate P.Ordinary); - admit = (fun ~observed:_ cs -> cs) } + admit = (fun ~observed:_ ~temporal:_ cs -> cs) } let collide_d2 = match D.make ~year:2026 ~month:2 ~day:10 with Ok d -> d | Error e -> failwith e let collide_transfer_target (_ : rank P.candidate) (_ : D.t) (_ : D.t -> rank Cel.t) = collide_d2 @@ -492,7 +492,7 @@ module Synthetic = struct let clean_sanctoral_rules : (season, rank) P.rules = { P.band = (fun _ c -> match c.P.origin with P.Temporal -> 0 | P.Sanctoral -> 10); disposition = (fun ~winner:_ ~loser:_ -> P.Commemorate P.Ordinary); - admit = (fun ~observed:_ cs -> cs) } + admit = (fun ~observed:_ ~temporal:_ cs -> cs) } end open Synthetic |
