aboutsummaryrefslogtreecommitdiff
path: root/tools/bootstrap_lectionary.ml
diff options
context:
space:
mode:
Diffstat (limited to 'tools/bootstrap_lectionary.ml')
-rw-r--r--tools/bootstrap_lectionary.ml309
1 files changed, 265 insertions, 44 deletions
diff --git a/tools/bootstrap_lectionary.ml b/tools/bootstrap_lectionary.ml
index a4c82ae..ed40c92 100644
--- a/tools/bootstrap_lectionary.ml
+++ b/tools/bootstrap_lectionary.ml
@@ -4,8 +4,19 @@
which does not belong in a pure kernel.
Only `first` and `gospel` are mapped -- the plan's Global Constraints fix
- the scope at Epistle + Gospel, and lectio's data carries nothing else. *)
+ the scope at Epistle + Gospel, and lectio's data carries nothing else.
+ Links `rite_ef` (fix round 1, coordinator review, Critical 2): the
+ generator used to have no way to check its own [colitur_keys] table
+ against reality, which is exactly how a translation this table forgot
+ (Lent's own Ember days) shipped silently -- both engines independently
+ fall through to the same wrong ferial answer there, so even the
+ differential could not see it (the "Holy Thursday was violet in both"
+ shape this project's own CLAUDE.md already names). [assert_reachable]
+ below sweeps {!Rite_ef.Temporal_ef.temporal} directly -- no sanctoral
+ layer, no lectionary of its own, no circularity with the file this tool
+ is generating -- exactly the same "code, not data" seam
+ tools/bootstrap_sanctoral.ml already links this library for. *)
open Colitur_kernel
let default_source = "../lectio/internal/caldata/tridentine-lectionary.ini"
@@ -72,12 +83,7 @@ let parse_ini path =
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-<weekday>" 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
+ A translation can widen ONE ini section into several colitur keys; 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). *)
@@ -102,14 +108,56 @@ let colitur_keys name =
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. *)
+ narrow" rule this table's own general design already intends. *)
[ "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 if String.equal name "ef-lent-1-wednesday" then
+ (* Critical 2, fix round 1 (coordinator review): the Lenten Ember days
+ (RG 91 entry 18, "Quatuor Tempora... post primam dominicam
+ Quadragesimae") fall on the Wednesday/Friday/Saturday of Lent's own
+ first week -- the SAME three civil days colitur names
+ "ef-lent-ember-{wed,fri,sat}" (Rite_ef.Temporal_ef's own [ember],
+ checked ahead of the generic week-numbering fallback, so colitur
+ NEVER emits "ef-lent-1-wednesday/friday/saturday" as its own slug
+ for ANY civil day -- confirmed by sweeping [Temporal_ef.temporal]
+ directly, not assumed; [assert_reachable] below would now catch it
+ if that stopped being true). A RENAME, not a widen, unlike
+ "ef-christmas-sunday-0" above: colitur has no second, legitimate use
+ for the un-renamed key the way it does there. temporal_ef.ml's own
+ comment on [ember] previously claimed "lectio has no Ember slug for
+ [Lent]" -- true only in the sense that lectio's OWN naming is the
+ generic "ef-lent-1-<weekday>" family, not that the DATA is missing;
+ it is right there in the ini, just unreachable under colitur's own
+ spelling until this rename. That comment is corrected alongside
+ this fix. *)
+ [ "ef-lent-ember-wed" ]
+ else if String.equal name "ef-lent-1-friday" then [ "ef-lent-ember-fri" ]
+ else if String.equal name "ef-lent-1-saturday" then [ "ef-lent-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 ]
+ | Some wd ->
+ (* Critical 1, fix round 1 (coordinator review): the original
+ version of this table widened "ef-passiontide-0-<weekday>" into
+ BOTH "ef-passiontide-1-<weekday>" (Passion week) AND
+ "ef-passiontide-2-<weekday>" (HOLY WEEK, including the entire
+ Sacred Triduum) on the strength of lectio's own citation being
+ byte-identical between the two weeks. That only proves lectio
+ CONFLATES the two weeks -- it has no Holy Week propers of its
+ own -- not that the Missal does: Holy Monday's real Mass
+ (docs/research/scan1.txt, "Feria II Hebdomadae sanctae, I
+ classis") is Isai. 50,5-10 / Io. 12,1-9, nothing like
+ Passion-week Monday's Ionae 3,1-10 / Io. 7,32-39 that the widen
+ was putting there, and Holy Thursday's is 1 Cor. 11,20-32 / Io.
+ 13,1-15 -- the Mass of the Lord's Supper reading Passion
+ Sunday's own ferial Mass was the actual defect, not merely a
+ citation nicety. Narrowed to Passion week ONLY; Holy Week's own
+ citations (Monday, Tuesday, Thursday, Saturday) are
+ hand-authored below, [holy_week_entries] -- see its own comment
+ for which two days of Holy Week (Wednesday, Good Friday) are
+ deliberately NOT included and why. *)
+ [ "ef-passiontide-1-" ^ wd ]
| None -> [ name ])
let convert sec =
@@ -146,12 +194,122 @@ let convert sec =
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 slug_or_die name = match Slug.of_string name with Ok s -> s | Error e -> die "%s" e
+
+let pair ~first ~gospel =
+ [ { Citation.part = Citation.First; reference = first };
+ { Citation.part = Citation.Gospel; reference = gospel } ]
+
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" } ] )
+ [ (slug_or_die "ef-nativity-vigil", pair ~first:"Rom 1:1-6" ~gospel:"Matt 1:18-21") ]
+
+(* Critical 1, fix round 1 (coordinator review): Holy Week's own Mass
+ propers, hand-authored the same discipline [vigil_entries] above
+ already uses for a genuine upstream-source gap -- lectio's own ini has
+ no Holy Week data at all (see [colitur_keys]'s own Passiontide comment
+ for the full account of what it has instead). Every citation below is
+ Missal-verified TWICE, independently (docs/research/scan1.txt AND
+ scan2.txt, the two different printings/scans Task 6 also cross-checked
+ between):
+
+ - [ef-passiontide-2-monday] (Holy Monday, "Feria II Hebdomadae sanctae,
+ I classis"): Isai. 50,5-10 / Io. 12,1-9 -- scan1.txt "Lectio Isaiae
+ Prophetae... Isai. 50, 5-10" + "Sequentia... secundum Ioannem. Io. 12,
+ 1-9"; scan2.txt corroborates both citations word for word.
+ - [ef-passiontide-2-tuesday] (Holy Tuesday, "Feria III Hebdomadae
+ sanctae, I classis"): Ier. 11,18-20 / the Passion according to Mark,
+ 14,32-72;15,1-46 -- scan1.txt "Lectio Ieremiae Prophetae... Ier. 11,
+ 18-20" + "Evangelium Passionis et Mortis Domini secundum Marcum.
+ 14,32-72; 15,1-46"; scan2.txt corroborates both.
+ - [ef-passiontide-2-thursday] (Holy Thursday, "Feria V in Cena Domini"):
+ 1 Cor. 11,20-32 / Io. 13,1-15 -- scan1.txt "Lectio Epistolae beati
+ Pauli Apostoli ad Corinthios... 1 Cor. 11, 20-32" + "Sequentia...
+ secundum Ioannem... Io. 13,1-15"; scan2.txt corroborates both.
+ - [ef-passiontide-2-saturday] (Holy Saturday, Missa Vigiliae Paschalis'
+ own Epistle+Gospel -- the actual Mass, distinct from the earlier
+ prophecies, explicitly labelled "Lectio EPISTOLAE"): Col. 3,1-4 /
+ Matt. 28,1-7 -- scan1.txt "Lectio Epistolae beati Pauli Apostoli ad
+ Colossenses... Col. 3,1-4" + "Sequentia... secundum Matthaeum. Mt.
+ 28,1-7"; scan2.txt corroborates both.
+
+ DELIBERATELY NOT INCLUDED, and recorded here rather than guessed:
+
+ - [ef-passiontide-2-wednesday] (Holy Wednesday, "Feria IV Hebdomadae
+ sanctae"): TWO Old Testament lessons (Isai. 62,11;63,1-7, then Isai.
+ 53,1-12), neither labelled "Epistola", before the Passion according
+ to Luke (22,39-71;23,1-53) -- no single reading occupies the
+ "Epistle" position this schema's [First]/[Gospel] pair assumes every
+ other entry in this file has, unlike Holy Saturday's genuinely
+ labelled Epistle above.
+ - [ef-passiontide-2-friday] (Good Friday, "Feria VI in Passione et
+ Morte Domini"): not even a Mass ("Solemnis Actio liturgica"), with
+ TWO peer lessons (Osee 6,1-6, then Exodus 12,1-11), again neither
+ labelled "Epistola", directly into the Passion according to John
+ (18,1-40;19,1-42).
+
+ Forcing either into a single First/Gospel pair would be an editorial
+ choice this generator has no textual warrant to make on its own --
+ unlike Holy Saturday, where the Mass's own Epistle is explicitly
+ labelled and distinct from its own preceding prophecies. Left absent:
+ data/ef/expected-divergences.sexp's own Layer C entry records what this
+ means for the differential (colitur emits [], an honest absence, not a
+ guess). *)
+let holy_week_entries =
+ [ (slug_or_die "ef-passiontide-2-monday", pair ~first:"Isai. 50, 5-10" ~gospel:"Io. 12, 1-9");
+ (slug_or_die "ef-passiontide-2-tuesday",
+ pair ~first:"Ier. 11, 18-20" ~gospel:"Mark 14, 32-72; 15, 1-46");
+ (slug_or_die "ef-passiontide-2-thursday", pair ~first:"1 Cor. 11, 20-32" ~gospel:"Io. 13, 1-15");
+ (slug_or_die "ef-passiontide-2-saturday", pair ~first:"Col. 3, 1-4" ~gospel:"Matt. 28, 1-7")
]
+(* Important 3(a), fix round 1 (coordinator review): the fixed Nativity-
+ Octave days (RG 91 entry 17, 29-31 December, colitur's own
+ [ef-nativity-octave-day-{5,6,7}]) have a DIRECT formulary in the
+ Missal, not merely a resolvable-by-walk-back gap -- "Diebus infra
+ octavam Nativitatis Domini, II classis": Tit. 3,4-7 / Luc. 2,15-20
+ (docs/research/scan1.txt:6281-6329, scan2.txt:6900-6960, both word for
+ word), and each specific date's own rubric points straight at it
+ ("Die 29 decembris... Missa Puer natus est nobis, ut supra [28]",
+ scan1.txt, repeated verbatim at 30 and 31 December). This is the SAME
+ shape [vigil_entries] above already uses (one direct entry, both
+ scans), not the Task-6-sized verification this task's own report
+ scoped Task 8 away from -- confirmed, not merely asserted, since a
+ single Missal heading answers it completely.
+
+ REJECTED, and recorded so it is not re-attempted: the FIRST guess tried
+ here gave these three slugs [ef-holy-name-sunday]'s own citation
+ instead (Gal. 4,1-7 / Luc. 2,33-40, "Dominica infra octavam Nativitatis
+ Domini" -- a DIFFERENT heading, for the Sunday specifically, not the
+ weekdays). Measured against the real fixture and reverted: lectio's own
+ citation for these dates is not uniform across years (2005-12-29 reads
+ Christmas Day's own Mass, Heb 1:1-12/John 1:1-14, since 25 December
+ 2005 was itself a Sunday that year; 2006-12-29 reads Advent IV's,
+ 1 Cor. 4:1-5/Luke 3:1-6) -- neither matches "Diebus infra octavam"'s
+ own formulary, because in BOTH those years the civil date landed on an
+ ordinary WEEKDAY within the octave, not the (different) Sunday the
+ first guess's citation was actually for. This entry is right for the
+ weekday case precisely because it is sourced from the weekday's own
+ heading, not the Sunday's.
+
+ IMPORTANT CAVEAT, carried into data/ef/expected-divergences.sexp's own
+ C6 entry, not fixed here: colitur's OWN [Temporal_ef] assigns
+ [ef-nativity-octave-day-N] to BOTH an ordinary weekday within 29-31
+ December AND a Sunday landing there, undifferentiated at the temporal-
+ slug level. RG 69 ("De dominica infra octavam Nativitatis Domini...
+ semper fit Officium... nisi dominica incidat in festum I classis") is
+ unconditional -- the Sunday's own distinct Office (RG 91 places II-class
+ Sundays above days within the octave) should be observed instead of the
+ weekday placeholder whenever 29-31 December IS a Sunday. This entry
+ therefore gives the CORRECT citation for the majority (weekday) case
+ and the WRONG one on the years the civil date is itself a Sunday -- a
+ pre-existing [Temporal_ef] defect this generator cannot fix (it has no
+ day-of-week logic of its own to add), out of this task's own scope, and
+ the same RG 67/69 gap data/ef/expected-divergences-missalemeum.sexp's
+ own M11 already tracks from a different differential layer. *)
+let nativity_octave_entries =
+ let cs = pair ~first:"Tit. 3, 4-7" ~gospel:"Luc. 2, 15-20" in
+ List.map (fun n -> (slug_or_die (Printf.sprintf "ef-nativity-octave-day-%d" n), cs)) [ 5; 6; 7 ]
+
(* 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
@@ -165,33 +323,11 @@ let vigil_entries =
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). *)
+ [ef-nativity-octave-day-{5,6,7}] does NOT reuse this value -- a first
+ attempt tried exactly that and was measured wrong and reverted; see
+ [nativity_octave_entries] above for the correct, DIFFERENT, directly
+ Missal-sourced formulary and the full account of why the two headings
+ differ. *)
let derived_entries entries =
let holy_name_sunday_citations =
match
@@ -211,12 +347,90 @@ let sha256 path =
ignore (Unix.close_process_in ic);
List.hd (String.split_on_char ' ' line)
+(* Critical 2, fix round 1 (coordinator review): "fix the class, not just
+ the instance". [colitur_keys] is a hand-maintained table with no
+ reality check of its own -- exactly how the Lent Ember mismatch
+ survived the first pass (both engines independently fall through to
+ the same wrong answer there, so even the differential could not see
+ it). This sweeps {!Rite_ef.Temporal_ef.temporal} directly over a real
+ civil-year range -- no sanctoral layer, no lectionary, no circularity
+ with the file this tool generates -- and collects every DISTINCT office
+ slug it ever actually produces. [assert_reachable] then requires every
+ key this generator is about to EMIT to be a member of that set (an
+ emitted key that is not a real Temporal_ef slug is dead data,
+ unreachable by any caller -- exactly the shape both Critical findings
+ had), dying loudly and naming every offender if not. Separately,
+ informational only, it prints every real Temporal_ef slug that has NO
+ entry in the final table -- not an error (most such gaps are the
+ correctly-unproper ferias step 3 already resolves, task-5-report.md's
+ own "297 of 304" measurement), but a standing audit log a human reader
+ can check against that same report rather than trusting silence.
+
+ 1583 is deliberately NOT the sweep's start: the full 1583-9999 domain is
+ the KERNEL's own contract, not this rite-specific bootstrap tool's --
+ sweeping the differential's own window (2005-2050), widened by one year
+ on each side for step-3 preceding/following-Sunday edge cases, is
+ enough to enumerate every DISTINCT slug FAMILY (season/week/weekday
+ combinations recur every year; only which YEAR exhibits a given
+ alignment changes, e.g. how many Sundays fall in Time after
+ Epiphany) -- confirmed against task-5-report.md's own domain-wide
+ "412 distinct temporal slugs" figure: this sweep alone already finds
+ all 412. *)
+let reachable_temporal_slugs () =
+ let tbl = Hashtbl.create 512 in
+ let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> die "%s" e in
+ for y = 2004 to 2051 do
+ let d = ref (mk y 1 1) in
+ let stop = mk y 12 31 in
+ while Date.compare !d stop <= 0 do
+ let t = Rite_ef.Temporal_ef.temporal !d in
+ Hashtbl.replace tbl (Slug.to_string t.Temporal.office.Celebration.slug) true;
+ d := Date.add_days !d 1
+ done
+ done;
+ tbl
+
+let assert_reachable entries =
+ let reachable = reachable_temporal_slugs () in
+ let dead =
+ List.filter (fun (s, _) -> not (Hashtbl.mem reachable (Slug.to_string s))) entries
+ in
+ (match dead with
+ | [] -> ()
+ | _ ->
+ List.iter
+ (fun (s, _) ->
+ Printf.eprintf
+ "bootstrap_lectionary: DEAD KEY -- %S is not a real Temporal_ef slug over any civil \
+ day 2004-2051, so nothing can ever look it up\n"
+ (Slug.to_string s))
+ dead;
+ die "%d emitted key(s) are unreachable (see above) -- fix colitur_keys/vigil_entries/\
+ holy_week_entries/nativity_octave_entries/derived_entries, never delete this check"
+ (List.length dead));
+ let emitted = Hashtbl.create 512 in
+ List.iter (fun (s, _) -> Hashtbl.replace emitted (Slug.to_string s) true) entries;
+ let uncovered =
+ Hashtbl.fold (fun s _ acc -> if Hashtbl.mem emitted s then acc else s :: acc) reachable []
+ |> List.sort compare
+ in
+ Printf.eprintf
+ "bootstrap_lectionary: %d of %d real Temporal_ef slugs (2004-2051) have no lectionary entry \
+ (informational -- most resolve correctly via step 3's ferial resumption; see \
+ task-5-report.md):\n"
+ (List.length uncovered) (Hashtbl.length reachable);
+ List.iter (fun s -> Printf.eprintf " %s\n" s) uncovered
+
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 translated = List.concat_map convert secs in
- let entries = translated @ vigil_entries @ derived_entries translated in
+ let entries =
+ translated @ vigil_entries @ holy_week_entries @ nativity_octave_entries
+ @ derived_entries translated
+ in
+ assert_reachable entries;
let lect =
match Lectionary.of_entries entries with
| Ok l -> l
@@ -227,18 +441,25 @@ let () =
"; data/ef/lectionary.sexp -- EF (1962) temporal lectionary (Epistle +\n\
; Gospel citations, never scripture text), bootstrapped from lectio.\n\
; Generator: tools/bootstrap_lectionary.ml -- do not hand-edit; re-run the\n\
- ; generator against a newer lectio and commit the diff instead.\n\
+ ; generator against a newer lectio and commit the diff instead. Every\n\
+ ; emitted key is asserted, at generation time, to be a slug\n\
+ ; Rite_ef.Temporal_ef actually computes (see this generator's own\n\
+ ; [assert_reachable]) -- a dead key cannot ship silently again.\n\
;\n\
; Source: %s\n\
; SHA-256: %s\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\
+ ; second source file [vigil_entries]; %d hand-authored directly from\n\
+ ; the Missal [holy_week_entries]; %d hand-authored directly from the\n\
+ ; Missal [nativity_octave_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\
+ ; see this generator's own comments on all five). Regenerate with:\n\
; eval $(opam env) && dune exec tools/bootstrap_lectionary.exe -- %s %s\n"
src (sha256 src) (List.length entries) (List.length secs) (List.length vigil_entries)
- (List.length entries - List.length translated - List.length vigil_entries)
+ (List.length holy_week_entries) (List.length nativity_octave_entries)
+ (List.length entries - List.length translated - List.length vigil_entries
+ - List.length holy_week_entries - List.length nativity_octave_entries)
src dst;
Sexplib.Sexp.output_hum oc (Lectionary.sexp_of_t lect);
output_char oc '\n';