summaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef/lectionary_ef.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-21 22:39:12 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-21 22:39:12 +0200
commit384b0789c0f4d9beb936080a5592bb4aa6134295 (patch)
tree943842766c2dd09761e1d449cb3b1c56346e8dae /lib/rites/rite_ef/lectionary_ef.ml
parent2ac3e3e8fcc66e20bf1687ae15f033d20b02216d (diff)
downloadcolitur-384b0789c0f4d9beb936080a5592bb4aa6134295.tar.gz
colitur-384b0789c0f4d9beb936080a5592bb4aa6134295.zip
feat(kernel,ef): the lectionary reports which Mass it said
Rite.readings now returns (Mass_formulary.t option * Citation.t list) instead of a bare citation list, and Liturgical_day.t carries the result as a new formulary field. Validate holds a rite that resolves a formulary at all to resolving one on every day, the same discipline it already applies to citations; the EF lectionary chain resolves Some on every day of every year 1583-9999, confirmed by a direct sweep over 2005-2050 as well as through Validate itself. Plan Tasks 2 and 3 are merged into this one commit on the coordinator's own instruction: Rite.readings' signature and the field that consumes it are one atomic edit, and the intermediate state does not compile on its own. Each of the four lectionary steps now builds its own Mass_formulary.t at the point it decides, not by re-deriving it afterwards from the citations it returns: step 1 tags Proper with the observed slug, step 2 tags Own_slug with the day's own temporal slug, step 3 tags Preceding_sunday with the resumed Sunday's temporal slug, and step 4 tags Common with the Common's own id -- Commons.find now returns that id alongside its citations rather than discarding it, since it is only ever in scope at the point the assignment is looked up. The RG 309(a) Saturday votive Mass of Our Lady, which answers between steps 4 and 2 rather than as one of the four numbered steps, is tagged Own_slug too: Mass_formulary.source has no dedicated constructor for it, and its own guard only ever fires when the observed celebration already is the day's own (reused ferial) temporal slug, which is exactly what Own_slug documents. Recorded as a judgement call in the task report, not a specified answer. test/cli.t's `emit --format sexp` line count is repinned (8472 to 8881): that command serializes Liturgical_day.t whole, so the new field grows its output. `colitur day` itself is untouched -- verified byte-identical against the pre-change binary across 1583, 1900, 2026, 2038 and 9999.
Diffstat (limited to 'lib/rites/rite_ef/lectionary_ef.ml')
-rw-r--r--lib/rites/rite_ef/lectionary_ef.ml58
1 files changed, 45 insertions, 13 deletions
diff --git a/lib/rites/rite_ef/lectionary_ef.ml b/lib/rites/rite_ef/lectionary_ef.ml
index d7b2497..4325bed 100644
--- a/lib/rites/rite_ef/lectionary_ef.ml
+++ b/lib/rites/rite_ef/lectionary_ef.ml
@@ -51,9 +51,9 @@ module Commons = struct
| None -> (
(* A formulary with no citations is indistinguishable at the call
site from "this saint has no Common" -- [commons_for] would
- return [Some []] and [readings] would emit [] either way. That
- is exactly the silent hole this project does not allow, so it
- is rejected here where it is still nameable. *)
+ return [Some (common, [])] and [readings] would emit [] either
+ way. That is exactly the silent hole this project does not
+ allow, so it is rejected here where it is still nameable. *)
match List.find_opt (fun (_, cs) -> cs = []) commons with
| Some (s, _) ->
Error (Printf.sprintf "commons: common %S has no citations" (Slug.to_string s))
@@ -72,10 +72,19 @@ module Commons = struct
(Slug.to_string saint) (Slug.to_string common))
| None -> Ok { commons; assigned })))
+ (* Returns the Common's own id ALONGSIDE its citations, not the citations
+ alone: [readings]' step 4 needs the id to build the day's
+ {!Colitur_kernel.Mass_formulary.t} ("the Common's own id as its slug"),
+ and the id is only ever in scope here, at the point [common] is looked
+ up -- re-deriving it afterwards would mean a second [assoc_opt] search
+ over [t.assigned] for a value this function already held. *)
let find t saint =
match List.assoc_opt saint t.assigned with
| None -> None
- | Some common -> List.assoc_opt common t.commons
+ | Some common -> (
+ match List.assoc_opt common t.commons with
+ | None -> None
+ | Some cs -> Some (common, cs))
(* Byte-for-byte the failure discipline of [Lectionary.load] (see its own
comments for why each catch-all is placed where it is): every parse and
@@ -250,7 +259,8 @@ let is_bvm_saturday_office (observed : Vocab_ef.rank Celebration.t)
let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
match observed.Celebration.citations with
- | _ :: _ as cs -> cs
+ | _ :: _ as cs ->
+ (Some { Mass_formulary.said = observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
| [] -> (
(* Step 4: a saint who is the day's observed office and has no proper
says his assigned Common. The assignment is explicit, never
@@ -346,7 +356,8 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
match
if sanctoral_office then commons_for ~commons observed.Celebration.slug else None
with
- | Some cs -> cs
+ | Some (common_id, cs) ->
+ (Some { Mass_formulary.said = common_id; via = Mass_formulary.Common }, cs)
| None -> (
(* The votive Mass of Our Lady on Saturday (RG 309(a)) runs HERE:
after the proper (step 1) and the Common (step 4), which answer
@@ -354,13 +365,32 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
lookup -- which is exactly what used to answer, with the feria's
own Mass, on a day whose office is Our Lady's. Placing it later
would be dead code; placing it earlier would let it outrank a
- real saint's proper. *)
+ real saint's proper.
+
+ FORMULARY PROVENANCE, a genuine judgement call: {!Mass_formulary.source}
+ has no fifth constructor for "the RG 309(a) seasonal votive Mass",
+ so this is tagged [Own_slug] -- [is_bvm_saturday_office] only ever
+ fires when [sanctoral_office] above is false, i.e. the observed
+ celebration already IS the day's own temporal office (the office
+ deliberately reuses the ordinary ferial slug, [Temporal_ef]'s own
+ [bvm_saturday_names]), so [said] is genuinely "the day's own
+ slug" -- [Own_slug]'s own documented meaning
+ (mass_formulary.mli's [said] comment) -- even though the
+ citations themselves come from [bvm_saturday_citations]'s
+ season table rather than a [Lectionary.find] hit. Flagged in the
+ task report as an interpretation, not a specified answer. *)
if is_bvm_saturday_office observed temporal then
- bvm_saturday_citations temporal.Temporal.season ~month:(Date.month date)
- ~day:(Date.day date)
+ let said = temporal.Temporal.office.Celebration.slug in
+ ( Some { Mass_formulary.said; via = Mass_formulary.Own_slug },
+ bvm_saturday_citations temporal.Temporal.season ~month:(Date.month date)
+ ~day:(Date.day date) )
else
match Lectionary.find lectionary temporal.Temporal.office.Celebration.slug with
- | Some cs -> cs
+ | Some cs ->
+ ( Some
+ { Mass_formulary.said = temporal.Temporal.office.Celebration.slug;
+ via = Mass_formulary.Own_slug },
+ cs )
| None -> (
(* Step 3: a feria with no proper of its own says the preceding
Sunday's Mass. WARRANT is the same as step 2's -- lectio's own
@@ -404,12 +434,14 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
season name), so deriving one from the other textually would be
a latent bug the moment a season's naming convention differs. *)
let offset = days_since_sunday temporal.Temporal.weekday in
- if offset = 0 then []
+ if offset = 0 then (None, [])
else
let sunday = Date.add_days date (-offset) in
let sunday_temporal = temporal_at sunday in
match
Lectionary.find lectionary sunday_temporal.Temporal.office.Celebration.slug
with
- | Some cs -> cs
- | None -> [])))
+ | Some cs ->
+ let said = sunday_temporal.Temporal.office.Celebration.slug in
+ (Some { Mass_formulary.said; via = Mass_formulary.Preceding_sunday }, cs)
+ | None -> (None, []))))