1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
(** 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
|