diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-26 14:28:50 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-26 14:28:50 +0200 |
| commit | e5cbc662227de4f3ab0089fe73f639f4f84601a4 (patch) | |
| tree | 264b29424d2d3371f6f31f2b87ac111c74622808 /lib/kernel/validate.ml | |
| parent | e5066c0d082a09eca3d4c8bbf8da1cc04dde9104 (diff) | |
| download | colitur-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 'lib/kernel/validate.ml')
| -rw-r--r-- | lib/kernel/validate.ml | 31 |
1 files changed, 20 insertions, 11 deletions
diff --git a/lib/kernel/validate.ml b/lib/kernel/validate.ml index f523b39..c7b6093 100644 --- a/lib/kernel/validate.ml +++ b/lib/kernel/validate.ml @@ -414,18 +414,26 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year = is why it is a match on the sorted part list rather than three separate tests: - - zero or two, never one -- an Epistle without a Gospel, or the - reverse, is a malformed Mass, not a partial one; - - only [First] and [Gospel] ever appear -- the chants (Psalm, - Second, Tract, Alleluia, Sequence) are deliberately unbuilt, - with no source and no oracle, so a citation carrying one is a - defect and not a feature arriving early; + - zero, or exactly one of the rite's own well-formed shapes, + never a partial one -- an Epistle without a Gospel, or the + reverse, is a malformed Mass regardless of which shapes the + rite declares; + - no part outside the rite's own {!Rite.t.citation_shapes} ever + appears -- for EF that is still only [First]/[Gospel] (the + other chants remain deliberately unbuilt there, with no + source and no oracle, so one appearing is a defect, not a + feature arriving early); for the OF it is [First]/[Second]/ + [Gospel], [Second] only ever alongside both of the others (its + own doc comment has the full citation and argument); - no part appears twice -- two Epistles and no Gospel has length two and would slip past a bare cardinality test. - [Citation.part]'s own constructor order puts [First] before - [Gospel], so the sorted well-formed list is literally - [[First; Gospel]] and nothing else. *) + Each of [rite.Rite.citation_shapes]'s own shapes is normalised + with the SAME [List.sort compare] a day's actual parts are put + through, so a rite may list them in any order without silently + failing to match -- robustness the single hardcoded + [[First; Gospel]] this replaced never needed to worry about. *) + let citation_shapes = List.map (List.sort compare) rite.Rite.citation_shapes in let year_has_citations = Array.exists (fun (d : ('s, 'r) Liturgical_day.t) -> d.Liturgical_day.citations <> []) @@ -439,7 +447,6 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year = List.map (fun (c : Citation.t) -> c.Citation.part) d.Liturgical_day.citations in match List.sort compare parts with - | [ Citation.First; Citation.Gospel ] -> () | [] -> (* Separate check name from the malformed case below on purpose: this is the lectionary chain falling through @@ -449,9 +456,11 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year = fail date "citations-unresolved" "no reading citations resolved for this day: the lectionary chain fell through \ every step" + | sorted when List.mem sorted citation_shapes -> () | sorted -> fail date "citations" - (Printf.sprintf "expected exactly one First and one Gospel, got [%s]" + (Printf.sprintf + "citation parts [%s] match none of this rite's own well-formed shapes" (String.concat "," (List.map Citation.part_to_string sorted)))) resolved; (* ---- Formulary invariant (Task 3, celebrant-rubrics-phase1) ---- |
