From 4fb9eac2fdb9bc22af2b63c1d5df6da7780b58cf Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Fri, 28 Aug 2026 16:15:13 +0200 Subject: test(of): pin the six corrupt Year II Gospels in Ordinary Time Built the litcal readings comparison the OF provenance notes had left as future work, and it found a defect class the existing layers structurally could not see: the litcal layer compares season, grade and identity, and stops there, so every reading colitur serves has been validated by nothing except the lineage it arrived on. Ten references are corrupt. Nine end at verse 22 where the OLM ends them at 20; the tenth reads "Matthew 17:9a,10-135", a verse Matthew 17 does not have. Every one lives in a Year II entry or in of-advent-2-saturday, which has no year cycle -- not one Year I entry is affected, which is what makes this a transcription fault rather than ten independent slips. This commit adds only the detection, and only for the six the invariant can reach. In Ordinary Time the OF weekday Gospel does not depend on the year of the cycle: OLM n. 69 point 4 alternates the FIRST reading and leaves the Gospel a single one-year series. So of-ordinary-time---i and its -ii twin must carry the same Gospel string, and six pairs do not. Scoped to Ordinary Time deliberately. The unscoped sweep reports 17 further disagreeing pairs in of-advent-3/4-* and of-christmas-*, all of them correct: those weekdays are keyed to the calendar DATE (OLM n. 69 point 3), so their -i/-ii entries describe two different dates rather than two years of one cycle. An unscoped test would report seventeen false positives and would, rightly, be switched off by whoever read it next. Pinned rather than green, because the fix is not ours to make here: data/of/lectionary.sexp's own header forbids hand-editing a generated artifact, and lectio's internal/caldata/of-lectionary.ini carries all ten verbatim. The exact six are listed, so the test fails if a seventh appears and equally if one is fixed without updating the list -- both are changes a reader must see. Mutation-tested in both directions: introducing a seventh violation reddens it, and correcting Mark 1:14-22 reddens it too. Evidence for the corrections, in docs/research/of/olm-1981-ocr.txt, the Latin OLM itself rather than a witness: "Mc 1, 14-20", "Mc 4, 1-20", "Mc 5, 1-20", "Mt 7, 15-20", "Mt 17, 14-20", "Mt 18, 15-20", "1 Sam 1, 9-20", "1 Sam 3, 1-10. 19-20", "Mt 17, 10-13". litcal independently confirms three of the six Gospels and all four references the invariant cannot reach. James 5:13-22 is unlocatable in the OCR but impossible on its face: James ends at 5:20. --- test/test_lectionary_of.ml | 78 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) (limited to 'test/test_lectionary_of.ml') diff --git a/test/test_lectionary_of.ml b/test/test_lectionary_of.ml index dc02176..6b599c1 100644 --- a/test/test_lectionary_of.ml +++ b/test/test_lectionary_of.ml @@ -488,6 +488,79 @@ let test_christmas_date_keyed_no_drift () = check_year 2024; check_year 2025 +(* ------------------------------------------------------------------ *) +(* In ORDINARY TIME the OF weekday Gospel does not depend on which year of + the two-year cycle is running: OLM n. 69 point 4 alternates the FIRST + reading between Year I and Year II and leaves the Gospel a single + one-year series. So for every [of-ordinary-time---i] there is an + [...-ii] whose Gospel reference is the SAME STRING, and a pair that + disagrees is a data defect rather than a liturgical fact. + + This is deliberately scoped to Ordinary Time. The same sweep over the + whole file reports 17 further disagreeing pairs in [of-advent-3/4-*] and + [of-christmas-*] -- all of them CORRECT: those weekdays are keyed to the + calendar DATE (17-24 December, and the Christmas octave, OLM n. 69 + point 3, the rule [date_keyed_slug] above implements), so their -i/-ii + entries describe two different dates rather than two years of one cycle. + An unscoped version of this test would report seventeen false positives + and would, correctly, be switched off by whoever next read it. + + PINNED, NOT GREEN (2026-08-28). Six pairs violate the invariant today, + and every one is the same transcription fault: a Year II Gospel ending at + verse 22 where the OLM ends it at 20. All six are confirmed against the + Latin OLM itself (docs/research/of/olm-1981-ocr.txt: "Mc 1, 14-20", + "Mc 4, 1-20", "Mc 5, 1-20", "Mt 7, 15-20", "Mt 17, 14-20", + "Mt 18, 15-20"), and three of them independently against litcal. + + The defect is UPSTREAM and verbatim -- lectio's own + internal/caldata/of-lectionary.ini carries all six -- and this file's own + header forbids hand-editing a generated artifact, so the correction has to + be made there and the file regenerated. Until then the exact six are + listed here. The test fails if a SEVENTH appears, and equally if one is + FIXED without updating this list: both are changes a reader must see. *) +let known_year_ii_gospel_defects = + [ "of-ordinary-time-1-monday"; + "of-ordinary-time-12-wednesday"; + "of-ordinary-time-18-saturday"; + "of-ordinary-time-19-wednesday"; + "of-ordinary-time-3-wednesday"; + "of-ordinary-time-4-monday" ] + +let test_ordinary_time_gospel_is_year_independent () = + let entries = Lectionary.entries (real_lectionary ()) in + let gospel_of cits = + match List.find_opt (fun c -> c.Colitur_kernel.Citation.part = Colitur_kernel.Citation.Gospel) cits with + | Some c -> Some c.Colitur_kernel.Citation.reference + | None -> None + in + let by_slug = List.map (fun (s, c) -> (Slug.to_string s, gospel_of c)) entries in + let find s = match List.assoc_opt s by_slug with Some g -> g | None -> None in + let has_prefix p s = String.length s >= String.length p && String.sub s 0 (String.length p) = p in + let has_suffix suf s = + String.length s >= String.length suf && String.sub s (String.length s - String.length suf) (String.length suf) = suf + in + let ordinary_time_stems = + List.filter_map + (fun (s, _) -> + if has_prefix "of-ordinary-time-" s && has_suffix "-i" s && not (has_suffix "-ii" s) then + Some (String.sub s 0 (String.length s - 2)) + else None) + by_slug + in + let stems = List.sort_uniq compare ordinary_time_stems in + let violations = + List.filter + (fun stem -> + match (find (stem ^ "-i"), find (stem ^ "-ii")) with + | Some a, Some b -> a <> b + | _ -> false) + stems + in + Alcotest.(check bool) "the sweep actually found Ordinary Time pairs to check" true (List.length stems > 100); + Alcotest.(check (list string)) + "Ordinary Time: Year I and Year II share one Gospel, except the six pinned upstream defects" + known_year_ii_gospel_defects (List.sort compare violations) + let suite = [ Alcotest.test_case "Sunday cycle table (OLM n.66, straddles Advent I)" `Quick test_sunday_cycle_table; Alcotest.test_case "weekday cycle table (OLM n.69.4, straddles Advent I)" `Quick test_weekday_cycle_table; @@ -504,5 +577,8 @@ let suite = Alcotest.test_case "O-Antiphon date-keyed reading does not drift (17 December)" `Quick test_o_antiphon_no_drift; Alcotest.test_case "Christmas-season date-keyed reading does not drift (2 January)" `Quick - test_christmas_date_keyed_no_drift + test_christmas_date_keyed_no_drift; + Alcotest.test_case + "Ordinary Time Gospel is year-independent (OLM n. 69.4); six upstream defects pinned" `Quick + test_ordinary_time_gospel_is_year_independent ] -- cgit v1.3 From b14a3078c1a4db87010f5df955442bf1e440e0ec Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 2 Sep 2026 12:54:07 +0200 Subject: fix(of): correct ten reading references, regenerated from lectio Nine ended at verse 22 where the OLM ends them at 20; the tenth read "Matthew 17:9a,10-135", a verse Matthew 17 does not have. Every one was in a Year II entry, or in of-advent-2-saturday, which has no year cycle. Not one Year I entry was affected -- that distribution is what made this a transcription fault rather than ten independent slips. Corrected upstream in lectio's own internal/caldata/of-lectionary.ini (its suite green) and this file regenerated from it, per the header's own instruction not to hand-edit a generated artifact. The diff is exactly the source SHA-256 pin plus the ten references; both pins in test_lectionary_of.ml move with it. The Ordinary Time invariant added in the previous commit now asserts outright rather than pinning six exceptions: in Ordinary Time the OF weekday Gospel does not depend on the year of the cycle, so a -i/-ii pair whose Gospel differs is a defect, and the expected violation set is empty. Mutation-tested against the Year II entry alone -- an earlier attempt changed both halves of the pair and passed for the wrong reason, which is worth knowing about this shape of test. Measured: the 20-vs-22 divergence class against litcal goes 26 -> 0 over the twelve witnessed years. 917 tests, every year 1583-9999. Also, L6/L7 (the Sacred Heart / John the Baptist tie at an equal Tabula entry) gains the citation its note said did not exist. Tabula I.3 enumerates "Sollemnitates Domini, beatae Mariae Virginis, et Sanctorum" -- an order, not a bare category, and read that way it settles the tie against colitur's current alphabetical answer. Recorded with its counter-argument (entry 4 marks its own internal order with explicit lettering; entry 2's list cannot be an ordering at all) and with what would settle it. Verdict stays open, behaviour unchanged. --- data/of/expected-divergences-litcal.sexp | 28 ++++++++++++++++++++ data/of/lectionary.sexp | 22 ++++++++-------- test/test_lectionary_of.ml | 44 +++++++++++++++----------------- 3 files changed, 60 insertions(+), 34 deletions(-) (limited to 'test/test_lectionary_of.ml') diff --git a/data/of/expected-divergences-litcal.sexp b/data/of/expected-divergences-litcal.sexp index 633d2e0..a045415 100644 --- a/data/of/expected-divergences-litcal.sexp +++ b/data/of/expected-divergences-litcal.sexp @@ -139,6 +139,34 @@ ; RG 112(a) does on the EF side for a narrower case. NOT fixed here (a ; kernel-level tie-break policy is out of this task's own scope regardless ; of verdict). +; +; UPDATE 2026-08-28: a CANDIDATE citation has since been found, and it +; points AGAINST colitur's current answer. Tabula I.3 does not name a bare +; category; it enumerates one in a definite order -- "Sollemnitates DOMINI, +; beatae Mariae Virginis, et SANCTORUM in Calendario generali inscriptae". +; The Sacred Heart is a Solemnity of the Lord; the Nativity of St John the +; Baptist is a Solemnity of a Saint. Read as ordered, that entry puts the +; Lord first, litcal's answer is right and colitur's alphabetical tie-break +; is reaching a conclusion the text already settles. colitur's own EF side +; models exactly this shape already -- RG 91 entry 14's "primum mobilia, +; deinde fixa" is a within-entry order, and Precedence_ef.band expresses it +; by scaling the whole table x10 so a half-row sits between its neighbours. +; Precedence_of.band is scaled x10 too, so I.3 could carry 30/31/32 with no +; structural change. +; +; The counter-argument, which is why this stays OPEN rather than becoming a +; fix: Tabula entry 4 marks its own internal order with explicit a) b) c) d) +; lettering, so the drafters had a way to signal ordering and used it. Entry +; 3 is a plain "A, B, et C" list. Entry 2's own list ("Nativitas Domini, +; Epiphania, Ascensio et Pentecostes...") cannot be an ordering at all, +; since those days can never collide with each other -- which is evidence +; that a list inside an entry need not imply precedence. +; +; What would settle it: any authoritative source treating 24 June in a +; Sacred-Heart-collision year -- an ordo, a Notitiae reply, or a +; conference decree. The collision is rare (Easter must be late enough to +; put Easter+68 on 24 June) and none of the project's current witnesses +; covers such a year. Verdict stays open; behaviour unchanged. ((id L6) (citation "precedence.ml's compare_by (Slug.compare tie-break); no Tabula/Normae text found ranking Sacred Heart above/below the Nativity of John the Baptist at equal band") (verdict open) diff --git a/data/of/lectionary.sexp b/data/of/lectionary.sexp index 693b768..bf3b5eb 100644 --- a/data/of/lectionary.sexp +++ b/data/of/lectionary.sexp @@ -63,7 +63,7 @@ ; COVERAGE (5) below for the measured count. ; ; Source: ../lectio/internal/caldata/of-lectionary.ini -; SHA-256: f0b527aef58b93e6ccdfbeafe52b340ed2a9d7d72805bf5083e3da04b080123c +; SHA-256: 7b6c6a26abf222312c3000aeaae131887bbb1e2af3863c68a3e5ad005cd07ba9 ; 988 ini sections (988 expected) -> 438 resolved (base, cycle-shape) pairs -> 435 colitur slugs mapped, 3 ini bases genuinely excluded (structurally ; unreachable -- see [excluded_bases]), 0 ini bases genuinely unmapped ; (no pattern, no override, no matching sanctoral slug) -> 771 emitted @@ -215,7 +215,7 @@ ((part Gospel) (reference "Luke 5:17-26")))) (of-advent-2-saturday (((part First) (reference "Sirach 48:1-4,9-11")) - ((part Gospel) (reference "Matthew 17:9a,10-135")))) + ((part Gospel) (reference "Matthew 17:9a,10-13")))) (of-advent-2-thursday (((part First) (reference "Isaiah 41:13-20")) ((part Gospel) (reference "Matthew 11:11-15")))) @@ -929,7 +929,7 @@ ((part Gospel) (reference "Mark 1:14-20")))) (of-ordinary-time-1-monday-ii (((part First) (reference "1 Samuel 1:1-8")) - ((part Gospel) (reference "Mark 1:14-22")))) + ((part Gospel) (reference "Mark 1:14-20")))) (of-ordinary-time-1-saturday-i (((part First) (reference "Hebrews 4:12-16")) ((part Gospel) (reference "Mark 2:13-17")))) @@ -946,13 +946,13 @@ (((part First) (reference "Hebrews 2:5-12")) ((part Gospel) (reference "Mark 1:21-28")))) (of-ordinary-time-1-tuesday-ii - (((part First) (reference "1 Samuel 1:9-22")) + (((part First) (reference "1 Samuel 1:9-20")) ((part Gospel) (reference "Mark 1:21-28")))) (of-ordinary-time-1-wednesday-i (((part First) (reference "Hebrews 2:14-18")) ((part Gospel) (reference "Mark 1:29-39")))) (of-ordinary-time-1-wednesday-ii - (((part First) (reference "1 Samuel 3:1-10,19-22")) + (((part First) (reference "1 Samuel 3:1-10,19-20")) ((part Gospel) (reference "Mark 1:29-39")))) (of-ordinary-time-10-friday-i (((part First) (reference "2 Corinthians 4:7-15")) @@ -1061,7 +1061,7 @@ ((part Gospel) (reference "Matthew 7:15-20")))) (of-ordinary-time-12-wednesday-ii (((part First) (reference "2 Kings 22:8-13;23:1-3")) - ((part Gospel) (reference "Matthew 7:15-22")))) + ((part Gospel) (reference "Matthew 7:15-20")))) (of-ordinary-time-13-friday-i (((part First) (reference "Genesis 23:1-4,19;24:1-8,62-67")) ((part Gospel) (reference "Matthew 9:9-13")))) @@ -1259,7 +1259,7 @@ ((part Gospel) (reference "Matthew 17:14-20")))) (of-ordinary-time-18-saturday-ii (((part First) (reference "Habakkuk 1:12-2:4")) - ((part Gospel) (reference "Matthew 17:14-22")))) + ((part Gospel) (reference "Matthew 17:14-20")))) (of-ordinary-time-18-thursday-i (((part First) (reference "Numbers 20:1-13")) ((part Gospel) (reference "Matthew 16:13-23")))) @@ -1313,7 +1313,7 @@ ((part Gospel) (reference "Matthew 18:15-20")))) (of-ordinary-time-19-wednesday-ii (((part First) (reference "Ezekiel 9:1-7;10:18-22")) - ((part Gospel) (reference "Matthew 18:15-22")))) + ((part Gospel) (reference "Matthew 18:15-20")))) (of-ordinary-time-2-friday-i (((part First) (reference "Hebrews 8:6-13")) ((part Gospel) (reference "Mark 3:13-19")))) @@ -1745,7 +1745,7 @@ ((part Gospel) (reference "Mark 4:1-20")))) (of-ordinary-time-3-wednesday-ii (((part First) (reference "2 Samuel 7:4-17")) - ((part Gospel) (reference "Mark 4:1-22")))) + ((part Gospel) (reference "Mark 4:1-20")))) (of-ordinary-time-30-friday-i (((part First) (reference "Romans 9:1-5")) ((part Gospel) (reference "Luke 14:1-6")))) @@ -1937,7 +1937,7 @@ ((part Gospel) (reference "Mark 5:1-20")))) (of-ordinary-time-4-monday-ii (((part First) (reference "2 Samuel 15:13-14,30;16:5-13")) - ((part Gospel) (reference "Mark 5:1-22")))) + ((part Gospel) (reference "Mark 5:1-20")))) (of-ordinary-time-4-saturday-i (((part First) (reference "Hebrews 13:15-17,20-21")) ((part Gospel) (reference "Mark 6:30-34")))) @@ -2050,7 +2050,7 @@ (((part First) (reference "Sirach 17:1-15")) ((part Gospel) (reference "Mark 10:13-16")))) (of-ordinary-time-7-saturday-ii - (((part First) (reference "James 5:13-22")) + (((part First) (reference "James 5:13-20")) ((part Gospel) (reference "Mark 10:13-16")))) (of-ordinary-time-7-thursday-i (((part First) (reference "Sirach 5:1-8")) diff --git a/test/test_lectionary_of.ml b/test/test_lectionary_of.ml index 6b599c1..cbffdd2 100644 --- a/test/test_lectionary_of.ml +++ b/test/test_lectionary_of.ml @@ -201,7 +201,7 @@ let test_sha256_pinned () = and test_citation_coverage_of.ml), and the header's "Epistle" wording is fixed to "First". Re-derived the same way, same discipline. *) Alcotest.(check string) "data/of/lectionary.sexp SHA-256" - "788ddc6d2d783286b6e64882a5372ae213cd5462c887e68aa2bea22388cdb4f7" (sha256_of_file lectionary_path) + "aaa57bd7229bbd1fb3372fb9467251419885af6f02dbd19d328a6c410030868d" (sha256_of_file lectionary_path) (* lectio's OWN of-lectionary.ini SHA-256, pinned inside data/of/ lectionary.sexp's own provenance header (tools/bootstrap_lectionary_of @@ -216,7 +216,7 @@ let test_source_sha256_pinned_in_header () = let n = in_channel_length ic in let content = really_input_string ic n in close_in ic; - let needle = "SHA-256: f0b527aef58b93e6ccdfbeafe52b340ed2a9d7d72805bf5083e3da04b080123c" in + let needle = "SHA-256: 7b6c6a26abf222312c3000aeaae131887bbb1e2af3863c68a3e5ad005cd07ba9" in let contains haystack needle = let hl = String.length haystack and nl = String.length needle in let rec go i = i + nl <= hl && (String.sub haystack i nl = needle || go (i + 1)) in @@ -505,26 +505,24 @@ let test_christmas_date_keyed_no_drift () = An unscoped version of this test would report seventeen false positives and would, correctly, be switched off by whoever next read it. - PINNED, NOT GREEN (2026-08-28). Six pairs violate the invariant today, - and every one is the same transcription fault: a Year II Gospel ending at - verse 22 where the OLM ends it at 20. All six are confirmed against the - Latin OLM itself (docs/research/of/olm-1981-ocr.txt: "Mc 1, 14-20", + CLOSED (2026-08-28, same day it was opened). Six pairs violated this when + the test was written, every one the same transcription fault: a Year II + Gospel ending at verse 22 where the OLM ends it at 20. All six are now + corrected upstream in lectio's own internal/caldata/of-lectionary.ini and + this file regenerated from it, so the expected violation set is EMPTY and + the test asserts the invariant outright rather than pinning exceptions to + it. Four further corruptions the same exercise found -- three First + readings and of-advent-2-saturday's "Matthew 17:9a,10-135", a verse + Matthew 17 does not have -- are fixed in the same upstream commit but are + invisible to THIS check by construction: no I/II symmetry constrains a + First reading, and Advent has no year cycle. They were caught by comparing + against litcal instead, and the two SHA-256 pins above are what stop the + data regressing underneath either. + + Corrections confirmed verbatim in the Latin OLM 1981: "Mc 1, 14-20", "Mc 4, 1-20", "Mc 5, 1-20", "Mt 7, 15-20", "Mt 17, 14-20", - "Mt 18, 15-20"), and three of them independently against litcal. - - The defect is UPSTREAM and verbatim -- lectio's own - internal/caldata/of-lectionary.ini carries all six -- and this file's own - header forbids hand-editing a generated artifact, so the correction has to - be made there and the file regenerated. Until then the exact six are - listed here. The test fails if a SEVENTH appears, and equally if one is - FIXED without updating this list: both are changes a reader must see. *) -let known_year_ii_gospel_defects = - [ "of-ordinary-time-1-monday"; - "of-ordinary-time-12-wednesday"; - "of-ordinary-time-18-saturday"; - "of-ordinary-time-19-wednesday"; - "of-ordinary-time-3-wednesday"; - "of-ordinary-time-4-monday" ] + "Mt 18, 15-20". *) +let known_year_ii_gospel_defects = [] let test_ordinary_time_gospel_is_year_independent () = let entries = Lectionary.entries (real_lectionary ()) in @@ -558,7 +556,7 @@ let test_ordinary_time_gospel_is_year_independent () = in Alcotest.(check bool) "the sweep actually found Ordinary Time pairs to check" true (List.length stems > 100); Alcotest.(check (list string)) - "Ordinary Time: Year I and Year II share one Gospel, except the six pinned upstream defects" + "Ordinary Time: Year I and Year II share one Gospel, with no exceptions" known_year_ii_gospel_defects (List.sort compare violations) let suite = @@ -579,6 +577,6 @@ let suite = Alcotest.test_case "Christmas-season date-keyed reading does not drift (2 January)" `Quick test_christmas_date_keyed_no_drift; Alcotest.test_case - "Ordinary Time Gospel is year-independent (OLM n. 69.4); six upstream defects pinned" `Quick + "Ordinary Time Gospel is year-independent (OLM n. 69.4)" `Quick test_ordinary_time_gospel_is_year_independent ] -- cgit v1.3