(** Everything a rite supplies, bundled. Passing these as loose arguments let a caller pair one rite's vocab with another's temporal; bundling makes that unrepresentable through the normal path. Carries functions, so it has no sexp form. *) type ('s, 'r) t = { id : string; vocab : ('s, 'r) Vocab.t; year_start : int -> Date.t; (** first day of the liturgical year opening in civil year y *) temporal : Date.t -> ('s, 'r) Temporal.t; anchors : int -> (string * Date.t) list; (** Easter-derived days: (expected slug, date) *) easter : int -> Date.t; (** The rite's own Easter for civil year [y]. Supplied by the rite, NOT computed in the kernel: {!Computus} ships both Gregorian and Julian reckonings, and choosing one here would hard-code a Roman assumption into rite-agnostic code and be silently wrong for a Julian-reckoning rite. Read by {!Layer.index} to resolve {!Date_spec.Easter_offset}. *) rules : ('s, 'r) Precedence.rules; season_runs : 's list; (** the expected run-length-compressed season sequence over one liturgical year. NOT necessarily [vocab.seasons]: a rite may have one season appear in two separate runs (the modern form's Ordinary Time does). *) transfer_target : 'r Precedence.candidate -> Date.t -> (Date.t -> 'r Celebration.t) -> Date.t; (** RG 96: where an impeded I-class feast goes. Given the deferred candidate, the date it was impeded on, and [occupant] -- a callback exposing what {!Calendar} currently resolves as observed on any given date -- returns the date to place it on. Deliberately one rite-supplied function, not a generic search Calendar drives itself: "not I or II class" is not derivable from [band] or [disposition] alone. RG 91's own table would let a universal I-class feast (entry 11) numerically outrank an ordinary Sunday (entry 15, II class) in a raw occurrence contest -- entry 11 comes before entry 15, and lower wins -- so testing "would the translated feast win here" is not the same question as "is this day free to receive a translation": RG 96 forbids landing on the Sunday regardless of which one would structurally win. Only the rite knows which of its own ranks are exempt from translation onto them. The rite also owns the search's starting point, because RG 96's exception is rite-specific too: the Annunciation does not search forward from its own impeded date at all, it goes straight to the Monday after Low Sunday (searching onward from there only if that day is itself blocked). [occupant] is supplied rather than a raw layer/temporal pair so the rite never has to re-implement occurrence resolution just to answer "what sits here". OBLIGATIONS (not enforced by the type, and {!Calendar}'s own termination argument depends on both): the result must be {b strictly later} than the [Date.t] argument (the date the candidate was impeded on) -- {!Calendar}'s placement pass treats [target = origin] or [target < origin] as a legitimate placement, not an error, so a rite whose search can stand still or go backward would silently loop candidates in place or resurrect an already-superseded occupant rather than failing loudly. The call must also {b terminate} on its own: {!Calendar}'s round guard (calendar.ml's [max_transfer_rounds]) bounds how many ROUNDS the whole-year placement pass takes, which is a distinct, outer thing from whatever internal search a single call to this function runs -- an implementation that walks forward day by day looking for an admissible date, without its own bound, can hang the caller outright on a rite/data shape it does not handle, never reaching the round guard at all. See rite_ef/precedence_ef.ml's [transfer_target] for a concrete termination argument (a structural step bound, not an appeal to the real calendar's own structure). *) readings : observed:'r Celebration.t -> temporal:('s, 'r) Temporal.t -> date:Date.t -> temporal_at:(Date.t -> ('s, 'r) Temporal.t) -> 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) without re-implementing the temporal cycle -- the same shape [transfer_target]'s own [occupant] callback established. *) creed : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> bool; (** Whether the Creed is said, post-Gospel/homily, at this day's Mass (EF: RG 475-476). A [bool], not an [option]: this is a decision, and a rite that has not implemented the rule returns [false] explicitly rather than leaving the question unanswered. [temporal] and [observed] are supplied for the same reason [readings] gets both: a rubric like this one can turn on either the day's TEMPORAL-cycle identity (e.g. "is this a Sunday, even one a feast has displaced") or on the celebration actually observed, and only the rite knows which. [date] is supplied for the same reason [readings] gets it too -- a rubric keyed to an Easter-relative window (e.g. "within the octave of Easter") needs the civil date and the rite's own Easter to test it, and neither [temporal] nor [observed] alone carries that arithmetic. *) }