summaryrefslogtreecommitdiff
path: root/lib/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/calendar.ml9
-rw-r--r--lib/kernel/liturgical_day.ml3
-rw-r--r--lib/kernel/liturgical_day.mli5
-rw-r--r--lib/kernel/rite.ml2
-rw-r--r--lib/kernel/rite.mli20
-rw-r--r--lib/kernel/validate.ml29
6 files changed, 60 insertions, 8 deletions
diff --git a/lib/kernel/calendar.ml b/lib/kernel/calendar.ml
index 98e9032..7fe9f67 100644
--- a/lib/kernel/calendar.ml
+++ b/lib/kernel/calendar.ml
@@ -543,6 +543,10 @@ let build_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.index)
|> List.map (fun c -> (c.Precedence.cel, reason_for c)))
@ rg33_omitted
in
+ let formulary, citations =
+ rite.Rite.readings ~observed:resolution.Precedence.observed.Precedence.cel ~temporal ~date
+ ~temporal_at:rite.Rite.temporal
+ in
{
Liturgical_day.date;
rite = rite.Rite.id;
@@ -553,9 +557,8 @@ let build_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.index)
transferred_in;
transferred_out;
omitted;
- citations =
- rite.Rite.readings ~observed:resolution.Precedence.observed.Precedence.cel ~temporal ~date
- ~temporal_at:rite.Rite.temporal;
+ citations;
+ formulary;
}
let year (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) (y : int) :
diff --git a/lib/kernel/liturgical_day.ml b/lib/kernel/liturgical_day.ml
index bbb52b8..709f878 100644
--- a/lib/kernel/liturgical_day.ml
+++ b/lib/kernel/liturgical_day.ml
@@ -23,5 +23,8 @@ type ('s, 'r) t = {
(** with the reason, never silent -- Task 12's no-celebration-lost
invariant reads this *)
citations : Citation.t list; (** always empty until Plan 4 *)
+ formulary : Mass_formulary.t option;
+ (** which Mass the day says, and how that was decided; [None] only for
+ a rite with no lectionary -- see {!Mass_formulary} *)
}
[@@deriving sexp]
diff --git a/lib/kernel/liturgical_day.mli b/lib/kernel/liturgical_day.mli
index f0ea3d9..4a71d6d 100644
--- a/lib/kernel/liturgical_day.mli
+++ b/lib/kernel/liturgical_day.mli
@@ -28,5 +28,10 @@ type ('s, 'r) t = {
["citations"] / ["citations-unresolved"] checks. (This said "always
empty until Plan 4" until Task 10; the lectionary landed before
Plan 4 did, and the comment outlived its truth.) *)
+ formulary : Mass_formulary.t option;
+ (** Which Mass this day says, and how that was decided -- see
+ {!Mass_formulary}. [None] only for a rite with no lectionary; for
+ EF it is [Some] on every day of every year 1583..9999, asserted by
+ {!Validate}. *)
}
[@@deriving sexp]
diff --git a/lib/kernel/rite.ml b/lib/kernel/rite.ml
index 600a691..a7d21d3 100644
--- a/lib/kernel/rite.ml
+++ b/lib/kernel/rite.ml
@@ -17,5 +17,5 @@ type ('s, 'r) t = {
temporal:('s, 'r) Temporal.t ->
date:Date.t ->
temporal_at:(Date.t -> ('s, 'r) Temporal.t) ->
- Citation.t list;
+ Mass_formulary.t option * Citation.t list;
}
diff --git a/lib/kernel/rite.mli b/lib/kernel/rite.mli
index 0324bf2..45f3ed6 100644
--- a/lib/kernel/rite.mli
+++ b/lib/kernel/rite.mli
@@ -71,10 +71,22 @@ type ('s, 'r) t = {
temporal:('s, 'r) Temporal.t ->
date:Date.t ->
temporal_at:(Date.t -> ('s, 'r) Temporal.t) ->
- Citation.t list;
- (** The day's Epistle and Gospel citations, or []. Rite-supplied for the
- same reason [transfer_target] is: what a day with no proper of its
- own falls back to is a rubric of a particular rite, not a universal.
+ Mass_formulary.t option * Citation.t list;
+ (** The Mass actually said -- which formulary, and how that was decided
+ -- paired with its Epistle and Gospel citations. Rite-supplied for
+ the same reason [transfer_target] is: what a day with no proper of
+ its own falls back to is a rubric of a particular rite, not a
+ universal.
+
+ The [Mass_formulary.t option] is [None] exactly when the rite's
+ lectionary is not built at all (the citation list is then also
+ []): a rite that HAS a lectionary is expected to resolve [Some] on
+ every day it covers, the same total-coverage discipline
+ {!Validate}'s own ["formulary"] check holds it to. [None] is never
+ a per-day "no Mass today" answer for a rite that otherwise
+ resolves readings -- that shape is coverage FAILURE, not a
+ legitimate outcome, which is exactly what makes the [Validate]
+ check meaningful.
[temporal_at] is a callback so the rite can reach another date's
temporal identity (the preceding Sunday's, for the ferial rule)
diff --git a/lib/kernel/validate.ml b/lib/kernel/validate.ml
index c9d4263..ce48615 100644
--- a/lib/kernel/validate.ml
+++ b/lib/kernel/validate.ml
@@ -453,5 +453,34 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year =
fail date "citations"
(Printf.sprintf "expected exactly one First and one Gospel, got [%s]"
(String.concat "," (List.map Citation.part_to_string sorted))))
+ resolved;
+ (* ---- Formulary invariant (Task 3, celebrant-rubrics-phase1) ----
+
+ Same rite-agnostic gating as the citation checks immediately
+ above, and for the same reason: a rite whose lectionary is not
+ built returns [(None, [])] from {!Rite.readings} on every day, so
+ [year_has_formulary] is false and this check never fires for it.
+ A rite that resolves a formulary AT ALL is held to resolving one
+ on every day of the year -- a day that says no Mass at all is a
+ defect, not a gap, the same discipline the ["citations-unresolved"]
+ check above already holds for the citations themselves. One check
+ name, not two: unlike [citations], there is no separate
+ "well-formed but wrong" shape to distinguish -- [Mass_formulary.t
+ option] is either the day's answer or it is missing, so
+ [year_has_formulary] gates a single ["formulary"] label. *)
+ let year_has_formulary =
+ Array.exists
+ (fun (d : ('s, 'r) Liturgical_day.t) -> d.Liturgical_day.formulary <> None)
+ resolved
+ in
+ if year_has_formulary then
+ Array.iter
+ (fun (d : ('s, 'r) Liturgical_day.t) ->
+ match d.Liturgical_day.formulary with
+ | Some _ -> ()
+ | None ->
+ fail d.Liturgical_day.date "formulary"
+ "no Mass formulary resolved for this day: the lectionary chain fell through \
+ every step")
resolved);
List.rev !failures