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". At the time this file was first written, EF reached zero
unconverted citations and OF did not; this file was the honest record of
exactly where it fell short.
MEASURED, not estimated: before fix wave I2, 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 was fixed at the source (book.ml's own
[of_lectionary_table] and parse.ml's own [verse_num]/[single_chapter]
additions). What remained, 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 runs and discloses): a hyphenated verse range whose
two endpoints lie in different chapters ("2:29-3:6", "1 John") -- a
{!Colitur_citation.Parse.t} shape [part]/[verse_range] did not
represent. Fix wave I7 (2026-08-26) grew the population from 1542 to
1725 citation fields (a new Second-reading extraction) and the residual
from 41 to 49 references, same shape, no new failure mode.
CLOSED (W4, this task): {!Colitur_citation.Parse.verse_end} now lets a
range's [last] endpoint name its own, later chapter, and
{!Colitur_citation.Render}'s [one_range] renders it back through the
part's own [chapter_verse] template. Every one of the 49 pinned
references now parses and round-trips -- including the COMPOUND shape
("Matthew 9:35-10:1,5a,6-8": a crossing range followed by further,
same-chapter verse references in the same comma list) and a crossing
that is not the list's first or only piece ("2 Samuel
18:9-10,14b,24-25a,30-19:3"). The residual below is therefore now EMPTY,
over the full 1725-field population -- measured, not merely no longer
observed on a sample.
The pinning discipline is kept rather than deleted: this file still
fails LOUDLY, in both directions, if the unconverted set ever stops
being empty -- a real regression (a genuinely new unparseable shape) and
a silently corrupted lectionary file are both caught by the same
mechanism 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 -- EMPTY since W4 closed the cross-chapter gap
(see this file's own header). Kept as a named binding, not inlined as
[[]] at each use site, so a future partial regression has one place to
list what it could not fix, exactly as the pre-W4 version of this file
did for the 49 references it once named here. *)
let known_cross_chapter_residual = []
(* 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) "1725 citation fields in data/of/lectionary.sexp today -- if this changes, the \
pinned residual below may need updating too, not just this count"
1725 !total;
Alcotest.(check (list string)) "the unconverted set is EXACTLY the known cross-chapter residual (empty, \
since W4) -- 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.
Since the residual is now empty, this exercises the FULL 1725-field
population, cross-chapter shapes included. *)
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 ] )
|