aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 14:28:50 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 14:28:50 +0200
commite5cbc662227de4f3ab0089fe73f639f4f84601a4 (patch)
tree264b29424d2d3371f6f31f2b87ac111c74622808 /tools
parente5066c0d082a09eca3d4c8bbf8da1cc04dde9104 (diff)
downloadcolitur-e5cbc662227de4f3ab0089fe73f639f4f84601a4.tar.gz
colitur-e5cbc662227de4f3ab0089fe73f639f4f84601a4.zip
fix(citation): make the well-formed reading-part set rite-supplied
I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5): OF Sundays and solemnities shipped without the Second reading, and the lectionary data header called the First reading an "Epistle" -- EF vocabulary. The sole blocker was Validate's own kernel-hardcoded assertion that a well-formed day's sorted citation parts are exactly [First; Gospel]; Citation.part already carried Second. Rite.t gains citation_shapes : Citation.part list list, the fourth EF-shaped kernel misfit (after Preface.t, Mass_formulary.source, transfer_target's strictly-later contract) and the first fixed rather than only documented. EF supplies its one shape unchanged; OF supplies two, [First; Gospel] on a feria/feast/memorial and [First; Second; Gospel] on a Sunday or solemnity (OLM 1981 Praenotanda n. 66.1 vs n. 69.1, n. 84(b)/(c), page-image verified against olm-1981.pdf). Validate's "citations" check now matches against the rite's own declared shapes rather than a single literal pattern, preserving all three invariants it always enforced: zero or one complete shape, never partial; no part outside what the rite declares; no part repeated. bootstrap_lectionary_of.ml's cite now extracts a Second reading from Sunday-cycle ini sections only, correctly excluding the one stray `second` field lectio's snapshot carries on a weekday-cycle section (a content-verified scraping artifact). The hand-authored Christmas Day entry gains its own Second reading from the same OLM passage already cited for its First/Gospel. Regenerated data/of/lectionary.sexp: 771 entries unchanged, citation fields 1542 -> 1725 (+183, all Second readings), SHA-256 re-pinned, header wording fixed and a new coverage bullet added. Responsorial Psalm is deliberately still not emitted -- OLM 1981 Praenotanda n. 71 itself lists it apart from the readings proper as a chant with its own refrain, and the finding driving this change names only the missing Second reading. EF output verified byte-identical to the branch start (empty lib/rites/rite_ef + data/ef diff since v1.0.0, plus a direct binary comparison of `day`/`readings` for 2026, 1583 and 9999 against a worktree build of the pre-change commit). Tests: three new fixtures in test_validate.ml prove the generalisation holds both directions (a multi-shape rite accepts either of its declared shapes; a rite that never declared a shape still rejects it). test_citation_coverage_of.ml's pinned residual grows 41 -> 49, all new entries the same already-understood chapter-crossing shape.
Diffstat (limited to 'tools')
-rw-r--r--tools/bootstrap_lectionary_of.ml136
1 files changed, 107 insertions, 29 deletions
diff --git a/tools/bootstrap_lectionary_of.ml b/tools/bootstrap_lectionary_of.ml
index f94a7db..83019b1 100644
--- a/tools/bootstrap_lectionary_of.ml
+++ b/tools/bootstrap_lectionary_of.ml
@@ -130,25 +130,51 @@ let parse_ini path =
path (List.length other));
List.rev !sections
-(* ---- Citation extraction -- First + Gospel only --------------------- *)
+(* ---- Citation extraction -- First (+ Second on Sundays/solemnities) + Gospel --- *)
-(* Psalm and Second are deliberately dropped, even though lectio's ini
- carries [psalm] (every entry) and [second] (Sundays/solemnities):
- Colitur_kernel.Validate's own ["citations"] check (kernel, off limits to
- this task) hard-asserts every day's citation-part list is EXACTLY
- [[First; Gospel]] -- the identical scope boundary
- tools/bootstrap_lectionary.ml's own [convert] already holds for EF
- ("Nothing here encodes... the chants (Psalm, Second, Tract, Alleluia,
- Sequence) are deliberately unbuilt"). Carrying Psalm/Second through here
- would not add capability today (nothing reads them) and would set up a
- guaranteed collision with that kernel check the moment a future task
- wires Validate over OF data. *)
-let cite sec =
+(* Fix wave I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5): Psalm is
+ still dropped -- it is a CHANT (sung, with a congregational refrain, OLM
+ 1981 Praenotanda n. 71's own "psalmi et versus inter lectiones
+ occurrentes", listed apart from the readings proper), not a reading, and
+ {!Colitur_kernel.Rite.t.citation_shapes} (see that field's own doc
+ comment) still declares no shape carrying it for either rite -- nothing
+ here would read a Psalm citation even if extracted. See the fix wave's
+ own report for the fuller argument and what was rejected.
+
+ Second is now extracted, but ONLY from a SUNDAY-CYCLE section (its own
+ [~is_sunday_cycle] argument, [`A]/[`B]/[`C] at the call site below) --
+ never from a WEEKDAY-CYCLE one ([`I]/[`II]), even on the one occasion
+ lectio's own snapshot carries a stray [second] field there
+ (advent-3-sat-II, "Zephaniah 3:14-17" -- content-verified as a
+ scraping-pipeline artifact, not a real OLM assignment: that citation is
+ the THIRD Sunday of Advent's own Year C first reading, evidently bled
+ into an unrelated Saturday ferial section by niedziela.pl's own
+ generator). This is not merely a guard against that one artifact; it is
+ the general rule OLM 1981 Praenotanda n. 66.1 vs n. 69.1 states (see
+ {!Rite_of.Rite_of.context}'s own citation): a ferial Mass has two
+ readings, never three, so a weekday-cycle section can never legitimately
+ carry a Second reading regardless of what stray data a snapshot exposes.
+ {!Colitur_kernel.Validate}'s own rite-supplied ["citations"] check (no
+ longer off limits to this task -- the kernel change this fix wave itself
+ makes) would reject an emitted weekday-cycle entry carrying [Second]
+ outright, since neither of the OF's two declared shapes admits it
+ alongside a non-Sunday/solemnity office; extracting it here regardless
+ would just move the failure from "silently wrong" to "loudly rejected at
+ test time" rather than avoiding it. *)
+let cite ~is_sunday_cycle sec =
let get k = match List.assoc_opt k sec.fields with None | Some "" -> None | Some v -> Some v in
match (get "first", get "gospel") with
| Some first, Some gospel ->
- Some [ { Citation.part = Citation.First; reference = first };
- { Citation.part = Citation.Gospel; reference = gospel } ]
+ let second =
+ if is_sunday_cycle then
+ Option.map (fun v -> { Citation.part = Citation.Second; reference = v }) (get "second")
+ else None
+ in
+ Some
+ (List.filter_map Fun.id
+ [ Some { Citation.part = Citation.First; reference = first };
+ second;
+ Some { Citation.part = Citation.Gospel; reference = gospel } ])
| _ -> None
(* ---- Cycle-family grouping ------------------------------------------ *)
@@ -216,21 +242,25 @@ let collapse_sunday base va vb vc =
Printf.eprintf
"bootstrap_lectionary_of: %s: cycle C disagreed with A/B (scraping-year \
artifact, majority kept): %s\n"
- base (match vc with [ f; _ ] -> f.Citation.reference | _ -> "?");
+ (* [f :: _], not a fixed-arity [[f; _]]: [cite] now emits either a
+ 2-element ([First; Gospel]) or 3-element ([First; Second; Gospel])
+ list, and [First] is its head either way -- a fixed-arity pattern
+ would silently fall through to "?" on the 3-element shape. *)
+ base (match vc with f :: _ -> f.Citation.reference | [] -> "?");
Flat va
end
else if va = vc then begin
Printf.eprintf
"bootstrap_lectionary_of: %s: cycle B disagreed with A/C (scraping-year \
artifact, majority kept): %s\n"
- base (match vb with [ f; _ ] -> f.Citation.reference | _ -> "?");
+ base (match vb with f :: _ -> f.Citation.reference | [] -> "?");
Flat va
end
else if vb = vc then begin
Printf.eprintf
"bootstrap_lectionary_of: %s: cycle A disagreed with B/C (scraping-year \
artifact, majority kept): %s\n"
- base (match va with [ f; _ ] -> f.Citation.reference | _ -> "?");
+ base (match va with f :: _ -> f.Citation.reference | [] -> "?");
Flat vb
end
else Sunday_cycle (va, vb, vc)
@@ -271,7 +301,8 @@ let resolve_sections secs =
match base_and_letter sec.name with
| None -> die "%s: no recognised cycle suffix (-A/-B/-C/-I/-II)" sec.name
| Some (base, letter) -> (
- match cite sec with
+ let is_sunday_cycle = match letter with `A | `B | `C -> true | `I | `II -> false in
+ match cite ~is_sunday_cycle sec with
| None -> die "%s: no first/gospel field" sec.name
| Some cs ->
let cur = try Hashtbl.find tbl base with Not_found -> (None, None, None, None, None) in
@@ -632,12 +663,18 @@ let slug_or_die name = match Slug.of_string name with Ok s -> s | Error e -> die
the VIGIL, not the Day). Confirmed sourceable, not merely "cannot
convert": docs/research/of/olm-1981-ocr.txt:4513-4520 ("16 Ad Missam in
die"), the same primary authority this whole module already cites for
- the cycle rules (lectionary_of.mli). First reading and Gospel only,
- matching this file's own scope everywhere else (data/of/lectionary.sexp
- :2's own header, and {!Colitur_kernel.Validate}'s [First; Gospel]
- assertion) -- OLM's own Second Reading (Heb 1:1-6) and Psalm (Ps 97) are
- out of scope for the identical reason every other day's Second Reading
- and Psalm are (I7, a kernel-level limitation, not this file's own).
+ the cycle rules (lectionary_of.mli).
+
+ UPDATED, fix wave I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5,
+ 2026-08-26): Christmas Day is a solemnity, so its own Second Reading is
+ now in scope and included -- Hebr 1, 1-6 ("Locutus est nobis Deus in
+ Filio"), same source line (docs/research/of/olm-1981-ocr.txt:4517),
+ confirming the value this comment already named before this fix wave
+ built the capability to emit it. Psalm (Ps 97) remains out of scope,
+ deliberately, not merely by omission -- see [cite]'s own doc comment
+ above for the argument (a chant, not a reading; no declared
+ {!Colitur_kernel.Rite.t.citation_shapes} shape carries it for either
+ rite).
The Gospel (Io 1, 1-18) carries OLM's own "longior/brevior" choice
("1-18 (longior) vel 1-5.9-14 (brevior)") -- the LONGER form is used,
@@ -654,6 +691,7 @@ let slug_or_die name = match Slug.of_string name with Ok s -> s | Error e -> die
let hand_authored =
[ ( slug_or_die "of-nativity",
[ { Citation.part = Citation.First; reference = "Isaiah 52:7-10" };
+ { Citation.part = Citation.Second; reference = "Hebrews 1:1-6" };
{ Citation.part = Citation.Gospel; reference = "John 1:1-18" } ] ) ]
let entries_of (slug, r) =
@@ -910,11 +948,25 @@ let () =
temporal_day_gap ~lectionary:lect ~year_start:Rite_of.Temporal_of.year_start
in
let cit_total, cit_bad = citation_conversion_census lect in
+ (* Fix wave I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5): how
+ many emitted citation FIELDS are a Second reading -- i.e. how many of
+ [entries]'s own Sunday-cycle entries actually carried one in lectio's
+ snapshot (Sunday-cycle sections without a NAMED Second in the ini, and
+ every weekday-cycle entry by [cite]'s own [~is_sunday_cycle] guard,
+ never get one). Printed in the header below alongside [cit_total] so a
+ reader can see the Second-reading count is a real, measured subset of
+ the total, not a guess. *)
+ let second_count =
+ List.concat_map snd entries
+ |> List.filter (fun (c : Citation.t) -> c.Citation.part = Citation.Second)
+ |> List.length
+ in
let oc = open_out dst in
Printf.fprintf oc
"; data/of/lectionary.sexp -- OF (2002) temporal + sanctoral lectionary\n\
- ; (Epistle + Gospel citations, never scripture text), bootstrapped from\n\
- ; lectio.\n\
+ ; (First, and on Sundays/solemnities also Second, and Gospel citations,\n\
+ ; never scripture text -- \"Epistle\" is EF vocabulary, not used here),\n\
+ ; bootstrapped from lectio.\n\
;\n\
; LINEAGE, stated loudly because it constrains what this file can show\n\
; (design spec 2026-08-24-colitur-of-rite-module-design.md sec4.4/sec5):\n\
@@ -962,6 +1014,19 @@ let () =
; three genuinely different structural-unreachability reasons, none of\n\
; them \"duplicate\" -- see [excluded_bases]'s own comment.\n\
;\n\
+ ; CORRECTED, fix wave I7 (final-review.md, 2026-08-25-colitur-of-\n\
+ ; phases-3-5, 2026-08-26): this file used to carry First and Gospel\n\
+ ; only, and called the First reading an \"Epistle\" above -- EF\n\
+ ; vocabulary, wrong for the OF (its First reading is often Old\n\
+ ; Testament or Acts, not an epistle at all; OLM 1981 Praenotanda\n\
+ ; n. 66.1). Both fixed at the source: {!Colitur_kernel.Rite\n\
+ ; .t.citation_shapes} (kernel change, {!Rite_of.Rite_of.context}'s own\n\
+ ; citation) makes the well-formed citation-part shape RITE-supplied\n\
+ ; rather than a kernel-hardcoded [[First; Gospel]], and [cite] above\n\
+ ; now extracts a Second reading from every SUNDAY-CYCLE (Sunday/\n\
+ ; solemnity) entry lectio's own snapshot carries one for. See\n\
+ ; COVERAGE (5) below for the measured count.\n\
+ ;\n\
; Source: %s\n\
; SHA-256: %s\n\
; %d ini sections (988 expected) -> %d resolved (base, cycle-shape) \
@@ -991,7 +1056,7 @@ pairs -> %d colitur slugs mapped, %d ini bases genuinely excluded (structurally\
; OLM norms working as designed for a saint with no proper of\n\
; their own, not a gap in this data.\n\
; (4) Citation-siglum conversion (fix wave I2, 2026-08-26 review): of\n\
- ; %d emitted (First, Gospel) citation fields, %d distinct\n\
+ ; %d emitted (First, Second, Gospel) citation fields, %d distinct\n\
; references (out of the full %d) do not parse -- every one a\n\
; hyphen range that crosses a chapter boundary (\"2:29-3:6\"), a\n\
; {!Colitur_citation.Parse.t} shape this parser's [part]/\n\
@@ -1004,6 +1069,19 @@ pairs -> %d colitur slugs mapped, %d ini bases genuinely excluded (structurally\
; .format}'s own documented contract on a parse miss. Pinned\n\
; exactly, both directions, by test/test_citation_coverage_of.ml.\n\
; Named below.\n\
+ ; (5) Second-reading coverage (fix wave I7, 2026-08-26): %d of the\n\
+ ; %d emitted citation fields above are a Second reading -- every\n\
+ ; one from a SUNDAY-CYCLE (-A/-B/-C, i.e. Sunday/solemnity)\n\
+ ; entry, never a weekday-cycle one ([cite]'s own\n\
+ ; [~is_sunday_cycle] guard), matching OLM 1981 Praenotanda\n\
+ ; n. 66.1/n. 69.1 and n. 84(b)/(c) (docs/research/of/olm-1981\n\
+ ; .pdf pp.32-33,37-38/\"XXXII-XXXIII\",\"XXXVII-XXXVIII\"): a\n\
+ ; Sunday or solemnity Mass has three readings, a feria/feast/\n\
+ ; memorial two. The Responsorial Psalm (lectio's own [psalm]\n\
+ ; field, present on nearly every section) is deliberately NOT\n\
+ ; extracted -- see [cite]'s own doc comment for the argument\n\
+ ; (a chant, not a reading; declared in neither rite's\n\
+ ; {!Colitur_kernel.Rite.t.citation_shapes}).\n\
; Regenerate with:\n\
; eval $(opam env) && dune exec tools/bootstrap_lectionary_of.exe -- %s %s\n"
src (sha256 src) (List.length secs) (List.length resolved) report.mapped
@@ -1011,7 +1089,7 @@ pairs -> %d colitur slugs mapped, %d ini bases genuinely excluded (structurally\
gap_total gap_count (List.length report.unmapped) (List.length resolved)
(Hashtbl.length sanctoral_slugs) report.sanctoral_passthrough
(Hashtbl.length sanctoral_slugs - report.sanctoral_passthrough)
- cit_total (List.length cit_bad) cit_total src dst;
+ cit_total (List.length cit_bad) cit_total second_count cit_total src dst;
Printf.fprintf oc "; Unmapped lectio bases (%d):\n" (List.length report.unmapped);
List.iter (fun b -> Printf.fprintf oc "; %s\n" b) report.unmapped;
Printf.fprintf oc "; Citations that do not parse (%d distinct, all chapter-crossing ranges):\n"