aboutsummaryrefslogtreecommitdiff
path: root/test/test_sanctoral_ef.ml
Commit message (Collapse)AuthorAgeFilesLines
* feat(ef): implement RG 33's third omission triggerLukasz Kasprzak2026-08-211-1/+71
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 33 omits a II/III-class vigil in three cases: it falls on a Sunday, it falls on a I-class feast, "vel si festum cui praemittitur in alium diem transferri aut ad commemorationem reduci contingat". Only the first two were built; the third was recorded in precedence_ef.ml as unimplemented on the grounds that no witness existed in the shipped data. That reasoning was wrong, and the rule fires on 1 744 days across 1583-9999. Both halves of the clause reduce to one observable question -- is the feast the OBSERVED office on the following day (RG 34 puts it there) -- so the kernel asks it once per candidate, after place_transfers has settled the year. No fixed point is needed: a vigil is a candidate only on its own day, never on its feast's, so suppressing it cannot change what the next day observes. Precedence.rules gains vigil_feast, which returns the slug of the feast a vigil precedes; the kernel cannot infer that itself, because only two of the five vigil/feast pairs share a slug stem. Blast radius, measured pre-change binary vs HEAD over the whole domain and classified: 1 744 days, three shapes, zero unexplained. 1 199 are the feast reduced to a commemoration (10 August on a Sunday, St Lawrence); 478 and 67 are the feast transferred under RG 96 after the Sacred Heart or Corpus Christi takes its day. The Assumption's and the Ascension's vigils never qualify -- their I-class feasts always keep their own day. Independently witnessed, which is unusual here. The published Ordo -- the only witness outside the Divinum Officium -> missalemeum -> lectio lineage -- omits St Lawrence's vigil on 2025-08-09, agreeing with colitur against both engines. That date had been read earlier as an Ordo gap; the Ordo was right, and correcting the misreading is what surfaced this clause. On 2027-08-09 the feast does keep its day and the Ordo omits a vigil colitur correctly keeps, which is a genuine Ordo gap. Allow-lists: C39 (lectio, 10 rows) and a 2038 oracle class citing the register, the 2026-2027 window having no instance. The golden pin asserting St Lawrence's vigil is violet moved 2025 -> 2027; its own comment had reasoned about the vigil's weekday and missed that RG 33 also looks at the feast's. Two new pins cover both shapes of the clause. The vigil/feast table is built with Slug.of_string_exn: mutation testing showed that of_string plus Result.to_option turns a typo into None, which this hook's contract reads as "not a vigil", switching the rule off in silence. Two tests assert the table against the shipped data in both directions.
* fix(ef): ubaldus and didacus are III-class feasts, not commemorationsLukasz Kasprzak2026-08-181-1/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Both carried rank Class3 with status Commemoration_only, bootstrapped from lectio, which inherits it from missalemeum, which is generated from Divinum Officium. The Missal's own universal calendarium ranks both "III classis" outright, with no commemoration rubric: "S. Ubaldi Ep. et Conf., III classis" (16 May) and "S. Didaci Conf., III classis" (13 November), corroborated in the second scan and in a published 1962 Ordo. Universal by construction, not by assumption: the calendarium carries no "pro aliquibus locis" marker in its 573 lines while the wider Missal carries 79 of them elsewhere. Each saint also has his own Mass entry in the Proprium, which a bare commemoration never has -- a commemoration carries an oration only. Found by auditing ALL 290 fixed-date entries against that calendarium, day by day, anchored on the Roman calends column because the arabic day column is OCR-wrecked and the dominical-letter column vanishes in some months. These two were the only status defects in the whole file. The 15 days the automated pass could not cover were read by hand and all agree. Neither needed a proper authored: each takes its Mass from a Common with only its own Oratio proper, per its own rubric at its own date. ubaldus shares patrick's Common exactly. didacus needed one newly transcribed -- common-of-a-confessor-not-a-bishop-2, the "Altera Missa" Iustus, 1 Cor 4:9-14 and Luke 12:32-34, read independently in both scans, which agree, so the page-image third reading this file's method requires on disagreement was not triggered. Blast radius, full 1583-9999 sweep, every day classified: 13 036. That is 5 564 ubaldus observed, 7 196 didacus observed, and 276 where the Ascension Vigil still wins on 16 May but its single RG 111(c) slot passes from rogation-wednesday to ubaldus -- correct and already pinned, since the Rogation days are the MINOR Litanies and so an ordinary commemoration, which RG 113's table order ranks below a III-class feast. Cited as C38 (70 rows) and M30 (3); C8, C17 and M18 shrink accordingly, each with the reason recorded rather than the count silently adjusted.
* feat(kernel): movable Date_spec variants, and Rogation WednesdayLukasz Kasprzak2026-08-171-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Date_spec carried one variant, Fixed(month, day), and its own header said Sunday- and Easter-relative forms would arrive with the OF sanctoral. They arrive early because two things needed them at once: a user-supplied overlay carrying a local movable feast had no way to express "the first Sunday of October", and Rogation Wednesday's commemoration has been recorded as architecturally blocked since 2026-08-13. That blockage turns out to have been a premise, not a fact. The register concluded the Rogation "is not Commemoration_only sanctoral data by nature... it needs a genuinely THIRD kind of thing this architecture has no name for" -- but the reasoning rested on there being "no civil (month, day) pair to anchor a Fixed entry to". That is a statement about Date_spec's expressiveness. With Easter_offset the entity is ordinary sanctoral data, the identical shape Add major-litanies already ships, differing only in how the date is written. No third channel was needed. The register is corrected rather than left contradicting the code. Two variants: Easter_offset of int, and Nth_weekday of {month; nth; weekday} with negative nth counting from the end. Fixed's sexp representation is untouched, so all 327 sanctoral entries and the whole overlay parse unchanged. Both are re-validated in t_of_sexp through their smart constructors, extending the idiom Fixed already used -- the failure that guards against is invisible, a spec deserialising into something that silently never resolves and a celebration vanishing with no diagnostic. Easter is supplied by the rite, not computed in the kernel. Computus ships Gregorian and Julian both, and picking one here would hard-code a Roman assumption into rite-agnostic code and be silently wrong for a Julian-reckoning rite. Rite.t gains an easter field; Rite_ef supplies the Gregorian one. Layer keeps a split index. Fixed entries stay in the year-independent (month, day) table, which preserves the fast path and the "30 November counted twice in a 371-day span" behaviour validate.mli documents for St Andrew. Movable entries have no year-independent key by construction -- the same spec lands on a different (month, day) each year -- so they resolve per civil year into a rata-die table. index_by_date is removed rather than kept as an alias: two ways to index a layer, one of which silently ignores movable entries, is the trap this change exists to avoid. Two domain-edge defects, both found by the suite rather than reasoning. A liturgical year is Advent-anchored, so resolving civil year y names y +/- 1, and at the edges those are 1582 and 10000, which Computus correctly refuses by raising. The ceiling surfaced through the domain-ceiling test, the floor through `colitur day 1583` failing outright. Fixed once, in Layer.index, the single point that calls the rite's easter -- a clamp repeated per caller would have been two places to get wrong a third time. Rogation Wednesday: RG 87 assigns the Litaniae minores to the Monday, Tuesday AND Wednesday before Ascension (scan1:691); colitur built the first two from the start and never the third. RG 88 keeps it out of the Office, so the Ascension Vigil remains observed. RG 89 routes it through the Major Litanies' rules, but RG 109's closed list names only maiores, so by RG 107 it is an ORDINARY commemoration -- confirmed in the output, not assumed. Name from the Missal's own "feria IV" (scan1:20495-20497), violet to match the Monday and Tuesday offices. Blast radius, full 1583-9999 sweep against the pre-change binary: 2257 days, every one the same shape, zero unclassified, line counts equal both sides. The complement is the informative half -- in the other 6160 years the Rogation does not appear at all, because RG 111 admits one commemoration on a II-class day and an impeded feast takes it under RG 113, a Commemoration_only candidate having no row in RG 91's table. Both shapes are pinned. Allow-lists unmoved: layer 3 compares no commemorations and is blind by construction, and layer 4's 2026-2027 window has a competing feast on both Wednesdays, so the Rogation is capped out in exactly the years the oracle can see. C8 is unchanged, asserted by its own count pin. Not built: Sunday-relative specs, which nothing needs yet, and the --overlay plumbing that would let a user actually supply a local movable feast. Nth_weekday therefore ships with synthetic and property coverage only; inventing a shipped entry to exercise it would be fabricating calendar data. Register: section 6.10.
* data(ef): carry sanctoral propers on the celebrationLukasz Kasprzak2026-08-151-1/+26
| | | | | | | | | | | | | | Celebration.citations has existed in the type, the .mli and the sexp schema since Plan 2 and has been emitted as () for every entry; the bootstrap now writes the real values for the 208 entries lectio has them for. On the celebration rather than in the lectionary table deliberately: a diocesan overlay adding a saint then carries his readings through the existing overlay algebra, with no second file to edit and no new directive. Asserted both ways -- the 13 January proper is present, and no Commemoration_only entry has readings, because in the EF a commemoration contributes an oration, not a reading.
* test(sanctoral_ef): fix round 1 (F8) -- guard against wholesale name.pl lossLukasz Kasprzak2026-08-121-0/+21
| | | | | | | | | | | | | | | | The previous commit's bootstrap_sanctoral.ml change (name.pl optional) is correct and verified not to silently drop a present name, but nothing in the suite would have caught a regeneration that dropped name.pl WHOLESALE -- the exact defect lectio itself shipped once (a regeneration that lost all 322 Polish names, only caught because that project's own review ran cmd/lectio-ef-dump, which happens to include name fields; this project's own test_load_and_counts checks entry/status/rank counts only, none of which move if every name.pl vanishes). Added a 322-of-327 count assertion (5 legitimately lack it, matching parse_names's own list), independently re-derived: `grep -c '^name.pl' tridentine-calendar.ini` = 322, cross-checked against `grep -c '(pl' data/ef/sanctoral.sexp` = 322 exactly.
* data(ef): re-bootstrap sanctoral.sexp from the corrected lectio sourceLukasz Kasprzak2026-08-121-2/+14
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Regenerated via tools/bootstrap_sanctoral.ml against lectio's tridentine-calendar.ini (SHA-256 6a25e634... -> 1b303ef2...), whose own generator was independently fixed in a concurrent session. 327 entries (223 feast, 104 commemoration-only), up from 322 (208/114): - 15 entries wrongly bootstrapped `rank = commemoration` are now `class-3` (a real III-class Feast) -- all 6 March-5 April, all primary-source-verified against the calendarium (register's own "Commemoration-only entries' inferred STATUS" item). - 5 new entries: agnes-secundo (28 Jan), boniface-martyr (14 May), eusebius-confessor (14 Aug), evaristus (26 Oct), theodore (9 Nov) -- all Commemoration_only, all primary-source-verified (register's "Four confirmed sanctoral DATA GAPS" item + expected-divergences-missalemeum .sexp's former M4/M6/M7/M9/M14). - commemoration-of-the-baptism-of-the-lord (13 Jan) is now `(subject Lord)` (the source's own `class` field, not a colitur overlay); most-holy-name-of-mary (12 Sep) no longer carries `class = lord` at all (the source dropped it), so the bootstrap default (Subject.Saint) is now correct there without help from adjustments.sexp's own Edit. test_sanctoral_ef.ml's pinned counts (327/104/12 Class1) are re-derived directly from the source INI (`grep -c '^\['`, `grep '^rank' | sort | uniq -c`), not transcribed from a bootstrap-tool or `colitur day` run. data/ef/adjustments.sexp and the differential/oracle allow-lists still need updating for the consequences of this regeneration -- follow-up commits.
* data(ef): bootstrap the 1962 sanctoral from lectioLukasz Kasprzak2026-08-121-0/+115
Convert lectio's tridentine-calendar.ini (322 entries) into data/ef/sanctoral.sexp via a validating OCaml converter, tools/ bootstrap_sanctoral.ml, rather than a hand-written script: every field is built through Slug.of_string, Colour.of_string and Vocab_ef.rank_of_string, so the emitted sexp is valid by construction. Two conversion decisions, both documented rather than buried: - subject defaults to Subject.Saint, overriding Celebration.make's kernel default of Subject.Temporal, for the 316 entries with no explicit class; - rank = commemoration maps to status = Commemoration_only with an inferred Class3 (not a citation -- it is what the 1960 reform reduced most simple feasts from), recorded as an open item in the rules register for the oracle to adjudicate. Every celebration is tagged layer = Precedence_ef.universal_layer, the provenance id RG 91's band classifier reads to tell the universal calendar from proper/indult data. The generated file carries a provenance header: source path, its SHA-256, and the UTC conversion date, so re-bootstrapping against a newer lectio is reproducible and diffable. Output is byte-identical across runs. test/test_sanctoral_ef.ml loads the file through Layer.load and checks the counts independently derived from the source INI (322 entries, 114 Commemoration_only, 12 Class1, no Subject.Temporal, every date resolves in a leap year), plus two named spot-checks against the INI's own text -- one entry with an explicit class field, one commemoration -- so a passing count cannot hide the wrong 322 entries having been converted.