(* 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 1540 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) "1540 citation fields in data/of/lectionary.sexp today -- if this changes, the \ pinned residual below may need updating too, not just this count" 1540 !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 ] )