1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
(* SPDX-License-Identifier: AGPL-3.0-or-later *)
(* Fix wave I2 (final-review.md, 2026-08-25-colitur-of-phases-3-5): the OF
counterpart of test_citation_coverage.ml. That file's own header states
the rule this one follows: "every citation the engine can emit must
parse". EF reaches zero unconverted citations; OF does not, and this
file is the honest record of exactly where it falls short, mirroring the
review's own explicit instruction -- "enumerate them rather than
lowering the bar to fit".
MEASURED, not estimated: before this fix wave, 259 of 730 citation
fields in `colitur readings --rite of 2026` (35%) printed unconverted --
345 distinct book spellings unregistered ({!Colitur_citation.Book}),
plus verse sub-letter markers ("11a") and a handful of single-chapter-
book misreads ({!Colitur_citation.Book.is_single_chapter}) the parser
could not read at all. All of that is now fixed at the source (book.ml's
own [of_lectionary_table] and parse.ml's own [verse_num]/[single_chapter]
additions), closing every genuinely fixable shape. What remains, walking
the FULL data/of/lectionary.sexp (not merely what one civil year happens
to observe -- a stricter check than EF's own year-walk, and the same one
tools/bootstrap_lectionary_of.ml's own generated header now runs and
discloses): 41 distinct references, out of 1542 total, EVERY ONE a
hyphenated range crossing a chapter boundary ("2:29-3:6", "1 John") --
a {!Colitur_citation.Parse.t} shape [part]/[verse_range] do not
represent (a [part] is one chapter and a list of within-chapter ranges;
representing a cross-chapter range would need restructuring that type
and {!Colitur_citation.Render}'s own consumption of it), deliberately
not built this task.
This file pins that residual EXACTLY -- not merely "at least these fail"
-- so it fails loudly in BOTH directions: a NEW unconverted reference
appearing (a regression) and one of these 41 starting to convert (this
list going stale, e.g. because a future task builds cross-chapter
support) are both caught, the same discipline every allow-list in this
project's suite already follows (`expected_rows`, `expected_bands`, the
litcal/missalemeum comparators). *)
let lectionary_path = "../data/of/lectionary.sexp"
let lectionary () =
match Colitur_kernel.Lectionary.load lectionary_path with
| Ok l -> l
| Error e -> Alcotest.failf "%s: %s" lectionary_path e
(* The exact, pinned residual -- copied from data/of/lectionary.sexp's own
generated "Citations that do not parse" listing, itself produced by the
SAME {!Colitur_citation.Parse.parse} call this test makes, so the two
can never silently drift apart without one of them visibly failing to
regenerate/re-test clean. *)
let known_cross_chapter_residual =
[ "1 Corinthians 12:31-13:13"; "1 John 1:5-2:2"; "1 John 2:29-3:6"; "1 John 3:22-4:6";
"1 John 4:19-5:4"; "2 Corinthians 3:15-4:1,3-6"; "2 Samuel 18:9-10,14b,24-25a,30-19:3";
"Colossians 1:24-2:3"; "Ecclesiastes 11:9-12:8"; "Ephesians 4:32-5:8"; "Exodus 11:10-12:14";
"Exodus 14:21-15:1"; "Ezekiel 2:8-3:4"; "Galatians 4:22-24,26-27,31-5:1"; "Genesis 1:1-2:2";
"Genesis 1:20-2:4a"; "Habakkuk 1:12-2:4"; "Hebrews 7:25-8:6"; "Isaiah 52:13-53:12";
"Isaiah 8:23b-9:3"; "John 15:26-16:4a"; "John 18:1-19:42"; "Jonah 1:1-2:2,11"; "Luke 7:36-8:3";
"Malachi 1:14b-2:2b,8-10"; "Mark 2:23-3:6"; "Mark 8:34-9:1"; "Matthew 10:34-11:1";
"Matthew 18:21-19:1"; "Matthew 9:35-10:1,5a,6-8"; "Matthew 9:36-10:8";
"Numbers 13:1-2,25-14:1,26a-29a,34-35"; "Philippians 3:17-4:1"; "Revelation 20:1-4,11-21:2";
"Sirach 27:30-28:7"; "The Acts 12:24-13:5a"; "The Acts 17:15,22-18:1"; "The Acts 7:51-8:1a";
"Wisdom 11:22-12:2"; "Wisdom 2:23-3:9"; "Wisdom 7:22b-8:1" ]
(* Every failure this walk finds really is a chapter-crossing hyphen range
(a "-" whose right side itself contains a ":") -- checked mechanically,
not merely asserted, so a future genuinely-different failure shape
cannot hide inside this test by coincidentally also being in the pinned
list above. No [Str]/regex (frozen deps): plain character scanning. *)
let looks_chapter_crossing reference =
let n = String.length reference in
let rec scan i saw_dash =
if i >= n then false
else if reference.[i] = '-' then scan (i + 1) true
else if reference.[i] = ':' && saw_dash then true
else scan (i + 1) saw_dash
in
scan 0 false
let test_of_citations_convert_or_are_the_known_cross_chapter_residual () =
let total = ref 0 in
let bad = ref [] in
List.iter
(fun (_slug, cits) ->
List.iter
(fun (c : Colitur_kernel.Citation.t) ->
incr total;
match Colitur_citation.Parse.parse c.Colitur_kernel.Citation.reference with
| Ok _ -> ()
| Error _ ->
if not (List.mem c.Colitur_kernel.Citation.reference !bad) then
bad := c.Colitur_kernel.Citation.reference :: !bad)
cits)
(Colitur_kernel.Lectionary.entries (lectionary ()));
let bad = List.sort compare !bad in
Alcotest.(check int) "1542 citation fields in data/of/lectionary.sexp today -- if this changes, the \
pinned residual below may need updating too, not just this count"
1542 !total;
Alcotest.(check (list string)) "the unconverted set is EXACTLY the known cross-chapter residual, no \
more and no fewer"
(List.sort compare known_cross_chapter_residual) bad;
List.iter
(fun r ->
Alcotest.(check bool) (Printf.sprintf "%s: really is a chapter-crossing range, not some other \
unrelated failure shape" r)
true (looks_chapter_crossing r))
bad
(* Everything OUTSIDE the pinned residual round-trips through Sigla, the
same structural (not textual) comparison test_citation_coverage.ml's own
EF version makes, and for the identical reason its own header gives. *)
let test_round_trip_outside_the_residual () =
let sg =
Colitur_citation.Sigla.make ~style:Colitur_citation.Render.default_style
~tradition:Colitur_citation.Book.vulgate
~names:(fun id _form -> Colitur_citation.Book.default_spelling id)
in
let bad = ref [] in
List.iter
(fun (_slug, cits) ->
List.iter
(fun (c : Colitur_kernel.Citation.t) ->
let r = c.Colitur_kernel.Citation.reference in
if not (List.mem r known_cross_chapter_residual) then
match Colitur_citation.Parse.parse r with
| Error e -> bad := Printf.sprintf "%s: unexpectedly does not parse (%s)" r e :: !bad
| Ok first -> (
let rendered = Colitur_citation.Sigla.format sg r in
match Colitur_citation.Parse.parse rendered with
| Error e -> bad := Printf.sprintf "%s: re-parse failed: %s" r e :: !bad
| Ok again -> if again <> first then bad := Printf.sprintf "%s: structure changed: %s" r rendered :: !bad))
cits)
(Colitur_kernel.Lectionary.entries (lectionary ()));
Alcotest.(check (list string)) "every OF citation outside the pinned residual round-trips through Sigla"
[] (List.sort compare !bad)
let suite =
( "citation-coverage-of",
[ Alcotest.test_case "every OF citation converts, or is the pinned cross-chapter residual" `Slow
test_of_citations_convert_or_are_the_known_cross_chapter_residual;
Alcotest.test_case "every OF citation outside the residual round-trips" `Slow
test_round_trip_outside_the_residual ] )
|