From 459d2c74711e6a152fb338f88d2327f12180b61f Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Sat, 15 Aug 2026 02:58:00 +0200 Subject: test(differential): compare reading citations, 16 801 days Layer 3 is NOT blind to the ferial resumption rule, unusually for this project: lectio implements the same rubric independently, so the differential checks colitur's rule against a second implementation rather than against silence. It IS blind to the Commons -- lectio has no such concept -- so chain step 4 rests on layer 4 alone. Divergence triage, 1 532 of 16 801 days differ (15 269 match exactly), 0 unexplained: - A genuine colitur bug found and fixed: data/ef/lectionary.sexp carried lectio's own ini section names verbatim, never translated into the slugs Temporal_ef actually computes for the identical office. Data the bootstrap already held (Passiontide both weeks, the Pentecost Ember days, Holy Name Sunday) was unreachable by chain step 2, silently falling through to a wrong step-3 answer -- invisible until citation content became comparable. tools/bootstrap_lectionary.ml gained colitur_keys (rename/widen), a hand-authored ef-nativity-vigil entry cross-sourced from lectio's own sanctoral calendar and corroborated by colitur's own (overlay-suppressed) sanctoral bootstrap of the same source, and a derived ef-holy-name entry (RG 17(a)'s single Mass heading covers both the Sunday and 2-January shapes). One guess (giving the fixed Nativity-Octave days Holy Name Sunday's own citation uniformly) was tried and reverted once measured against the fixture -- Missal-verified to be right only for the genuine "Sunday within the Octave" case, wrong whenever the true preceding Sunday is Christmas Day or Advent IV instead. - 10 existing/widened Layer C entries (C1/C6/C8/C14/C15/C16/C17/C18 -- C6 and C17 narrowed with the removed population accounted for, not silently re-absorbed) plus two new ones: C19 (the already-adjudicated Time-after-Epiphany week-numbering offset, now visible in citation content), C20 (lectio computes no reading at all for the seven readingless saints Task 6 gave a Common or proper -- colitur is more complete, not wrong). - One entry, C21, is a genuine colitur LOGIC limitation found but not fixed: Lectionary_ef.readings' step 3 is a single hop to the preceding Sunday, insufficient for a run of colitur-only fixed Christmastide slugs whose own preceding Sunday is itself another entry-less fixed slug. Recorded verdict open (data/ef/expected-divergences-missalemeum.sexp's own established precedent for this shape), not left silently failing -- fully diagnosed in task-8-report.md, recommended as a dedicated follow-up task rather than a hasty fix to a shared, multi-round-reviewed kernel-adjacent function. --- tools/bootstrap_lectionary.ml | 159 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 9 deletions(-) (limited to 'tools') diff --git a/tools/bootstrap_lectionary.ml b/tools/bootstrap_lectionary.ml index 52c7e50..a4c82ae 100644 --- a/tools/bootstrap_lectionary.ml +++ b/tools/bootstrap_lectionary.ml @@ -52,12 +52,67 @@ let parse_ini path = close_in ic; List.rev !sections +(* Task 8 (branch ef-lectionary, differential fix round): lectio's own ini + section names are NOT always the slug {!Rite_ef.Temporal_ef} computes + for the IDENTICAL office. Confirmed the hard way: the first version of + this generator carried section names through {!Slug.of_string} + verbatim, so the data these renamed families need was sitting in this + very file all along -- under lectio's spelling -- while + [Lectionary_ef.readings]' step 2 looked it up under colitur's own, + found nothing, and silently fell through to step 3's ferial-resumption + answer instead. Invisible until test/test_differential.ml first + compared citation CONTENT (this task): a day's SLUG/rank/colour already + matched, so nothing before this task ever noticed the READING was + wrong. [colitur_keys] is the closed translation table -- the SAME + families test/test_differential.ml's own Layer A ([norm_slug]) already + established as pure vocabulary, carrying no liturgical substance, not + re-derived here. Kept in the generator, not in [Lectionary_ef.readings] + itself, because a bootstrap step exists exactly to translate an + external vocabulary into colitur's own ONCE, at data-generation time -- + baking an alias table into the rite's own runtime lookup would just + move the vocabulary problem, not solve it. + + A translation can widen ONE ini section into several colitur keys + (Passiontide: lectio never distinguishes Passion week from Holy week at + the slug level, "ef-passiontide-0-" for both -- confirmed + empirically, its OWN computed reading is byte-identical between the two + weeks' same weekday throughout 2005-2050 -- so both of colitur's own + week-specific keys legitimately point at the one source entry); it + never narrows or drops one, and every key not named here passes through + unchanged (e.g. "ef-easter-8-{monday,tuesday,thursday}", the three + non-Ember Pentecost-octave ferias colitur does NOT rename). *) +let weekdays = [ "monday"; "tuesday"; "wednesday"; "thursday"; "friday"; "saturday" ] + +let colitur_keys name = + if String.equal name "ef-christmas-sunday-0" then + (* WIDEN, not rename -- fix round (found by test_lectionary_ef.ml's own + pre-existing test_step3_christmas_feria_resumes_sunday going from + PASS to FAIL): "ef-christmas-sunday-0" is not only lectio's own + calendar-level slug for RG 17(a)'s Holy Name Sunday + (test_differential.ml's own [norm_slug] comment) -- it is ALSO + colitur's own GENUINE, DIRECTLY-COMPUTED temporal slug (Rite_ef. + Temporal_ef.temporal, "if m = 12 && dd >= 26 then Some + \"ef-christmas-sunday-0\"") for an ordinary Sunday landing on 26-28 + December, the historical "Sunday within the Octave of the Nativity" + -- a DIFFERENT civil window than Holy Name Sunday (2-5 January), and + Missal-confirmed (docs/research/scan1.txt, "Dominica infra octavam + Nativitatis Domini, II classis") to be the IDENTICAL Mass (Gal. + 4,1-7 / Luc. 2,33-40), not a coincidence. The first version of this + translation REPLACED the key outright and broke that pre-existing, + working case -- caught by the existing test suite, not by this + generator's own reasoning; keeping the original key alongside the + Holy-Name-Sunday alias fixes it, following the same "widen, never + narrow" rule Passiontide below already established. *) + [ "ef-christmas-sunday-0"; "ef-holy-name-sunday" ] + else if String.equal name "ef-easter-8-wednesday" then [ "ef-pentecost-ember-wed" ] + else if String.equal name "ef-easter-8-friday" then [ "ef-pentecost-ember-fri" ] + else if String.equal name "ef-easter-8-saturday" then [ "ef-pentecost-ember-sat" ] + else ( + match List.find_opt (fun wd -> String.equal name ("ef-passiontide-0-" ^ wd)) weekdays with + | Some wd -> [ "ef-passiontide-1-" ^ wd; "ef-passiontide-2-" ^ wd ] + | None -> [ name ]) + let convert sec = - let slug = - match Slug.of_string sec.name with - | Ok s -> s - | Error e -> die "bad slug %S: %s" sec.name e - in let cite part key = match List.assoc_opt key sec.fields with | None | Some "" -> None @@ -69,7 +124,86 @@ let convert sec = die "%s: has one reading, not two -- an Epistle without a Gospel (or the \ reverse) is malformed and must be investigated, not silently shipped" sec.name; - (slug, cs) + List.map + (fun key -> + match Slug.of_string key with + | Ok slug -> (slug, cs) + | Error e -> die "bad slug %S (translated from %S): %s" key sec.name e) + (colitur_keys sec.name) + +(* [ef-nativity-vigil] (RG 91 entry 5, {!Rite_ef.Temporal_ef}'s own [named]) + is a TEMPORAL office with no section of its own in THIS ini at all -- + lectio computes ITS citations from a different source file entirely, + its SANCTORAL calendar (tridentine-calendar.ini's own + "[vigil-of-christmas]" section: "reading.first = Rom 1:1-6", + "reading.gospel = Matt 1:18-21"). colitur's own sanctoral bootstrap + (Task 3) already carried those identical citations onto + data/ef/sanctoral.sexp's own `vigil-of-christmas` entry -- inert there + because data/ef/adjustments.sexp suppresses it (that overlay's own + comment: the SAME celebration as this temporal office, not a second + one) -- which corroborates this value independently rather than merely + asserting it. Hand-authored here, the same discipline + data/ef/adjustments.sexp's own RG 110 companion and Major Litanies + `Add` directives already use for a genuine upstream-source gap this + generator's own single-ini design cannot reach on its own. *) +let vigil_entries = + [ ( (match Slug.of_string "ef-nativity-vigil" with Ok s -> s | Error e -> die "%s" e), + [ { Citation.part = Citation.First; reference = "Rom 1:1-6" }; + { Citation.part = Citation.Gospel; reference = "Matt 1:18-21" } ] ) + ] + +(* One more colitur-only slug, DERIVED from [ef-holy-name-sunday]'s own + citations (just translated above) rather than a second hand-typed copy + of the same text: [ef-holy-name] is RG 17(a)'s own fallback ("secus die + 2 ianuarii", 2 January in a year with no Sunday 2-5 January). The + calendarium's own table (data/ef/expected-divergences.sexp's own C16 + note quotes it) gives ONE heading for both the Sunday and the fallback + shape -- the identical Mass, not two -- so this is the same citation + pair, not an independent lookup. Safe to hard-wire (unlike a step-3 + fallback, which would walk back into a DIFFERENT, unrelated Sunday of + the OLD liturgical year): RG 17(a)'s whole point is that this Mass is + said in place of, not alongside, whatever ferial reading a bare + fallback would otherwise find. + + REJECTED, and recorded so it is not re-attempted: giving + [ef-nativity-octave-day-{5,6,7}] (29-31 December, RG 91 entry 17) this + SAME derived value too. Tried, then measured against the real + differential and REVERTED -- it is wrong. lectio's own citation for + those three dates is NOT uniform across years (confirmed against the + fixture: 2005-12-29 reads "Heb 1:1-12"/"John 1:1-14", 2006-12-29 reads + "1 Cor. 4:1-5"/"Luke 3:1-6" -- two different Masses, not one). Read + against the Missal directly (docs/research/scan1.txt, "Dominica infra + octavam Nativitatis Domini, II classis": Epistle "Gal. 4, 1-7", Gospel + "Luc. 2, 33-40" -- the SAME pair as Holy Name Sunday, corroborating the + guess would have been right ONLY for that specific case): the two + observed fixture values are each the PREVIOUS true civil Sunday's OWN + Mass -- Christmas Day itself (25 Dec 2005 was a Sunday that year: + "In Nativitate Domini... Missa in die", Heb 1:1-12/John 1:1-14) or, in + a year with no Sunday between Christmas and New Year at all, all the + way back to Advent IV (24 Dec 2006 was a Sunday; 1 Cor. 4:1-5/Luke + 3:1-6 is that Mass's own citation, already in [ef-advent-sunday-4] + above). colitur's OWN existing step 3 ([Lectionary_ef.readings], NOT + this generator) already walks back to the true PRECEDING CIVIL Sunday + regardless of season and would find EXACTLY these same answers on its + own -- a hard-wired entry here would have SHORT-CIRCUITED that correct, + already-general mechanism with one wrong, uniform guess. Left absent + deliberately: these three dates stay whatever step 3 resolves through + [ef-advent-sunday-4]/[ef-holy-name-sunday] et al, or [] on a date whose + own preceding Sunday has no entry either (data/ef/expected- + divergences.sexp's own C21 entry records what remains, if anything, + after that). *) +let derived_entries entries = + let holy_name_sunday_citations = + match + List.find_opt (fun (s, _) -> String.equal (Slug.to_string s) "ef-holy-name-sunday") entries + with + | Some (_, cs) -> cs + | None -> + die "internal: ef-holy-name-sunday missing after translation -- cannot derive its dependants" + in + match Slug.of_string "ef-holy-name" with + | Ok s -> [ (s, holy_name_sunday_citations) ] + | Error e -> die "%s" e let sha256 path = let ic = Unix.open_process_in (Printf.sprintf "sha256sum %s" (Filename.quote path)) in @@ -81,7 +215,8 @@ let () = let src = if Array.length Sys.argv > 1 then Sys.argv.(1) else default_source in let dst = if Array.length Sys.argv > 2 then Sys.argv.(2) else default_dest in let secs = parse_ini src in - let entries = List.map convert secs in + let translated = List.concat_map convert secs in + let entries = translated @ vigil_entries @ derived_entries translated in let lect = match Lectionary.of_entries entries with | Ok l -> l @@ -96,9 +231,15 @@ let () = ;\n\ ; Source: %s\n\ ; SHA-256: %s\n\ - ; %d entries. Regenerate with:\n\ + ; %d entries (%d ini sections translated/widened into colitur's own\n\ + ; Temporal_ef vocabulary via [colitur_keys]; %d hand-authored from a\n\ + ; second source file [vigil_entries]; %d derived from an already-\n\ + ; translated entry above rather than re-typed [derived_entries] --\n\ + ; see this generator's own comments on all three). Regenerate with:\n\ ; eval $(opam env) && dune exec tools/bootstrap_lectionary.exe -- %s %s\n" - src (sha256 src) (List.length entries) src dst; + src (sha256 src) (List.length entries) (List.length secs) (List.length vigil_entries) + (List.length entries - List.length translated - List.length vigil_entries) + src dst; Sexplib.Sexp.output_hum oc (Lectionary.sexp_of_t lect); output_char oc '\n'; close_out oc; -- cgit v1.3