aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef/lectionary_ef.mli
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-15 01:40:54 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-15 01:40:54 +0200
commit1299815ea03c2b1e4ff19d92d32e386fec24f4e4 (patch)
tree7fe91b561af482d7079fb4808ec52552e3228d08 /lib/rites/rite_ef/lectionary_ef.mli
parentfe94cd9c4150fd6285dab49b06e23b6db42d131b (diff)
downloadcolitur-1299815ea03c2b1e4ff19d92d32e386fec24f4e4.tar.gz
colitur-1299815ea03c2b1e4ff19d92d32e386fec24f4e4.zip
ef(lectionary): the Commons, chain step 4
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.
Diffstat (limited to 'lib/rites/rite_ef/lectionary_ef.mli')
-rw-r--r--lib/rites/rite_ef/lectionary_ef.mli96
1 files changed, 82 insertions, 14 deletions
diff --git a/lib/rites/rite_ef/lectionary_ef.mli b/lib/rites/rite_ef/lectionary_ef.mli
index e54a0b1..baf25d4 100644
--- a/lib/rites/rite_ef/lectionary_ef.mli
+++ b/lib/rites/rite_ef/lectionary_ef.mli
@@ -3,12 +3,13 @@ open Colitur_kernel
(** The EF lectionary resolution chain. All rubric knowledge about what a day
with no proper falls back to lives here, not in the kernel.
- [lectionary] is caller-supplied, not loaded by this module -- the same
- reasoning rite_ef.mli's own [context] doc comment already gives for why
- the sanctoral {!Colitur_kernel.Layer.t} stays a separate argument rather
- than an embedded field: it lets a caller load data/ef/lectionary.sexp
- however suits it, and leaves room for a future diocesan/proper
- lectionary overlay to attach without this module changing at all.
+ [lectionary] and [commons] are both caller-supplied, not loaded by this
+ module -- the same reasoning rite_ef.mli's own [context] doc comment
+ already gives for why the sanctoral {!Colitur_kernel.Layer.t} stays a
+ separate argument rather than an embedded field: it lets a caller load
+ data/ef/lectionary.sexp and data/ef/commons.sexp however suits it, and
+ leaves room for a future diocesan/proper lectionary overlay to attach
+ without this module changing at all.
An eager filesystem read at module initialisation was tried first and
reverted (fix round 1, coordinator review): [readings] used to close
@@ -18,17 +19,84 @@ open Colitur_kernel
missing from a bare `dune build`'s own default target (it was only
present because test/dune's own deps happened to materialise it,
masking the gap in every test run). See the task report for the
- reproduction.
+ reproduction. The Commons follow the same path for the same reason. *)
- Steps 1-3 (Tasks 4-5): the observed celebration's own proper, else the
- day's own temporal slug in the lectionary, else -- for a weekday whose
- own slug has no entry -- the preceding Sunday's temporal slug (never its
- observed one; a Sunday is guarded out because it has no PRECEDING Sunday
- to resume, not because consulting itself would loop -- [readings] is not
- recursive, see its own implementation comment). A day matching none of
- the three gets [] for now -- the Commons (Task 6) are not built here. *)
+(** The Commons of the 1962 Missal (Epistle and Gospel citations only) and
+ the per-saint assignments that route a readingless class-3 feast to one.
+
+ Two tables rather than one, because the two facts have different
+ warrants and different lifetimes: a FORMULARY is read from the Commune
+ Sanctorum and is the same for every saint sent to it, while an
+ ASSIGNMENT is read from one saint's own date in the Proprium Sanctorum.
+ A diocesan overlay adds assignments; it rarely adds formularies.
+
+ Assignment is explicit per saint, never inferred: {!Colitur_kernel.Subject.t}
+ is [Temporal|Saint|Bvm|Lord] and {!Colitur_kernel.Celebration.t} carries
+ no martyr/confessor/virgin/bishop/abbot classification at all, so there
+ is nothing to infer one from -- those words appear only inside display
+ names. A saint with no proper and no assignment gets no Common. *)
+module Commons : sig
+ type t
+
+ (** No formularies and no assignments -- the identity for this table, and
+ what a caller that genuinely has no Commons data should pass. Every
+ lookup returns [None]; nothing is silently invented. *)
+ val empty : t
+
+ (** Loads from a sexp file. Parse and validation failures come back as
+ [Error], never as an exception, and never at module-initialisation
+ time -- the same contract {!Colitur_kernel.Lectionary.load} makes.
+
+ [Error] (never a silently-degraded lookup) on: a duplicate common id;
+ a duplicate assignment for one saint; a formulary with no citations
+ (indistinguishable downstream from "no Common at all"); and an
+ assignment naming a common that does not exist (likewise). *)
+ val load : string -> (t, string) result
+
+ (** The Commons themselves, canonically sorted by id. *)
+ val formularies : t -> (Slug.t * Citation.t list) list
+
+ (** Saint slug -> common id, canonically sorted by saint. *)
+ val assignments : t -> (Slug.t * Slug.t) list
+end
+
+(** The Common assigned to a saint who has no proper, if any. Exposed for the
+ golden pins, which must show WHICH Common fired, not merely that two
+ citations appeared.
+
+ Takes the table explicitly for the same reason {!readings} takes
+ [~lectionary]: the data is the caller's, not this module's. *)
+val commons_for : commons:Commons.t -> Slug.t -> Citation.t list option
+
+(** The day's Epistle and Gospel citations, or [].
+
+ Four steps, in EXECUTION order 1, 4, 2, 3 (the numbers are the plan's and
+ are kept as written, so that every "step 3" already recorded in a test
+ name, comment or report still means the same branch):
+
+ - {b Step 1} -- the observed celebration's own proper.
+ - {b Step 4} -- a saint who is the day's observed office and has no
+ proper says his assigned Common. Runs before the temporal fallbacks,
+ not after them: this is the only step in the chain with a direct
+ primary-source warrant (the Missal names the Mass at each such saint's
+ own date), and placing it last makes it unreachable on every date in
+ 1583-9999 as well as wrong on the days it would fire. The full
+ argument, with the measurement behind it, is on the branch itself.
+ Guarded so it can only ever apply to a SANCTORAL observed office --
+ a feria, a Sunday, the Triduum and the RG 78 Saturday Office of the
+ BVM (whose observed celebration is its own temporal office) are
+ structurally excluded, not merely absent from the data.
+ - {b Step 2} -- the day's own temporal slug in the lectionary.
+ - {b Step 3} -- for a weekday whose own slug has no entry, the preceding
+ Sunday's temporal slug (never its observed one; a Sunday is guarded
+ out because it has no PRECEDING Sunday to resume, not because
+ consulting itself would loop -- [readings] is not recursive, see its
+ own implementation comment).
+
+ A day matching none of the four gets []. *)
val readings :
lectionary:Lectionary.t ->
+ commons:Commons.t ->
observed:Vocab_ef.rank Celebration.t ->
temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
date:Date.t ->