From 1988d350242b47aa52aa07904c495e7e2c0eba82 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 22:11:17 +0200 Subject: fix: audit findings — parser strictness, name ambiguity, and errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by auditing the shipped program rather than the diff. The parser accepted OCaml integer-literal syntax, so "Luke 1_1:5" read as chapter ELEVEN and "+5" as 5 -- a typo silently becoming a different chapter, reachable through any user overlay. Numbers are now plain digits and positive, and a descending range is rejected: 1:20-10 is always a transcription error. No shipped citation changed. FOUR PAIRS OF DIFFERENT BOOKS SHARED A FULL TITLE. 1 and 2 Corinthians both rendered "Epistola ad Corinthios", as did Thessalonians, Timothy and Peter -- 108 citations in 2027 alone that a reader cannot resolve to a book. This is the Kings defect fixed earlier and not generalised. The titles now carry their volume numeral, marked CONSTRUCTED, and a test asserts no two books share a name -- while allowing the case where two ids ARE the same book under different numbering, which a tradition relates. Spec section 8.5 is now delivered rather than merely recorded. Shipped styles did not re-parse their own output: 32 of 52 Latin abbreviations and 49 of 52 full titles failed, so a citation copied from colitur's own output into an overlay was passed through untouched and printed in the wrong language, silently. Every shipped name is registered as a spelling and split_book learned multi-word titles by longest-token match. Now 0 of 52 fail beyond the same-book aliases. Overlay errors were written for a compiler author: they named an OCaml source file the reader does not have and buried the useful token. The existing five-path rewriter is replaced by a generic one, applied to every load path rather than one, so "rank: is not one of the allowed values (at Class9)" replaces the raw Of_sexp_error dump. Also: the new-overlay scaffold documented citations and layer without showing them, and its comment implied the wrong nesting -- the single easiest thing to get wrong; error messages echoed whole file lines, copying an unrelated file's contents into stderr when a flag pointed at one; and config --show validated partway down its table, exiting 2 after writing five rows to stdout. --- lib/citation/book.ml | 116 +++++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 49 deletions(-) (limited to 'lib/citation/book.ml') diff --git a/lib/citation/book.ml b/lib/citation/book.ml index 2a2b9c0..986a898 100644 --- a/lib/citation/book.ml +++ b/lib/citation/book.ml @@ -12,57 +12,57 @@ let to_string t = t the lectionary and was the source of every token missed in the first pass. *) let table = - [ ("genesis", [ "Gen" ]); - ("exodus", [ "Ex"; "Exod" ]); - ("leviticus", [ "Lev" ]); - ("numbers", [ "Num" ]); - ("kings_3", [ "3 Kings"; "3 Kgs." ]); - ("kings_4", [ "4 Kings" ]); - ("esdras_2", [ "2 Esd." ]); - ("tobit", [ "Tob" ]); - ("judith", [ "Judith" ]); - ("esther", [ "Esther" ]); - ("proverbs", [ "Prov" ]); - ("song_of_songs", [ "Song" ]); - ("wisdom", [ "Wis"; "Wis." ]); - ("ecclesiasticus", [ "Ecclus"; "Sir"; "Eccli" ]); - ("isaiah", [ "Isa"; "Isa." ]); - ("jeremiah", [ "Jer" ]); - ("ezekiel", [ "Ezech"; "Ezek" ]); - ("daniel", [ "Dan" ]); - ("osee", [ "Osee" ]); - ("joel", [ "Joel" ]); - ("jonas", [ "Jonas" ]); - ("malachi", [ "Mal" ]); - ("matthew", [ "Matt"; "Matt." ]); - ("mark", [ "Mark" ]); - ("luke", [ "Luke" ]); - ("john", [ "John" ]); - ("acts", [ "Acts" ]); - ("romans", [ "Rom" ]); - ("corinthians_1", [ "1 Cor"; "1 Cor." ]); - ("corinthians_2", [ "2 Cor"; "2 Cor." ]); - ("galatians", [ "Gal" ]); - ("ephesians", [ "Eph"; "Eph." ]); - ("philippians", [ "Phil" ]); - ("colossians", [ "Col"; "Col." ]); - ("thessalonians_1", [ "1 Thess"; "1 Thess." ]); - ("thessalonians_2", [ "2 Thess" ]); - ("timothy_1", [ "1 Tim." ]); - ("timothy_2", [ "2 Tim"; "2 Tim." ]); - ("titus", [ "Titus" ]); - ("hebrews", [ "Heb" ]); - ("james", [ "Jas"; "James" ]); - ("peter_1", [ "1 Pet"; "1 Pet." ]); - ("peter_2", [ "2 Pet." ]); - ("john_1", [ "1 John" ]); + [ ("genesis", [ "Gen"; "Liber Genesis"; "Genesis" ]); + ("exodus", [ "Ex"; "Exod"; "Liber Exodi"; "Exodus" ]); + ("leviticus", [ "Lev"; "Levit"; "Liber Levitici"; "Leviticus" ]); + ("numbers", [ "Num"; "Liber Numeri"; "Numbers" ]); + ("kings_3", [ "3 Kings"; "3 Kgs."; "3 Reg"; "Liber Regum III" ]); + ("kings_4", [ "4 Kings"; "4 Reg"; "Liber Regum IV"; "4 Kgs." ]); + ("esdras_2", [ "2 Esd."; "2 Esdr"; "Liber Esdrae"; "2 Esdras" ]); + ("tobit", [ "Tob"; "Liber Tobiae"; "Tobias" ]); + ("judith", [ "Judith"; "Iudith"; "Liber Iudith"; "Jth" ]); + ("esther", [ "Esther"; "Esth"; "Liber Esther" ]); + ("proverbs", [ "Prov"; "Proverbs" ]); + ("song_of_songs", [ "Song"; "Cant."; "Canticle of Canticles" ]); + ("wisdom", [ "Wis"; "Wis."; "Sap"; "Liber Sapientiae"; "Wisdom" ]); + ("ecclesiasticus", [ "Ecclus"; "Sir"; "Eccli"; "Ecclesiasticus" ]); + ("isaiah", [ "Isa"; "Isa."; "Isai"; "Isaias Propheta"; "Isaias" ]); + ("jeremiah", [ "Jer"; "Ier"; "Ieremias Propheta"; "Jeremias" ]); + ("ezekiel", [ "Ezech"; "Ezek"; "Ezechiel Propheta"; "Ezechiel" ]); + ("daniel", [ "Dan"; "Daniel Propheta"; "Daniel" ]); + ("osee", [ "Osee"; "Osee Propheta" ]); + ("joel", [ "Joel"; "Ioel"; "Ioel Propheta" ]); + ("jonas", [ "Jonas"; "Ionae"; "Ionas Propheta" ]); + ("malachi", [ "Mal"; "Malach"; "Malachias Propheta"; "Malachias" ]); + ("matthew", [ "Matt"; "Matt."; "Matth"; "Evangelium secundum Matthaeum"; "Matthew" ]); + ("mark", [ "Mark"; "Marc"; "Evangelium secundum Marcum" ]); + ("luke", [ "Luke"; "Luc"; "Evangelium secundum Lucam" ]); + ("john", [ "John"; "Ioann"; "Evangelium secundum Ioannem" ]); + ("acts", [ "Acts"; "Act"; "Actus Apostolorum"; "Acts of the Apostles" ]); + ("romans", [ "Rom"; "Epistola ad Romanos"; "Romans" ]); + ("corinthians_1", [ "1 Cor"; "1 Cor."; "Epistola I ad Corinthios"; "1 Corinthians" ]); + ("corinthians_2", [ "2 Cor"; "2 Cor."; "Epistola II ad Corinthios"; "2 Corinthians" ]); + ("galatians", [ "Gal"; "Epistola ad Galatas"; "Galatians" ]); + ("ephesians", [ "Eph"; "Eph."; "Ephes"; "Epistola ad Ephesios"; "Ephesians" ]); + ("philippians", [ "Phil"; "Epistola ad Philippenses"; "Philippians" ]); + ("colossians", [ "Col"; "Col."; "Epistola ad Colossenses"; "Colossians" ]); + ("thessalonians_1", [ "1 Thess"; "1 Thess."; "Epistola I ad Thessalonicenses"; "1 Thessalonians" ]); + ("thessalonians_2", [ "2 Thess"; "Epistola II ad Thessalonicenses"; "2 Thessalonians" ]); + ("timothy_1", [ "1 Tim."; "1 Tim"; "Epistola I ad Timotheum"; "1 Timothy" ]); + ("timothy_2", [ "2 Tim"; "2 Tim."; "Epistola II ad Timotheum"; "2 Timothy" ]); + ("titus", [ "Titus"; "Tit"; "Epistola ad Titum" ]); + ("hebrews", [ "Heb"; "Hebr"; "Epistola ad Hebraeos"; "Hebrews" ]); + ("james", [ "Jas"; "James"; "Iac"; "Epistola beati Iacobi Apostoli" ]); + ("peter_1", [ "1 Pet"; "1 Pet."; "1 Petri"; "Epistola I beati Petri Apostoli"; "1 Peter" ]); + ("peter_2", [ "2 Pet."; "2 Petri"; "Epistola II beati Petri Apostoli"; "2 Pet"; "2 Peter" ]); + ("john_1", [ "1 John"; "1 Ioann"; "Epistola beati Ioannis Apostoli" ]); (* "Apoc" is the Vulgate spelling and "Rev" its modern equivalent, but BOTH sit inside Vulgate-tradition data, so both resolve to the same Vulgate id here -- see book.mli's note by [apocalypse] never being an [of_token] result under that name. Do not add "revelation" as a spelling: it exists only as a tradition target (below), and giving it an [of_token] entry would let one book carry two different ids. *) - ("apocalypse", [ "Apoc"; "Rev" ]) ] + ("apocalypse", [ "Apoc"; "Rev"; "Liber Apocalypsis"; "Apocalypse" ]) ] (* Targets a tradition can map ONTO that the Vulgate data never cites directly. Present so [tradition_of_fields] can validate both sides. @@ -70,16 +70,34 @@ let table = "Sir" and "Rev" already resolve to the Vulgate ids [ecclesiasticus] and [apocalypse] above, so a modern-numbering tradition maps ONTO these targets rather than data ever citing them directly. *) -let tradition_targets = - [ "kings_1"; "kings_2"; "nehemiah"; "sirach"; "hosea"; "jonah"; "revelation" ] + +(* The modern-numbering targets. They have table rows so that colitur's OWN + rendered output re-parses: with --sigla-tradition modern the book prints + as "1 Reg" or "1 Kings", and a user copying that back into an overlay must + get it read correctly. The shipped data never cites them directly, which + is why they are listed apart. *) +let tradition_target_table = + [ + ("kings_1", [ "1 Reg"; "Liber Regum I"; "1 Kgs"; "1 Kings" ]); + ("kings_2", [ "2 Reg"; "Liber Regum II"; "2 Kgs"; "2 Kings" ]); + ("nehemiah", [ "Neh"; "Liber Nehemiae"; "Nehemiah" ]); + ("sirach", [ "Liber Ecclesiastici"; "Sirach" ]); + ("hosea", [ "Os"; "Hos"; "Hosea" ]); + ("jonah", [ "Ion"; "Jon"; "Jonah" ]); + ("revelation", [ "Apocalypsis"; "Revelation" ]); + ] + +let tradition_targets = List.map fst tradition_target_table let all = List.map fst table @ tradition_targets let tokens = - List.concat_map (fun (id, sp) -> List.map (fun s -> (s, id)) sp) table + List.concat_map + (fun (id, sp) -> List.map (fun s -> (s, id)) sp) + (table @ tradition_target_table) let default_spelling id = - match List.assoc_opt id table with + match List.assoc_opt id (table @ tradition_target_table) with | Some (first :: _) -> first | Some [] | None -> id -- cgit v1.3