(** 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 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. [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. 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