aboutsummaryrefslogtreecommitdiff
path: root/lib/kernel/precedence.mli
blob: d394cb1d6a7766c958b78e47df7c67a7af4d2b27 (plain) (blame)
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
(** 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) 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.

          [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