aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-11 20:07:55 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-11 20:07:55 +0200
commit953427d8d1e3a34994be53b60e18662ec26fef4e (patch)
tree9d4876bbbfd44af738a00e0da9d0ae727542ede3 /lib
parent8de560db7fb1b7b7d3ca93285068c6a4214e0bff (diff)
downloadcolitur-953427d8d1e3a34994be53b60e18662ec26fef4e.tar.gz
colitur-953427d8d1e3a34994be53b60e18662ec26fef4e.zip
kernel: carry omitted celebrations on Liturgical_day.t, reason and all
Precedence.resolution already tracked what happened to every losing candidate -- commemorated, deferred, or omitted with a reason -- but Liturgical_day.t had nowhere for the deferred and omitted buckets to land, so Calendar dropped them at the door. Task 12's no-celebration-lost invariant needs to read that accounting off the day result itself, not re-resolve every day to reconstruct it, so a reason recorded nowhere is not recorded. Add Liturgical_day.omitted : ('r Celebration.t * string) list, after transferred_out and before citations. Calendar.resolve_day now folds resolution.omitted (Precedence's own native omissions, reasons intact) and resolution.deferred (mapped to "deferred: transfer placement not yet implemented (Task 6)") into it. Adds a full-day accounting test against the whole Calendar pipeline: four colliding sanctoral entries plus the day's feria, checked as a slug set (matching test_precedence.ml's own nothing-silently-lost test) so a candidate silently dropped or duplicated into two buckets would fail it, plus an identity check that the deferred and admission-limit reasons don't get swapped.
Diffstat (limited to 'lib')
-rw-r--r--lib/kernel/calendar.ml21
-rw-r--r--lib/kernel/calendar.mli13
-rw-r--r--lib/kernel/liturgical_day.ml3
-rw-r--r--lib/kernel/liturgical_day.mli3
4 files changed, 29 insertions, 11 deletions
diff --git a/lib/kernel/calendar.ml b/lib/kernel/calendar.ml
index fd23377..1cdd1fa 100644
--- a/lib/kernel/calendar.ml
+++ b/lib/kernel/calendar.ml
@@ -35,6 +35,15 @@ let year_bounds (rite : ('s, 'r) Rite.t) (y : int) : Date.t * Date.t =
in
(start, stop)
+(* [resolution.deferred] (RG 96-98 transfer candidates) has nowhere to be
+ PLACED yet -- Task 6 adds the fixed-point pass that does -- but it must
+ still be accounted for on the day it lost, not silently dropped: Task
+ 12's no-celebration-lost invariant reads [Liturgical_day.omitted], so a
+ deferred candidate folds in there too, with its own reason distinct from
+ Precedence's native omissions ("omitted: yielded to a higher day",
+ "omitted: admission limit reached"). *)
+let deferred_reason = "deferred: transfer placement not yet implemented (Task 6)"
+
(* RG 91's contest for one date: the temporal office against every sanctoral
entry whose Date_spec resolves to it. [Layer.on_date] is keyed on exactly
(month, day), which for a [Fixed] spec -- the only form Plan 2 ships -- is
@@ -53,13 +62,10 @@ let resolve_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.by_date) (date : Date.t
in
let ctx = { Precedence.date; season = temporal.Temporal.season; weekday = temporal.Temporal.weekday } in
let resolution = Precedence.resolve rite.Rite.rules ctx ~temporal:temporal_candidate ~sanctoral in
- (* [resolution.deferred] (RG 96-98 transfer candidates) and
- [resolution.omitted] (yielded/admission-limit losers) have no field to
- land in on Liturgical_day.t yet, so both are simply absent from today's
- result -- deliberately incomplete for a deferred candidate, which is
- thereby neither observed nor commemorated here, and not yet placed on
- any later day either ("deferred: transfer placement not yet implemented
- (Task 6)"). Task 6's fixed-point pass closes this gap. *)
+ let omitted =
+ List.map (fun (c, reason) -> (c.Precedence.cel, reason)) resolution.Precedence.omitted
+ @ List.map (fun c -> (c.Precedence.cel, deferred_reason)) resolution.Precedence.deferred
+ in
{
Liturgical_day.date;
rite = rite.Rite.id;
@@ -69,6 +75,7 @@ let resolve_day (rite : ('s, 'r) Rite.t) (idx : 'r Layer.by_date) (date : Date.t
List.map (fun (c, p) -> (c.Precedence.cel, p)) resolution.Precedence.commemorations;
transferred_in = None;
transferred_out = None;
+ omitted;
citations = [];
}
diff --git a/lib/kernel/calendar.mli b/lib/kernel/calendar.mli
index 50ff8f8..2469f2a 100644
--- a/lib/kernel/calendar.mli
+++ b/lib/kernel/calendar.mli
@@ -9,10 +9,15 @@
This module resolves each day's temporal-vs-sanctoral contest but does
not yet place deferred transfers (RG 96-98): a losing candidate the
- rite's rules send to [Precedence.Transfer] is absent from the result
- entirely on this pass -- not observed, not commemorated, and
- [transferred_in]/[transferred_out] both stay [None] everywhere. Task 6
- adds the fixed-point placement pass that closes this gap. *)
+ rite's rules send to [Precedence.Transfer] is not observed and not
+ commemorated on the day it lost, and [transferred_in]/[transferred_out]
+ both stay [None] everywhere -- but it is not silently dropped either. It
+ lands in that day's [Liturgical_day.omitted] with the reason ["deferred:
+ transfer placement not yet implemented (Task 6)"], alongside
+ [Precedence]'s own native omissions (yielded to a higher day; admission
+ limit reached), each with its own reason. Task 6 adds the fixed-point
+ placement pass that actually places these; until then, this is the
+ day's complete, honest accounting of what happened to every candidate. *)
(** [year rite layer y] resolves every day of the liturgical year that opens
in civil year [y]: from [rite.year_start y] through the day before
diff --git a/lib/kernel/liturgical_day.ml b/lib/kernel/liturgical_day.ml
index 65e3ba5..cbaca9c 100644
--- a/lib/kernel/liturgical_day.ml
+++ b/lib/kernel/liturgical_day.ml
@@ -14,6 +14,9 @@ type ('s, 'r) t = {
(** arrived here from an impeded day *)
transferred_out : Date.t option;
(** this day's celebration went there *)
+ omitted : ('r Celebration.t * string) list;
+ (** with the reason, never silent -- Task 12's no-celebration-lost
+ invariant reads this *)
citations : Citation.t list; (** always empty until Plan 4 *)
}
[@@deriving sexp]
diff --git a/lib/kernel/liturgical_day.mli b/lib/kernel/liturgical_day.mli
index 3c331c3..38e7c76 100644
--- a/lib/kernel/liturgical_day.mli
+++ b/lib/kernel/liturgical_day.mli
@@ -12,6 +12,9 @@ type ('s, 'r) t = {
(** arrived here from an impeded day *)
transferred_out : Date.t option;
(** this day's celebration went there *)
+ omitted : ('r Celebration.t * string) list;
+ (** with the reason, never silent -- Task 12's no-celebration-lost
+ invariant reads this *)
citations : Citation.t list; (** always empty until Plan 4 *)
}
[@@deriving sexp]