aboutsummaryrefslogtreecommitdiff
path: root/lib/kernel/validate.mli
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/validate.mli')
-rw-r--r--lib/kernel/validate.mli79
1 files changed, 62 insertions, 17 deletions
diff --git a/lib/kernel/validate.mli b/lib/kernel/validate.mli
index 709a711..5e55fc5 100644
--- a/lib/kernel/validate.mli
+++ b/lib/kernel/validate.mli
@@ -5,26 +5,71 @@ type failure = { year : int; date : string; check : string; detail : string }
val failure_to_string : failure -> string
-(** [run vocab ~year_start ~temporal ~anchors ~year] returns every invariant
- violation in the liturgical year opening in civil year [year]. An empty
- list means the year is clean.
+(** [run rite layer ~year] returns every invariant violation in the
+ liturgical year opening in civil year [year]. An empty list means the
+ year is clean.
- [anchors y] is the rite's own independent restatement of its fixed and
- Easter-derived named days for civil year [y], as (expected slug, date)
- pairs -- not derived from [temporal] itself, so a drift between the two
- is caught rather than invisible. [run] consults both [anchors year] and
- [anchors (year + 1)], since a liturgical year straddles two civil years,
- and checks only the pairs whose date actually falls within the year
- walked.
+ [rite.Rite.anchors y] is the rite's own independent restatement of its
+ fixed and Easter-derived named days for civil year [y], as (expected
+ slug, date) pairs -- not derived from [rite.Rite.temporal] itself, so a
+ drift between the two is caught rather than invisible. [run] consults
+ both [anchors year] and [anchors (year + 1)], since a liturgical year
+ straddles two civil years, and checks only the pairs whose date actually
+ falls within the year walked.
+
+ ["slugs"]: no two dates within the walked liturgical year may carry the
+ same office slug (Plan 2 carried item 4). Asserted outright, with no
+ exemption for the resumed-Sunday reuse a slug's own name might suggest:
+ a resumed Sunday only ever backfills a week number Septuagesima cut
+ short that same year, so by construction it never repeats a number that
+ year's own January Sundays actually used.
+
+ The season check compares the run-length-compressed season sequence
+ against [rite.Rite.season_runs], not [rite.Rite.vocab.seasons]: a rite may
+ have one season appear in two separate runs (the modern form's Ordinary
+ Time does), so the two are not necessarily the same list.
+
+ [layer] is resolved against [rite] via {!Calendar.year} (spec ยง2.4's
+ occurrence/transfer/commemoration pass), and the resulting fully-resolved
+ liturgical year is checked for five further invariants a temporal-only
+ pass cannot see (Task 12), each its own ["check"] label:
+ - ["observed"]: a day's [observed] celebration is never ALSO listed among
+ that same day's [commemorations] or [omitted] -- a day reports one
+ winner, not a winner that also lost to itself.
+ - ["lost"]: no sanctoral entry is silently dropped. Per slug, the number
+ of times it is actually sighted ([observed] + [commemorations] +
+ [omitted], summed over the whole year -- NOT [transferred_out], which
+ would double-count a successfully placed transfer against its own
+ arrival) must never fall below the number of times its own
+ {!Date_spec} resolves within the year's span (an entry with two
+ occurrences, e.g. 30 November in the nine liturgical years where the
+ 371-day span reaches it twice, must be sighted twice, not once). Also
+ fires if resolving the year raises at all -- an escaping exception is
+ the most total form of silent loss, and the kernel contract forbids
+ [run] itself from propagating it.
+ - ["duplicated"]: the same per-slug count must never EXCEED the number of
+ {!Date_spec} resolutions either. Deliberately NOT "no slug appears
+ twice in the year" -- a fixed date can legitimately resolve twice, per
+ ["lost"] above -- it is "resolutions and sightings agree", the property
+ that actually distinguishes a transfer that moved from one that
+ duplicated.
+ - ["unconverged"]: no day's [omitted] carries the reason {!Calendar}'s
+ placement pass records when its round guard (calendar.ml's
+ [max_transfer_rounds]) is hit before every deferred candidate reaches a
+ fixed point.
+ - ["admission"]: the rite's own [rules.admit] is a fixed point on what it
+ already admitted -- re-offering a day's [commemorations] back to
+ [admit] (reconstructed with {!Precedence.Sanctoral} origin; the real EF
+ admit reads only rank and slug, never origin, so this reconstruction is
+ exact for it) must return exactly that same set. A cap-enforcing
+ selector that is not idempotent on its own output has, by definition,
+ admitted something its own rule would not admit if asked again -- the
+ rite-agnostic form of "the admission limit was not exceeded" available
+ without embedding a rite's specific numeric caps (RG 111's, for EF)
+ into kernel code.
Total over the whole 1583..9999 domain, including [year] = 9999: the
liturgical year opening there continues into out-of-domain civil year
10000, so the walk is clamped to 31 December 9999 and the checks run
against that truncated final year rather than raising. *)
-val run :
- ('s, 'r) Vocab.t ->
- year_start:(int -> Date.t) ->
- temporal:(Date.t -> ('s, 'r) Temporal.t) ->
- anchors:(int -> (string * Date.t) list) ->
- year:int ->
- failure list
+val run : ('s, 'r) Rite.t -> 'r Layer.t -> year:int -> failure list