aboutsummaryrefslogtreecommitdiff
path: root/test
Commit message (Collapse)AuthorAgeFilesLines
* fix(render): derive citation keys from the day, not a hardcoded pair (W5)Lukasz Kasprzak2026-08-273-1/+113
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | lib/render/view.ml hardcoded exactly two template keys, "first" and "gospel", regardless of what a day's citations actually contain. The OF now resolves a third reading, Second, on Sundays and solemnities (OLM 1981 Praenotanda n. 69.1/n. 84(b)(c)) via the rite-supplied Rite.t.citation_shapes, which the render layer could not express. Latent today (bin/main.ml's reject_rite_for refuses --rite on every command that reaches View.of_days, so only EF ever reaches this path), but the wrong shape to carry forward. View.citation_fields now derives one key per Citation.part ACTUALLY PRESENT in a day's own citations, in Citation.all_parts order, using Citation.part_to_string for the key name. An absent part contributes no key at all, not a key holding the empty string: Template.render already treats a missing key and a present Str "" identically in Var, Section and Inverted position, so nothing is lost, and Emit_json.year dumps the whole Obj verbatim, so an unconditionally-present-but-empty key for a still-unbuilt part (Psalm/Tract/Alleluia/Sequence) would sit in every EF day's JSON forever. Filtering to present parts alone is what makes EF's output identical byte for byte BY CONSTRUCTION rather than by a rite check: EF's citations are always exactly [First; Gospel], so the derived key set is always {"first"; "gospel"}, in that order. padding_cell keeps its own fixed "first"/"gospel" pair rather than deriving from a (nonexistent) day: it mirrors the closed set every rite currently reachable through this render path (EF alone) always carries, documented as debt for whenever a rite whose citation_shapes includes Second is admitted here -- test_padding_and_real_share_key_set would catch the mismatch immediately. Downstream consumers: Emit_json needed no change (already a verbatim dump). Emit_xml and Emit_ics gained a third, order-preserving element/line (a <citation part="second"> element; a "Second ..." DESCRIPTION line spliced between Epistle and Gospel) since neither is a fixed-column format. Emit_csv is deliberately left unwidened and commented: RFC 4180's header-row contract makes a variable column count illegal, and OF cannot reach `emit` today regardless, so there is nothing to widen for yet; admitting OF here later needs a disclosed, deliberate format change, not a silent one. term_keys (line ~239) is left unchanged, with a comment recording the judgment call: "epistle" and "lesson" are indeed EF/Roman vocabulary (Epistola vs. a non-apostolic Lectio, e.g. an Ember day's Old Testament reading) sitting in an otherwise rite-agnostic list, but nothing forces a fix yet -- no OF template exists, and every in-repo EF template already reads term.epistle, so renaming it would break 11 shipped templates and any user template for zero present benefit. "lesson" already sits unused, already translated in both lang files, and is the natural neutral term a future OF ordo template would reach for. Tests: test_view.ml gains test_ef_days_never_carry_a_second_key (a whole year, not one day) and test_three_citation_day_exposes_all_three, driving View.of_days directly with a hand-built Liturgical_day.t since OF cannot reach this path through the CLI yet -- the only honest way to exercise a latent path. test_emit.ml/test_ics.ml extend coverage to Emit_xml/Emit_ics (presence, value, and reading order) and pin Emit_csv's deliberate 16-column non-widening. EF byte-identity: zero diff in data/ef/ or lib/rites/rite_ef/ against the pre-change commit. `colitur day`, `readings`, `emit --format csv/xml/json/ ics`, and `table --template templates/ef/ordo.txt` for 2026, all non-empty, all byte-identical (cmp) against a worktree build of the pre-change commit.
* fix(citation): represent chapter-crossing verse ranges (W4)Lukasz Kasprzak2026-08-263-65/+88
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Parse.verse_range's [last] endpoint gains an optional chapter (Parse.verse_end: { chapter : int option; verse : verse_num }), so a hyphen range whose two endpoints lie in different chapters ("1 John 1:5-2:2") can be represented at all. Rejected: a bare [int option] living alongside [last] as a second field on verse_range -- that would let 'a chapter with no verse' exist as a constructible value. parse_part now splits a part's leading "chapter:" at the FIRST colon only (not every colon), so the verses side can itself carry a second colon from a crossing range. parse_range detects a crossing by checking whether the range's right-hand side contains ':', and skips the same-chapter descending-range guard for that case (a later chapter is always "ahead", whatever its own verse numbers are). Handles the compound shape too -- a crossing range followed by further, same-chapter verse references in the same comma list ("Matthew 9:35-10:1,5a,6-8") -- since those trailing pieces parse as ordinary bare verses/ranges, unaffected by the preceding crossing. Render's one_range renders a crossing [last] through the same chapter_verse template one_part already uses for the part's own leading "chapter:verses", so a style that reconfigures the chapter/verse separator renders a crossing endpoint in that same convention rather than a hardcoded ':'. This closes the W4 known-wrong: 41 (now 49, after an intervening Second-reading extraction) of the OF lectionary's citations printed unconverted, every one this exact shape. test_citation_coverage_of.ml's pinned residual is now empty and asserted exactly, over the full 1725-field data/of/lectionary.sexp population, including the round-trip check (parse -> render -> parse structural equality). test_citation.ml gains direct parse-suite cases for the basic crossing, the compound shape, a mid-list crossing, a crossing with a sub-verse letter, and a malformed-crossing rejection. data/of/lectionary.sexp is regenerated via its own generator (tools/bootstrap_lectionary_of.ml, whose own embedded header text is updated to match); only comment lines change, confirmed by diff -- no lectionary entry differs. test_lectionary_of.ml's whole-file SHA-256 pin is updated to match. EF is unaffected: data/ef/ is untouched since v1.0.0, and a direct byte comparison of `colitur day`/`colitur readings` for 2026, 1583 and 9999 against a git-worktree build of 1c0137d is identical on all six outputs.
* fix(of): dispatch Holy Family's Normae n.35(a) fallback (W2)Lukasz Kasprzak2026-08-264-154/+271
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | temporal_of.ml's temporal only ever reached holy_family's own correct 26-31 December / 30 December fallback logic through sunday_slug, which returns None immediately for a non-Sunday date. In a year 25 December is itself a Sunday (26-31 December then holding no Sunday of its own), the feast vanished entirely and fell through to an ordinary Christmas ferial slug -- confirmed on 2022, 2033, 2039, 2044, 2050 (and roughly 1583..9999 domain-wide, one year in seven). temporal's dispatch now consults holy_family directly, independent of weekday, between the sunday_slug and christmas_feria_slug branches, carrying the identical identity the Sunday case already builds (subject Lord, holy_family_names, rank Festum). holy_family and anchors were already correct and needed no change. Added a direct example-based test for the fallback across five affected years plus a control for the unaffected Sunday case, and a domain-wide property (Holy Family is observed in every liturgical year 1583-9999) folded into both the 200-sample QCheck run and the exhaustive sweep -- the property that would have caught this directly. Un-pinned the now-fixed behaviour everywhere it was recorded as known-wrong: test_rite_of.ml's own 1583 pin (rewritten to assert the fix), test_validate_of.ml's own independent exhaustive-sweep counter and landmark-year filter (found via make check, not the initial survey), and data/of/expected-divergences-litcal.sexp's L2/L3 allow-list entries against the litcal oracle (closed and removed, with a dedicated regression test replacing the allow-list's own silence).
* fix(of): anticipate St Joseph onto 18 March per Normae n. 56(f)Lukasz Kasprzak2026-08-262-58/+109
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | W1: when 19 March (St Joseph) falls on Palm Sunday, colitur sent him forward to Easter+9 instead of anticipating the solemnity backward to 18 March, the one rule in the whole Normae transfer machinery that names an earlier date. Live in 16 of 1583-2100 (1595, 1606, 1617, 1690, 1758, 1769, 1780, 1815, 1826, 1837, 1967, 1978, 1989, 2062, 2073, 2084). Blocked on a kernel contract: Rite.t.transfer_target required its result be strictly later than the impeded date, an obligation argued purely from the EF's RG 96. Traced against Calendar's actual placement pass rather than assumed unsafe: the round loop re-resolves the whole year fresh every round, and its two safety nets (~start/~stop; the round-count guard) both test the target's own value with no comparison against origin, so a bounded backward jump converges the same way a bounded forward one does. The one requirement that stays load-bearing is target <> origin: Precedence.resolve never deduplicates by slug, so a candidate returning its own argument unchanged would collide with itself in the same round's contest. Relaxed the obligation accordingly (rite.mli) and added the fourth transfer_target branch in Precedence_of, ahead of the existing three (Precedence_of.transfer_target, Rule 0). calendar.ml needed no logic change, only two comments that asserted "every genuine transfer moves forward" as a blanket fact. Unpinned the two tests that asserted the old wrong (forward) behaviour and replaced them with assertions of the correct one, plus a new direct unit test of the backward branch across two independent years. EF output is untouched: data/ef/ and lib/rites/rite_ef/ carry no diff, and colitur day/readings for 2026, 1583 and 9999 are byte-identical against a build of the prior commit. OF's own 2005-2050 window carries none of the 16 affected years and shows zero unexpected diffs; the full 1583-9999 domain still reaches a fixed point under the exhaustive sweep.
* fix(cli): render the Second reading on colitur readingsLukasz Kasprzak2026-08-261-1/+19
| | | | | | | | | | | | | | | | | | | | | The OF lectionary (data/of/lectionary.sexp) carries 183 Second readings, 146 of them on Sunday keys, and the kernel resolves them correctly -- but readings_line (EF) and readings_line_of (OF) in bin/main.ml only ever printed Citation.First and Citation.Gospel, so `colitur readings --rite of` silently dropped every Second reading it computed. Both functions gain a second_suffix lookup, spliced between the existing First and Gospel fields, present only when the day carries a Second citation. No rite check: EF's citation_shapes is [[First; Gospel]] alone, so EF rows are unaffected by construction, not by special-casing. EF byte-identity verified two ways: `git diff --stat v1.0.0..HEAD -- data/ef/` is empty, and `colitur day`/`colitur readings --rite ef` for 2026, 1583 and 9999 cmp identical against a build of the prior commit. test/cli.t: fixed the one now-stale assertion and added coverage for the three-citation OF Sunday shape alongside the unchanged two-citation OF weekday and EF shapes.
* fix(citation): make the well-formed reading-part set rite-suppliedLukasz Kasprzak2026-08-264-18/+109
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5): OF Sundays and solemnities shipped without the Second reading, and the lectionary data header called the First reading an "Epistle" -- EF vocabulary. The sole blocker was Validate's own kernel-hardcoded assertion that a well-formed day's sorted citation parts are exactly [First; Gospel]; Citation.part already carried Second. Rite.t gains citation_shapes : Citation.part list list, the fourth EF-shaped kernel misfit (after Preface.t, Mass_formulary.source, transfer_target's strictly-later contract) and the first fixed rather than only documented. EF supplies its one shape unchanged; OF supplies two, [First; Gospel] on a feria/feast/memorial and [First; Second; Gospel] on a Sunday or solemnity (OLM 1981 Praenotanda n. 66.1 vs n. 69.1, n. 84(b)/(c), page-image verified against olm-1981.pdf). Validate's "citations" check now matches against the rite's own declared shapes rather than a single literal pattern, preserving all three invariants it always enforced: zero or one complete shape, never partial; no part outside what the rite declares; no part repeated. bootstrap_lectionary_of.ml's cite now extracts a Second reading from Sunday-cycle ini sections only, correctly excluding the one stray `second` field lectio's snapshot carries on a weekday-cycle section (a content-verified scraping artifact). The hand-authored Christmas Day entry gains its own Second reading from the same OLM passage already cited for its First/Gospel. Regenerated data/of/lectionary.sexp: 771 entries unchanged, citation fields 1542 -> 1725 (+183, all Second readings), SHA-256 re-pinned, header wording fixed and a new coverage bullet added. Responsorial Psalm is deliberately still not emitted -- OLM 1981 Praenotanda n. 71 itself lists it apart from the readings proper as a chant with its own refrain, and the finding driving this change names only the missing Second reading. EF output verified byte-identical to the branch start (empty lib/rites/rite_ef + data/ef diff since v1.0.0, plus a direct binary comparison of `day`/`readings` for 2026, 1583 and 9999 against a worktree build of the pre-change commit). Tests: three new fixtures in test_validate.ml prove the generalisation holds both directions (a multi-shape rite accepts either of its declared shapes; a rite that never declared a shape still rejects it). test_citation_coverage_of.ml's pinned residual grows 41 -> 49, all new entries the same already-understood chapter-crossing shape.
* feat(of-lectionary): hand-author the Christmas Day Mass, close the gapLukasz Kasprzak2026-08-265-73/+140
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | colitur readings --rite of printed "- | -" on 25 December every year -- lectio's own 988 keys never included the Christmas DAY Mass at all, only the Vigil (already correctly noted, but left unfilled, in tools/bootstrap_lectionary_of.ml's own named_overrides comment). Sourced, not guessed: docs/research/of/olm-1981-ocr.txt:4513-4520, "16 Ad Missam in die", the same primary authority this module already cites for the cycle rules. First reading and Gospel only, matching this file's own scope everywhere else (First/Gospel is the kernel's own boundary, I7, a separate deferred task) -- Isaiah 52:7-10 / John 1:1-18 (OLM's own LONGER form; the file's LINEAGE section already discloses that short/long-form choices are not modelled anywhere in this data, so this is an application of an existing limitation, not a new one). Injected via a new hand_authored table in the generator, merged into entries before assert_reachable/Lectionary.of_entries -- there is no ini section to derive this from. One new lookup_any call (readings' own existing temporal-slug fallback, unmodified) now resolves BOTH the formulary and the citations for 25 December from the same entry, so both halves of the "citations-unresolved"/"formulary" gap close together, not just the citation half. Verified across the WHOLE domain, not assumed: test_validate_of.ml's exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1, all 8 416 years) now asserts the Nativity gap fires on NONE of them, down from all 8 416 before this fix -- and is kept as a live, dedicated "must never recur" counter rather than deleted. test_rite_of.ml's landmark-year check and test_lectionary_of.ml's 2026 coverage pins are updated the same way (the unresolved-day set that used to be pinned as {25 December} is now pinned as empty, not merely a count going to zero). A concrete golden pin (test_golden_of.ml) locks the exact citations through the full resolved pipeline. data/of/lectionary.sexp regenerated (771 entries, was 770); its own SHA-256 re-pinned. make check (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, unpiped, foreground): exit 0, 895 tests, ~536s.
* feat(of-lectionary): disclose the citation-conversion residual in the headerLukasz Kasprzak2026-08-261-1/+8
| | | | | | | | | | | | | | | | | | | | Fix option (c) from the I2 review finding, done alongside the parser fix itself rather than deferred: tools/bootstrap_lectionary_of.ml now runs every emitted reference through Colitur_citation.Parse.parse at generation time and adds a fourth COVERAGE item plus a named listing to data/of/lectionary.sexp's own header, so the 41-reference residual (all chapter-crossing hyphen ranges, see the citation-fix commit) is disclosed in the artifact a reader actually opens, not only in a task report or test file. Regenerated against the pinned lectio snapshot (dune exec tools/bootstrap_lectionary_of.exe -- ../lectio/internal/caldata/ of-lectionary.ini data/of/lectionary.sexp); diffed against the pre-change file to confirm the change is header-only, zero lines removed, the sexp payload byte-identical. Re-pins the file's own SHA-256 in test_lectionary_of.ml accordingly (re-derived with sha256sum, not transcribed from generator stdout, matching that test's own stated discipline).
* fix(citation): recognise English-canonical OF books, verse sub-lettersLukasz Kasprzak2026-08-263-3/+147
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 35% of OF citation fields (259/730 on colitur readings --rite of 2026) printed unconverted -- 1 John renders on 2 January but not 3 January. Two independent causes, both in the citation/siglum path, neither in the OF data itself: 1. Book.table only ever surveyed the three EF citation-bearing files, so 345 references citing a book no EF file happens to use (Job, Ruth, Judges, 1/2 Samuel, 1/2 Chronicles, 1/2 Maccabees, Baruch, Ecclesiastes, Habakkuk, Haggai, Nahum, Zechariah, Zephaniah, Deuteronomy, Amos, Micah, Lamentations, Ezra, Joshua, 2/3 John, Jude, Philemon, plus "Isaiah"/"Jeremiah"/"Ezekiel"/"Malachi"/"Mat"/ "The Acts"/"Tobit"/"Song of Solomon" spelling variants of books EF already knows) failed as "unknown book". Added as a new, separate of_lectionary_table rather than folded into the EF-surveyed table: none of these 25 new books is attested in the EF's own scans, and lang/la.ini's own header refuses to fabricate an uncited Latin title, so they are resolvable (parse + render, falling back to their own English spelling) but deliberately excluded from Book.all -- test_lang_coverage.ml's la.ini-completeness promise is preserved exactly for the ids it already covered, not silently weakened. 2. Parse's grammar could not read a verse number carrying a lectionary sub-verse letter ("11a", "1bcde") at all -- the dominant remaining failure shape once (1) was fixed. verse_range now carries a verse_num { n; suffix } on each boundary, PRESERVED through rendering rather than dropped (dropping would silently lose real precision the source text carries). Chapter numbers are untouched (nothing in the data ever attaches a letter to one). A third, subtler bug surfaced by (1): registering "jude"/"philemon"/ "2 John"/"3 John" exposed Parse's existing "leading comma-number is a chapter" heuristic misreading a single-chapter book's bare verse list ("Jude 17,20b-25") as chapter 17 -- a wrong PARSE, worse than the previous safe "unknown book" failure. Book.is_single_chapter now tells Parse to skip that heuristic for the four one-chapter books and default to chapter 1. Residual, honestly enumerated rather than forced to zero: 41 distinct references (of 1540) are hyphenated ranges crossing a chapter boundary ("2:29-3:6") -- a Parse.t shape verse_range/part do not represent, a type restructuring deliberately not attempted this task. Pinned exactly by the new test_citation_coverage_of.ml, both directions (a new failure or one of these 41 starting to convert both go red), and disclosed in data/of/lectionary.sexp's own regenerated provenance header (tools/ bootstrap_lectionary_of.ml now runs the same parser at generation time and names the count and the set). Verified EF-unaffected: git diff v1.0.0..HEAD -- lib/kernel/ lib/rites/rite_ef/ data/ef/ is empty, and `colitur day`/`readings` output for 2027 is byte-identical against the pre-fix binary.
* fix(of): print the OF's own transcribed Latin/English names, not EF'sLukasz Kasprzak2026-08-262-1/+122
| | | | | | | | | | | | | | | | | | | | | | | | | | | day_line_of/readings_line_of resolved every displayed name through lang/la.ini -- data documented as "transcribed from the 1962 Missale Romanum", i.e. EF vocabulary, keyed on slug. 18 of 222 shipped OF sanctoral slugs collide with an EF slug there and all 18 printed the WRONG (1962) title (e.g. "S. Marthae Virg.", a rank word the 1969 reform abolished, silently overriding amendment 011's own "Ss. Marthae, Mariae et Lazari"); the other 204 had no entry and degraded to a bare slug, so none of the 208 titles calendar-2002.sexp itself transcribes (cited against the 2002 Missal) was ever reachable. Add observed_name_of: prefer the observed celebration's own Celebration.names (looked up by the requested language code), fall back to the lang/*.ini table only on a miss. Under --raw this is a no-op (Lang.raw's own code, "raw", is not a valid 2-letter ISO code, so the names lookup always misses and falls through to the identity table exactly as before). EF's day_line/readings_line are untouched -- EF's Celebration.names is almost always empty by design, lang/la.ini is its real, independently-cited name table. Regression net, two layers: test_lang_coverage.ml gains a test walking the real assembled OF layer's entries, asserting that every genuine lang/la.ini collision resolves to the OF's own name, never the EF table's; test/cli.t pins one real collision (Martha, 29 July) through the actual CLI in both la and en.
* test(of): pin the Mary Mother of the Church / Anthony of Padua tieLukasz Kasprzak2026-08-261-1/+59
| | | | | | | | | | | | | | | | | | | data/of/amendments/006-mary-mother-of-the-church.sexp's own header documented, at length, that Easter+50 can land on 13 June and tie with Anthony of Padua at Tabula III.10 (both universal Memoria_obligatoria) -- the ledger's remedy (F-MEMORIAL-TIE) asked to document the lacuna AND pin the current behaviour so it cannot drift silently. Only the documentation half was done. Add test_mater_ecclesiae_anthony_tie_2011_known_arbitrary, in the same style as the Joseph/Palm Sunday and Holy Family fallback pins already in this file, but labelled KNOWN ARBITRARY rather than KNOWN WRONG: no rubric decides this collision (the 2018 decree is silent on it, and Normae n. 14's third clause resolves only the ad-libitum/ad-libitum case), so colitur's Slug.compare tie-break is a generic kernel fallback, not a colitur-invented rule. Asserts the 2011 Easter/offset arithmetic by hand, the current observed slug, and that the loser is recorded in `omitted` rather than silently dropped.
* test(of): validation layers -- Layer 2 properties, litcal grade/identity, ↵Lukasz Kasprzak2026-08-264-16/+1194
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | golden pins Task 6 (2026-08-25-colitur-of-phases-3-5), the final task of OF Phases 3-5. Layer 2 (test/test_validate_of.ml, new): Colitur_kernel.Validate.run against the real, assembled Rite_of.context -- landmark years, the 9999 domain edge, a 200-year QCheck sample, and the full 8417-year exhaustive sweep under COLITUR_EXHAUSTIVE_SWEEP=1, all filtered through the same three pinned-not-fixed gaps test_rite_of.ml already names (Normae n.35(a) Holy Family fallback, the 25-December lectionary gap, St Joseph/Palm Sunday), duplicated verbatim rather than shared so this file's own filtering cannot drift from Task 5's silently. Two dedicated 200-sample properties confirm Ordinary Time weeks are 1..34 with the final week always 34, and Calendar.year covers its own liturgical year exactly once with no gaps, both through the resolved pipeline (a genuinely different code path from Phase 1's own already-exhaustive Temporal_of-level property). An early version folded a second Calendar.year pass into the exhaustive loop for these two properties too and measured ~365s for that alone (a scratch timing harness, not committed); dropped in favour of one Val.run-only exhaustive loop (~200s), matching the EF harness's shape (one property, one sweep) rather than tripling it. test/test_litcal_of.ml extended beyond season/week to GRADE (litcal's own grade_lcl bucketed against the real Tabula band Precedence_of.band assigns the day's observed office) and IDENTITY (observed slug against a hand-verified event_key -> slug table for the closed 46-entity FIXED/ NAMED set -- deliberately not the numbered Sunday/weekday series, a stated, counted scope limit). Both follow the counted-and-allow-listed discipline: 1800/4346 rows compared for grade (1763 match, 37 allow- listed across 5 new entries), 508/4346 for identity (505 match, 3 allow- listed across 3 new entries), zero unexplained either way. Real divergences found and adjudicated, not suppressed: litcal's own grade vocabulary conflates Tabula I.2/I.3 for Trinity/Corpus Christi (verdict colitur); litcal's Mary Magdalene data predates the 2016 CDW decree (verdict litcal); a genuine, unadjudicated Sacred-Heart/Nativity-of-John- the-Baptist tie-break at equal band, found live at the one Easter date in this fixture's window that creates it (verdict open, no citation found either way); a second instance of precedence_of.mli's own documented forward-only transfer_target limitation, on a date shape the mli's own worked example does not cover (verdict colitur, known limitation). test/test_golden_of.ml (new, 7 pins): a year Ordinary Time skips a week (2026) plus Mary Mother of the Church same date; Advent IV on Christmas Eve in two independent years (2028, 2034); the Annunciation transferred out of Holy Week to Easter+8 (2027, both ends of the transfer pinned); All Souls; the Sacred Heart; 17 December's date-keyed reading in two civil years/weekday-cycle letters. Every new layer mutation-proved: reverting the Advent-IV fix reddens two golden pins; corrupting Precedence_of.band's Tabula I.3 value reddens the litcal grade comparator across every Solemnity in the fixture; renaming one temporal slug reddens the litcal identity comparator; dropping Ordinary Time's second season_runs entry reddens both the landmark-years test and the sampled property. Each mutation applied to lib/rites/rite_of/, confirmed red, then reverted -- git diff --stat lib/ is empty. make check (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force): exit 0, 890 tests, 363.5s. EF byte-identity holds: git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/ lib/kernel/ data/ef/ is empty.
* fix(of-temporal): make anchors agree with temporal on the Nativity VigilLukasz Kasprzak2026-08-262-4/+103
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Temporal_of.named correctly guards the Nativity Vigil: when 24 December falls on a Sunday it is the Fourth Sunday of Advent (Tabula I.2 outranks I.3), not the Vigil, and named returns None so temporal's dispatch falls through to the ordinary Sunday slug. anchors, however, still listed ("of-nativity-vigil", 24 December) unconditionally -- a regression from the earlier R8 fix, which added the guard to named but never updated anchors to match. In 2028 and 2034, anchors claimed the Vigil where temporal actually returns "of-advent-sunday-4". anchors exists precisely as an independent restatement whose stated purpose is to catch an accidental single-site drift rather than leaving it invisible -- it caught exactly this drift, just later than it should have, only once Validate.run was wired for the OF rite for the first time (nothing before that compared anchors against named/temporal, so the two were free to disagree unnoticed). Fixed by restating named's own guard exactly: the Vigil entry is omitted on a Sunday year rather than replaced, since the Fourth Sunday of Advent is an ordinary generic week-numbered Sunday, not one of the named days anchors exists to restate. Audited every other anchors entry the same way, across the full 1584-9997 domain (test_temporal_of.ml's own exhaustive sweep, run under COLITUR_EXHAUSTIVE_SWEEP=1): no further drift found. The sweep does surface one already-known, already-pinned mismatch -- the "of-holy-family" entry, in any year 26-31 December holds no Sunday (Normae n.35(a)'s own 30-December fallback) -- but it is a different shape, not a second instance of this bug: there anchors computes the right date and it is temporal that fails to reach it, which test_rite_of.ml already found, pinned and deferred as out of scope. Corrected that file's own stale comment, which still described the now-fixed Vigil disagreement as an open gap. New tests in test_temporal_of.ml cover both a Sunday-24-December year (2028, 2034) and a non-Sunday one, checking every anchors entry against temporal directly, plus a positive assertion that the Vigil entry is correctly present or absent depending on the year.
* fix(of-lectionary): stop serving drifting readings for O-Antiphon and ↵Lukasz Kasprzak2026-08-261-7/+112
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Christmas-season dates The bootstrap excluded 19 lectio bases as "date-keyed duplicates", but 17 of them were not duplicates at all: the 8 O-Antiphon days (17-24 December) and 8 further Christmas-season dates (29-31 December, 2-5 and 7 January) each carry unique per-date content found nowhere else among the emitted entries, verified against lectio's own ini directly. 17 December is Gen 49:2,8-10 / Matthew 1:1-17, but colitur was falling through to that year's unrelated weekday-keyed "advent-3-mon" content instead. OLM n. 69.3 explains why: those ferias are fixed by civil date, unlike every other Advent/Christmastide/Paschaltide feria, which is merely non-alternating within a weekday slot. Temporal_of's own ferial slugs for these dates are weekday-keyed, so looking one of those up served whichever OTHER date happened to share that year's weekday alignment -- a citation that silently drifted year to year on ~16 real civil dates (6 January stays excluded: Temporal_of fixes Epiphany there unconditionally, so that date can never reach the ferial path at all; two more, easter-6-thu and advent-4-sat, stay excluded for their own, different structural reasons). Fixed with a date-keyed lookup route (Lectionary_of.date_keyed_slug), tried before the weekday-keyed one in readings' own step 3. Temporal_of's slugs are unchanged -- only which lectionary key resolves a day's citations. tools/bootstrap_lectionary_of.ml now maps the 17 bases to synthetic date-keyed slugs instead of excluding them, and its own reachability sweep calls date_keyed_slug directly so the two can never drift apart. Re-emitted data/of/lectionary.sexp (754 -> 770 entries) and corrected its provenance header, which previously described these dates as excluded duplicates. Also states, for the first time, a Minor finding from the same review: 190 of 222 shipped sanctoral slugs have no dedicated lectionary entry and correctly fall through to the ferial per OLM norms -- expected behaviour, not a gap, but never said in the coverage report before. New tests pin 17 December (Gen 49:2,8-10 / Matthew 1:1-17) and 2 January (1 John 2:22-28 / John 1:19-28) in two years with different weekday alignments, proving neither reading drifts, plus direct boundary coverage of date_keyed_slug itself (both windows, the 6 January exclusion, and the Sunday guard).
* feat(of): assemble Rite_of.context and wire --rite of into the CLILukasz Kasprzak2026-08-264-0/+409
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Task 5 of Phases 3-5 (the payoff task): bundles Tasks 1-4's separately built pieces (Temporal_of, Precedence_of, Rubrics_of, Lectionary_of) into a (Vocab_of.season, Vocab_of.rank) Colitur_kernel.Rite.t, and wires colitur day/readings --rite of <year> into the CLI. Default stays ef; every pre-existing invocation is byte-identical (asserted directly in test/cli.t and against a real v1.0.0 build for three years, plus git diff --stat v1.0.0..HEAD over lib/rites/rite_ef/, lib/kernel/, data/ef/ is empty). No kernel change was needed, confirming the spec's central claim. season_runs needed real thought rather than the brief's own suggested Vocab_of.seasons: Temporal_of.season's own 'first run'/'second run' comments confirm Ordinary Time is genuinely interrupted by Lent and Easter over one liturgical year, so the expected run-length-compressed sequence names it twice, six runs over five seasons -- exactly the case Rite.t.season_runs's own doc comment already names as its illustration for why this field is rite-supplied rather than derived. easter/fixed_key are as specified, each carrying its own citation (Gregorian computus; the 2002 Missal's one bissextilis occurrence is a leap-year table entry, not a kalends-doubling rule, so fixed_key is identity). Follows rite_ef.ml's template and its lectionary-parameter lesson exactly: lectionary is caller-supplied, not closed over. test/test_rite_of.ml resolves the real assembled rite (calendar-2002 .sexp + all 13 amendment overlays + lectionary.sexp) through Calendar and Validate.run, the same discipline test_validate.ml/test_rite_ef.ml already apply to EF. This is the first time rite_of has been checked this way, and it surfaced two new, real, cited gaps outside this task's own scope to fix: Temporal_of never reaches Normae n.35(a)'s 30-December Holy Family fallback in a year with no Sunday in 26-31 December (first hit 1583, the domain floor), and Temporal_of.anchors disagrees with temporal on 24 December when that date is itself a Sunday (Advent IV outranks the Vigil, first hit 2006). Both pinned as known-wrong, not fixed, the same treatment the brief's own pre-known St Joseph/Palm Sunday gap (Normae n.56(f)) gets, pinned on 2062. A third gap was found in data/of/lectionary.sexp's own coverage (audited against one civil year only, so the other weekday-cycle letter is unaudited and at least one pairing is asymmetric on shipped data) -- that file is excluded from this task, so the wide 2005-2050 sweep checks structural invariants only and leaves the citation-chain checks to the small, individually-audited landmark-year set. make check (COLITUR_EXHAUSTIVE_SWEEP=1): exit 0, 867 tests, ~316s.
* feat(of): Lectionary_of -- reading-cycle arithmetic and the lectionary chainLukasz Kasprzak2026-08-263-0/+375
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Task 4 of Phases 3-5: the last Rite.t field OF still needed, `readings`. The cycle rules are CODE, cited to OLM (Ordo Lectionum Missae) 1981 Praenotanda -- verified against the real page image (docs/research/of/olm-1981.pdf pp.32-33/"XXXII-XXXIII"), not the document's own unreliable OCR text layer, per the design spec's own caveat. n. 66 (with its footnote 102's worked example, 1980=C/1981=A/ 1982=B/1983=C) gives the three-year Sunday cycle; n. 69 point 4 gives the two-year weekday cycle, Year I in odd label years, Year II in even. Both derive the "label year" the same way: one more than the civil year Advent I opened in, per n. 66's footnote 102 itself ("nempe a prima hebdomada Adventus, quae cadit in anno civili praecedente") -- so the cycle turns at Advent I, not 1 January, exactly where the task brief said to get it right. The design spec's own citation for the weekday rule, "Praenotanda n. 649", does not survive a direct check against the primary source (search finds "649" only in unrelated index/page-number entries); corrected here to n. 69, the paragraph the rule's own Latin text actually sits under, confirmed by the page image's own printed margin number. The data is bootstrapped from lectio's of-lectionary.ini (988 keys, niedziela.pl, harvest 2020-2025) by tools/bootstrap_lectionary_of.ml -- OCaml, not the brief's own suggested .py: tools/bootstrap_lectionary .ml already established this exact shape for EF, including a real safety net ([assert_reachable], sweeping Rite_of.Temporal_of.temporal AND the real merged sanctoral layer directly) that a second-language reimplementation would either duplicate or lose. Handles two real data anomalies found while building it: one malformed ini line (a Polish Septuagint-numbering annotation for Sirach 3, "Syr 3,2-6.12-14" against OLM/CEI/USA's shared Nova Vulgata, matching the design spec's own sec5 finding); and two Sunday-cycle-labelled bases (Annunciation, Immaculate Conception) where a scraping-year artifact left one of three cycle letters carrying a different day's Mass entirely -- resolved by a general, mechanical 2-of-3 majority rule, not a per-saint hand edit, and logged. Coverage measured in both directions, per the brief's own Step 1: - 1 of 365 days in civil year 2026 (25 December, the Christmas DAY Mass) has no Temporal_of-slug match anywhere in lectio's 988 keys -- only the Vigil is present. Named in the data file's own header. - 0 of 438 distinct lectio bases map to no colitur slug, after 19 are deliberately excluded (late-Advent/Christmas-season DATE-keyed duplicates lectio also carries in a WEEKDAY-keyed form colitur's own slugs actually match -- Lectionary.t has no date-keyed lookup at all, so the date-keyed family is transcription-inert, not silently dropped: named explicitly, not a residual). Lectionary_of.readings is three steps (no Commons indirection, unlike EF -- out of this task's scope): the observed celebration's own embedded citations; else, for a real sanctoral winner, its own slug looked up directly (flat, then both cycle letters); else the day's own temporal slug, same lookup. No preceding-Sunday fallback: OF's own two-year cycle assigns every Ordinary Time weekday its own reading by design, and no OF norm was found instructing otherwise. Tests (test/test_lectionary_of.ml, 12 cases): a cycle-arithmetic table straddling Advent 1980/1981/1982/2025 inside single civil years, reproducing OLM n.66's own worked example directly; SHA-256 pins on both the emitted file and lectio's own source (embedded in the provenance header); a pinned, NAMED 2026 coverage result (364 resolve, exactly {25 December} does not -- never a bare count); two resolution- chain spot checks (a plain ferial, and a saint's day beating the ferial it would otherwise fall on); and a real subprocess test that a missing lectio source file fails the generator loudly, non-zero exit, no output written -- the exact bug Task 1's own generator shipped and needed a review round to fix. dune test: exit 0, 856 tests. make check (COLITUR_EXHAUSTIVE_SWEEP=1, full 1583-9999 sweep): exit 0, 862 tests, ~342s, both run unpiped in the foreground. git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/ lib/kernel/ data/ef/ still empty.
* fix(of): rename credo to creed, correct three comment inaccuraciesLukasz Kasprzak2026-08-261-5/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fix round 1 for Task 3, addressing review findings. Rename the exported creed function from `credo` to `creed` across the .ml, .mli and test call sites, so the OF rite bundle can write `creed = Rubrics_of.creed` with no gratuitous mismatch against Rite.t's own field name, matching Rite_ef.Rubrics_ef.creed's own naming. IGMR n. 67's own heading is "Professio fidei" and its body says "Symbolum", never "Credo" -- documented as a comment on the now-correctly-named function rather than kept as the exported name. Local test helpers (credo_on_temporal, check_credo, test_credo_*) and Alcotest description strings keep the Latin shorthand, since only the call sites against RO.creed needed to change. Corrected two comment inaccuracies: - The Annunciation (25 March) is NOT always inside Lent, unlike St Joseph (19 March): 25 March sits after Easter's own earliest possible date (22 March), so in an early-Easter year (2008, Easter 23 March) it falls inside the Easter Octave instead, or is transferred under Normae n. 60/5. No functional effect (gloria/creed read observed/temporal as actually resolved), but the stated reason was wrong. - The Requiem-silence argument's counts: three All Souls Mass formularies (not two), and roughly 147 Gloria+Credo markers combined elsewhere in the Missal (not "over 150", which conflated Gloria alone with the combined figure) -- recounted directly against a fresh pdftotext -layout extraction rather than estimated. Added: an independent corroborating citation for the Holy Thursday / Easter Vigil Gloria exception, Vigil n. 31 -- the Easter Vigil's own praenotanda ("VIGILIA PASCHALIS IN NOCTE SANCTA"), a THIRD independently-numbered rubric set inside the same Missal PDF, confirming the Vigil's own Gloria directly rather than resting solely on the Holy Thursday rubric's forward-looking "usque ad" clause. dune test: exit 0, 845 tests. make check (COLITUR_EXHAUSTIVE_SWEEP=1, full 1583-9999 sweep): exit 0, 851 tests, 330.3s, both run unpiped in the foreground. git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/ lib/kernel/ data/ef/ still empty.
* feat(of): Rubrics_of -- Gloria, Credo, and an all-None Preface verdictLukasz Kasprzak2026-08-262-0/+308
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Task 3 of OF Phases 3-5: the celebrant-rubrics module for the OF rite, supplying three of Rite.t's four remaining fields. creed/gloria implement IGMR n. 53 (Gloria) and n. 67-68 (Symbolum), both quoted in full in the module header. temporal is read, not observed, for both Sunday clauses: Temporal_of.temporal tags every Sunday of Advent/Lent/Easter Sollemnitas (the privileged-season convention), so an observed.rank test alone would wrongly grant Gloria on an Advent or Lent Sunday, which n. 53's own "extra tempus Adventus et Quadragesimae" explicitly withholds. Credo is deliberately narrower than Gloria: n. 68 has no "et festis" clause, so an ordinary Festum does not get the Creed, unlike EF's RG 475(b)/(c). Two Missale-proper exceptions (Holy Thursday evening Mass, the Easter Vigil Mass) are cited from the Missal's own "Dicitur Gloria in excelsis" rubric, not the IGMR, which is silent on both. The Requiem question is likewise IGMR-silent (nn. 379-385 say nothing about Gloria/Credo); flagged as an inference from the Missal's own consistent marker pattern, applied only to the one Requiem-shaped slug the shipped OF data can construct (all-souls), the same closed-population discipline Rubrics_ef's own Colour.Black proxy uses. preface returns None unconditionally, a measured verdict rather than a stub. The module header transcribes the 2002 Missal's own "INDEX PRAEFATIONUM" in full: nine of Preface.t's 15 EF-cited constructors name an OF occasion with multiple distinct texts (Nativity x3, Lent x4-9, Easter x5, Ascension x2, Holy_spirit x2, Bvm x5, Apostles x2, Common split across two independent 7/8-member families, Requiem x5) -- structurally lossy to collapse into one constructor. The other six (Epiphany, Trinity, Sacred_heart, Christ_the_king, St_joseph, Holy_cross) match cardinality with a single OF text apiece, and were checked and rejected anyway: Preface.t's own constructors are RG-cited by construction, and IGMR n. 364's own "quibus Missale Romanum ditatur" (prefaces the Missal is ENRICHED with) is direct textual evidence the 2002 collection is not a bare carry-over -- corroborated directly for Sacred Heart, whose OF text was read and found to be a different composition from its RG 491 namesake. Reusing any of the six on cardinality and theme alone would be the exact "sounds similar" trap the task brief warns against. test/test_rubrics_of.ml: 26 table-driven cases. Every temporal-origin row builds observed via Temporal_of.temporal on a real date; every sanctoral-origin row (a solemnity in Lent, an ordinary Festum, a weekday obligatory memorial, All Souls) pairs a real Temporal_of.temporal result with an observed record copied verbatim from data/of/calendar-2002.sexp, since no full OF Rite.t/Calendar pipeline exists yet to derive one. Includes same-date Gloria/Credo pairs that prove the two rules genuinely diverge (a Sunday of Advent, an ordinary Festum) rather than one silently mirroring the other. dune test: exit 0, 845 tests. make check (COLITUR_EXHAUSTIVE_SWEEP=1, full 1583-9999 sweep): exit 0, 851 tests, 303.6s, both run unpiped in the foreground. git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/ lib/kernel/ data/ef/ is empty.
* feat(of): post-2002 GRC amendment overlays, one file per decreeLukasz Kasprzak2026-08-263-0/+231
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Adds data/of/amendments/*.sexp: 13 overlay files, one per decree, carrying the General Roman Calendar's amendments since the 2002 typical edition (docs/research/of/PROVENANCE-amendments.md's own 15-item inventory, items 10-12 sharing one decree; items 2 and 3, though issued the same day, carry different protocol numbers and are counted as two decrees). 14 Add directives (Padre Pio, Juan Diego, Guadalupe, John XXIII, John Paul II, Mary Mother of the Church, Paul VI, Loreto, Faustina Kowalska, Gregory of Narek, John of Avila, Hildegard of Bingen, Teresa of Calcutta, John Henry Newman) and 2 Edit directives (Mary Magdalene's rank memoria->festum; Martha/Mary/Lazarus's name widened, same slug, same date and rank -- a continuation of the existing memorial per the decree's own text, not a suppression and replacement). Mary, Mother of the Church is the one movable entry (Easter_offset 50, the Monday after Pentecost) -- verified against the decree's own text and against Colitur_kernel.Computus.gregorian_easter in three real years, no kernel change needed. data/of/calendar-2002.sexp is untouched, per spec sec4.1. Adds test/test_amendments_of.ml: every file parses, each has a pinned SHA-256, applying all 13 in decree order yields 222 entries (208 base + 14 Add) with an empty Overlay.merge diagnostics list, and Mary Mother of the Church resolves to the Monday after Pentecost in three real years. Two Precedence_of interactions are recorded, not fixed (out of scope for a data-only task): a movable Memoria_obligatoria can land on an existing fixed one (verified for 13 June 2011), a genuine band tie Normae n. 14 does not address and Precedence_of.ml does not implement; and adding Hildegard of Bingen changes which of two ad-libitum memorials on 17 September Precedence.resolve reports as observed, by alphabetical tie-break alone.
* fix(of): correct subject over-match, provenance truth, and stale hazard commentLukasz Kasprzak2026-08-252-11/+76
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Four fixes from fix round 2 review: classify_subject's "maria"/"b.m.v" substring test over-matched 12 saints who merely carry "Maria" in their own name (Maximilian Mary Kolbe, John Mary Vianney, Mary Magdalene among them) as Bvm. Replaced with an explicit BVM_DATES table of the 13 genuinely Marian entries. Bvm 25 -> 13. The provenance header claimed lectio is "never a source", which omitted that slugs and English names both come from it. Corrected to state exactly what does (slugs, en names) and does not (every substantive field) come from lectio. A missing lectio file used to degrade silently to a different SHA-256 with no en names and no warning; it now fails loudly. lectio's own SHA-256 is now pinned alongside the Missal PDF's. Four more entries carried a second pdftotext artifact distinct from the letter-spacing already handled -- a combining diacritic rendered as a stray spacing character (Kim Taegŏn/Chŏng Ha-sang, Đũng Lạc, Makhlūf, Kęty). Hand-repaired the same way Peter Damian was, each corroborated by the identical corruption recurring in the Missal's own index. precedence_of.ml's PHASE 3 HAZARD comment about All Souls (2 November) was asserting a gap that no longer exists now that OF sanctoral data ships. Added test_all_souls_beats_an_ordinary_sunday, which resolves the shipped All Souls entry against a real per-annum Sunday (2025-11-02) through the actual Precedence.resolve pipeline, and updated the comment to record the resolution instead of demanding it.
* fix(of): add the Sacred Heart of Jesus and Immaculate Heart of MaryLukasz Kasprzak2026-08-251-8/+68
| | | | | | | | | | | | | | | | | | | | Both are printed in the 2002 calendar table, immediately after 30 June, as prose headings rather than (kalends, day) rows -- the shape parse_rows' generic movable-heading skip branch was built for, which is why the first pass silently dropped them. They belong in the transcription, not in Temporal_of: adding them completes the table this file transcribes rather than amending the temporal cycle. Produced as Date_spec.Easter_offset entries (68 and 69 respectively), the same variant already used for EF's Rogation Wednesday and needing no kernel change. Pentecost is Easter+49, so the Friday and Saturday after the Second Sunday after Pentecost are Easter+68/+69 -- verified against three real Easter dates (2026, 2027, 2035), not merely computed on paper. The extractor now recognises these two headings specifically among the seven "Dominica/Feria/Sabbato ... :" headings the table contains; a full audit of the other five confirms each is already covered by Temporal_of and correctly stays excluded here. 206 -> 208 entries.
* feat(of): transcribe the 2002 General Roman CalendarLukasz Kasprzak2026-08-253-1/+210
| | | | | | | | | | | | | | | | | Extracted from the Latin typical edition, which is the authority; lectio's roman-calendar.ini is a cross-check only and every divergence is adjudicated against the Missal in the provenance header. A blank grade column means Memoria ad libitum, per the calendar's own footnote, rather than a missing value. 206 entries; 1 January, 6 January and 25 December are deliberately excluded because Temporal_of.named already computes them, and shipping them here too would create a duplicate candidate for the same day. Colour is derived from IGMR n. 346, cited per class in the extractor; the lectio cross-check surfaces one rank divergence (22 July, Mary Magdalene -- lectio reflects the 2016 post-2002 elevation decree, out of scope here) and 17 colour divergences, all adjudicated in the provenance header.
* fix(of): Nativity Vigil no longer suppresses a Sunday of AdventLukasz Kasprzak2026-08-252-11/+48
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Temporal_of.named claimed 24 December unconditionally as of-nativity-vigil (Sollemnitas), and temporal's dispatch matched named before sunday_slug. On a Sunday 24 December, the Fourth Sunday of Advent -- Tabula I.2, Normae n. 5's own privileged Sundays of Advent, Lent and Easter -- was silently replaced by the Vigil. named now returns None for 24 December when that date is itself a Sunday, so temporal's dispatch falls through to sunday_slug and the day resolves as of-advent-sunday-4. The Vigil Mass is an evening Mass of the following solemnity, not an office belonging to 24 December's own civil day -- the same civil-day-granularity reasoning already applied to Holy Thursday (Precedence_of.band's entry-1 comment) and to expected-divergences-litcal.sexp's L1 entry. Every non-Sunday 24 December is unaffected. Audited every other named entry for the same latent shape (see .superpowers/sdd/of-r8-advent-iv-report.md): none of them has it, either because the date can never fall on a Sunday, or because the date IS the definition of that Sunday (Palm Sunday, Easter, Pentecost, Trinity, Christ the King), or because the competing movable-Sunday windows are constructed to exclude that date. Added test_nativity_vigil_yields_to_advent_sunday, covering 2028-12-24 and 2034-12-24 (both inside the litcal fixture window) and the 2026-12-24 non-Sunday control. Re-ran the litcal layer: no allow-list counts moved, since it compares only season and Ordinary-Time week, and both readings of 24 December are "advent" either way. Updated test_precedence_of.ml's "entry 3, not 9" comment, which had carried this as a known, unfixed defect, to describe the fix; its own pinned assertion (a hand-built Thursday candidate) needed no change.
* fix(of): correct season citation, note All Souls gap, guard transfer searchLukasz Kasprzak2026-08-251-1/+20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fix wave from the final whole-branch review of OF Phase 2 (0 Critical, 5 Important, 8 Minor). Applies the five items ruled in-scope and records two more, per the controller's R7/R8/R9 rulings. Fixes: - temporal_of.ml: season-boundary header cited "Normae n. 71-77", a range that does not exist (the Normae end at n. 61) and collides with real IGMR nn. 71-77 (Preparation of the Gifts) in the same PDF -- EF leakage from RG 71-77. Corrected to Normae Titulus II, nn. 17-47, with a note against reintroducing the wrong range. - precedence_of.ml entry 3: added the Tabula's second clause (Commemoratio omnium fidelium defunctorum / All Souls) to the comment, and recorded the Phase 3 hazard -- Vocab_of.rank has no constructor for it yet, and every natural tagging choice gets 2 November's precedence wrong in a different way. No code change; nothing can construct the entity yet. - precedence_of.ml transfer_target's search: added a domain_max_date guard (following Rite_ef.Precedence_ef's existing shape and reasoning) so the walk can no longer probe Computus past 9999-12-31, and it now returns the last date visited on bound exhaustion instead of failwith-ing. .mli updated to drop the @raise Failure clause. - precedence_of.ml is_privileged_sunday: added its own inline Tabula I.2 citation, matching its sibling helpers. - precedence_of.ml entry 10: trimmed the reused "universal or temporal-origin" comment -- Temporal_of never constructs a Memoria_obligatoria candidate, so the temporal-origin half was vacuous there. Recorded without fixing (both comment-only, per explicit scope ruling): - test_precedence_of.ml: corrected the entry-3 Nativity Vigil test's characterisation. The pinned band-30 assertion (2026-12-24, a Thursday) is correct, but is not the value on a 24-December Sunday (e.g. 2028, 2034), where is_privileged_sunday instead bands the same candidate 20. Named the underlying Phase 1 defect this exposes -- named() claims 24 December unconditionally and is matched before sunday_slug, so the Fourth Sunday of Advent gets no office at all in those years, invisible to the litcal layer -- as known and carried to the next task. - precedence_of.mli transfer_target: documented Normae n. 56(f) (St Joseph on Palm Sunday anticipates backward to 18 March) as a known, cited, unimplemented fourth transfer rule, and named the underlying kernel constraint (Rite.t.transfer_target's strictly-later obligation, justified only by the EF's RG 96) as the reason a real fix needs a kernel change. dune test: 799 tests, exit 0. make check (exhaustive sweep): 805 tests, exit 0, 224.7s. No kernel/rite_ef/data changes (git diff empty).
* feat(of): transfer_target per Normae nn. 5 and 60Lukasz Kasprzak2026-08-251-1/+77
| | | | | | | | | | | | | | | | Three rules, in priority order: the Annunciation in Holy Week goes to Easter+8; any other solemnity on a Sunday of Advent, Lent or Easter goes to the following Monday (n. 5, which n. 60 defers to explicitly), except on Palm Sunday or Easter Sunday; otherwise the nearest later day free of Tabula entries 1-8. The n. 5 Monday rule is easy to miss -- n. 60 names it only by cross reference, and reading n. 60 alone yields a single search rule where the sources give three. Shaped to match Rite.t.transfer_target so a later phase wires it unchanged, with its own step bound: Calendar's round guard bounds rounds, not one call's internal search.
* feat(of): admit and vigil_feast, both argued degenerateLukasz Kasprzak2026-08-251-1/+24
| | | | | | | | | | admit returns the empty list because disposition never returns Commemorate; vigil_feast returns None because the OF has no vigil office that its feast's movement could orphan -- 'of-nativity-vigil' is a Mass formulary of the solemnity, not a separate celebration. Both are argued from the sources rather than stubbed, and Precedence_of.rules now assembles the four functions the kernel resolver consumes.
* feat(of): disposition per Normae n. 60Lukasz Kasprzak2026-08-251-1/+57
| | | | | | | | | | An impeded solemnity transfers; every other loser is omitted for that year ('Reliquae celebrationes eo anno omittuntur'). Commemorate is never returned, and that is the rule rather than a gap: the OF has no commemoration of a losing celebration in the EF's sense, and an optional memorial is an election rather than a commemoration. A property over every rank pairing asserts it, so a later edit cannot quietly introduce one.
* test(of): prove the Tabula is total over 1583-9999Lukasz Kasprzak2026-08-251-1/+40
| | | | | | | | | | A hand-transcribed 13-entry table is only trustworthy if nothing the engine constructs falls through it. Sampled property plus an exhaustive sweep behind COLITUR_EXHAUSTIVE_SWEEP, matching the claim Precedence_ef.band already carries for RG 91's 28 entries. Mutation-proved rather than assumed: deleting the entry-13 branch reddens it on a named date.
* feat(of): transcribe Tabula part III, completing the tableLukasz Kasprzak2026-08-251-1/+50
| | | | | | | | | | | | | | | | Entries 10-13. Entry 12 is one undivided row -- the Tabula draws no universal/proper split for optional memorials, unlike entries 3/4, 7/8 and 10/11 -- so it deliberately does not read the layer. Entry 13's four clauses need no date arithmetic of their own: every privileged feria is entries 2 and 9, tested earlier in the chain, so what reaches the final branch is exactly the residue those clauses name. Entry 10 uses [not (is_proper layer)], not the brief's original [is_universal layer]: is_universal was deleted as dead code in Task 2, because every temporal-origin candidate carries layer = "temporal", never universal_layer, so a positive is_universal test can never match one -- the same correction entries 3, 5 and 7 already apply.
* test(of): pin the Nativity Vigil at entry 3, not entry 9Lukasz Kasprzak2026-08-251-0/+25
| | | | | | | | | | | | | | | | | | | | | | | Fix round 1 (coordinator review). The Nativity Vigil (24 December, Sollemnitas) bands at entry 3 (30) rather than entry 9 (90) as a direct consequence of this task's ruling, but nothing pinned it -- a future branch-reorder (e.g. inserting an entry between 3 and 9, or loosening entry 9's date guard) could silently revert it to 90 with the suite green. Adds one row to part2_cases with a comment recording: the Tabula's own entry 9 textually covers 24 December as a late-Advent feria, yet Phase 1 gives that civil day the of-nativity-vigil identity at Sollemnitas because the Vigil Mass is celebrated that evening -- the same civil-day-granularity shape Holy Thursday already settles (entry 2, not entry 1); Phase 2 classifies Phase 1's emitted identity rather than overriding it. Unobservable on the universal calendar today (no competing candidate on 24 December); live under a proper/diocesan overlay, where a proper solemnity (entry 4, band 40) loses to the Vigil at 30 but would beat it at the textually-arguable 90. No production code change -- test-only, per the coordinator's ruling to pin current behaviour rather than alter it.
* feat(of): transcribe Tabula part II into precedence_ofLukasz Kasprzak2026-08-251-1/+87
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Entries 5-9. Entries 5 and 7 differ only by subject -- 'Festa Domini' against 'Festa beatae Mariae Virginis et Sanctorum' -- so they key on Celebration.t.subject, the same field RG 91 entry 14 uses on the EF side, never on the slug. Entry 9's three clauses (late-Advent ferias from 17 December, the Nativity octave, Lenten ferias) are what separates a privileged feria from the ordinary one at entry 13. CORRECTION to the task brief, applied here rather than after: entries 3, 5 and 7 test 'not (is_proper layer)', not 'is_universal layer'. Every temporal-origin candidate Temporal_of.temporal constructs carries layer = "temporal" unconditionally, never universal_layer, so a positive universal-layer test silently excluded every temporal-cycle solemnity and Lord-subject feast from these entries -- Corpus Christi came out unclassified outright, and Trinity/Christ the King/Holy Family/the Baptism of the Lord landed one entry too low. is_universal is now dead and removed; entry 3's branch (written in Task 1) is amended along with the new entries 5 and 7. Only entries 4 and 8 still test is_proper directly, matching the Tabula's own proper-calendar rows. Also fixes a pre-existing typo in the brief's own 'entry 6: a Sunday per annum' test row: rank was Sollemnitas, which -- being tested at entry 3, earlier in the chain -- made the row fail regardless of the is_universal/is_proper question, since no real Sunday per annum is ever Sollemnitas. Changed to Festum, matching the adjacent 'Sunday of Christmas time' row and real Temporal_of output.
* feat(of): transcribe Tabula part I into precedence_ofLukasz Kasprzak2026-08-252-1/+73
| | | | | | | | | | | | The Tabula dierum liturgicorum's entries 1-4, one band branch each, scaled x10 as Precedence_ef's own table is. The Triduum is identified structurally by Easter offset because Temporal_of emits Good Friday and Holy Saturday as ordinary Lent week-6 ferias, leaving no slug to key on. Holy Thursday sits at entry 2, not entry 1: the Tabula's own entry 2 runs 'a feria II ad feriam V inclusive' and the Triduum opens with an evening Mass inside that civil day. Same granularity decision the litcal layer's L1 records on the season axis.
* test(of): validate Phase 1's Ordinary Time resumption against litcalLukasz Kasprzak2026-08-254-1/+4766
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Adds a sixth-lineage external validation layer for the OF rite module's Phase 1 temporal cycle, targeting the single highest-risk function that report named: the two-block Ordinary Time week arithmetic (Normae n. 44), which has no external witness anywhere in Phase 1's own suite and whose output selects the Mass formulary, not merely a label. Witness: litcal (Liturgical Calendar API), Apache-2.0, already archived and SHA-256-pinned by the sibling lectio project. AELF was considered and correctly excluded earlier (its CGU forbids redistribution) -- litcal is what it was replaced with. - tools/extract_litcal_ordo.py verifies all twelve archived calendar-YYYY-en.json files (2024-2035; the task brief said eleven, but twelve are actually pinned in lectio's manifest -- all twelve are used) against lectio's own manifest.tsv, then builds one fixture spanning the contiguous 4382-day range those twelve liturgical-year files cover. - test/fixtures/litcal-temporal-2024-2035.sexp: one row per day, season and (where witnessed) Ordinary Time week, with full provenance, licence, per-year Easter dates and the row-selection rules in its own header. - test/test_litcal_of.ml calls Temporal_of.temporal directly (no Rite.t) and compares 1:1 against the fixture: zero Ordinary Time week mismatches on all 1876 witnessed days (zero tolerance, no allow-list); the only season divergence class is the Sacred Triduum, which litcal tags with a season value colitur's vocabulary deliberately has none of -- one cited allow-list entry, data/of/expected-divergences-litcal.sexp, 36 rows, exact. - Mutation-tested: dropping the backward-from-34 adjustment reddens this layer (1511 of 1876 days) plus three pre-existing Phase 1 tests; the mutation was not committed. Does not touch lib/rites/rite_ef/, data/ef/, or lib/rites/rite_of/ -- this validates Phase 1, it does not change it. Full suite: 786 tests via dune test, 791 via make check, exit 0 both; lib/bin diff empty throughout.
* feat(of): Phase 1 of the OF rite module — vocab_of, temporal_ofLukasz Kasprzak2026-08-253-2/+396
| | | | | | | | | | | | | | | | | | | | | | | | | | | The first code for a second rite. lib/rites/rite_of/vocab_of implements the OF's five-way rank vocabulary (Sollemnitas/Festum/Memoria_obligatoria/ Memoria_ad_libitum/Feria) and five-season vocabulary (Advent/Christmas/ Lent/Easter/Ordinary_time), each constructor cited against the 2002 Missale Romanum's Normae universales and the Tabula dierum liturgicorum. temporal_of implements season boundaries, week numbering — including Ordinary Time's own two-block resumption, derived from the Missale's own "HEBDOMADA I/XXXIV 'per annum'" headings and empirically verified across the whole 1583-9999 domain — named temporal days, and Sunday/ferial slugs. Both satisfy Colitur_kernel.Temporal.RITE with no kernel change. No Rite.t is assembled and no CLI wiring is added (Phase 2's own scope); lib/rites/rite_ef and data/ef are untouched, verified byte-identical against the v1.0.0 tag across a domain-spanning sample. test/test_temporal_of.ml adds unit tests for every boundary and named day plus seven QCheck properties (seasons contiguous, Ordinary Time weeks in 1..34, Advent always four Sundays, slug uniqueness, weekday agreement, determinism, consecutive liturgical years partition civil time), with a committed exhaustive sweep mirroring test_validate.ml's own convention. Full account, including the Ordinary Time derivation's evidence and a list of EF concepts that do not carry over, in .superpowers/sdd/of-phase1-report.md (untracked, gitignored).
* feat(ordo): wire the extraordinaryform.org preface column in as a third witnessLukasz Kasprzak2026-08-246-0/+1603
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Extends the preface comparison (RG 482-499) with a third, independent publisher, extraordinaryform.org's three annual Ordo editions (2024-2025, 2025-2026, 2026-2027), previously declined for two reasons that are now resolved: - "Advent" is not a preface: RG 494(b) grants the Trinity preface on every Advent SUNDAY, RG 498 the residual Common on every Advent FERIA; this source's own "Advent" label covers both indiscriminately (confirmed live on all 11 non-impeded Advent Sundays in the corpus). Classified by the day's own civil weekday, not a static table. - The known Vigil-of-St-Lawrence gap is reconfirmed, with a nuance not present in the earlier audits: colitur's own RG33-third-trigger now omits the vigil in some years too, so the corpus's gap and colitur's answer coincide in 2025 (a full match, not a coincidence) while 2027 still shows the classic identity-wrong-but-preface-invisible shape. Re-derives the known menu-trap office filter (Requiem-skip, "and "-chain) against the live JSON and finds a second, general pattern: "Saturday of Our Lady" (RG78's BVM Saturday Office), whenever listed, is unconditionally the office -- checked exhaustively across all 38 instances in the corpus, zero counter-examples. A third, generic "absorption" rule was deliberately not built: it would resolve 2 of the 11 residual divergences but wrongly misfires on a structurally identical but semantically different shape (an optional Rogation-day Mass option). The JSON's own extraction carries a genuine classifier gap: parse_ordo.py's hardcoded PREFACES set omits "Christ the King", misfiling it into comms; recovered in the generator, not left blank. 11 of 1088 comparable rows diverge, resolving to 7 adjudicated root causes (E1-E7): two Commemoration_only Marian titles reduced to commemorations (Mt Carmel, Ransom), two genuine sanctoral data gaps cross-confirmed absent by the FIUV/LMS witnesses (Miraculous Medal, St John before the Latin Gate), two single-witnessed efdotorg outliers against FIUV/LMS agreement (St Michael's Dedication, All Saints on a Sunday), and one isolated source labelling slip. Mutation-tested: corrupting the RG495 BVM-Saturday clause reddens 9 tests across this new layer and both existing Ordo witnesses. Per-edition agreement: 358/363 (2024-2025), 358/363 (2025-2026), 361/362 (2026-2027).
* feat(ef): RG 497, the Conversion of St Paul takes the Apostles prefaceLukasz Kasprzak2026-08-231-1/+70
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 497 ("in Missis festivis et votivis Apostolorum et Evangelistarum") carries no natalicium restriction, unlike RG 475(e)'s Creed clause -- preface_apostle_slugs already includes one non-natalicium sibling (In Commemoratione S. Pauli, 30 June) on this exact reasoning. conversion-of-st-paul (25 January) was left off pending a witness: the FIUV Ordo's own single window has 25 January impeded by a Sunday. Now witnessed four times, two independent publishers, four civil years: the Latin Mass Society Ordo's 2023-2024 and 2024-2025 editions both read "Pr of the Apostles" on their own unimpeded 25 January (2024, 2025); extraordinaryform.org's 2024-2025Ordo.pdf and 2026-2027Ordo.pdf independently agree on their own unimpeded years (2025, 2027, spot-checked, not wired in as a fixture). Every source's 2026 edition agrees 25 January is impeded that year and produces no data point at all. No source disagreed with the textual reading. Surveyed every other Apostle/Evangelist-named slug in data/ef/sanctoral.sexp before concluding the list needed exactly this one addition: the Dedication of the Basilicas of Sts Peter & Paul (18 November) is confirmed NOT an RG 497 trigger (all three LMS editions read "Pr of the Dedication of a Church", not "Apostles" -- it is about the buildings, not the Apostles in person); the Vigil of Sts Peter & Paul (28 June) is confirmed "Common Pr", corroborating the pre-existing "no vigil ever reaches this branch" comment. Blast radius, measured exhaustively (1583-9999, new permanent sweep in test_rubrics_ef.ml): 7217 domain years have 25 January unimpeded and observed; all 7217 now read Apostles, zero exceptions. Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* feat(ordo): wire the LMS preface column into the witness suiteLukasz Kasprzak2026-08-234-993/+1709
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Extends tools/extract_lms_ordo.ml's row type with a praef field (the first "Pr of ..." / "Common Pr" match in each day's universal block, same first-in-reading-order discipline already used for Gl/Cr) and regenerates all three LMS fixtures. Also brings lms-ordo-2024-2025.sexp under the generalised, probe-verified provenance template the Witnesses task previously added only for the two newer editions -- it had kept Task 6's original hand-written header. Characterisation finding: this Ordo prints OPTION LISTS ("Pr of X or Pr of Y or Common Pr"), not a single value per day, and several named options (Martyrs, All Saints and Patron Saints, the Dedication of a Church, the Most Holy Sacrament, St John the Baptist, the Angels) are genuinely absent from RG 484-497's own fourteen named prefaces. Every instance found in the universal block pairs such an extra with a genuine RG-482 answer, so test/test_lms_ordo.ml compares by membership (classify_praef_options, a 15-entry prefix table split on the standalone token "or"), not string equality. Opposite-prediction probes (Nativity/Apostles/Requiem) are hard- verified inside the extractor itself before any fixture is trusted, the same discipline already used for the Creed column. Result: 396/396, 399/399, 398/398 comparable rows agree across the three editions (Good Friday excluded per edition, no Mass that day), zero allow-list entries needed. extraordinaryform.org's own three editions were characterised but deliberately not wired in this pass -- see .superpowers/sdd/2026-08-21-colitur-celebrant-rubrics-phase1/preface-witnesses-report.md for the full account (the field is genuinely per-day, contrary to a naive header-count read, but carries its own classification anomaly during Advent and reconfirms the corpus's known St Lawrence vigil defect, and building a full extractor for it is a separate, comparably large task not needed to decide the RG 497 question this same report settles). Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* feat(ef): the Mass preface, RG 482-499Lukasz Kasprzak2026-08-225-26/+573
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Item 2 of Phase 3 (celebrant-rubrics-phase1), the bulk of this phase and the last EF Mass rubric this project scoped: which preface is said, deferring to seasonal/proper-title rules for the fourteen named prefaces, then RG 498's Common residual. RG 482 gives the resolution chain: the Mass's own proper preface, failing that the seasonal one, failing that Common. Read literally, RG 484-497 look like fourteen separate rules, but each numbered rubric's own propria/de-Tempore pair produces the SAME preface identity either way, so the whole chain collapses into one priority- ordered decision: title/mystery triggers (Holy Cross, Sacred Heart, Christ the King, Trinity, St Joseph, BVM, the Nativity octave, the Apostles, Epiphany), each independent of season, then six seasonal windows (Nativity, Epiphany, Lent, Holy Cross/Passiontide, Easter, Ascension, Holy Spirit, Trinity-for-ordinary-Sundays), then Common. The "one genuinely unproven piece" the design spec worried about -- per-feast proper prefaces extracted from the Missal's propers at scale -- turned out not to be a large-scale extraction problem at all: every one of the fourteen propers is a closed, small, subject/slug-keyed trigger (mirroring Precedence_ef.band's own RG 91 table), not thousands of individual saints' pages. The one genuine open question (RG 488, the Chrism Mass) is N/A: this engine resolves one Mass per civil day and has no separate Chrism-Mass dimension. Preface is a new kernel type (lib/kernel/preface.ml[i]), not an EF-specific one, the same placement as Colour/Subject/Mass_formulary: Liturgical_day.t is parameterised only over season/rank, so any field it carries generically must live in the kernel even though only EF constructs a value of it today. Wired exactly as creed/gloria were (Rite.t.preface, Calendar.ml, Rite_ef.context), but Preface.t option, not a bare bool: unlike creed/gloria, a preface is said only at a Mass, and Good Friday (1955-restored Holy Week) resolves an observed celebration but has no Mass at all -- None is the honest answer there, and also the neutral value an unimplemented rite returns. Two priority-order findings only the oracle settled, not derivable from the Latin text in isolation, both cross-checked against 358 individually classified entries in the FIUV Ordo's own praef column (test/fixtures/fiuv-ordo-2025-2026.sexp, already captured, wired up here as a new comparison axis): RG 484(b)'s own "except Masses with a proper of the divine mysteries or Persons" is narrower than every other window's implicit exception (an Apostle inside the Nativity octave is overridden to Nativity; outside it, keeps his own preface even inside another window); and RG 495's "et votivis" half is live for the one office this engine models without a votive-Mass dimension (the Saturday Office of the BVM), which also proved a vigil is not a "festum" for this purpose (the Assumption's own vigil takes Common, not BVM, correcting an initial reuse of Precedence_ef.marian_slugs that had no reason to make that distinction for its own, different rubric). A third, RG16(a)-shaped fix landed the same way: RG 494(b)'s own Trinity grant must read the day's TEMPORAL season, not the celebration that actually won it, or a Class1 feast with no preference of its own (All Saints) wrongly falls to Common on a Sunday it merely commemorates. colitur rubrics gains a sixth TAB-separated column. Domain-wide 1583-9999 exhaustive sweep confirms every Christmastide day resolves Nativity/Epiphany/Bvm, every Paschaltide day one of its own three windows or a season-independent title, every Lent day Lent or a title, and Passiontide legitimately produces Easter exactly once a year (the Vigil Mass) -- 8416 of the domain's 8417 years, the one short year being the domain's own upper boundary (the liturgical year opening in 9999 cannot construct dates in year 10000, a pre-existing edge this sweep re-confirms rather than a new one). day/readings verified byte-identical to the branch's own state before this phase (commit 9c96e0a) across a 455-year sample spanning the whole domain -- not literally to the v0.10.1 tag, which 22 earlier commits on this same branch (Phase 1/2, the bissextile shift, the Rogation colour fix) had already moved past before this phase began.
* feat(ef): RG 111(a), the sung-Mass commemoration capLukasz Kasprzak2026-08-222-2/+91
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Item 1 of Phase 3 (celebrant-rubrics-phase1): the "at Low Mass" commemoration-placement rule the design spec recorded as unread. It is not a new rule -- it is the sung/low axis of RG 111, which colitur already implements. RG 111(a) (LT.txt, "Ratio admittendi commemorationes"): a liturgical day of the first class, AND any non-conventual sung Mass regardless of the day's own class, admits at most one commemoration, and only if it is privileged. (b)/(c)/(d), the same rubric's remaining clauses, give the wider caps colitur's admit already computes -- which is exactly the LOW MASS answer. Exposed as Precedence.sung_mass_commemorations, a pure derivation over the existing Low-Mass admitted list (filter to Privileged, keep the first), not a new stored field on Liturgical_day.t: the input list is already validated and privilege-tagged, so a second field would only create a second place for the same fact to drift out of sync with the first, with no new information gained. Liturgical_day.t.commemorations is now documented as the Low Mass set explicitly, removing the ambiguity its .mli previously left unstated. Tested against synthetic Low-Mass sets (none/one/two privileged, already-first, empty) and two real calendar days resolved through the normal Cal.day pipeline: 2026-08-14 (Vigil of the Assumption, an ordinary-only commemoration, dropped at Sung Mass) and 2026-04-25 (the Major Litanies, RG 80/109(f), privileged, kept at both Masses).
* fix(ef): ferias after the Ascension repeat its own Mass, not a Sunday'sLukasz Kasprzak2026-08-2216-24/+225
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Ascension has no octave under the 1960 rubrics -- confirmed structurally: the Proprium de Tempore's own table of contents runs straight from "In Ascensione Domini" to "Dominica post Ascensionem" with no per-feria headings in between. Step 3 of the reading chain therefore fell through to Preceding_sunday on every unoccupied weekday in that stretch, walking each one back to the 5th or 6th Sunday after Easter's own citation. The Missal's own Proprium de Tempore carries an explicit marginal rubric immediately after the Ascension's Mass propers, before "Dominica post Ascensionem" begins: "Diebus a feria VI post Ascensionem usque ad feriam VI ante vigiliam Pentecostes inclusive, dicitur Missa de festo Ascensionis, cum Gloria et praefatione de Ascensione, sine Credo et Communicantes proprio" -- every feria without its own proper, from the Friday after the Ascension through the Friday before the Vigil of Pentecost, repeats the Ascension's own Mass. RG 490(b) corroborates independently: it assigns the Ascension preface "tamquam de Tempore" across that identical window, which only makes sense if the Mass actually repeated there is the Ascension's, not a numbered Sunday's (whose own Mass would carry no need for a preface override at all). Witnessed directly, not only inferred from the rubric: the LMS Ordo prints "Mass of the Ascension" literally on every affected day across all three of its fixture windows, in BOTH the tail of Ascension's own week and the following week -- ruling out the alternative reading (that the second week instead resumes "Dominica post Ascensionem"'s own Mass, which a first pass over the FIUV Ordo's own week-labelled titles could suggest at a glance; its own [praef] field for the same two dates independently reads "Ascensionis", resolving the apparent conflict). Implemented as data, not logic, matching this codebase's own established precedent for the identical RG 299 shape (the week after Trinity Sunday, data/ef/lectionary.sexp's [trinity_week_entries]): six new hand-authored entries, [ascension_week_entries] in tools/bootstrap_lectionary.ml, regenerated against a git-pinned copy of lectio's own source ini (SHA-256 verified unchanged) so the regeneration carries no unrelated drift. The window's two Saturdays are deliberately excluded -- RG 78's votive Office of Our Lady on Saturday always wins an otherwise-unoccupied Class4 Saturday first, before step 2 (this fix's own entry point) is ever reached, so an entry for either slug would be dead data. Blast radius measured in-process across the full 1583-9999 domain (throwaway probe, not committed): 16 350 days total across the six slugs, every one resolved via step 2 (Own_slug), zero anomalies, and the window's two Saturdays confirmed to contribute zero hits via this cause across the whole domain, corroborating the RG78-interception reasoning by direct sweep. Closed L2 in data/ef/expected-divergences-lms.sexp. New cited entries: C41 in data/ef/expected-divergences.sexp (95 of 16801 rows, 2005-2050 -- lectio has no equivalent rubric and still walks these ferias back); M31 in data/ef/expected-divergences-missalemeum.sexp (3 rows in the 2026-2027 window, plus the same shape independently reconfirmed in the 2035 and 2038 live-capture windows) -- missalemeum, Divinum Officium's lineage, still gives the same wrong answer colitur itself used to give, which is why these rows used to agree with M18's shape and now escape it (M18 373 -> 370, the same mechanism M23-M27 established when the citation axis was first built). Golden pin: 2031-05-23, the exact date the bug report was filed against. Mutation-tested: reverting data/ef/lectionary.sexp alone (a git-diff capture/restore, not a source-code revert, since the fix is data) reddens 19 tests across the differential, both oracle fixtures, all three LMS Ordo windows, the golden pin, the lectionary unit test and every Render golden template; restored and reconfirmed green (681 tests; 683 under COLITUR_EXHAUSTIVE_SWEEP=1, ~175s).
* fix(ef): Rogation Monday/Tuesday are white, not violet (RG 119)Lukasz Kasprzak2026-08-2213-34/+96
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Temporal_ef.temporal's Rogation Monday/Tuesday branch hardcoded Colour.Violet with no citation (data/ef/expected-divergences-lms.sexp's L6, surfaced by the LMS Ordo). RG 119 assigns white to the de-Tempore Office and Mass from the Paschal Vigil Mass to the Pentecost Vigil Mass exclusive; Easter+36/+37 sit inside that window. RG 128's own exhaustive four-case violet list never names Rogation days, and its vigil clause (d) is doubly inapplicable: these are not vigils, and they are inside Paschaltide, not outside it. RG 88 explains the violet actually seen in print sources: "de Litaniis minoribus nihil fit in Officio, sed tantum in Missa" -- the violet belongs to the Rogation Mass/procession, a distinct votive text this engine does not model, not to the day's own Office. Witnessed directly: LMS Ordo 2023-2024, 2024-05-06, "FERIA IV Cl W" with "Gl", the Office and Mass printed separately from the optional violet votive Mass of the Rogations. Cascade verified, not assumed: RG 432(b) omits the Gloria on every violet Mass, so the wrong colour was also silencing the Gloria on these two weekdays even though RG 237(e) (Breviary, ordinary Paschaltide ferias say Te Deum) already made it true underneath. White removes the 432(b) block, and the Gloria now follows Te Deum via 431(a), matching the Ordo's own "Gl" -- confirmed with dedicated golden pins in both test_golden.ml (colour) and test_rubrics_ef.ml (the Gloria flip), 2028-05-22/23 (neither day impeded that year). Closed L6 in data/ef/expected-divergences-lms.sexp. Reopened C8 in data/ef/expected-divergences.sexp (26 of 16801 rows, 2005-2050): lectio independently computes these two days violet too, so the two engines used to agree by sharing the same uncited assumption; colitur's colour is now correct and lectio's is not, so the comparator disagrees again, for the opposite reason. While reopening C8, found and corrected a genuine misattribution in its own prior closure note, which claimed lectio's violet was "RG 128(d)" -- 128(d) governs vigils outside Paschaltide and has nothing to do with Rogation days. Checked docs/research/rules-register.md for the same claim: not present there, but a related conflation was found and corrected at its O'Connell section 4(d) note, which had used a Rogation MASS/procession citation to "confirm" the day's own OFFICE colour. Blast radius measured in-process across the full 1583-9999 domain (throwaway probe, not committed): 4722 days total (2368 Rogation Mondays + 2354 Rogation Tuesdays actually observed outright, i.e. not impeded by a stronger sanctoral feast that year), all Violet->White, all with the Gloria flipping false->true alongside, zero anomalies. Mutation-tested: reverting the branch to Colour.Violet reddens 16 tests across Rubrics_ef, the lectio differential, the LMS Ordo suite and both golden pin files; restored and reconfirmed green (680 tests).
* fix(ordo): correct L5/F3 misadjudication -- Cum Sanctissima, not a gapLukasz Kasprzak2026-08-223-496/+765
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | data/ef/expected-divergences-lms.sexp's L5 (16 dates, LMS Ordo Gloria=true vs colitur's false) and data/ef/expected-divergences-fiuv.sexp's F3 (6 dates, FIUV Te Deum=true vs colitur's false) were both misadjudicated as colitur's own gap under an RG 302(b)/431(b) hypothesis. Both are wrong. These are occurrence differences from the 2020 decree Cum Sanctissima, which colitur excludes from its 1962 core by binding design (CLAUDE.md decision 2). RG 303(b) ("dici potest tantum si occurrit dies liturgicus IV classis") already prohibits 302(b) on the Class3 privileged ferias both entries cover -- the original text quoted 303(b) but never followed it to its conclusion. Decisively, the FIUV Ordo prints BOTH readings on the six shared dates, separated by a literal "-VEL-" token: the primary, strict- 1962 reading (feria wins, sine Gloria, no Te Deum) matches colitur exactly; the second, Cum-Sanctissima reading (saint kept, Gloria, Te Deum) matches the LMS Ordo exactly. F3 itself was never a real Te Deum finding: tools/extract_fiuv_ordo.ml's extract_te_deum scanned each block unbounded for "Ad Mat.", so on these six days -- whose primary office states no "Ad Mat." of its own -- it silently read the second office's own Te Deum instead. Fixed with a new split_at_alt_office, cutting every block at "-VEL-" before any field extraction runs; the alternative is captured, not discarded, in a new alt_te_deum field. A hard characterisation probe asserts the split fires on exactly the six known dates. Auditing for the same trap elsewhere (this is the third such manufactured divergence in this project) found a related but narrower marker, "Vel (sec. decretum <<Cum sanct.>>) Missa ...", offering an alternate Mass formulary within a single office. It never corrupted Gloria/Credo (always stated before the marker on all 20 real instances, confirmed by diff), only the uncompared praef field; hardened defensively anyway, before it could ever fire live. The LMS Ordo (all three editions) carries no equivalent dual-print mechanism at all. expected-divergences-missalemeum.sexp's M27 is the same genus but was already correctly adjudicated, not touched. Both allow-list entries are rewritten, not deleted: the original SHAPE/ CITATION/WHY-NOT-FIXED-HERE reasoning is preserved verbatim as a recorded, superseded hypothesis, matching this project's own correction convention. test_lms_ordo.ml's matching predicate is unchanged (the 16-date population still fires, and always will, because it reflects a standing exclusion, not a bug); only its citation comment is corrected. test_fiuv_ordo.ml's F3-specific predicate/count assertion is removed, matching this file's own prior F2/F4 closure discipline. lib/ is untouched. dune test: 676/676. make check (COLITUR_EXHAUSTIVE_SWEEP=1, full 1583-9999 domain): 678/678.
* feat(ef): the Gloria in excelsis, RG 431-432, deferring to Breviary 237-238Lukasz Kasprzak2026-08-227-65/+684
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Phase 2 of celebrant-rubrics-phase1: colitur rubrics gains a fifth column, whether the Gloria is said. Follows the Creed's own seam exactly -- Rite.t.gloria, Liturgical_day.t.gloria, wired through calendar.ml the same way. RG 431(a)/432(a) defer the Gloria to the Breviary's own Te Deum rule (nn. 237-238), so te_deum is implemented as its own named predicate, cited clause by clause, not collapsed into a colour heuristic. 431(c) (Holy Thursday, the Easter Vigil Mass) and 432(b)/(d) (violet; a Requiem) are independent overrides checked ahead of the Te Deum-derived answer. Every clause this engine has no dimension to model (votive Mass classes, the wider n.302 "Missa festiva" categories) is stated as N/A with its own reasoning, not silently dropped. Validated against the FIUV universal Ordo (Gloria and Te Deum) and all three LMS editions (Gloria). A first pass over-trusted a clean-looking 15-for-15 FIUV contradiction of 237(b)'s own Septuagesima exception and replaced it with a blanket "every Sunday" rule; the evidence was itself corrupted -- the FIUV extractor recognised only one of the source's two Te Deum negations ("non dicitur", not "sine"), so every "sine Te Deum" Sunday read wrongly true. Fixed in tools/extract_fiuv_ordo.ml, fixture re-extracted, and the literal 237(b) reading restored once the corrected data confirmed it. A second bug surfaced alongside it (Palm/ Passion Sunday wrongly reading true via Temporal_ef.named's own table membership, then Christ the King wrongly reading false from an over-broad fix) is closed with an explicit two-slug exclusion. Domain-wide 1583-9999: every violet or Rose day is gloria=false except the Easter Vigil (RG 431(c) lex specialis), every Requiem is gloria=false, both measured exhaustively, zero exceptions. Mutation- proved: disabling 431(c) reddens 8 tests including all four oracle comparisons; disabling 238(c)'s feria-I-classis exclusion reddens exactly the dedicated Ash Wednesday unit test, a genuine blind spot in both oracle layers, reported rather than hidden. Two open, cited findings, neither fixed here (out of this task's "follow creed's exact seam" scope): a privileged Lenten/Passiontide feria carrying one commemoration reads Gloria=true in the LMS Ordo but Te-Deum=true/Gloria=false in FIUV -- the two oracles disagree with each other, not merely with colitur (data/ef/expected-divergences-lms.sexp L5, expected-divergences-fiuv.sexp F3); and a pre-existing, uncited Colour.Violet bug on Rogation Monday/Tuesday in Temporal_ef.temporal, surfaced by this comparison but root-caused as a separate defect (L6). day/readings verified byte-identical against a build from the branch tip before this task (v0.10.1's own tag predates an already-landed bissextile fix that legitimately changed both, so it is not the right baseline). 671 tests green (dune test); 678 with the exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, ~104s).
* feat(ef): implement the calendarium's bissextile February shiftLukasz Kasprzak2026-08-225-12/+133
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The Missale Romanum's calendarium footnote for February (LT.txt:5011-5014, scan-corroborated scan2.txt:3050-3058): in a leap year the sixth kalends of March (24 February) is doubled rather than a 29th day appended, so St Matthias moves 24->25 February and St Gabriel of Our Lady of Sorrows 27->28, with 24 February itself carrying no fixed office that year. data/ef/expected-divergences-lms.sexp entry L3 tracked this as an open gap. Implemented as the rubric's own general mechanism (every fixed entry from 24 through 28 February shifts one civil day later in a leap year), not as "move these two named saints": the two readings are indistinguishable on shipped data (nothing else is fixed in that window) and diverge only for a future --overlay entry in the same range, which the mechanism reading covers correctly and a two-saints special case would not. This project has already been bitten once (RG 16(a)) by a rule implemented against shipped data's coincidental shape rather than the rubric itself. Kernel stays rite-agnostic: Rite.t gains a fixed_key field (Date.t -> (int * int) option), the same seam easter already establishes, read only by Layer.on_date's FIXED half -- the movable half (Easter_offset/Nth_weekday) is untouched. Default is the identity mapping, an optional argument on on_date, so every existing caller and every rite that supplies nothing is byte-identical to before this field existed. Date.is_leap is exposed in date.mli (already existed in date.ml) so the rite reads the kernel's own single-sourced leap-year definition. The EF implementation lives in Rite_ef.Temporal_ef.bissextile_fixed_key, fully cited, wired into Rite_ef.context. Blast radius, measured over the full 1583-9999 domain (Calendar.year run twice per civil year, real fixed_key vs identity, every day diffed): all 2,041 leap years in the domain show a visible change for each saint; 6,983 individual liturgical days change total, zero unclassified, across four shapes (Matthias vacates 24 Feb in 1,803 years / occupies 25 Feb in 1,811; Gabriel vacates 27 Feb in 1,699 / occupies 28 Feb in 1,670). L3's own prior estimate (1,650 years, observed-outright only) is confirmed close on the same definition (1,677 measured); the broader observed-or- commemorated count is higher, not lower, showing the narrow estimate undercounted visible impact rather than overcounting it. The lectio differential (2005-2050 fixture) gains a new cited entry, C40 in data/ef/expected-divergences.sexp: lectio implements no such shift, so every leap year in its window now diverges on the two shifted days (21 rows, not the naive 44 -- the comparator never compares commemorations, so a side where the shifted saint has zero comparator-visible footprint on both engines produces no row). The LMS Ordo's 2023-2024 fixture -- the window L3 was originally found in -- now matches on the Creed comparison; its formulary-override bucket count is corrected 182->181 (24 February leaves the counted Proper population, becoming Own_slug-sourced). The missalemeum oracle fixtures (2026-2027, 2038, 2035) cover no leap year and are unaffected. L3 is closed: converted from an active allow-list record to a prose closure paragraph (L1's own precedent), citation preserved verbatim, fix and measured blast radius recorded. The id-list assertion narrows to [L4] alone, and the now-dead "2024-02-24" -> "L3" date mapping is removed. Six golden values pinned across four new test cases: Matthias in a leap year (both the vacated and occupied sides) and a common-year control; Gabriel likewise, deliberately choosing a leap year where he is admitted only as an ordinary commemoration rather than winning outright, a different shape from Matthias's. Mutation-tested: forcing fixed_key back to identity reddens exactly five test cases -- the differential's Layer C count-pin (C40 drops to 0 actual vs 21 expected), the LMS Ordo's Creed and formulary-bucket checks, and both new golden pins -- confirming the tests actually exercise the fix. Reverted; dune test, the exhaustive sweep, and make check are all green.
* test(fiuv-ordo): add the FIUV universal Ordo as a second, independent witnessLukasz Kasprzak2026-08-224-1/+1829
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Wires in the FIUV (Foederatio Internationalis Una Voce) Ordo for 2025-2026 as validation layer seven -- the first UNIVERSAL (not diocesan) source in this project, and the first compiled by someone other than the LMS editions' own Peter Day-Milne (Joseph Shaw, FIUV President). tools/extract_fiuv_ordo.ml hand-parses pdftotext's -layout dump of the Latin-language ordo (no Str/regex, frozen deps). Three real parsing traps were found and fixed by tracing actual false results against the raw source, not assumed in advance: - "Gloria" is also the first word of "Gloria Patri" (the psalm doxology), which can appear INSIDE the very Mass clause being scanned (Good Friday: "...omittuntur ps. Iudica me et Gloria Patri), Gloria, sine Credo..."). Guarded by rejecting any "Gloria" hit whose next token is "Patri". - Christmas Day's own rubric uses the PLURAL "Missae"/"Missae" ("tres Missae celebrantur..."), never the singular "Missa" every other real day anchors on -- silently produced gloria=None/credo=None on the one most doctrinally unambiguous day in the calendar until traced and fixed, guarded in turn against "Missae defunctorum" (an unrelated Requiem-prohibition notice appearing at the end of many day-blocks). - The Vespers boundary (bounding the primary Mass option before a second "Vel Missa" alternative or before Vespers information) missed the ligature spelling "VESPERÆ", the dominant form in this source, letting a whole day's trailing prose leak into the captured [praef] field on the task brief's own worked example day (19 September). A distinct second stop-marker bug: the malformed trailing entry this source shares structurally with the LMS PDFs (a stray, mislabelled duplicate of 1 January) was initially over-matched by a generic "first token is 4 digits" heuristic, which false-positived on an indulgence-decree date citation wrapped mid-sentence in October ("...20 augusti / 1885, 26 augusti 1886..."), truncating the extraction to 308 of 400 real days. Fixed by anchoring on the tail entry's own distinctive "1st Jan" opening instead. Five opposite-prediction Creed probes are re-verified by the tool itself against its own extracted rows (a failed probe is a hard exit, not a printed claim) before the fixture is written. test/test_fiuv_ordo.ml compares the Creed against colitur across all 399 comparable days (Holy Saturday excluded, no Mass that day) -- CLEAN, zero unexplained divergences, a genuine second confirmation of {!Rite_ef.Rubrics_ef.creed} independent of the LMS lineage. [praef], [te_deum] and [class_] (including the FIUV ordo's own "III cl." vs "III cl. (Priv.)" Cum Sanctissima convention, stated on its title page) are captured into the fixture but deliberately not compared -- colitur has no Gloria/preface predicate yet, and its default configuration models no Cum Sanctissima overlay to compare the class convention against. All 634 tests green (dune test), 635 with the exhaustive sweep and make check. No lib/ file touched. Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* test(lms-ordo): extend the LMS witness layer from one year to threeLukasz Kasprzak2026-08-225-408/+3437
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Generalises tools/extract_lms_ordo.ml's start-of-body detection: the hardcoded "End of November 2024" sentinel assumed every edition opens with an "End of November" stub section. The 2023-2024 edition has none (Advent Sunday 2023 fell on 3 December, so the compiler folded the two tail days into the "December 2023" header directly) -- the generalised detector anchors on the body's own "ORDO" title line instead, verified unique in all three known editions and distinct from the Table of Contents' lowercase "Ordo" entries. Extracts test/fixtures/lms-ordo-2023-2024.sexp (397 rows) and lms-ordo-2025-2026.sexp (399 rows), each with a provenance header carrying its own SHA-256, source citation, and a per-edition characterisation record: five opposite-prediction Creed probes, independently re-verified against this run's own extracted rows (a failed probe is now a hard die, not a printed claim). The 2025-2026 edition needed a different sharp II-class pairing than 2024-2025's Joachim/Stephen one, because Joachim (16 August) is impeded by a Sunday that year -- recorded in the fixture header, not silently swapped. test/test_lms_ordo.ml is refactored into a parameterised make_suite, instantiated once per edition; every count Task 6 hardcoded against the single 2024-2025 window is now a per-edition parameter, several of which turned out to differ once measured (BVM-Saturday population 12/14/13, Ascension-week exclusion 1/3/2). Two mapping gaps surfaced only by the new windows and fixed here (test-side, not colitur): Low Sunday reached via Preceding_sunday for the first time (absent from 2024-2025's own window), and the Ascension-week exclusion was wrongly keyed off week number alone -- Rogation Monday also resumes week 6's Sunday but falls BEFORE Ascension, not after, and the Ordo prints the ordinary Sunday text there. Re-bounded by date, against Ascension and Pentecost. Two genuine divergences surfaced in the 2023-2024 window and are allow-listed (L3, L4) rather than fixed, per this task's no-lib-changes scope: - L3 (verdict ordo): colitur has no bissextile calendar rule. The 1962 Missal's own calendarium moves St Matthias from 24 to 25 February in leap years (docs/research/LT.txt:5011); colitur keeps him on the 24th every year. Domain-wide upper bound measured with a throwaway sweep: 1,650 of 2,041 leap years in 1583-9999 currently observe him there. - L4 (verdict open): Christmas Eve falling on the 4th Sunday of Advent (2023-12-24, roughly 1 year in 7). colitur says the Creed is said, reading RG 475(a)'s Sunday clause; the Ordo says it is not. RG 30(a)'s own "locum tenet ... nulla fit commemoratio" for this exact vigil reads like RG 16(a)'s Feast-of-the-Lord-on-Sunday shape, which colitur already treats as Creed-preserving -- but RG 475(a) names "festo" specifically, and a vigil is its own liturgical-day category (RG 35), so the counter-reading is real too. Left open rather than adjudicated. All 630 tests green (dune test), 631 with the exhaustive sweep. Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* docs(cli): --help's rubrics section drifted to three fields, it has fourLukasz Kasprzak2026-08-221-0/+17
| | | | | | | | | | | | | | | | | | | | Task 5 (celebrant-rubrics-phase1) added a fourth rubrics column, creed, and updated the man page for it. bin/main.ml's own built-in --help was not: its "output formats" section and worked example still showed three fields (date, formulary slug, source), and nothing caught it because test/cli.t pins command dispatch but never the help body. Fixed the format line and its example, and added a short mention of the creed column to the surrounding prose paragraph (source, format, what --overlay/--lang/--raw do to it), matching the man page's own account without copying it verbatim. test/cli.t now cross-checks --help's own rubrics example against what `colitur rubrics 2026` actually prints for the identical day, so the two cannot silently drift apart again in either direction. Mutation-proved: reverting the field count in --help alone (output unchanged) reddens exactly this new check; reverted clean.
* fix(kernel): Mass_formulary.t.said is honestly optional -- was false for VotiveLukasz Kasprzak2026-08-225-27/+82
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The .mli promised said is "the slug whose Mass is said". For Votive (RG 78/309(a), the Saturday votive Mass of Our Lady) it was set to the day's own ferial slug -- whose Mass is exactly the one NOT said. A consumer joining rubrics to readings on that slug would silently get the wrong Mass: 2026-01-03 reports ef-christmas-1-saturday, which has zero entries in data/ef/lectionary.sexp, because the citations actually come from bvm_saturday_citations, a season-keyed function with no slug of its own anywhere in the shipped data. Chose the type-honest fix over the interim documentation one: said is now Slug.t option, None exactly for Votive, because there is genuinely no slug in the shipped data this field could report for that one source. Adding real ids for the five seasonal BVM Masses (the reviewer's first option) is out of scope -- a data restructuring this round explicitly does not carry. Threading the office slug through a second field was considered and rejected as redundant: the day's own office is already available on the same Liturgical_day.t via observed.slug, which every caller already has in scope regardless of via, so said does not need to duplicate it. colitur rubrics stays byte-identical: rubrics_line already has d.observed in scope and falls back to its slug when said is None, printing the exact value it always printed for a Votive row (verified directly, diffed against pre-fix output across four years). colitur day/readings are unaffected (neither reads Mass_formulary at all). colitur emit --format sexp's pretty-printed line count for 2027 moved 9011 -> 9025: every day's formulary record widened by said's own extra option wrapping, and to_string_hum wraps by column width. Cosmetic only, diffed line by line to confirm every change is this shape or a consequent wrap shift; recorded in test/cli.t alongside the 476(f) note it now sits next to.
* test(ef): compare the LMS Ordo's formulary override STRING, not just presenceLukasz Kasprzak2026-08-221-13/+207
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | test_lms_ordo.ml's formulary-override check only asked WHETHER a Preceding_sunday day showed a "Mass of ..." override line, never WHICH Sunday it named. colitur could have resumed the wrong Sunday on all 66 such days and this layer would have stayed green -- resuming the right Sunday is precisely what step 3 of the reading chain exists to do. test_formulary_override_matches now derives the Ordo's own expected prose from Mass_formulary.t.said's own resolved slug (parsed directly, no Str/regex) and compares it against the Ordo's real text. Two narrow, evidenced exceptions, both counted rather than silently absorbed: - Christ the King's own Sunday reaches this population under a NAMED slug (ef-christ-the-king) that carries no embedded ordinal; its position among the Sundays after Pentecost varies by year, so Temporal_ef.week is read for that one slug specifically, not called generically (an earlier version of this fix read [week] for every day and was wrong -- caught by its own assertion on the very first run, because Time_after_pentecost's own last-Sunday and surplus-Sunday renumbering diverge from the raw week count. Reading the number back out of the already-resolved slug sidesteps both special cases and is not tautological with the wrong-Sunday failure mode this fix exists to catch). - Three real days between Ascension and the following Sunday (2025-05-30, 2025-06-02, 2025-06-03) are a genuine, newly-found defect: colitur resumes the nearest calendar Sunday, but the Missal, and the Ordo, keep saying Ascension's own Mass through this stretch instead. Structural (Ascension is a fixed Easter+39), not a fixture artefact, and not fixed here -- it needs a lectionary-data change out of this fix round's own scope. Documented in expected-divergences-lms.sexp as a prose-only note (no active sexp record: this file's id-lookup machinery is scoped to the Creed axis, and a second axis reusing it would let one test's "declared but never fired" check spuriously trip on the other's entry) and enforced directly by this test's own count assertion instead. Mutation-proved: offsetting step 3's Sunday lookup by an extra 7 days reddens this test (23 failures total across the suite: layers 3-6, golden pins, and lectionary_ef's own unit tests), confirmed via the compiled test binary directly; reverted clean, full suite green again.
* fix(ef): RG 476(f) -- the Creed is not said at a Requiem MassLukasz Kasprzak2026-08-222-2/+100
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | colitur said the Creed IS said on All Souls' Day and every other Requiem-shaped day: RG 475(b)'s "in festis I classis" branch fired on rank alone, with nothing checking RG 476(f), "Non dicitur symbolum ... f) in Missis defunctorum". Allow-listed as L1 in expected-divergences-lms.sexp on the argument that [creed] has no notion of a Requiem Mass in its inputs and could not express the rule without a signature change. That argument does not hold. Colour.Black is used by exactly two celebrations in the whole engine, verified by grepping every occurrence in lib/ and data/: commemoration-of-all-souls (data/ef/sanctoral.sexp) and Good Friday (temporal_ef.ml, already false via the earlier RG 23(b) feria exclusion and has no Mass at all in the 1955-restored Holy Week). So on every day this engine can construct, colour = Black if and only if the Mass is a Requiem -- a citable implication (RG 117 assigns black to Masses of the dead) on this data, not a heuristic. Rite_ef.Rubrics_ef.creed now reads observed.colour as RG 476(f)'s own guard, positioned ahead of 475(b)'s rank branch. A new test, test_colour_black_population_is_exactly_two, sweeps both the shipped data and Temporal_ef's own code across a century and fails loudly the day a third Colour.Black celebration is ever introduced, so the proxy cannot silently rot. L1 is closed in expected-divergences-lms.sexp, citation preserved. Blast radius, measured (full 1583-9999 sweep, grouped by observed slug): 8 416 days change from true to false, every one commemoration-of-all-souls (missing from exactly 1 of the 8 417 domain years, a boundary-year transfer effect at 1583/9999). A further 8 416 Colour.Black days are Good Friday, unaffected (already false before this fix). colitur day/readings stay byte-identical to v0.10.1 (checked directly against a v0.10.1 worktree build, 9 domain-spanning years); colitur rubrics changes on exactly the 8 416 days above. colitur emit --format sexp's pretty-printed line count for 2027 moved 9010 -> 9011 as a side effect: Sexplib.Sexp.to_string_hum wraps by column width, and All Souls' Day 2027's record grew one wrapped line because "false" is one character longer than "true". Cosmetic only -- no field's data changed -- and pinned in test/cli.t with the measurement recorded.