aboutsummaryrefslogtreecommitdiff
path: root/data
Commit message (Collapse)AuthorAgeFilesLines
* fix(lectionary): the week after Trinity says its own assigned MassLukasz Kasprzak2026-08-173-7/+28
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 299 located (scan1:1096-1098), which does two things at once. First it closes a standing gap: the ferial-Mass selection rule was recorded in the register as unconfirmed against the primary source, and step 3 said so in its own comment -- "lectio's own observed behaviour, not a confirmed Missal citation". It is confirmed now. "In reliquis feriis dicitur Missa dominicae praecedentis, nisi a rubricis aliter provisum sit", restated in the propers as "...nisi propria Missa assignetur". Second, that escape clause splits M26 shape 2 in half, which is why the shape was never resolvable as one thing. Shape 2(a), the week after Trinity, is fixed. Trinity is the temporal identity of its own Sunday, so a plain walkback lands on Trinity's Mass -- but the Missal prints a named formula for the following week, "Missa dominicae I post Pentecosten" (scan1:21758), precisely because the feast has taken that Sunday's own. Those ferias now resolve at step 2 from it and never reach the walkback. Shape 2(b), Christ the King's week, turns out not to be a bug at all. No proper Mass is assigned there -- checked directly, no rubric precedes that Mass heading (scan1:39163ff) -- so RG 299's default governs and "the Mass of the preceding Sunday" genuinely is Christ the King's own, which is what colitur emits and missalemeum does not. Kept open only because no rubric specific to that week was located either way; the verdict is now argued from the primary text rather than resting on oracle disagreement. No Thursday in the slug list: Corpus Christi is Easter+60, which IS that week's Thursday every year, so the ferial slug never exists. Found by the bootstrap's own reachability guard refusing to emit a key no Temporal_ef slug can match, not reasoned out in advance. Allow-lists: C35 opened (69 rows, verdict colitur -- lectio still walks back); C17 narrowed 406 to 397, nine Saturdays moving to C35 because they carry both a citation and a colour difference, with C35 gated to require the citation so a colour-only Saturday still falls to C17; M26 narrowed 30 to 27; M18 widened 352 to 355, the same three days keeping only their observed-identity difference. Register: section 6.11.
* feat(kernel): movable Date_spec variants, and Rogation WednesdayLukasz Kasprzak2026-08-171-0/+50
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Date_spec carried one variant, Fixed(month, day), and its own header said Sunday- and Easter-relative forms would arrive with the OF sanctoral. They arrive early because two things needed them at once: a user-supplied overlay carrying a local movable feast had no way to express "the first Sunday of October", and Rogation Wednesday's commemoration has been recorded as architecturally blocked since 2026-08-13. That blockage turns out to have been a premise, not a fact. The register concluded the Rogation "is not Commemoration_only sanctoral data by nature... it needs a genuinely THIRD kind of thing this architecture has no name for" -- but the reasoning rested on there being "no civil (month, day) pair to anchor a Fixed entry to". That is a statement about Date_spec's expressiveness. With Easter_offset the entity is ordinary sanctoral data, the identical shape Add major-litanies already ships, differing only in how the date is written. No third channel was needed. The register is corrected rather than left contradicting the code. Two variants: Easter_offset of int, and Nth_weekday of {month; nth; weekday} with negative nth counting from the end. Fixed's sexp representation is untouched, so all 327 sanctoral entries and the whole overlay parse unchanged. Both are re-validated in t_of_sexp through their smart constructors, extending the idiom Fixed already used -- the failure that guards against is invisible, a spec deserialising into something that silently never resolves and a celebration vanishing with no diagnostic. Easter is supplied by the rite, not computed in the kernel. Computus ships Gregorian and Julian both, and picking one here would hard-code a Roman assumption into rite-agnostic code and be silently wrong for a Julian-reckoning rite. Rite.t gains an easter field; Rite_ef supplies the Gregorian one. Layer keeps a split index. Fixed entries stay in the year-independent (month, day) table, which preserves the fast path and the "30 November counted twice in a 371-day span" behaviour validate.mli documents for St Andrew. Movable entries have no year-independent key by construction -- the same spec lands on a different (month, day) each year -- so they resolve per civil year into a rata-die table. index_by_date is removed rather than kept as an alias: two ways to index a layer, one of which silently ignores movable entries, is the trap this change exists to avoid. Two domain-edge defects, both found by the suite rather than reasoning. A liturgical year is Advent-anchored, so resolving civil year y names y +/- 1, and at the edges those are 1582 and 10000, which Computus correctly refuses by raising. The ceiling surfaced through the domain-ceiling test, the floor through `colitur day 1583` failing outright. Fixed once, in Layer.index, the single point that calls the rite's easter -- a clamp repeated per caller would have been two places to get wrong a third time. Rogation Wednesday: RG 87 assigns the Litaniae minores to the Monday, Tuesday AND Wednesday before Ascension (scan1:691); colitur built the first two from the start and never the third. RG 88 keeps it out of the Office, so the Ascension Vigil remains observed. RG 89 routes it through the Major Litanies' rules, but RG 109's closed list names only maiores, so by RG 107 it is an ORDINARY commemoration -- confirmed in the output, not assumed. Name from the Missal's own "feria IV" (scan1:20495-20497), violet to match the Monday and Tuesday offices. Blast radius, full 1583-9999 sweep against the pre-change binary: 2257 days, every one the same shape, zero unclassified, line counts equal both sides. The complement is the informative half -- in the other 6160 years the Rogation does not appear at all, because RG 111 admits one commemoration on a II-class day and an impeded feast takes it under RG 113, a Commemoration_only candidate having no row in RG 91's table. Both shapes are pinned. Allow-lists unmoved: layer 3 compares no commemorations and is blind by construction, and layer 4's 2026-2027 window has a competing feast on both Wednesdays, so the Rogation is capped out in exactly the years the oracle can see. C8 is unchanged, asserted by its own count pin. Not built: Sunday-relative specs, which nothing needs yet, and the --overlay plumbing that would let a user actually supply a local movable feast. Nth_weekday therefore ships with synthetic and property coverage only; inventing a shipped entry to exercise it would be fabricating calendar data. Register: section 6.10.
* feat(cli): install the runtime data, and resolve it in three waysLukasz Kasprzak2026-08-171-0/+23
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | `dune install` produced a colitur that could not run: the binary locates its data relative to its own path, nothing installed the four .sexp files anywhere, and an installed colitur exited 2 unable to read sanctoral.sexp. `dune build @install` produced an empty tree. data/dune installs the four RUNTIME files into <prefix>/share/colitur/ef. Deliberately not the two allow-lists: those describe where colitur and a comparison oracle disagree, are read only by the differential and oracle tests, and are meaningless to a running colitur -- installing them would ship an assertion about lectio's and missalemeum's behaviour as though it were calendar data. No glob, on purpose: a glob would silently start shipping them again the moment one was renamed. Resolution now probes rather than computing one path and hoping. An installed prefix is tried first, the build tree second, and a candidate counts only if sanctoral.sexp is actually readable inside it -- so a failed or half-removed install falls through to a working tree instead of shadowing it and failing later with a per-file error. COLITUR_DATA_DIR overrides both and NEVER falls through. This was the one real design question and the first version got it wrong: it treated the override as just another candidate, and a deliberately bogus value produced a full, plausible, entirely un-flagged year computed off the build tree. Someone who names a directory has stated an intent, and silently calendaring off a different one is the silent substitution this project refuses everywhere else. It is now exit 2 naming the directory. Verified end to end, not inferred: installed to a scratch prefix, then ran the binary from an unrelated cwd with no build tree near it, and confirmed 2038-03-06 still resolves Perpetua and Felicitas through the Common route. All four paths exercised -- build tree, valid override, bogus override, and an empty share/ falling through. Environment reads stay confined to bin/. The kernel's contract forbids them and nothing below the CLI learns where the data came from; the loaders take a path.
* fix(lectionary): two Judith citations wrong in both enginesLukasz Kasprzak2026-08-173-10/+30
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A live-missalemeum sweep over a full liturgical year (2028, 364 days) returned 333 matching and 31 differing days: 17 already-known or pure notation, 3 new. All three now adjudicated against the photographic scans of the 1962 Missal. The Assumption's Epistle reads "Iudith 13, 22-25; 15, 10" (scan1:35357). Both engines carried "Judith 13:22-25; 13:15; 13:10" -- a chapter carry-over defect that re-applied chapter 13 to the second citation and so invented two verses the Missal never cites. The Seven Sorrows reads "Iudith 13, 22 et 23-25" (scan1:36938, corroborated at 28468); both carried "Judith 13:22; 13:25", dropping the range. The third, St Monica, resolves the other way: the Missal gives 1 Timothy (scan2:32679, heading and incipit both legible; scan1:29524 agrees) where missalemeum serves the Common of Holy Women's Proverbs -- colitur is right, no change. Layer 3 could not have found either citation defect. colitur's data is bootstrapped from lectio, and lectio is the other side of that differential, so while both engines produced the identical wrong answer the comparison held it against itself and reported agreement -- the shared-lineage blindness CLAUDE.md names, now with a second live witness beside Holy Thursday's colour. Only an external oracle could see it, and only outside the committed fixture's own window. Fixed upstream in lectio, re-bootstrapped here (provenance SHA-256 moved to 54f6bfc5, verified against the source), fixture regenerated. The upstream fix inverted layer 3 from blind to load-bearing here: reverting the Assumption citation now reddens both layers, where before it would have reddened neither. Verified by mutation, both directions. Allow-lists: - C22 closed and removed (128 rows -> 0), not re-adjudicated -- the other engine was fixed to agree, the same shape as the C2-C5/C7/C9-C13/C21 closures. Its own note had already flagged the Lenten Ember dead-key bug for upstream; lectio now also fails its build on any unreachable lectionary key rather than serving a plausible wrong Mass. - M28 opened (3 rows, verdict colitur), pinning both sides' citation text rather than the date and diff shape alone, so a different divergence landing on the same date cannot be swallowed. - M8's gate widened, expected_rows unchanged at 2: 2027-08-15 carries both its cause and M28's, and one entry is admitted per day. It stays under M8, which now requires its own commemoration difference to be present and admits the citation difference only when it satisfies M28's two-sided predicate. Neither cause absorbs the other. layer_m_reason's oracle-row parameter, kept named since M2 closed for exactly such a future reader, is live again. Register: docs/research/rules-register.md section 6.6.
* test(oracle): fix round 1 -- C30 disclosure, RG 17(a) misattribution, M26 shapesLukasz Kasprzak2026-08-172-8/+37
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three Important findings from coordinator review of 8d1eb25, all fixed. 1. C30's own note claimed its 91-row population "is C25's OWN former population, before this task narrowed it" -- false. Independently re-derived against the real resolver (pre-fix vs post-fix binary) and the lectio fixture: only 44 of the 91 rows were ever in C25 (Wed 6/ Thu 19/Fri 19, matching C25's own fix-round-3 breakdown exactly); the other 47 (all of Monday/Tuesday, plus part of Wednesday/Friday) previously matched lectio outright -- both engines independently landing on the same wrong value -- and are newly allow-listed only because colitur is now right. Rewritten to the same "both engines silently agreed on the wrong answer" pattern C33/C34 already used. 2. "Diebus ferialibus" is not RG 17(a) -- RG 17(a) states only the day-identity privilege; the ferial-Mass rubrics are Mass-propers rubrics, a distinction the register's own §6.3 already drew before this task's first pass blurred it in code/allow-list comments. A further slip: C31/C32 called their own rubric "RG 17(a)'s own next/ third sentence" when it is a different rubric block ~230 lines away, in Holy Family's (not Holy Name's) own feast propers. Fixed the attribution chain throughout tools/bootstrap_lectionary.ml, data/ef/expected-divergences.sexp's C29-C34, and (gitignored, outside this commit) docs/research/rules-register.md -- closed the §6.3 park, added §6.5 recording all four citations with corrected attribution. Quotes and line numbers were already right; only the chain of custody to RG 17(a) was wrong. 3. M26's own 30 rows classify into two nameable shapes, not "three confirmed shapes" with an unexplained 23-row residue that was in fact empty: 21 rows are RG 78's own already-documented BVM votive-Mass gap; 9 rows are a second, distinct gap -- a week whose own Sunday was displaced by a movable named feast (Trinity->Corpus Christi, "Missa Dominicae I post Pentecosten"; Christ the King), where the Missal requires the displaced Sunday's own Mass to keep informing that week's ferias but step 3 walks back to the displacing feast's citation instead -- proven directly by colitur's own citation on the Christ- the-King rows being literally that feast's own Gospel. verdict open unchanged (not fixed here); the shape is now named instead of buried. Minors: section count corrected 1458->1456 (four places); "exactly one candidate, zero ambiguous" corrected to "correctness rests on ordering, not uniqueness" (2 of 1456 sections genuinely yield two candidates, Holy Saturday's own Lectio, both years); the stale "nothing in layers 3-5 compares citations" claim corrected (only layer 5 still doesn't); M18's note corrected (five new entries not six; ~18 of its remaining 352 rows only agree now because of this task's own fixes, not "already" before it); "five" corrected to "six" otherwise-unoccupied Saturdays in bootstrap_lectionary.ml; scan2 quotes softened where OCR-garbled. Also done outside the git-tracked files: the step-4 (Commons) coverage gap is now recorded in data/ef/expected-divergences-missalemeum.sexp's own header, not only the commit body and the (gitignored) report. dune test: 358 tests green. COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force: 359 tests green, 95.7s.
* test(oracle): compare reading citations against missalemeumLukasz Kasprzak2026-08-173-18/+138
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Layer 4 (test_oracle.ml) now extracts and compares the Epistle/Gospel citation for every one of the 730 days in the 2026-2027 fixture, the same axis Task 8 already added to layer 3. tools/ extract_missalemeum_oracle.py gained a structural citation-span detector (the first "*...*"-wrapped span under 40 chars containing a chapter:verse-shaped digit pair), verified exhaustively over all 1 458 Lectio/Evangelium sections. 728 of 730 days are comparable; the 2 that are not (Good Friday, both years) have no such section in missalemeum's own JSON at all -- a structural limit on the oracle's own side (M23), not a colitur gap. 451 of 730 days differ and are all explained across 19 cited entries; 0 unexplained. Chain step 4 (the Commons) has no layer-4 witness: confirmed empirically (a scratch sweep, not merely re-asserted from Task 6's own report) that none of the 15 Commons/proper saints is ever the observed office in this window. The comparison surfaced three genuine colitur bugs in data/ef/lectionary.sexp, all fixed at the source in tools/bootstrap_lectionary.ml: 1. Holy Name of Jesus (ef-holy-name-sunday/ef-holy-name) carried a mis-derived citation, borrowed by analogy from an unrelated ini section ("ef-christmas-sunday-0", the Sunday-within-the-Octave's own Mass) on the wrong assumption the two share a Mass. Disproved by missalemeum's own extraction for both real Holy Name Sundays in its window, confirmed against the Missal itself, twice, both scans (Acts 4:8-12/Luke 2:21, not Gal 4:1-7/Luke 2:33-40). 2. RG 17(a)'s own "diebus ferialibus" rubric (immediately following the Holy Name heading, both scans) was never implemented: 2-5 January ferias repeat 1 January's Mass, 7-12 January ferias before Holy Family Sunday repeat Epiphany's, and Holy Family's own week has a named formula ("Missa Dominicae Primae post Epiphaniam"). All three fell to step 3's undifferentiated Sunday walkback instead. Fixed for three 5-weekday families, deliberately excluding Saturday from all three -- RG 78's BVM Saturday Office wins there unconditionally, confirmed against missalemeum's own title on this window's own such Saturdays. 3. Corpus Christi and Sacred Heart (both real, never-impeded I-class movable feasts) had no lectionary entry at all. Both engines independently fell back to Trinity Sunday's own citation and silently agreed on the wrong answer -- the "Holy Thursday violet in both" shape CLAUDE.md names for exactly this reason. Because lectionary.sexp is shared with layer 3 (the lectio differential, 2005-2050), these fixes changed colitur's output for the whole domain, not only 2026-2027. data/ef/expected-divergences.sexp and test_differential.ml needed matching updates: C1 narrowed 361->173 and C25 narrowed 57->13 (rows that now resolve correctly moved to new entries), C16's note corrected (count unchanged), and six new entries added (C29-C34, 397 rows total, all verdict colitur). test_lectionary.ml and test_lectionary_ef.ml's own pins were updated to match (entry count 131->148; one step-3 regression test re-pointed to a date that still genuinely exercises that step). One entry, M26 (30 rows, verdict open), is a genuine unfixed gap: colitur's step-3 "resume the preceding Sunday" fallback is its only ferial-Mass mechanism, and missalemeum's own selection is more granular in three confirmed ways (a rotating votive Mass of the BVM, a named weekly formula for at least one week, and later Time-after-Pentecost weeks this task's own investigation did not narrow to a single rule) -- the same "needs a season-keyed selection mechanism, new behaviour not new data" shape data/ef/expected-divergences.sexp's own C6/C25 and Task 6's own residual concern already name, out of this task's own scope to build. dune test: 358 tests green. COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force: 359 tests green, 89.5s.
* test(differential): fix round 3 -- two more Ember Saturdays, doc fixesLukasz Kasprzak2026-08-152-11/+24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two Ember Saturdays shipped an apostle's Mass verbatim from lectio's own ini, the exact shape already found and fixed for Passion Tuesday (C26): ef-advent-ember-sat carried St Thomas the Apostle's Mass (Eph 2:19-22 / John 20:24-29, 21 December) instead of the real "Sabbato Quatuor Temporum Adventus" (2 Thess 2:1-8 / Luke 3:1-6); ef-september- ember-sat carried St Matthew's Mass (Ezek 1:10-14 / Matt 9:9-13, 21 September) instead of the real "Sabbato Quatuor Temporum septembris" (Heb 9:2-12 / Luke 13:6-17). Both verified against both primary-source scans independently. Both readings are explicitly labelled "Lectio Epistolae" in the Missal on both days, so neither needed the "last lesson before the Gospel" convention Holy Wednesday/Good Friday relied on -- a textual fact, not an editorial choice. Fixed at the source in tools/bootstrap_lectionary.ml: colitur_keys excludes both wrong ini sections; a new ember_saturday_corrections list hand-authors the real citations. New allow-list entries C27/C28 (40 rows each, not the naive 46 -- traced to the identical 6 civil years where 21 September and 21 December, always exactly 91 days apart, land on a Saturday and the fixed saint's feast wins outright). Both values pinned in test_lectionary.ml and mutation-tested. Also: C25's per-weekday breakdown was mislabelled (right multiset, rotated weekday labels) in both test_differential.ml and expected- divergences.sexp -- corrected to Wednesday 6/Thursday 19/Friday 19/Saturday 13, and the claim that this split came directly from the comparator's own output is narrowed to what's actually true (only the total, 57, does). C26's scan2 citation range was extended to include the Gospel (previously stopped after the Epistle). lib/kernel/liturgical_day.mli's "citations always empty until Plan 4" comment is now stale on this branch -- left untouched (Task 11's own documentation pass), noted in the task report so it isn't lost. dune test: 358 tests green; COLITUR_EXHAUSTIVE_SWEEP=1: 359 green.
* test(differential): fix round 2 -- Good Friday, Passion Tuesday, stale headerLukasz Kasprzak2026-08-152-52/+73
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Critical, and worse than what it replaced: removing a lectionary key does not mean "no reading" to Lectionary_ef.readings -- it means step 3 silently resumes the preceding Sunday. Good Friday, which has no Mass at all, was emitting Palm Sunday's own Epistle and Passion narrative every year; Holy Wednesday had the same defect. Fixed by authoring both days fully: the Gospel was always unambiguous and is now always authored; the Epistle uses the same "last lesson before the Gospel" convention this file's own Lenten Ember Wednesday entry already established for an identical two-peer-lesson shape, applied for consistency rather than invented to close this gap. C24 is retired, folded into a widened C23 covering all six Holy Week days. Important: Passion Tuesday was shipping Holy Tuesday's Mass, a defect the previous round's own footnote had found and left unrecorded. Fixed at the source -- colitur_keys now excludes lectio's own (wrong) ini section for this one weekday rather than translating it; a new passion_tuesday_entry hand-authors the real Missal citation (Dan 14:27, 28-42 / John 7:1-13, both scans). New entry C26 records the resulting divergence, 43 of 46 years, traced (not merely counted) to three years where a competing I-class feast impedes the day entirely. Important: the allow-list header was stale one level up, still naming and restating the diagnosis of an id (C21) retired in the previous round. Rewritten in full rather than layered with another correction. Minors: all three Lent Ember values now pinned (only Wednesday was before); C22's citation corrected to attribute the date-anchor text and the rank text to their own separate sources; assert_reachable's own comment no longer overclaims what it can catch; C25's falsified reasoning replaced with the directly observed fact (a first replacement derivation was itself found wrong on a second reading and is recorded as rejected, not silently discarded); hand-authored citations standardised to English book names with a colon, matching this file's own dominant convention, with one stated exception where a value must stay byte-identical to lectio's own data rather than be reformatted. dune test: 358/358. COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force: 359/359.
* test(differential): fix round 1 -- Holy Week, Lent Ember, RG 69Lukasz Kasprzak2026-08-152-40/+79
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two Critical findings, both real: Holy Week (including the entire Sacred Triduum) was reading Passion Week's Masses -- the original colitur_keys widened lectio's single, conflated ini section into both weeks instead of Passion week alone. Narrowed to Passion week only; Holy Week's own four cleanest days (Monday, Tuesday, Thursday, Saturday) are hand-authored directly from the Missal, verified twice against both scans (C23); Holy Wednesday and Good Friday are genuinely multi-lesson liturgies with no single Epistle slot and are deliberately left unanswered, not guessed (C24). The Lenten Ember days were still dead keys: Temporal_ef computes ef-lent-ember-{wed,fri,sat}, lectio's ini names the same data ef-lent-1-{wednesday,friday,saturday}, and colitur_keys never translated it -- both engines independently fell through to the same wrong ferial answer, so even the differential stayed green (C22, a genuine lectio bug recorded for upstream). Fixed the instance and the class: colitur_keys gained the rename, and tools/bootstrap_lectionary.ml gained assert_reachable, sweeping Rite_ef.Temporal_ef.temporal at generation time so a translation table entry naming a slug colitur never computes dies loudly instead of shipping silently, plus an informational log of every real slug with no entry at all. C21 ("colitur's step 3 cannot reach a correct answer for the fixed Christmastide slugs") is retired, superseded rather than merely patched: the Nativity-Octave days now have their own direct Missal formulary (Tit. 3,4-7/Luc. 2,15-20, "Diebus infra octavam Nativitatis Domini"), resolved at step 2, not step 3 at all. That surfaced a real RG 69 gap underneath it: a Sunday landing 26-31 December should keep its own Office, not the fixed weekday placeholder, which Temporal_ef does not currently distinguish. C6's verdict is re-opened (open, not colitur) for the 19 of its 138 rows where this applies directly, and a new entry C25 (57 rows) records the same gap propagating one hop further through step 3 into Holy Name week. Both are out of this task's own scope -- a behaviour change to a shared, multi-round-reviewed kernel-adjacent function -- and recorded, not fixed, per the coordinator's own instruction. C6's own note is rewritten in full rather than layered with another correction; C18's stale trailing count is fixed to match its own pin; the Vigil-misapplication risk flagged as a Minor closed itself once the Nativity-Octave fix landed, confirmed by inspection rather than assumed. dune test: 358/358. COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force: 359/359.
* test(differential): compare reading citations, 16 801 daysLukasz Kasprzak2026-08-152-23/+97
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Layer 3 is NOT blind to the ferial resumption rule, unusually for this project: lectio implements the same rubric independently, so the differential checks colitur's rule against a second implementation rather than against silence. It IS blind to the Commons -- lectio has no such concept -- so chain step 4 rests on layer 4 alone. Divergence triage, 1 532 of 16 801 days differ (15 269 match exactly), 0 unexplained: - A genuine colitur bug found and fixed: data/ef/lectionary.sexp carried lectio's own ini section names verbatim, never translated into the slugs Temporal_ef actually computes for the identical office. Data the bootstrap already held (Passiontide both weeks, the Pentecost Ember days, Holy Name Sunday) was unreachable by chain step 2, silently falling through to a wrong step-3 answer -- invisible until citation content became comparable. tools/bootstrap_lectionary.ml gained colitur_keys (rename/widen), a hand-authored ef-nativity-vigil entry cross-sourced from lectio's own sanctoral calendar and corroborated by colitur's own (overlay-suppressed) sanctoral bootstrap of the same source, and a derived ef-holy-name entry (RG 17(a)'s single Mass heading covers both the Sunday and 2-January shapes). One guess (giving the fixed Nativity-Octave days Holy Name Sunday's own citation uniformly) was tried and reverted once measured against the fixture -- Missal-verified to be right only for the genuine "Sunday within the Octave" case, wrong whenever the true preceding Sunday is Christmas Day or Advent IV instead. - 10 existing/widened Layer C entries (C1/C6/C8/C14/C15/C16/C17/C18 -- C6 and C17 narrowed with the removed population accounted for, not silently re-absorbed) plus two new ones: C19 (the already-adjudicated Time-after-Epiphany week-numbering offset, now visible in citation content), C20 (lectio computes no reading at all for the seven readingless saints Task 6 gave a Common or proper -- colitur is more complete, not wrong). - One entry, C21, is a genuine colitur LOGIC limitation found but not fixed: Lectionary_ef.readings' step 3 is a single hop to the preceding Sunday, insufficient for a run of colitur-only fixed Christmastide slugs whose own preceding Sunday is itself another entry-less fixed slug. Recorded verdict open (data/ef/expected-divergences-missalemeum.sexp's own established precedent for this shape), not left silently failing -- fully diagnosed in task-8-report.md, recommended as a dedicated follow-up task rather than a hasty fix to a shared, multi-round-reviewed kernel-adjacent function.
* ef(lectionary): the Commons, chain step 4Lukasz Kasprzak2026-08-152-1/+400
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fifteen class-3 saints had no readings anywhere in colitur -- the 6 March - 5 April cluster whose RANKS lectio's generator fix of 2026-08-12 restored without backfilling readings, and lectio has no Commons concept to have backfilled them from. They are exactly the fifteen `status Feast` entries in data/ef/sanctoral.sexp carrying `(citations ())`. The Missal splits them eight/seven, and NOT as the plan predicted. It expected propers for Benedict, Patrick, Thomas Aquinas and Gregory the Great; only Thomas Aquinas has one. Benedict ("Missa Os iusti, de Communi Abbatum"), Patrick ("Missa Statuit, de Communi Confessoris Pontificis I loco") and Gregory the Great ("Missa Si diligis me, de Communi unius aut plurium Summorum Pontificum") are all sent to a Common, with only Oratio, Secreta and Postcommunio printed at the date. All three reversals verified on the page images (pp. 488, 492) as well as in both OCR text layers. PROPERS (data/ef/adjustments.sexp, `Edit ... Set_citation`): thomas-aquinas 7 Mar Sap 7:7-14 / Mt 5:13-19 john-of-god 8 Mar Eccli 31:8-11 / Mt 22:34-46 forty-holy-martyrs 10 Mar Hebr 11:33-39 / Luc 6:17-23 cyril-of-jerusalem 18 Mar Eccli 39:6-14 / Mt 10:23-28 gabriel-the-archangel 24 Mar Dan 9:21-26 / Luc 1:26-38 john-damascene 27 Mar Sap 10:10-17 / Luc 6:6-11 john-of-capistrano 28 Mar Sap 10:10-14 / Luc 9:1-6 francis-of-paola 2 Apr Philipp 3:7-12 / Luc 12:32-34 COMMONS (data/ef/commons.sexp), each quoting the Missal's own instruction at the saint's date: sts-felicitas-perpetua 6 Mar non Virginum I Eccli 51:1-8,12 / Mt 13:44-52 frances-rome 9 Mar non Virginum II Prov 31:10-31 / Mt 13:44-52 gregory-the-great 12 Mar Summ. Pontificum 1 Petri 5:1-4,10-11 / Mt 16:13-19 patrick 17 Mar Conf. Pont. I Eccli 44:16-27;45:3-20 / Mt 25:14-23 benedict 21 Mar Abbatum Eccli 45:1-6 / Mt 19:27-29 isidore-of-seville 4 Apr Doctorum 2 Tim 4:1-8 / Mt 5:13-19 vincent-ferrer 5 Apr Conf. non Pont. I Eccli 31:8-11 / Luc 12:35-40 Every value read in three independent places -- both OCR text layers (two DIFFERENT printings, whose Commune Sanctorum pagination differs by 1-2, so the data keys on heading plus Mass incipit and never on the bracketed page number) and the rendered page images. That caught a genuine typographical error in one printing: it gives St Gabriel's Gospel as "Luc. 2, 26-38" (on the page image, so not an OCR artefact). Luke 1:26-38 is settled by the other printing, by the pericope text in both, and by the SAME edition citing the identical pericope correctly two pages later at the Annunciation. Step 4 EXECUTES SECOND (order 1, 4, 2, 3), not last as the plan sketched. Measured: every day on which one of the fifteen is actually the observed office already has a non-empty step-2 or step-3 answer waiting, so a step 4 placed after the ferial fallback is unreachable on every date in 1583-9999 -- and wrong where it would fire (2038-03-06 would emit Septuagesima II Saturday's Mass on a day whose office is Sts Perpetua and Felicity). Step 4 is also the only step in the chain with a direct primary-source warrant: the Missal names the Mass at each such saint's own date, while steps 2 and 3 rest only on lectio's observed behaviour. Guarded so it can never divert a day whose observed office IS its temporal office -- ferias, Sundays, the Triduum and the RG 78 Saturday Office of the BVM (which deliberately shares the ferial slug) are structurally excluded. Step NUMBERS are unchanged so every "step 3" already written down still names the same branch. The Commons are caller-supplied, like the lectionary: `Rite_ef.context` gains a REQUIRED `~commons`, not an optional one -- a silently-defaulted empty table would give a rite whose class-3 saints quietly lose their Mass, and nothing in layers 3-5 compares citations, so that loss would be invisible. `Commons.load` rejects the four defects indistinguishable downstream from "this saint has no Common": duplicate common id, duplicate assignment, empty formulary, assignment naming a common that does not exist. KNOWN LIMITS, recorded rather than papered over. There is no oracle: lectio has no readings for these feasts and missalemeum's 2026-2027 window never observes one of the fifteen. Eight of the fifteen are NEVER the observed office anywhere in the domain (10-28 March always falls to a Lenten or Passiontide feria that outranks a III-class feast), so they have no end-to-end witness and are covered by dedicated table tests instead. The Commune Sanctorum's own opening rubric permits any Epistle or Gospel of a Common in any Mass of that Common unless one is assigned, so what is emitted is the reading PRINTED WITH the named formulary, not the only lawful one; labelled alternatives (e.g. the widow's Epistle 1 Tim 5:3-10, squarely available to St Frances of Rome) are noted in the data and deliberately not encoded. The BVM Common is NOT authored: the RG 78 Saturday Office shares the ordinary ferial slug, so assigning a Common to it would hand the BVM Mass to every feria sharing that slug; routing it needs a subject-keyed lookup and a season-keyed choice among the Missal's five "Missae de sancta Maria in sabbato" -- new behaviour, not new data. It currently emits its feria's Mass, now pinned as such. Blast radius, measured (git archive HEAD binary vs HEAD, full 1583-9999 `colitur day` sweep, diffed): ZERO differing lines, 3 074 246 lines each side, byte-identical -- the CLI prints no citations, so this change is invisible to layers 2-5 by construction, and the sanctoral overlay perturbed nothing else. 357 tests green (344 before), 358 with the exhaustive sweep. Mutation-tested six ways, all reverted after confirming: relegating step 4 to last reddens exactly the four step-4 pins and nothing else; corrupting a Common's citation, dropping an assignment, dropping a proper Edit, and corrupting either an unreachable proper or an unreachable Common each redden their own pins. The load-bearing assertion is stated over the loaded layer rather than a hard-coded list: every sanctoral entry that can ever BE the observed office now has either a proper or an assigned Common -- fifteen failures before, zero after, and a future re-bootstrap adding a readingless class-3 feast fails there instead of silently emitting its feria's Mass.
* data(ef): carry sanctoral propers on the celebrationLukasz Kasprzak2026-08-151-209/+834
| | | | | | | | | | | | | | Celebration.citations has existed in the type, the .mli and the sexp schema since Plan 2 and has been emitted as () for every entry; the bootstrap now writes the real values for the 208 entries lectio has them for. On the celebration rather than in the lectionary table deliberately: a diocesan overlay adding a saint then carries his readings through the existing overlay algebra, with no second file to edit and no new directive. Asserted both ways -- the 13 January proper is present, and no Commemoration_only entry has readings, because in the EF a commemoration contributes an oration, not a reading.
* data(ef): bootstrap the temporal lectionary from lectioLukasz Kasprzak2026-08-141-0/+366
| | | | | | | 119 entries, Epistle + Gospel only. The generator refuses a section carrying exactly one of the two: an Epistle without a Gospel is malformed and wants investigating, not shipping. SHA-256 of the source INI is in the provenance header, as with sanctoral.sexp.
* fix(ef): three colour corrections that contradicted the Missal by nameLukasz Kasprzak2026-08-143-8/+46
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The sanctoral audit set chair-of-st-peter (22 Feb), conversion-of-st-paul (25 Jan) and peter (25 Jan, commemoration) to Red on RG 124(b). All three are reverted to White. RG 120(f), fifteen lines above RG 124(b) in the same column of the same page, names two of them in its enumeration of white: "Cathedrae S. Petri (22 februarii); Conversionis S. Pauli (25 ianuarii)". RG 124(b)'s own qualifier "in eorum die natalicio" independently excludes them, and RG 475(e) confirms it a third time by naming the Chair separately from "natalician feasts of Apostles". lectio and missalemeum both said white because both were right. peter rested on "match the host day's Red", whose host is white by that same clause, so the precedent inverts. Root cause: the audit's page-image method, its real advance, was applied to the calendarium and not to the rubric. RG 124 was read from the OCR text layer, where the two-column interleave shreds RG 120(f) across rows belonging to RG 115 and hyphen-splits Cathedrae, so a text-layer grep for it returns nothing while RG 124's enumeration survives cleanly. That read finds 124 and structurally cannot find 120(f). Two sentinels had been reporting this correctly and were overridden. The golden pin at 1902-02-22 asserted colour=white, was changed to red, and had its comment rewritten to guard against "a regression back to the old one" -- the only pin asserting a colour for any of the 21 slugs. And C18 and M21 both listed the two slugs under an RG 124(b) citation where both oracles were right. Pin restored; slugs dropped; C18 re-derived 526->450 from the observed-day counts, M21 20->18, M15 17->18. The audit's own discriminator would have isolated these. Its stated corroboration was that lectio's name.pl contradicts lectio's colour; run as a gate across all 21 that splits 17 genuine defects from 4 needing adjudication, and three of those four are these. It was quoted as illustration rather than used as a gate. 18 of 21 corrections stand, and the twelve whites are recited to RG 120(e) "Sanctorum non Martyrum" rather than to RG 117's negative residual. Also records Anastasia's deliberate non-fix in the tracked tree beside barbara, where the next auditor will be standing.
* data(ef): sanctoral audit — one genuine gap, 21 wrong colours (RG124)Lukasz Kasprzak2026-08-143-2/+109
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Full re-extraction of the primary CALENDARIUM (both photographic scans, missale-romanum-1962.pdf and Missale Romanum 1962_text.pdf, rendered page images read directly rather than trusting either PDF's own OCR text layer) against all 327 base sanctoral entries plus the two prior overlay Adds. Both scans agree throughout; no OCR ambiguity required adjudication this round. Add `barbara` (4 December): both scans carry a Commemoratio nested under Peter Chrysologus's own entry, absent from data/ef/sanctoral.sexp AND from lectio's own tridentine-calendar.ini (confirmed directly) — the same upstream-gap shape as the five entries the ef-rebootstrap task already added (agnes-secundo/boniface-martyr/eusebius-confessor/evaristus/ theodore). Correct 21 sanctoral colours against RG 124 ("De coloribus paramentorum"), scan-verified word for word: 124(b) red for an Apostle/Evangelist's own feast day except John's (27 December, already correct); 124(e) red for a Martyr's own martyrdom; RG 117's five-colour residual otherwise white. Every one of the 21 is a confirmed lectio/missalemeum bootstrap defect — checked individually against lectio's own ini, several of whose Polish name fields already contradict that same record's own colour tag (e.g. `martha`: "Dziewicy", Virgin, no "Męczennicy", yet `colour = red`) — the identical root cause and mechanism the `eusebius-confessor` fix already established. Colour is confirmed read nowhere in Precedence_ef's resolution logic (grep), so these are output-correctness fixes with zero observed-office blast radius; three (apollinaris/josaphat/chair-of-st- peter) additionally disagree with missalemeum's own oracle, not only with lectio, so they are adjudicated from RG 124's text directly rather than by which engine happens to agree. Both corrections applied via data/ef/adjustments.sexp's established Overlay mechanism (data/ef/sanctoral.sexp stays generated, untouched). Pinned: test_rite_ef.ml (two new tests, verified to fail against the pre-fix data); test_differential.ml's new C18 predicate (526 rows, 2005-2050) and test_oracle.ml's new M21/M22 predicates (20 + 2 rows, 2026-2027), each with data/ef/expected-divergences{,-missalemeum}.sexp entries citing RG124 or the matching upstream-gap precedent; test_golden.ml's 1902-02-22 pin corrected (chair-of-st-peter was white in the stale pin, RG124(b) makes it red). All 329 tests green, including the full 1583-9999 exhaustive sweep. Full method, per-entry adjudication table and corrected bucket counts: docs/research/rules-register.md (gitignored) and .superpowers/sdd/2026-08-12-colitur-rg16a/sanctoral-audit-report.md (gitignored).
* docs: a confidence raised while its revisit trigger was deletedLukasz Kasprzak2026-08-131-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Five follow-ups from the fix-round re-review, none touching a computed result. M20's note had said "moderate-high, not certain" with a specific revisit trigger attached. The fix round raised the label to "near-certain" and deleted the trigger in the same edit. Upgrading a confidence while removing the condition that would lower it again is the one move this record must not make, so the trigger is restored alongside the higher label: no primary text anywhere names the Major Litanies in a Mass-orations-count worked example, only the general II-class-Sunday rule twice over. RG 434(b) closes the Office-shaped doubt and nothing further. "WORD-IDENTICAL" overstated the relation between RG 111(b) and n. 434(b). Only the trailing "quae tamen omittitur si commemoratio privilegiata facienda sit" is verbatim in both; the openings differ, n. 434(b) recasting the rule into the orations register. The claim was self-refuting -- both texts are quoted adjacent to it -- and the argument never depended on it. Now "identical in its operative clause". And n. 434 is not "a different part of the same document": the running heads put RG 111 under Rubricae generales and n. 434 under Rubricae generales Missalis Romani, two distinct corpora bound in one volume, which is the entire force of the corroboration. The code comment had understated its own point. Also corrects the register's LT line range for n. 434(b) (3574-3576, not 3564-3570; the (b) clause is not in the cited range), and records in calendar.ml the diagnostic that channel (3) trades away: a rite whose transfer_target names a date its own disposition omits used to raise a loud, mislabelled Validate failure and is now silent at the origin. The kernel cannot distinguish that from a deliberate omission without rite knowledge it must not have, so accepting it is right -- but the signal is gone, and that should be written down rather than discovered later.
* docs(ef): fix round 1 corrections (F2-F5) -- inert fields, a date, an ↵Lukasz Kasprzak2026-08-131-2/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | attribution, RG434(b) F2: the task report's "colour/subject/names on the Litanies entry are currently inert" was false for names -- test_oracle.ml's identity axis reads Names.find on every commemoration, and the invented English name is exactly why 2026-04-25 registers as a Comm_identity_mismatch, the reason M5 had to be re-gated. Corrected precisely, field by field, in the report. F3: Easter 2027 is 28 March, not 18 April, as previously stated in the report's own "layer 4 is blind to the transfer" reasoning. The conclusion survives (28 March is not 25 April); the date is corrected. F4: "the exhaustive property sweep caught this immediately" was a wrong attribution, in the report, the register, and calendar.ml's own comment. The committed exhaustive sweep walks 1583-9998 in order and aborts at the first failure; the years actually reported (5700, 4747) were draws from prop_invariants, the default dune test run's 200-year random sample. Corrected in all three places. F5: the Litanies' privileged commemoration is due in the MASS specifically (RG 108 + RG 81), never a separate Office answer -- colitur emits one resolved day, and the M20 adjudication rests on that being the Mass reading. Stated explicitly now in M20's own note, the register, and precedence_ef.ml's own RG111(b) comment. Also added to M20 and the register: Rubricae Generales Missalis Romani n. 434(b) ("VIII - De diversis Missae partibus", "D) De orationibus", "I - De orationibus in genere"), verified word for word against all three documents -- word-identical to RG 111(b) but explicitly scoped to the Mass ("post orationem Missae"), answering the objection that RG 111 (Caput XVI, which RG 106 says governs both Office and Mass) might be read as Office-shaped. Moves the M20 adjudication from "moderate-high, not certain" to near-certain.
* test(ef): oracle allow-list for the Major Litanies, and a source-fidelity ↵Lukasz Kasprzak2026-08-131-5/+12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | correction M5 (the pre-existing register-cited gap this task closes) had a genuine backwards claim in its own note: it said missalemeum's single admitted commemoration on 25 April 2027 (the Sunday-conflict year) was "the Major Litanies". Checked directly against the raw fixture row while adjudicating this task's own new divergence -- missalemeum's own commemorations field there is "St. Mark"; "Pro rogationibus" (the Litanies) is the one listed as displaced. Corrected in place, both the .sexp entry and this file's own header note. M5 itself now covers only the ordinary case (25 April 2026): presence agrees now that the entity is built, identity does not (colitur's own English name vs missalemeum's Latin-ish descriptive string), the same declared comparator limit M15/M18 already document, hit here for a different reason. The real, substantive divergence the correction exposes: colitur now admits the Litanies on 2027-04-25 where missalemeum admits Mark. New entry M20, adjudicated colitur -- RG 109(f) places the Litanies in the same closed privileged list as (a)-(e), and RG 111(b)'s own text is unconditional ("the II-class commemoration is dropped if a privileged one is due", no table-order qualifier). The same mechanism already primary-source-verified and used for other RG 109 categories elsewhere in this codebase; honestly flagged as the first real, non-synthetic test of this specific admit clause, consistent with this oracle's own already-documented pattern of RG 108-111 gaps (M1, M8, M10). M18's own count moves 395 -> 394 (2027-04-25 no longer falls into "observed- identity-unresolved alone"; it moved into M20), traced to the specific date and mechanism, not merely re-baselined. data/ef/expected-divergences.sexp (the lectio allow-list) needed no change: lectio computes no Major Litanies at all, and its own row type carries no commemorations field -- layer 3 is entirely blind to this change, confirmed rather than assumed.
* feat(ef): the Major Litanies -- entity, RG80 transfer, RG109(f) privilegeLukasz Kasprzak2026-08-131-0/+88
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 80 (Caput X, "De Litaniis maioribus et minoribus", A): the Major Litanies are assigned to 25 April, transferred to the following Tuesday (always Easter+2) when that date is Easter Sunday or Easter Monday. RG 81, same division: nothing is done in the Office, only in the Mass. RG 109(f) (Caput XVI): a privileged commemoration. Entity: Commemoration_only, Fixed(4,25), via data/ef/adjustments.sexp's `Add major-litanies` -- RG 81 denies it Office standing entirely, which is exactly what Commemoration_only already means to Precedence.resolve (held out of the observed contest by construction). Rank Class4 deliberately, not Class1, to keep the new RG109(f) privilege branch live rather than shadowed by privilege_of's existing "of a I-class day" branch. Colour Violet by consistency with the sibling Minor Litanies; subject Saint, the same "no special exclusion" default this codebase's other Commemoration_only entries use. Names not primary-sourced, flagged as such in the data file's own comment. Transfer: reuses the existing RG 96 placement machinery (Precedence.disposition's Transfer constructor + Calendar's place_transfers/resolve_with_injected) rather than a new candidate channel -- RG 80's own transfer is structurally the same operation RG 96 already performs for an impeded I-class feast. disposition gains a branch, checked before the Commemoration_only catch-all: the Litanies losing to winner slug "ef-easter-sunday" or "ef-easter-1-monday" is Transfer, not Commemorate. transfer_target gains a branch, checked before the general RG 96 search is even computed, returning Easter+2 directly with no search at all -- necessary, not merely simpler, since Easter+2 is itself I-class and a search from there would walk past the exact day RG 80 names (a commemoration needs no unoccupied day the way a displaced feast does). Privilege: privilege_of's previously-dead RG 109(f) branch now recognises the Litanies' own slug. No other code change -- admit's existing Class1/Class2-Sunday branches already implement "a privileged commemoration categorically takes the slot" correctly, from an earlier task. Full blast-radius measurement, adjudication of the resulting missalemeum divergence, and the mutation proofs are in the task's own report, .superpowers/sdd/2026-08-12-colitur-rg16a/major-litanies-report.md.
* docs: an invented Caput, a list that was not closed, and a blind spotLukasz Kasprzak2026-08-131-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three corrections from the fix-round re-review, none changing behaviour. RG 431(e) was attributed to "Caput XX, De hymno Gloria in excelsis". There is no Caput XX -- the Rubricae Generales' Caput series ends at XIX. The paragraph number, letter and subsection title were all right; only the containing division was invented. It sits under "VIII -- De diversis Missae partibus", subsection "C) De hymno Gloria in excelsis". Shipping a new invented structural citation in the very round that corrected a source-fidelity overclaim is what the gate exists to catch. marian_slugs claimed TWO entries found and deliberately excluded after individual consideration, "not merely omitted". There was a third: vigil-of-the-assumption, 14 August, which is de B. Maria Virg. and whose collect implores her protection. It was merely omitted. Added to the list -- behaviourally inert, verified: it wins 7196 times domain-wide, always with the non-Marian eusebius-confessor as its only commemoration, and never loses -- but a closed list's whole value is its enumeration claim, and that claim was false as written. And the new rule has no comparison-layer witness at all. Reverting it reddens three tests, none of them a comparison layer: the differential's row type carries no commemorations, and the oracle's window has no 16-July Saturday. Worse, lectio's fixture DOES carry commemorations and prints +our-lady-of-mt-carmel on all 8 in-window dates where colitur now prints nothing -- a real divergence, neither fixed nor cited, swallowed by a blind spot, which binding decision 1 forbids. Recorded, with the note that colitur's reading here has no reference confirmation anywhere and only extending the oracle fixture to 2033 or later would settle it. Also records the RG 112(d) branch's ordering constraint: it precedes RG 95's Transfer branch, which it does not need to, and must move below it if a Class1 Marian entry is ever added.
* data(ef): close and derive the RG 91 entry 27 allow-list rowsLukasz Kasprzak2026-08-132-18/+49
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | The BVM Saturday office is now genuinely correct, so both comparators' allow-lists move -- every count derived from the harnesses' own failure output, never transcribed or guessed. lectio differential (data/ef/expected-divergences.sexp, C17, new, colitur): lectio builds no equivalent office at all, so it keeps showing the plain season colour on every otherwise-unoccupied Class4 Saturday where colitur now shows white. Season, slug and rank all still agree (the office deliberately reuses the ordinary ferial slug), so Colour_f alone is the complete diff set -- gated on colitur's own rank/weekday/colour rather than a date list, the same shape M2 used, at 416 rows over 2005-2050. missalemeum oracle (data/ef/expected-divergences-missalemeum.sexp): M2 is CLOSED, REMOVED, the same shape as this file's own M4/M6/M7/M9/M12/ M14/M17 precedents -- not re-adjudicated to a different verdict, gone because the colour divergence it named no longer occurs on any of its former 22 dates. What is left on all 22 is Observed_identity_unresolved alone (colitur's BVM-Saturday candidate is temporal-origin and deliberately carries no English name) -- exactly M18's own shape, not a distinct citation any more. Keeping M2 as a separate id would have kept silently absorbing these rows under a stale "colour differs" citation whose own reason had stopped firing (subset's own diff-set test admits a SMALLER set than the one named) -- the same "count proving cardinality where identity was required" trap this project's review process watches for. M18's own expected_rows moves 373 -> 395 accordingly.
* fix round 1: RG 110(c)'s ordering was inverted; M19 lacked an identity gateLukasz Kasprzak2026-08-131-10/+15
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 110(c), word for word: "quoties vero oratio unius Apostoli addenda est ad modum commemorationis, huic orationi additur altera immediate, ante omnes alias commemorationes." "Huic orationi" (dative, "to this oration") refers to the trigger's own oration; "additur altera" means the companion is added to it, i.e. follows it. "Ante omnes alias commemorationes" governs the pair's position relative to other, unrelated commemorations, not the pair's own internal order. The previous build prepended the companion uniformly in both RG 110 shapes, citing this same clause for both -- correct for (a)/(b) (the day's own oration, not a list member, has nothing to order against), backwards for (c), where the trigger is itself a list member and the companion belongs after it. Fixed in rg110_additions: shape (a)/(b) still prepends; shape (c) now splices the companion in immediately after its own trigger. Re-pinned the unit test that had asserted the wrong order, and corrected both rows' comments to cite the clause that actually governs each shape. No layer in this codebase compares commemoration order: the lectio differential never compares commemorations, the oracle sorts both sides into a multiset, and the golden tests' own describe function sorts its commemorations field too. Recorded as a new, permanent blind spot in CLAUDE.md and the register, not merely fixed around -- the admit_cases unit table is the only place order is asserted at all. Re-measured the full blast radius against the same pre-change binary: same 14,627 differing days, same four shapes and counts, confirming the fix changed only the content of the 593 shape-(c) rows and nothing else. Mutation-tested: reverting the splice back to a uniform prepend reddens exactly the shape-(c) unit test; a new golden pin for a real shape-(c) day (2004-02-22) stays green under the same mutation, living proof that golden pins cannot see this axis, not merely a claim. Separately, M19's own predicate checked a date and a diff shape but not which candidate -- proved insufficient by fabricating a second, bogus companion on 30 June and confirming the whole suite stayed green. Fixed by requiring the admitted commemoration to be exactly commemoration-of-st-peter, mirroring C16's own identity guard. Added a golden pin for 30 June, the only assertion of that entry's identity outside the allow-list. Also: corrected a false claim in the task report that the electronic transcription carries no corroboration of the Holy Name fallback -- its own table of contents does, at a line not checked on the first pass. Added two further citations, both re-verified against the scan: RG 17's own closing paragraph, settling the Sunday shape's Sunday- exclusion at the rubric level rather than only in the propers; RG 124(c), which assigns red to 30 June by paragraph number rather than by the generic apostle/martyr default. Noted the fragility of deriving the Sunday shape's RG 109(a) privilege from a slug-substring match rather than from a citation. Cosmetic: a stray blank line and an indentation inconsistency.
* precedence(ef): RG 110, the inseparable Peter/Paul commemorationLukasz Kasprzak2026-08-132-8/+75
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 110 (Caput XIV): "In Officio et Missa S. Petri semper fit commemoratio S. Pauli, et vicissim... pro unica habeantur" -- in the Office and Mass of either Peter or Paul, a commemoration of the other is always made, uncapped by RG 111's own admission count. Both photographic scans, full text including sub-clause (c) (a separate trigger this register previously had not transcribed: the same rule also fires when one Apostle's oration is admitted merely as an ordinary/privileged commemoration, not only when it is the day's own office). Three real pairs in the 1962 calendar, all confirmed on both scans: 25 January (conversion-of-st-paul + peter), 22 February (chair-of-st-peter + paul), 30 June (in-commemoratione-sancti-pauli-apostoli + a new commemoration-of-st-peter). The first two already existed as ordinary sanctoral candidates but were wired into RG 111's ordinary, capped admission contest instead of this rule's own uncapped one -- the defect this closes. 30 June had no companion at all: a genuine gap in lectio's own source data (confirmed directly against tridentine-calendar.ini) and in missalemeum's own oracle output for both fixture years, not only a colitur bootstrap miss, so colitur is now uniquely correct on this date among the three engines compared. Added via data/ef/adjustments.sexp's own Add directive, the architecture's existing extension point for exactly this shape of gap, since data/ef/sanctoral.sexp is generated and lectio must not be touched. Precedence_ef.admit gained rg110_additions/rg110_companion_slug, layered on after admit's own four ordinarily-capped branches decide their normal result: checks both the observed candidate and every already-admitted candidate against the three-pair lookup and prepends any companion found, uncapped and additional, never competing for RG 111's own slot. Measured full blast radius (1583-9999, pre-change binary from git archive at 59fbda8 vs the fixed binary, 3,074,246 days each side, 14,627 differing days, all four predicted shapes, zero unclassified): 3,619 Holy Name fallback days (previous commit), 3,533 days Paul was wrongly excluded from Chair of St Peter's own day by a competing privileged feria, 593 days Chair of St Peter itself was admitted only as an ordinary commemoration with Paul entirely absent, and 6,882 days the new 30 June companion is added. The 3,533+593 figure supersedes an earlier, narrower 852-day estimate that measured only the delta between two older code commits, not the rule's full scope. The lectio differential needed no change: it does not compare commemorations at all, and this fix never touches season, slug, rank, or colour. The missalemeum oracle allow-list needed three changes: M12 removed (22 February 2027 no longer diverges in commemoration count), M15's date list gained 2027-02-22 (now reaching identity comparison for the first time, where it lands on that entry's own pre-existing, unrelated limit -- one of the two commemorations is temporal-origin and unnamed), and a new M19 for the 30 June gap. Mutation-tested, both halves separately: disabling the admit-level addition reddens the two new unit tests, the oracle's own M12-shaped checks, and an existing golden pin (updated to expect the Paul companion); removing only the 30 June data entry reddens solely the oracle's new M19 count, confirming the code and data halves are independently load-bearing.
* temporal(ef): Holy Name of Jesus, RG 17(a) -- fallback + Lord subjectLukasz Kasprzak2026-08-131-0/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 17(a): "festum Ss.mi Nominis Iesu, celebrandum dominica quae occurrit a die 2 ad 5 ianuarii (secus die 2 ianuarii)" -- both photographic scans, confirmed on the calendarium's own January table and the Mass propers' own heading. Two defects closed together, since they interact through RG 91 entry 14/RG 16(a): 1. The Sunday shape (2-5 January) now tags subject = Lord, matching RG 91 entry 14 ("Festa Domini II classis") -- previously subject = Temporal, the same defect shape Holy Family had before it. 2. The fallback (RG 17(a)'s own "secus die 2 ianuarii", the calendarium's "vel, ea deficiente, die 2 ianuarii") is now built: 2 January carries the feast whenever no Sunday falls 2-5 January that year. Before this fix colitur emitted no Holy Name office at all in such a year -- a real II-class feast simply missing, on 3,619 of 8,417 years in the domain (43%; 20 of the 46 years 2005-2050, independently re-derived against `date -d`). Both shapes band identically at RG 91 entry 14's MOVABLE half (Precedence_ef.entry_14_movable_band) -- one feast, not two differently classified ones, per the Mass propers' own single heading covering both dates. No live collision exists in the current sanctoral data (nothing fixed-date falls 2-5 January besides a Commemoration_only entry on 5 Jan), so the RG 16(a)/RG 112(a) interaction is proved with synthetic precedence-level fixtures mirroring Holy Family's own precedent, not a live-data regression. One asymmetry the synthetic tests found and kept honest rather than forced: a losing Holy-Name-Sunday is privileged (RG 109(a), "of a Sunday") and survives RG 111(a)'s cap; a losing Holy-Name-fallback is not (2 January is not a Sunday, and no other RG 109(a)-(f) category applies to it), so it gets no commemoration at all if ever outranked -- both individually correct readings of RG 109/111's closed lists. Golden pins follow the brief's own vacuity warning: the Sunday shape already emitted the right slug/rank/colour before this fix (an ordinary Sunday and Holy Name coincide on those fields), so its pin asserts subject explicitly rather than pinning the whole line uncritically; the fallback pin (2029-01-02) is the one with teeth against the larger defect, reddening on slug/rank/subject together if reverted. Mutation-tested: each half of the fix reverted separately reddens exactly its own tests (Sunday subject: the dedicated unit test, the golden pin's subject field, one precedence end-to-end test; fallback: the fallback unit/anchor/golden tests, the new C16 differential count, and both shapes of the precedence end-to-end tests) and nothing else. Adds data/ef/expected-divergences.sexp's C16 (lectio has no fallback for this window at all) -- the lectio differential's own count, independently re-derived against `date -d` for the fixture's 2005-2050 span, not transcribed from the domain-wide figure. The missalemeum oracle window (2026-2027) exercises only the Sunday shape and needed no new entry: subject is not one of its compared fields, and both years' rows already corroborate the Sunday-shape ranking (missalemeum's own 2026-01-04 and 2027-01-03 rows: rank 2, white, title "Holy Name of Jesus").
* test(oracle): fix round 1 (F8) -- correct the M18 headline, name the blind ↵Lukasz Kasprzak2026-08-131-1/+1
| | | | | | | | | | | | | | | | | | | | spot precisely The full breakdown, precisely: of 730 days, 331 are resolved (330 matching, 1 mismatched -- M13, Joseph vs the Seven Sorrows), 399 unresolved -- 373 in M18's own bucket, 26 absorbed inside M1/M2/M3/ M16's own widened subsets where the axis fires alongside their pre-existing citation. M18's own note previously stated only its own 373 without this fuller context. Also names the blind spot the axis exists to close in exact terms, not just "the observed day's identity": a TEMPORAL-origin observed day silently replaced by a DIFFERENT temporal-origin observed day of the SAME rank and colour -- exactly Holy Family's own shape, and the reason every prior layer stayed green through the whole gap. States explicitly what the count pin does and does not prove: it guards against the 373/399 population moving silently in either direction, but cannot say which specific day moved or why.
* data(ef): fix round 1 (F6) -- restore C1's provenance record for 361Lukasz Kasprzak2026-08-131-1/+1
| | | | | | | | | | | The previous refresh's own note recorded HOW 368 was derived (a Python cross-check against the comparator's own failure output, not hand-counted and not copied from a green run); this task's rewrite explained WHY 361 replaces it (C15's own split) but dropped the provenance sentence, stating 368 -> 361 with no record of how the new number was checked. Restored: 361 read directly from the OCaml comparator's own failure output, with the 368-minus-7 arithmetic noted as corroboration, not the derivation.
* test(oracle): green -- widen five entries, add M18 for observed identityLukasz Kasprzak2026-08-131-2/+20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | layer_m_reason's own five entries whose diff shape was checked by EXACT list equality (M1, M2, M3, M13, M16) lost their match once the observed-identity axis widened every row's diff set two commits ago. Each is widened the same way commemoration-identity was already widened for M13 once before: the axis now ALSO fires alongside their existing citation, same root cause, same id -- M1/M3 (temporal-origin observed day, no name), M2 (same, plus five Paschaltide dates that used to match cleanly and now surface for the first time), M13 (Joseph, sanctoral, now a genuine title MISMATCH, not merely unresolved), M16 (the temporal Passiontide feria observed there). M18 covers the remainder: 373 of 730 days whose observed celebration is temporal-origin and so carries no English name at all -- the overwhelming majority of the window, including Holy Family's own two dates in it (2026-01-11, 2027-01-10), counted here rather than shown as a false Matched. Gated on the diff SHAPE alone (this file's own convention for a population this size, the same reasoning M2's own title-substring predicate already uses instead of a date list), verdict unresolvable -- the same fourth verdict M15 already established for the identical limit on the commemoration axis. Building an English name onto the temporal cycle is Plan 4's own scope, register §6. Teeth, per the brief: temporarily corrupted hilary's own English name in data/ef/sanctoral.sexp (14 January 2026, an OBSERVED sanctoral day, rank and colour left untouched) and reran. Reddened immediately: 2026-01-14: observed-identity-mismatch differ -- colitur=(rank=3 colour=w ...) oracle=(rank=3 colours=[w] title="St. Hilary" ...) rank and colour agree on both sides -- exactly the shape the pre-strengthening comparator would have slept through completely. Reverted (git diff on the data file is empty) before this commit.
* test(differential): split the Holy Family/Baptism collision out of C1 (C15)Lukasz Kasprzak2026-08-131-2/+7
| | | | | | | | | | | | | | | | | | | | | | | Before this task's Holy Family fix, colitur's generic Sunday fallback lost outright to the fixed Commemoration of the Baptism of the Lord on 13 January whenever it fell on a Sunday, producing the SAME slug lectio's own tridentine-calendar.ini shows there -- squarely inside C1's own 6-13 January blanket (only Season differed). Now that Holy Family (RG 17(b)) correctly outranks the fixed Baptism and excludes it as a commemoration (RG 91 entry 14, RG 112(a)), colitur's own slug on those seven dates (2008, 2013, 2019, 2030, 2036, 2041, 2047) changes to ef-time-after-epiphany-sunday-1 -- a genuine identity divergence against lectio, which has no Holy Family at all. C1's own predicate does not absorb it (jan_6_13_slug has no entry for that slug), so it surfaced as unexplained, exactly the check the task brief asked for: split it out into its own cited entry rather than widening C1's predicate to swallow it silently. C1's own expected_rows moves 368 -> 361 (back to its pre-rebootstrap value, for a third and different reason than either of the first two moves recorded in its own note); the new C15 covers the 7 rows, gated on the literal date list AND colitur's own slug, the same identity guard C1/C6/C8/C14 already apply.
* test(differential): refresh the lectio fixture, close 9 entries, add C14Lukasz Kasprzak2026-08-121-53/+33
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | lectio moved 2386a45 -> 3b32c00 (seven EF calendar defects fixed, plus a sanctoral-generator root-cause fix); the committed fixture predates all of them. Regenerated with the same command the provenance note already named (`go run ./cmd/lectio-ef-dump 2005 2050`), read-only against lectio -- nothing there was touched. New SHA-256 asserted in both the provenance note and test_differential.ml's fixture_sha256. The dumper's two new name_en/name_pl columns are kept verbatim (the fixture is meant to be the exact command's raw output; the comparator already discards everything past field 7). Every C-entry's expected_rows was re-derived independently against the refreshed fixture (a Python re-implementation of diff_fields/ layer_c_reason, cross-checked against the real OCaml comparator's own failure output), not transcribed from a green run. Nine entries -- C2, C3, C4, C5, C7, C10, C11, C12, C13 -- now match zero rows and are closed: sexp row removed, layer_c_reason predicate and its now-dead helper bindings removed, citation preserved in docs/research/rules-register.md's new §3d with the specific lectio commit that fixed each one. C1 (361 -> 368) absorbs C13's former 7 rows now that lectio's own slug matches there too. C6 (138, unchanged count) narrows to a slug-only shape now that lectio's rank agrees. C8 (31, unchanged) survives untouched -- lectio still computes no Rogation days. C9's old shape ("Joseph observed ON a Lent Sunday") is also closed, but a narrower, different divergence survived under the same saint's slug: 3 years (2008, 2035, 2046) where Joseph's own congested RG96 walk collides with the Annunciation's separately-transferred "sedes propria". The old predicate's broad "any diff involving this slug" match was silently absorbing both under one citation -- split into a new id, C14, rather than folded back into C9, since the two are unrelated rules. lectio's own commit history (09a1444) documents this residual as a gap it deliberately left unresolved; colitur gives Joseph a real transferred day either way, which is why C14's verdict stands regardless of a genuinely open sub-question (recorded in the register, not resolved here) about which of the two feasts should claim the contested day first under RG98's "prius impeditum" wording. Re-triaged the full remaining 4975 raw differences: only 10 distinct field-diff signatures now, all resolving to Layer A/B noise or the four surviving citations, 0 unexplained. The crude "Sunday-rank" and "Holy Thursday" residues the task flagged both fully resolve once the real Layer A/B machinery runs (season-spelling and week-numbering artifacts, not substantive divergences) -- see the task report for the derivation. Teeth proof: perturbing ferial_rank to reproduce C4's old defect made 140 rows reappear as unexplained (the exact prior C4 count), reverted after capturing the failure message. 277 tests green (dune test) and 278 with COLITUR_EXHAUSTIVE_SWEEP=1 (the extra case is the 1583-9999 sweep itself).
* data(ef): fix round 1 (F7) -- track the 5 new entries' primary citationsLukasz Kasprzak2026-08-121-2/+29
| | | | | | | | | | | | | | Removing M4/M6/M7/M9/M14 (an earlier commit) deleted the only TRACKED primary-source citations for these five entries: docs/research/ (where the full quotes originally lived) is gitignored, and data/ef/sanctoral.sexp is generated data with no citations field of its own, so the public repo held no record of why agnes-secundo, boniface-martyr, eusebius-confessor, evaristus and theodore exist. Added a provenance note quoting each one's calendarium line verbatim from the photographic scan. Also fixes a name omitted from the "FIVE MORE REMOVED" note's own list (boniface-martyr was missing; no other consequence, since neither the removal itself nor the row-count pin ever depended on that prose list).
* data(ef): fix round 1 (F3) -- eusebius-confessor is white, not lectio's redLukasz Kasprzak2026-08-121-1/+28
| | | | | | | | | | | | | | | | | | | | | | | | data/ef/sanctoral.sexp bootstraps colour=red for this new entry (14 Aug), inherited from lectio's own ini; missalemeum's fixture independently tags it :r too -- two non-primary witnesses agreeing with each other, not with the Missal, the exact "trap" this project's own transcription-audit task already named once. Checked directly against the scan (missale-romanum-1962.pdf), not taken on a reviewer's transcription: the 14 August proper reads "S. Eusebii Conf. / Commemoratio / Missa Iustus, ut in festo S. Pauli primi Eremitae, die 15 ianuarii" -- Conf. (Confessor, not Martyr), borrowing the SAME Mass 15 January's own Paul the First Hermit uses, a Confessor's Common, white in both engines' own data for THAT entry. Every other Confessor/Abbot commemoration in lectio's own data is white (maur-abbot, giles, remigius, didacus, ubaldus, hilarion, sabbas, silvester, alexis); Eusebius Confessor is the one exception, with no textual support found for it. No behavioural impact today (Commemoration_only entries are never the printed observed colour under the current pipeline), fixed via the overlay mechanism since sanctoral.sexp is generated and the source is upstream. New test proves it through the real pipeline, the same discipline already applied to the subject retags.
* test(differential): fix round 1 (F2, F5) -- split 13-Jan-Sunday into C13Lukasz Kasprzak2026-08-121-3/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 13 January retagged subject=Lord (previous commit's regeneration) makes RG16(a) fire on the 7 years it falls on a Sunday within 2005-2050 (2008, 2013, 2019, 2030, 2036, 2041, 2047): colitur now observes the Baptism of the Lord itself where it used to observe the generic Sunday slug. The diff signature against lectio's fixture widens from {season;colour} to {season;slug;colour} -- but jan_6_13_slug already pre-authorised this exact slug across the whole 6-13 January window (correctly, for the other 39 non-Sunday years), so layer_c_reason routed the new, wider-shape rows into C1 unchanged, and C1's own expected_rows (a pure row count) did not move. A count proving cardinality where identity was required -- on the one day this branch itself already flags as still wrong under RG 112. Split into its own entry, C13, matched by an exact 7-date literal list checked before C1 in the if/elif chain. C1's own expected_rows corrected 368 -> 361 (independently re-derived: a Python script reading data/ef/sanctoral.sexp's regenerated output and the committed lectio fixture directly, replicating diff_fields/layer_c_reason's own logic outside the OCaml comparator -- not transcribed from the failing comparator's own count). C13's own citation records a refinement over the coordinator's framing, checked directly rather than assumed: lectio's committed fixture is a frozen snapshot that predates lectio's own equivalent fix to this entry's class field (the same staleness shape as C12). Confirmed by building lectio's own cmd/lectio-ef-dump from its current source tree (read-only) and running it fresh for all 7 dates -- lectio's LIVE engine now agrees with colitur's slug/rank/colour exactly, disagreeing only on season (the already-understood C1 divergence). This is not evidence colitur's answer is right against the Missal -- it is not (register's own RG112 item) -- only that this specific divergence from the fixture is a staleness artifact, not a live disagreement between the two engines' current logic. Also fixes F5: C12's own note said "5 of the 15 corrected slugs" while listing and counting 7 -- corrected to 7. Also corrects the module header's stale "13 January ... slug/rank/colour already equal lectio's exactly, in all 46 years" claim (true only for the 39 non-Sunday years now).
* test(oracle): close M4, M6, M7, M9 and M14 -- the five now-fixed data gapsLukasz Kasprzak2026-08-121-25/+23
| | | | | | | | | | | | | | | | | | Each was recorded as a genuine data gap "traced upstream into lectio, unfixable without touching lectio". That constraint is gone: all five commemorations (St Agnes secundo 28 Jan, St Eusebius Confessor 14 Aug, St Evaristus 26 Oct, St Theodore 9 Nov, St Boniface Martyr 14 May) are now present in the re-bootstrapped sanctoral.sexp, and each divergence stops occurring in the 2026-2027 oracle fixture -- verified by re-running the comparator and confirming zero rows for all five ids, not assumed from the data change alone. Removed rather than kept as dead 0-row citations, matching the M17 precedent already in this file: the count pin (test_layer_m_counts_match_citations) now enforces that none of these five divergences can silently reappear. Corresponding dead code (m4_dates .. m14_dates and their layer_m_reason branches) removed from test_oracle.ml; a short pointer comment left at each id's old location.
* test(differential): allow-list C12 for the 15-entry rank correctionLukasz Kasprzak2026-08-121-0/+5
| | | | | | | | | | | | | | | | | | | | | | | Re-bootstrapping sanctoral.sexp (previous commit) makes 7 of the 15 newly-corrected III-class feasts win an occurrence they previously lost as Commemoration_only: RG 91 entry 24 (a universal III-class feast) outranks entry 28 (a IV-class feria), and the occurrence code that resolves this already existed and needed no change. lectio's own committed differential fixture (test/fixtures/lectio-ef-2005-2050.txt) is a frozen snapshot predating lectio's own equivalent generator fix, so it still shows the class-4 feria observed with the same saint only as a trailing commemoration -- a new, expected divergence from that fixture, 14 rows in the 2005-2050 window. expected_rows (14) is independently re-derived by grepping the regenerated sanctoral.sexp's own post-regeneration civil-day output against the committed lectio fixture directly (two flat-file extractions cross-checked against each other), not transcribed from the comparator's own failing-test output. C12 reuses an id last used, and removed, by the RG16(a) task's own reverted Purification retag -- noted at both the old and new entries' own comments so the id's history is visible to a future reader.
* data(ef): document why adjustments.sexp's directives survive the re-bootstrapLukasz Kasprzak2026-08-121-0/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Neither of adjustments.sexp's two directives is a no-op after the regeneration, and neither is removed -- reasoned through deliberately rather than deleted on sight, per Overlay.apply's own documented diagnostic behaviour (Edit has no "already correct" case; it unconditionally folds every field_edit over the current value when the slug is present, no diagnostic either way). `Edit most-holy-name-of-mary (Set_subject Bvm)`: the base bootstrap no longer needs this correction (the source dropped `class = lord` for this slug, so the default is already Subject.Saint), but the overlay still forces `Saint -> Bvm`. Kept for two reasons: data precision (the calendarium's "Sanctissimi Nominis Mariae" title still argues for Bvm over the generic Saint default, independent of what lectio's class field says) and as a regression guard (Set_subject is unconditional, so it would also correct a future re-introduction of `class = lord` for this slug, the one subject value RG16(a) behaviourally reads). `Suppress vigil-of-christmas`: re-verified, not assumed -- the slug is still present, unchanged, in both the regenerated source and the regenerated sanctoral.sexp, so the directive still fires its ordinary slug-present path. test_rite_ef.ml's own comment on the overlay-in-effect test previously claimed the base file carries `subject Lord` for most-holy-name-of-mary, which the regeneration made false; corrected in place rather than left stale, per this project's own discipline around wrong claims.
* data(ef): re-bootstrap sanctoral.sexp from the corrected lectio sourceLukasz Kasprzak2026-08-121-35/+60
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Regenerated via tools/bootstrap_sanctoral.ml against lectio's tridentine-calendar.ini (SHA-256 6a25e634... -> 1b303ef2...), whose own generator was independently fixed in a concurrent session. 327 entries (223 feast, 104 commemoration-only), up from 322 (208/114): - 15 entries wrongly bootstrapped `rank = commemoration` are now `class-3` (a real III-class Feast) -- all 6 March-5 April, all primary-source-verified against the calendarium (register's own "Commemoration-only entries' inferred STATUS" item). - 5 new entries: agnes-secundo (28 Jan), boniface-martyr (14 May), eusebius-confessor (14 Aug), evaristus (26 Oct), theodore (9 Nov) -- all Commemoration_only, all primary-source-verified (register's "Four confirmed sanctoral DATA GAPS" item + expected-divergences-missalemeum .sexp's former M4/M6/M7/M9/M14). - commemoration-of-the-baptism-of-the-lord (13 Jan) is now `(subject Lord)` (the source's own `class` field, not a colitur overlay); most-holy-name-of-mary (12 Sep) no longer carries `class = lord` at all (the source dropped it), so the bootstrap default (Subject.Saint) is now correct there without help from adjustments.sexp's own Edit. test_sanctoral_ef.ml's pinned counts (327/104/12 Class1) are re-derived directly from the source INI (`grep -c '^\['`, `grep '^rank' | sort | uniq -c`), not transcribed from a bootstrap-tool or `colitur day` run. data/ef/adjustments.sexp and the differential/oracle allow-lists still need updating for the consequences of this regeneration -- follow-up commits.
* fix(rite-ef): band has no table entry for a bare commemoration, everLukasz Kasprzak2026-08-121-17/+17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fix round 1 on the commemoration-identity/tie-break work: the "genuine same-band tie" M17 adjudicated as open was not a tie at all. RG 91's table enumerates only "dies liturgici" (real feasts) -- entry 24's own text is "Festa III classis, in calendario Ecclesiae universae inscripta" -- so a Celebration.t.status = Commemoration_only candidate (a saint reduced to a bare commemoration) has no row in the table to begin with. Precedence_ef.band used to read rank alone, so it silently lent such a candidate the same table entry as a genuine Feast of its own rank; the calendarium's own 22 September row confirms the distinction in its own notation ("S. Thomae de Villanova ... III classis. / Commemoratio Ss. Mauritii et Soc. Mm." -- Thomas carries a class number, Maurice carries none). Fixed at the source, not by adding a status tier to compare_precedence: band now checks status first, ahead of every rank-keyed branch, and returns unclassified for any Commemoration_only candidate. M17 deleted (not re-adjudicated) -- 22 September now resolves identically on both sides, with no divergence left to name. Blast radius measured independently (a temporary git worktree at the pre-fix commit): 4451 days change across the whole 1583-9999 domain, exactly 4 verified shapes -- 3576 x maurice-and-companions-martyrs -> thomas-of-villanova and 282 x eleutherius -> philip-neri (admitted set changes), 502 x appollonia/cyril-of-alexandria and 91 x dorothy/titus (order only, independently confirmed against the calendarium's own listing convention). Zero days change who is observed. Further swept the whole domain for a genuine "two different candidates on the identical real table entry" residual -- the shape RG 113 truly gives no instruction for -- and found it empty: of 109201 same-rank ties domain-wide, 83950 now resolve by band alone and the remaining 25251 are exclusively two Commemoration_only candidates tied at unclassified, never two real feasts. The residual admit still breaks alphabetically is real, but narrower than previously documented. The "66 days" figure is reconciled, not merely disputed: 599 is the tie population, 65 (whole-comparator: 67) is the real admitted-set decision count within it (independently reproduced against the pre-Task-B binary), 149 is order-only. Every one of the 65 real decisions traces to the same Commemoration_only/Feast root cause as Maurice/Thomas. Also: validate.ml's admission fixed-point check recovers a commemoration's real origin (by matching its slug against the day's own temporal office) instead of reconstructing every candidate as Sanctoral, since band -- unlike EF's own admit -- does read origin; precedence_ef.ml/.mli's motivating example and blast-radius claims corrected to no longer rest on the fixed Maurice/Thomas case; test_identity_rank_corroboration's own population count corrected now that 22 September (and 21 other Feast-status matches) became reachable; its rank-agreement pin documents exactly what it does and does not prove. Register (§4, §6.1, §6 checklist) and CLAUDE.md corrected in place, including the RG 113 first-sentence gap this exercise surfaced but did not close, recorded as a new open item. 275 -> 276 tests (with the exhaustive sweep), all green.
* feat(test): oracle layer compares commemoration identity, not just countLukasz Kasprzak2026-08-121-8/+29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | test_oracle.ml (validation layer 4) used to compare commemoration presence and List.length only -- cardinality, never which candidate is admitted. A reviewer proved this vacuous by reversing admit's dignity sort (the engine admitting the worst commemoration, an outright RG 111 violation) and finding every assertion in this file green across all 730 days. tools/extract_missalemeum_oracle.py now also emits each commemoration's own missalemeum id (field 9, commemoration_ids), parallel to the existing title field; fixture regenerated from the same underlying lectio snapshot (commit d7da4b0, re-verified, first 8 fields byte-identical), read from a scratch copy so ~/git/projects/lectio was never touched. SHA-256 pin and provenance updated. The mapping: colitur identifies a commemoration by slug, missalemeum by an English title string. No hand-built slug<->title table -- colitur's own Celebration.t.names already carries the same English text for every SANCTORAL-origin commemoration (bootstrapped from lectio, verified to match missalemeum's titles character-for-character). This resolves identity for every sanctoral-origin commemoration but cannot resolve a TEMPORAL-origin one (Rite_ef.Temporal_ef never sets a celebration name) -- that case is a new, distinct outcome (Comm_identity_unresolved), counted and allow-listed, never silently skipped: 19 of 227 non-empty-commemoration days over 2026-2027. Two genuine identity mismatches found and adjudicated (new allow-list entries M15-M17, one is the unresolved bucket): M16, 27 March 2026, the already-known unimplemented Seven Sorrows of Passion Friday office (verdict missalemeum); M17, 22 September 2027, the RG 113 same-band tie-break residual (verdict open, per the sibling admit fix). A new corroborating check cross-references missalemeum's own id-embedded rank against colitur's, split by status: Feast-status matches must agree exactly (none reachable in this 2-year window, checked regardless); Commemoration_only-status matches either genuinely agree or fit a known, pre-existing, already-registered convention gap (colitur's bootstrap rank vs missalemeum's own sentinel) -- not a title-matching defect. Perturbation test performed and reverted: reversing admit's dignity- based ordering (compare_precedence's real committed form uses band, not dignity -- this reproduces the historical defect shape directly) turns the oracle layer red: "no differences outside the cited allow-list" fails with an unexplained commemoration-identity-mismatch on 2026-09-23 (colitur admits "St. Thecla" where the rubric and missalemeum both require "St. Linus"). Reverted; diff against the prior commit confirms no residue. CLAUDE.md's "know what each layer cannot see" section updated: the commemoration-identity gap it documented as open is now closed, with the mapping's limits and the RG 113 tie-break finding summarised. 271 -> 273 tests (274 with the exhaustive sweep), all green.
* fix(ef): M6 was convicted on the transcription's silence, not a scanLukasz Kasprzak2026-08-121-3/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | data/ef/expected-divergences-missalemeum.sexp's M6 entry and its matching comment in test/test_oracle.ml both cited 1962-06-23,_SS_Ioannes_XXIII, _Missale_Romanum,_LT.pdf's 14 August row ("Vigilia, II classis." -- nothing else) as if it were the calendarium itself. It is an electronic transcription that silently drops commemoration lines the photographic scans carry. missale-romanum-1962.pdf's 14 August row reads "Vigilia, II classis, Commemoratio S. Eusebii Conf.", with its own proper following -- independently confirmed in the second scan, Missale Romanum 1962_text.pdf. M6's verdict flips colitur -> missalemeum: this is a genuine colitur data gap, the same shape as M4/M7/M9/M14 (absent from data/ef/sanctoral.sexp and from lectio's own tridentine-calendar.ini), not a spurious oracle entry. Not fixed here for the same reason those four are not: it requires touching lectio's own bootstrap source, out of reach for this task. expected_rows is unchanged (2) -- the diff shape the allow-list predicate matches does not depend on which side is judged correct, confirmed by the suite passing unchanged. A companion register ruling that convicted romanus (9 August) on the identical reasoning -- and the register's own sanctoral-audit bucket counts that followed from it -- are corrected in docs/research/ rules-register.md (gitignored, not part of this commit). Full audit method, the complete list of dropped commemorations found across the whole calendarium, and every other ruling checked for the same defect are in .superpowers/sdd/2026-08-12-colitur-rg16a/transcription-audit- report.md (also gitignored).
* fix(rite-ef,kernel): follow the oracle on the Purification; admit reads the ↵Lukasz Kasprzak2026-08-122-38/+38
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | day's own Sunday-ness, not observed Fix round 1 review, two findings landed together because each golden pin's correctness depends on both. CRITICAL (item 1): reverts this branch's own first-pass retag of the Purification (2 February) from Lord back to Bvm -> Lord. The calendarium titles it 'B. Mariae Virg.', but missalemeum -- this project's designated EF oracle -- treats it as taking an occurring II-class Sunday's place outright, commemorations empty, exactly RG16(a)'s own festum Domini treatment (2020-02-02, 2014-02-02, independently fetched). Control, 2019-09-08 (the Nativity of the BVM, an ordinary Marian feast, on a Sunday): the Sunday observed, the feast merely commemorated -- the opposite pattern, proving the Purification's own oracle treatment is deliberate, not a gap. RG16(a) alone, on the untouched data, would have matched the oracle character for character; retagging Bvm is what moved colitur away from it on 1200 domain-wide days. The user has ruled: follow the oracle. Genuine primary-text counter-evidence (RG120(b), the colour rule grouping 2 February under 'B. Mariae Virg.', a category separate from 'Domini') is recorded in the register, not discarded -- a different project could reasonably rule the other way on the same evidence. most-holy-name-of-mary stays retagged Bvm; that one was never disputed. Removes the now-unneeded C12 lectio allow-list entry (data/ef/ expected-divergences.sexp, test_differential.ml): with the Purification Lord again, colitur matches lectio's own class = lord on every affected date, no divergence to allow-list. IMPORTANT (item 2): Precedence.rules.admit gains a ~temporal parameter -- a genuine kernel signature change, unlike RG16(a) itself. RG111(b)'s own Sunday rank floor ('one commemoration, namely of a II-class feast') used to be detected by reading observed's own slug for a Sunday marker, which silently assumed observed IS the day's temporal-cycle office. RG16(a) breaks that assumption on purpose: a Feast of the Lord standing in for an impeded Sunday 'cum omnibus iuribus et privilegiis' keeps the day a dominica II classis for RG111(b) too, even though the FEAST, not the Sunday, is observed. Before this fix an unrelated saint (Pope Sixtus II et al., 6 August, Class3) was wrongly admitted into the freed slot on every one of 1178 domain-wide days. Oracle-confirmed directly: 2023-08-06 (a Sunday) shows him displaced, commemorations empty; 2026-08-06 (an ordinary Thursday) shows him admitted -- being a Sunday is exactly what excludes him. admit now reads Sunday-ness off ~temporal (the day's own temporal-cycle candidate, passed through from Precedence.resolve unchanged) instead of observed. Validate's own admission fixed-point invariant updated to pass the same real temporal candidate through. Both golden pins in test_golden.ml were promoted from actual colitur day output without independently consulting the rubric or the oracle first -- a real defect the review caught. Re-derived from both before re-pinning: 2028-08-06 and 2025-02-02 now pin comms=[], no commemoration at all, matching missalemeum exactly in both cases.
* data(ef): retag the Purification and Most Holy Name of Mary as BVM, not LordLukasz Kasprzak2026-08-122-1/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Both entries carried (subject Lord) in data/ef/sanctoral.sexp, inherited unchecked from lectio's own class = lord field. The calendarium says otherwise: 2 February reads "IN PURIFICATIONE B. MARIAE VIRG." and 12 September reads "Sanctissimi Nominis Mariae" -- both name Mary, not the Lord, unlike the four genuine Lord entries' own "D. N. I. C." formula (Precious Blood, Transfiguration, Exaltation of the Holy Cross, Dedication of the Archbasilica of the Most Holy Saviour), all of which were checked against the same calendarium and left untouched. This matters together with RG16(a): applying the rule while the Purification was still mistagged Lord would have made it outrank an occurring II-class Sunday and then wrongly suppress that Sunday's own commemoration under RG16(a) too, the opposite of the intended fix. Retagged, the Purification no longer bands at RG91 entry 14, so the Sunday is observed and the Purification survives as its own ordinary commemoration. Corrected via a new data/ef/adjustments.sexp overlay (Set_subject Bvm), the same mechanism that file already uses to suppress vigil-of-christmas as a bootstrap duplicate, not a hand-edit of sanctoral.sexp itself: that file's own header asks for edits to go through the generator, and its SHA-256 provenance covers a byte-faithful mirror of lectio's INI. The Purification retag changes which day the lectio differential observes on 2 February whenever it falls on a Sunday (six years, 2005-2050); allow- listed as C12 in data/ef/expected-divergences.sexp, expected_rows derived independently (date -d <year>-02-02 for each year in range), not transcribed from a comparator run.
* docs+test: small factual corrections (item 7, part 1)Lukasz Kasprzak2026-08-121-3/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Six independent, small corrections found during the final review: - dune (workspace root): the comment said the stanza used "(:standard)" to preserve dune's default `default` alias target; the stanza actually spells that out explicitly via (alias_rec install). Comment now matches the code. - test_validate.ml's test_easter_extremes asserted `List.length ys = 2` where an identity check was called for -- the comment already named 1598 and 1666, but nothing confirmed extreme_years() found THOSE two rather than some other pair with the right cardinality. Now asserts the identities directly (the project's "cardinality where identity was required" vacuity flavour, per the review). - test_oracle.ml and expected-divergences-missalemeum.sexp both claimed "one entry (M13) is [verdict open]" -- M11 is open too (its own verdict changed from colitur to open in fix round 1); both now say "two entries (M11 and M13)". - expected-divergences-missalemeum.sexp's M2 note attributed `band` to temporal_ef.ml; `band` is precedence_ef.ml's own function. - lib/kernel/precedence.mli documented `dropped`/`admit`'s physical- equality obligation nowhere -- it lived only in one rite's own module (Rite_ef.Precedence_ef.admit's doc comment), but this signature is what an author of the next rite actually reads. Added the obligation here, cross-referencing the EF instance as precedent, not the only source. - README's opam install line omitted sexplib and ppx_sexp_conv (both in dune-project's own depends; `dune build` fails without them for a contributor following the README verbatim) and documented only `colitur easter`, though `temporal` and `day` both exist and are the more useful entry points. Fixed both. No behaviour change: comment/doc/test-assertion corrections only (the easter-extremes fix strengthens an assertion, it does not change what passes). Verified byte-identical `colitur day` output across 1583, 1900, 1902, 2008, 2011, 2026, 2038, 9999. 259/259 tests green.
* docs(citations): migrate register line-number pointers to stable anchorsLukasz Kasprzak2026-08-121-5/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 68 "register line N" pointers in tracked code and data cited the register's line numbers directly. The register has moved since: line 334 (cited six times as RG 91 entry 8) is now rose/colour prose; lines 375-376 (cited as "RG 109 leaves Advent/Lent Ember ordinary") are now RG 26, the paragraph that refutes that exact reading; line 374 (cited as RG 109(a)) is now RG 25's Latin; line 378 (RG 111) is blank. expected-divergences.sexp even wrote "RG 91 line 332" three times, as though the line offset were part of the rubric's own identity. Since docs/research/rules-register.md is gitignored, no reader of the public repo could resolve any of these, and -- since the register has moved on its own copy too -- neither can the author's. Replaced every pointer with an anchor that survives editing: the RG paragraph/entry number itself (already present in nearly every case, since the citation text already names "RG 91 entry N" or "RG 109(a)" next to the stale line number -- the line number was redundant, not load-bearing) plus a "§4" or quoted-subsection-heading pointer into the register for readers who want to locate the fuller discussion. Also corrected one genuinely stale content error found while migrating its anchor: test_precedence_ef.ml's RG33 disposition-cases comment still said "a I/II-class vigil impeded by any Sunday" -- the pre-Task-16 mistranscription the register itself corrects to II/III class; fixed the prose alongside its citation, since leaving a wrong RG-class claim next to a freshly-verified anchor would be worse than the stale line number it replaced. Covers lib/rites/rite_ef/precedence_ef.ml (18), precedence_ef.mli (already fixed with item 1), test/test_precedence_ef.ml (44, including two instances that only word-wrapped "register\nline N" across a line break and so did not match a same-line grep), and data/ef/expected-divergences.sexp (5, including three "RG 91 line 332" instances). All 68 original pointers resolved -- none needed guessing; every citation's target rule was already named in the surrounding prose. No behaviour change: every edit is a comment/citation/data-note change. Verified byte-identical `colitur day` output across 1583, 1900, 1902, 2008, 2011, 2026, 2038, 9999. 259/259 tests green.
* test(oracle): correct M11's citation and verdict (fix round 1, F3)Lukasz Kasprzak2026-08-121-3/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | M11 (the Nativity Octave's 26/27/28 December commemoration) previously read "No RG paragraph found to support this" with verdict colitur. Wrong on both counts, found by fix-round-1 review. RG 67 ("dies infra octavam sunt II classis") and RG 109(c) ("de diebus infra octavam Nativitatis Domini") both name this commemoration; the 1962 calendarium's own text confirms it directly, verbatim, under all three dates: "S. STEPHANI PROTOMARTYRIS, II classis. / Com. octavae Nativitatis." (same pattern for John the Evangelist and the Holy Innocents). It is also a genuine regression the previous commit's own commemoration-eligibility fix introduced: before that fix, colitur happened to commemorate the day's generic Christmastide loser unconditionally, which coincidentally kept presence agreeing with the oracle even though the reasoning was wrong; the fix correctly stopped that, but 26-28 December's own temporal candidate falls through to [ferial_rank]'s Christmastide catch-all, which gives it Class4 -- the wrong ferial rank for a day RG 67 says is II class. A naive fix would also get RG 69 backwards: when this window falls on a Sunday, RG 69 names the SUNDAY as observed with a commemoration of THE FEAST ITSELF (Stephen/John/the Innocents), never the generic octave placeholder -- so this needs a commemoration that attaches to whichever named feast is observed, not one more competing RG 92-95 candidate the way everything else in this engine is modelled. Out of this fix round's own scope. Verdict changed from colitur to open: neither side is confidently right without that fix, and the previous "colitur is correct, nothing to implement" framing was itself the risk under review.
* test: oracle vs missalemeum 2026-2027; audit the sanctoralLukasz Kasprzak2026-08-121-0/+102
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Validation layer 4: an oracle harness against missalemeum (Divinum Officium data), independent of the lectio bootstrap chain colitur's own sanctoral data comes from -- the only layer that can catch an error inherited from that bootstrap, and the only one that can validate commemorations at all (the lectio differential explicitly excludes them, per its own header comment). tools/extract_missalemeum_oracle.py shapes the fixture from lectio's sources/snapshot.tar.gz outside the test (no JSON library in this project's frozen deps, same reasoning test_differential.ml's own fixture already documents). test/fixtures/missalemeum-ef-2026-2027 .txt (730 days, SHA-256 pinned and asserted) + its own .provenance note record exactly how to regenerate it. test_oracle.ml compares three axes the oracle actually supports: rank, colour (SET MEMBERSHIP -- 14 of 730 days carry two colours, e.g. rose+violet on Gaudete/Laetare, which independently vindicates this project's own rose reading against lectio's violet-only one, recorded in the register), and commemoration presence/count. Slug identity is deliberately out of scope (needs a title->slug mapping, the data audit's own business, not the automated comparator's). Of 730 days, 688 matched cleanly outright. The remaining 42 are all named in data/ef/expected-divergences-missalemeum.sexp (14 cited entries, M1-M14): most are genuine primary-source-confirmed findings this task adjudicated and fixed in the two preceding commits (RG 33, RG 109/111, Holy Thursday's colour); the rest are real, cited, deferred feature/data gaps (RG 91 entry 27's BVM-Saturday office, RG 110's inseparable Peter/Paul commemoration, four sanctoral entries missing from lectio's own source) or genuine oracle-side artifacts -- honestly verdicted against whichever side this task's own primary-source research actually backs, never defaulted to colitur. One entry (M13, St Joseph vs the Friday of Passion Week 2027) is verdict open: adjudicated as unresolved after real search effort, not guessed past. The data audit: every sanctoral entry the comparison flagged was hand-checked against the 1962 calendarium, plus a 20-entry deterministic random control sample (seed 20260812) drawn independently of the flagged set. The control sample caught two entries (benedict, frances-rome) marked Commemoration_only in the bootstrapped data when the primary calendarium lists them as plain III-class feasts with their own Office -- traced to lectio's own source, not fixable here, and reported as a signal (10% of a random sample) rather than a blanket claim. Coverage recorded honestly in docs/research/rules-register.md's own three buckets: confirmed by oracle (200/322), confirmed by hand (23/322, 2 of them wrong), unverified (115/322) -- the unverified bucket stated explicitly rather than left implicit. Harness teeth demonstrated and reverted (not committed): a fixture rank/colour edit on a previously-clean day fails both the checksum pin and the no-unexplained-differences assertion independently; an expected_rows drift on the allow-list fails the citation-count assertion. Both captured with their exact failure messages, both reverted before this commit.
* kernel(temporal): Holy Thursday is white, per RG128(b)'s named exceptionLukasz Kasprzak2026-08-121-0/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 128(b) excepts "Missa sive Chrismatis sive in Cena Domini feria V Hebdomadae sanctae" from the Septuagesima-to-Easter-Vigil violet span as a whole-Mass exception (unlike Palm Sunday's blessing/procession, which the same sentence carves out as only part of that day) -- so Holy Thursday's Mass is white, not violet. temporal_ef.ml fell through to the generic Passiontide ferial path (season_colour Passiontide = violet) for this date, since [named] has no entry for the Triduum at all (rank was already correct via [privileged_feria]; only colour was wrong). Found via Task 16's missalemeum oracle comparison: every other Triduum day's oracle colour set includes violet as an option (Good Friday "bv", Holy Saturday "vw" -- already-flagged, deliberately deferred per-action gaps, register §3b), but Holy Thursday's is white alone. This is a genuine NEW divergence from lectio too (lectio has no such exception and still prints violet for Holy Thursday) -- added as Layer C11 to the differential harness and data/ef/expected-divergences .sexp, 46 rows (2005-2050), rather than silently changing what that harness accepts. Also corrected a stale comment on season_colour claiming white's RG paragraph was never pinned -- RG 119 was found and cited in the register on 2026-08-11 but the correction was never copied into this file's own comment.
* test: differential vs lectio 2005-2050 with a cited allow-listLukasz Kasprzak2026-08-121-0/+69
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Validation layer 3 (design spec's five): compares colitur's real day-by-day EF output against lectio (sibling project, Go), 2005-2050, one line per civil day. Of 16801 day-pairs, 11206 already agree on the seven leading columns; the 5595 that don't resolve into exactly 25 distinct field-diff signatures, all triaged. Three strictly separate layers, per the controller's ruling (the brief's single flat allow-list assumed a handful of differences, not 5595): - Layer A (test_differential.ml, norm_season/norm_slug): vocabulary. An explicit, closed table of naming synonyms with no liturgical substance (lectio's easter/christmas vs colitur's paschaltide/christmastide; a handful of slugs that are two names for the identical office). No wildcards -- every entry is a literal string pair. - Layer B (strip_epiphany_index): numbering. The one slug family whose lectio/colitur index offset is not a constant (Time-after-Epiphany week numbering, register 3c#5) has its embedded digit stripped to a common form on both sides before comparing; rank and colour stay fully compared. - Layer C (data/ef/expected-divergences.sexp): the cited allow-list. Ten genuine liturgical disagreements, each citing its RG paragraph and naming which engine is right (always colitur, verified against the Missal/register, never against lectio's own behaviour). This is the only layer permitted to cover a difference in rank, colour, or which celebration is observed. Five extend or restate register 3c's already- documented divergences (season boundary, Sunday I-class, Advent Ember ferias, Rogations); five are new, found and adjudicated in this task (Lent Ember days, the Nativity Octave, Ember-day-vs-saint precedence, the St Joseph transfer off a Lent Sunday, and the 2011 Sacred Heart / Precious Blood / Visitation collision). expected_rows on each entry is an exact regression pin, asserted by the test, not documentation. 13 January (register 6's long-open "Baptism of the Lord" item) is confirmed empirically fixed already -- Task 11's sanctoral wiring closed it before this task started -- so it is not allow-listed; the only residual difference there is the season boundary already covered by C1. Two stated limits carried from the brief (commemorations are not comparable; lectio's own EF oracle asserts season only, 2025-2026 only, so a rank/colour difference is not presumptive evidence against colitur) plus a third found during this task (the week column is a display convention on both sides, not a liturgical fact, and is not compared at all) are documented in the test file's own doc comment. Fixture: test/fixtures/lectio-ef-2005-2050.txt, committed as plain text (1.4 MB), generated by lectio commit 2386a45; provenance recorded in the sibling .provenance file. Colitur's side is recomputed fresh from the library on every run, through the same Calendar/Rite_ef pipeline `colitur day` uses, not the compiled binary. Proved the harness has teeth by two reverted perturbations: a genuine colour difference injected into a fully-covered fixture row fails the "no unexplained differences" check with the exact mismatched row printed; a one-row drift in an allow-list entry's expected_rows fails the count check independently, showing it is not merely a duplicate of the first assertion. 236/236 tests green, clean-build verified, deterministic across OCAMLRUNPARAM=R.
* cli: colitur day <year> dumps resolved liturgical daysLukasz Kasprzak2026-08-121-0/+13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Assembles Rite_ef.context (lib/rites/rite_ef/rite_ef.ml[i]): temporal, anchors and vocab from Temporal_ef; rules from Precedence_ef's band, disposition and admit; season_runs = Vocab_ef.seasons; transfer_target newly implemented here. transfer_target (RG 96): the next following day that is not I or II class, with the Annunciation's own exception (Monday after Low Sunday). Terminates by a structural step bound on its internal search, independent of Calendar's own round guard, which bounds rounds across a year, not one call's walk; documented as an obligation on rite.mli's transfer_target field, which did not previously state it. Fixes the vigil-naming mismatch Task 7's review predicted: the sanctoral bootstrap names its vigils with a vigil-of-X prefix (lectio's own convention), while Precedence_ef's is_vigil only recognised the temporal cycle's own X-vigil suffix. Both are now recognised, fixing RG 91 entries 21/26 and RG 33's vigil omission for the four affected celebrations. Verified by unit test and by mutation-testing the fix (reverting it fails exactly the new rows) and against real output across several years. Suppresses data/ef/sanctoral.sexp's vigil-of-christmas via a new overlay, data/ef/adjustments.sexp: it is the same celebration as the temporal cycle's own ef-nativity-vigil, both dated 24 December. colitur day <year>: one line per civil-year day, temporal and sanctoral fully resolved through Layer, Overlay, Precedence_ef and Calendar -- the first CLI path exercising the whole Plan 3 pipeline against real data. Verified the All Souls transfer chain (Tasks 7-8-11) end to end against real output for both a Sunday year (2025, lands on 3 Nov) and a non-Sunday year (2026, observed directly on 2 Nov).
* data(ef): bootstrap the 1962 sanctoral from lectioLukasz Kasprzak2026-08-121-0/+2644
Convert lectio's tridentine-calendar.ini (322 entries) into data/ef/sanctoral.sexp via a validating OCaml converter, tools/ bootstrap_sanctoral.ml, rather than a hand-written script: every field is built through Slug.of_string, Colour.of_string and Vocab_ef.rank_of_string, so the emitted sexp is valid by construction. Two conversion decisions, both documented rather than buried: - subject defaults to Subject.Saint, overriding Celebration.make's kernel default of Subject.Temporal, for the 316 entries with no explicit class; - rank = commemoration maps to status = Commemoration_only with an inferred Class3 (not a citation -- it is what the 1960 reform reduced most simple feasts from), recorded as an open item in the rules register for the oracle to adjudicate. Every celebration is tagged layer = Precedence_ef.universal_layer, the provenance id RG 91's band classifier reads to tell the universal calendar from proper/indult data. The generated file carries a provenance header: source path, its SHA-256, and the UTC conversion date, so re-bootstrapping against a newer lectio is reproducible and diffable. Output is byte-identical across runs. test/test_sanctoral_ef.ml loads the file through Layer.load and checks the counts independently derived from the source INI (322 entries, 114 Commemoration_only, 12 Class1, no Subject.Temporal, every date resolves in a leap year), plus two named spot-checks against the INI's own text -- one entry with an explicit class field, one commemoration -- so a passing count cannot hide the wrong 322 entries having been converted.