(** The invariant harness -- validation layer 2. Checks run over a *liturgical* year, not a civil year: a season that straddles January would otherwise look as though it recurs. *) type failure = { year : int; date : string; check : string; detail : string } val failure_to_string : failure -> string (** [run rite ~year] returns every invariant violation in the liturgical year opening in civil year [year]. An empty list means the year is clean. [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. 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. 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) Rite.t -> year:int -> failure list