summaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef/lectionary_ef.ml
Commit message (Collapse)AuthorAgeFilesLines
* feat(ef): the votive Mass of Our Lady on Saturday (RG 309(a))Lukasz Kasprzak2026-08-171-0/+99
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 78's office has been built since ef-bvm-saturday: an otherwise unoccupied IV-class Saturday is de sancta Maria in sabbato, white, IV class. The Mass stayed the feria's, so the day observed Our Lady and then read Colossians on the parable of the weeds. That is M26 shape 1. RG 309(a) is the rule -- the Masses the Missal assigns "iuxta temporum diversitatem" -- and it prints five, each under its own season heading. All ten citations are transcribed from the scans with line numbers in the source. Masses II-V were confirmed twice, against the scan and against a live missalemeum capture of 2038 which names them and agrees on both readings. Mass I rests on the scan alone: no oracle year to hand has an Advent Saturday carrying this office. One witness where the others have two, and the code says so rather than blurring it. The mapping looked like the hard part and dissolved. The Missal's ranges are cut by civil dates and movable feasts, not by this rite's seasons -- II ends mid-season at the Purification, III spans four seasons. But measured against what a IV-class Saturday can actually be, it reduces to a seasonal mapping plus one date test at 2/3 February, with no Easter arithmetic at all: III's end to IV's start leaves the Triduum unassigned, unreachable because Holy Saturday is I class; IV's end to V's start leaves Pentecost week unassigned, also unreachable because that week's Saturday is an Ember Saturday. Time after Pentecost then begins exactly at Trinity, exactly where Mass V begins. Both gaps verified by sweep -- zero IV-class Saturdays in either across 2000-2059 -- not inferred. The office is identified structurally, never off the slug, which it deliberately shares with the ordinary feria: subject Bvm, IV class, Saturday. Blast radius: 87611 reading rows over the full domain, every one a Saturday. Allow-lists: M26 27 to 6, shape 1 gone, leaving only the Christ-the-King residue argued in colitur's favour last commit. The 2038 suite's BVM-SATURDAY-MASS class is removed rather than zeroed -- an id with no rows must not be declared. M18 355 to 376. C17 widened 397 to 445, taking its own Saturdays back from C1, C19 and C25, since one cause should not be filed under two ids; C31 89 to 103; C25 closed at 0 rows, its Holy-Name-week Saturdays now answered before step 3 is reached. Three unit tests pinned the old behaviour and are corrected rather than re-pinned: two asserted the day "keeps its ferial Mass", which was the honest thing to assert while this was unimplemented. The third still tests what it always did -- that step 4 refuses a temporal office -- and only what answers instead has changed. Register: section 6.13.
* ef(lectionary): fix round 1 -- the step-4 guard, and a miscited authorityLukasz Kasprzak2026-08-151-8/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Review re-verified all fifteen saints and all seven formularies against both printings and found zero citation discrepancies, confirming the Gabriel printing error and all three proper/Common reversals; the reordering was adjudicated sound. Two Important findings, both about the guard rather than the data. IMPORTANT 1 -- the guard had no test, and a comment claimed it did. `test_step4_never_diverts_a_temporal_office` asserted 2026-07-04 keeps its ferial Mass and its comment claimed to "pin it behaviourally". It did not: no shipped assignment names a temporal slug, so the guard is a NO-OP on shipped data and that test passes with the guard removed. The reviewer proved it by forcing `sanctoral_office = true` -- all 357 tests stayed green. That mattered more than an ordinary missing test: the guard is the structural claim the whole reordering argument leans on, and on a task with no oracle the tests are the entire safety net. Fixed by exposing `Commons.of_tables` (already present internally; it applies exactly `load`'s validation, so a synthetic table is a well-formed table with a WRONG assignment rather than malformed data the loader would reject before the guard is reached -- and it is the constructor a future diocesan overlay needs anyway), adding `day_with ~commons` beside `day`, and writing test_step4_guard_refuses_a_common_assigned_to_a_ferial_slug: a synthetic Commons assigning the Common of Abbots to `ef-time-after-pentecost-5-saturday`, a really-observed ferial slug, asserting 2026-07-04 still says its ferial Mass. Two checks, the first making the second mean something -- it asserts the hostile assignment IS present and resolvable, so the ferial Mass wins because the GUARD refused it and not because the lookup missed. Mutation-verified: with the guard disabled exactly one test fails, this one; before this round that mutation reddened nothing. The old test is renamed test_step4_leaves_a_temporal_office_alone_on_shipped_data and its comment corrected to say it does not pin the guard. IMPORTANT 2 -- the guard cited an authority that does not say what was claimed. The comment said "[Validate] already asserts slug uniqueness per liturgical year, so a sanctoral feast can never collide with a temporal slug". validate.ml's check maps over `Temporal.office` slugs ONLY, compared to each other across the days of one year; it says nothing about cross-stream collision. An assumption was promoted to an asserted invariant -- the exact defect class this project's citation discipline exists to catch. Rewritten to state it as an assumption (no sanctoral slug carries the `ef-` prefix every temporal slug does; 0 of 327 today), to quote the real authority verbatim (validate.ml's own note that this "is the same assumption the rest of this codebase already leans on"), to carry a CORRECTED marker naming the wrong citation so it is not re-derived, and to record the failure mode if it ever broke: benign and one-directional -- a colliding saint is denied his Common and falls through to steps 2/3, i.e. to exactly the answer the unguarded chain would have given him. No day gains a reading it should not have. MINOR -- the previous commit body led with "Blast radius, measured ... ZERO differing lines", which is true but oversold: the CLI prints no citations, so that sweep is STRUCTURALLY INCAPABLE of observing the reorder or any citation value. It is a valid negative control for the sanctoral `Edit` overlay and nothing else. The evidence for the reorder is mutation 1 (relegating step 4 to last reddens exactly the four step-4 pins) plus those pins' own explicit expected values -- not the sweep. Correcting the record here. MINOR -- test_commons_load_rejects_bad_data wrote four FIXED-name files into the temp dir and removed none, colliding across concurrent runs. Replaced with a local `with_temp_file` copied from test_lectionary.ml's own (`Filename.temp_file` + `Fun.protect ~finally`); the four labels moved into the assertion messages where they belonged. Verified zero leftovers after a full run. MINOR -- added the sharper argument for the reorder, which makes the deviation obviously right rather than merely well-evidenced, and it now LEADS the branch comment as warrant (1), with the Missal citation demoted to a corroborating warrant (2): step 4 is STEP 1'S CONTINUATION. Step 1 already runs the observed office's own proper ahead of steps 2 and 3; step 4 is that same rule for the saints whose readings the Missal keeps in a Common instead of printing on the celebration. Placing it last would have made the chain internally inconsistent with code that already existed -- St Joseph beating a competing temporal entry because his readings sit on his Celebration.t, and St Vincent Ferrer losing to one because his sit one indirection away. Nothing in the rubrics draws that distinction; it is an artefact of where colitur stores the data. So the plan's ordering did not merely disagree with the data, it contradicted step 1. 358 tests green (357 before), 359 with the exhaustive sweep.
* ef(lectionary): the Commons, chain step 4Lukasz Kasprzak2026-08-151-52/+207
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* ef(lectionary): fix round 1 -- correct a wrong test, a wrong comment, and ↵Lukasz Kasprzak2026-08-151-6/+19
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ba0adba's own wrong commit body Three Important findings, all traceable to the task-5 brief rather than the implementation itself; all confirmed against the real data and the real resolver before fixing, not taken on trust. 1. test_step3_uses_temporal_not_observed (2028-12-26) did not exercise step 3 at all: 26 December is always Stephen, a real sanctoral entry with its own citations, so that date resolves entirely at step 1. Its justifying comment was also wrong -- 24 December's TEMPORAL slug is ef-nativity-vigil, IDENTICAL to its observed slug (Temporal_ef.named hard-codes the Vigil for that date ahead of any Sunday computation), so there was never a temporal/observed split on that date to exploit. Replaced with 2025-02-03: 2 February 2025 (Sunday) is observed as the Purification (own citations Mal 3:1-4 / Luke 2:22-32) but its TEMPORAL identity is ef-time-after-epiphany-sunday-4 (Rom 13:8-10 / Matt 8:23-27, a different lectionary entry); 3 February has no proper of its own and reaches step 3, which must return the Sunday's TEMPORAL reading, not the Purification's. Verified against data/ef/sanctoral.sexp and data/ef/lectionary.sexp directly. 2. The termination-argument comment in lectionary_ef.ml (and its echo in lectionary_ef.mli) claimed an unguarded Sunday would loop. It would not: readings is not recursive -- step 3's fallback is one flat Lectionary.find, never a re-entrant call into readings -- so an unguarded Sunday would just repeat step 2's own already-failed lookup once (same pure inputs, same None) and return [] normally. Rewritten to say what is actually true: the guard exists because a Sunday has no PRECEDING Sunday to resume, not because skipping it would be dangerous; the chain terminates because every step consults data or a strictly earlier date, and no step ever calls back into readings. 3. Correcting the record, per instruction, rather than amending ba0adba: that commit's own body said Advent ferias carry 'Advent I's own readings copied onto the following Monday-Thursday'. Both details are wrong, verified directly against data/ef/lectionary.sexp: the duplicated readings are ef-advent-SUNDAY-2's (Rom 15:4-13 / Matt 11:2-10), not Advent I's, and they appear on ef-advent-2-monday, -tuesday, -thursday and -saturday -- four non-contiguous days, not a Monday-to-Thursday span. The in-code comment and the task-5 brief's own commit template both already said 'Advent II' correctly; only ba0adba's commit body had the error. dune test --force: 344 tests, all green (unchanged count -- one test's body changed, none added or removed).
* ef(lectionary): a feria says the preceding Sunday's MassLukasz Kasprzak2026-08-151-2/+55
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Chain step 3. Guarded on weekday <> Sun: a Sunday reaching this branch would look up its own slug via days_since_sunday Sun = 0 and loop -- every other chain step consults data, this one consults a strictly earlier date, so that guard is the whole chain's termination argument. Reaches the Sunday by Date.add_days plus a fresh temporal_at call, never by string surgery on the day's own slug -- the slug shapes are genuinely inconsistent across seasons (ef-advent-sunday-1 vs ef-advent-1-monday, week number on opposite sides of the season name). Uses the preceding Sunday's TEMPORAL slug, never its observed one: the rubric is the preceding Sunday's Mass even in a year a feast displaced that Sunday from being observed (pinned: 2028-12-26, the Monday after a Vigil-displaced Advent IV Sunday, still takes Advent IV's Mass). Measured over the full 1583-9999 domain (temporal cycle only, no sanctoral contest): of 412 distinct temporal slugs, 305 carry no lectionary entry of their own; of the 304 that are feria (non-Sunday) slugs, step 3 alone resolves 297 of them via their preceding Sunday. The 7 that remain, plus the 1 uncovered Sunday slug itself (ef-holy-name-sunday), all trace to the same two missing lectionary entries (Holy Name Sunday and 30 December), not to eight independent gaps or a step-3 defect -- traced date-by-date, not merely counted. Day-level effect, 2005-2050 (full Precedence+Calendar pipeline, matching this project's existing differential window): 16807 days, 16531 resolved (98.36%), 276 still empty; step 3 alone accounts for 4503 of the resolved days, more than either step 1 or step 2. Warrant is the same class as step 2's, not a confirmed Missal citation: lectio hard-codes this shape as literal duplicated data on the four Advent ferias (Advent I's own readings copied onto the following Monday-Thursday) and leaves the rest of that same shape simply absent; step 3 turns the duplication into a rule. dune test --force: 344 tests, all green (was 340).
* kernel+ef: fix round 1 -- lectionary caller-supplied, not eagerLukasz Kasprzak2026-08-151-29/+15
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Critical (coordinator review): a clean `dune build` produced a `colitur` that died at startup on EVERY subcommand, including ones touching no lectionary data at all. Root cause was two-fold: data/ef/lectionary.sexp was never added to the root default-build alias (only materialised as a side effect of the test suite's own deps, which is why every check in the prior report passed), and Rite_ef.context loaded it as a module-init side effect via failwith, undoing Lectionary.load's own "never raises" promise at a point no caller could catch. Fixed structurally: Rite_ef.context is now a function taking ~lectionary, Lectionary_ef.readings takes ~lectionary, and neither touches the filesystem any more -- the same caller-supplied discipline the sanctoral layer already had, restoring rite_ef.mli's own pre-existing claim about it and leaving a seam for a future diocesan lectionary overlay. bin/main.ml grows load_ef_lectionary, a sibling of load_ef_layer, routed through the same colitur: %s / exit 2 path. data/ef/lectionary.sexp added to the root default alias. Every caller of Rite_ef.context updated to supply it. Also: two new tests that genuinely distinguish chain step 1 from step 2 (19 March 2026, Joseph's own proper over a competing temporal entry; 13 January 2030, Holy Family reached only through the temporal slug, the Baptism entirely absent) -- the prior two tests both survived swapping the chain order. Both new pins verified directly against the real data. The chain's own comment now states plainly that its warrant is lectio's observed behaviour, not a confirmed Missal citation, per the rules register's own open item.
* kernel+ef: resolve readings, chain steps 1 and 2Lukasz Kasprzak2026-08-151-0/+40
Liturgical_day.citations has read "always empty until Plan 4" since Plan 3; it is now filled. Rite.t gains a readings function, rite-supplied for the same reason transfer_target is: what a day with no proper falls back to is a rubric, not a universal. Calendar calls it and passes its own temporal function as the callback the rite needs to reach another date. Steps 1 and 2 only: the observed celebration's own proper, else the day's own temporal slug. Nothing encodes "Lent has daily propers" -- the presence of an entry is the discriminator.