(** The rite-parameterised resolver: RG 91 says who wins, RG 92-95 says what happens to the loser, RG 108-111 says how many commemorations are admitted. Three separate rite-supplied functions, because the loser's fate depends on the loser's own rank, not the winner's -- conflating them would resist extension to a second rite. *) (** Which of the day's two office streams a candidate came from. *) type origin = Temporal | Sanctoral [@@deriving sexp] (** RG 111: an admitted commemoration's own standing, distinct from its rank. *) type privilege = Privileged | Ordinary [@@deriving sexp] (** What becomes of a losing candidate. *) type disposition = | Omit (** yields with no trace in the day's celebration *) | Commemorate of privilege (** kept as a commemoration of the observed day *) | Transfer (** moved to the next free day (RG 92-95) *) | Repose (** kept only in a votive/private sense; not commemorated today *) [@@deriving sexp] (** A celebration together with the office stream it was drawn from. Parameterised by the rite's rank type only, matching {!Celebration.t}. *) type 'r candidate = { cel : 'r Celebration.t; origin : origin } [@@deriving sexp] (** The day a resolution is computed for. Parameterised by the rite's season type only -- a context has no rank of its own. *) type 's context = { date : Date.t; season : 's; weekday : Date.weekday } (** The rite's three resolution functions. *) type ('s, 'r) rules = { band : 's context -> 'r candidate -> int; (** RG 91: orders candidates for the day; lower wins. *) disposition : winner:'r candidate -> loser:'r candidate -> disposition; (** RG 92-95: the loser's fate, which depends on the loser's own rank. *) admit : observed:'r candidate -> temporal:'r candidate -> ('r candidate * privilege * int) list -> ('r candidate * privilege) list; (** RG 108-111: how many commemorations are admitted, and in what order; anything filtered out here is recorded in {!resolution.omitted}, not dropped. Each input triple's trailing [int] is that candidate's own {!band} value, computed once by {!resolve} (RG 113: "in admittendis et ordinandis aliis commemorationibus, servetur ordo tabellae praecedentiae" -- ADMITTING and ORDERING commemorations is governed by the same table-of-precedence order {!band} already supplies for picking the day's own winner; docs/research/rules-register.md §4 "Commemorations"). Supplied rather than left for [admit] to compute itself because [admit] has no [context] (date/season/weekday) of its own -- {!resolve} already holds one and calls {!band} with it for every candidate regardless. A rite's [admit] is free to ignore the value entirely (e.g. fall back to [Vocab.rank] alone), the same as it may ignore [temporal] below. [temporal] is {!resolve}'s own [~temporal] argument, passed through unchanged -- the day's temporal-cycle candidate, regardless of whether it won. Fix round 1 (RG16(a) task): before this, a rite's [admit] could only infer properties of the CIVIL DAY (chiefly "is this a Sunday", RG 111(b)'s own two-tier admission rule) from [observed]'s own fields -- a proxy that breaks the moment something OTHER than the day's own temporal candidate can be [observed], the exact shape RG 16(a) introduces (a Feast of the Lord standing in the impeded Sunday's place "cum omnibus iuribus et privilegiis", RG 91 entry 14). This is NOT a kernel definition of "Sunday" -- the kernel does not gain any rite-specific knowledge by this parameter, it only threads through a value {!resolve} already holds; a rite's own [admit] is free to ignore it entirely, the same as [observed]. OBLIGATION ON THE IMPLEMENTATION, not enforced by this type: every candidate this function returns must be a value taken UNCHANGED from its input list, never rebuilt (e.g. via a [{ c with ... }] record update, even one that copies every field back unchanged). {!resolve}'s own [omitted] accounting distinguishes an admitted candidate from a dropped one by PHYSICAL equality ([==]) on the candidate value, not structural equality -- a rebuilt record is [=] to the original but not [==], so {!resolve} cannot match the rebuilt copy against the original it was given. The celebration then surfaces TWICE in the same day's result -- once in {!resolution.commemorations} (the rebuilt copy, admitted) and once in {!resolution.omitted} (the original, which nothing in the admitted set matches). One admission, double-reported, silently rather than raising. This obligation previously lived only in one rite's own module documentation (Rite_ef.Precedence_ef.admit); stated here because this signature -- not any one rite's implementation of it -- is what an author of the next rite reads. *) } (** The outcome of resolving one day's candidates. *) type 'r resolution = { observed : 'r candidate; commemorations : ('r candidate * privilege) list; deferred : 'r candidate list; omitted : ('r candidate * string) list; (** each with a reason *) } (** Total: the temporal candidate is passed separately, so there is no empty-candidate case. Ties break on slug, so the result never depends on input order. A [Commemoration_only] celebration is held out of the contest and can never be [observed]. Every input candidate appears exactly once in [observed], [commemorations], [deferred] or [omitted] — nothing is dropped silently. *) val resolve : ('s, 'r) rules -> 's context -> temporal:'r candidate -> sanctoral:'r candidate list -> 'r resolution