summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 17:13:06 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 17:13:06 +0200
commita2654a1e4d8987a251bcefbad2b0f0b867a6c6e6 (patch)
tree39360eccd3fa8ee5379fc46276d1e0ef0e6ff2b5 /lib
parente19adb9c7ea369cafe76e7eff50e9248a4a954d0 (diff)
parente5b368dec8fdc9ab983ee0f1dee69c37883cbe12 (diff)
downloadcolitur-a2654a1e4d8987a251bcefbad2b0f0b867a6c6e6.tar.gz
colitur-a2654a1e4d8987a251bcefbad2b0f0b867a6c6e6.zip
Merge branch 'ef-rg16a': RG 16(a), RG 111(b), RG 113, and commemoration identity
Closes the largest known-wrong-output item on record (RG 16(a): a Feast of the Lord occurring on a II-class Sunday leaves the Sunday uncommemorated -- 5996 wrong days over 1583-9999) together with RG 111(b)'s rank floor on the day it holds, and gives the oracle layer the ability to compare commemoration identity rather than only presence and count. Also corrects a methodological defect that had produced wrong rulings in both this project and its sibling: docs/research/ holds an electronic transcription alongside two photographic scans, and the transcription silently drops commemoration lines -- seven across the year. Reasoning from its silence had convicted the oracle wrongly on 14 August and ruled a genuine 9 August commemoration spurious. The scans are the primary source; the rule is now recorded at the top of the register. RG 113 replaces an alphabetical same-rank tie-break that had no rubrical warrant. A bare Commemoratio has no row in RG 91's table at all, so band no longer hands one the entry of a III-class universal feast: 4451 days, all four reordered pairs verified against a photographic scan, zero observed-day changes.
Diffstat (limited to 'lib')
-rw-r--r--lib/kernel/celebration.mli13
-rw-r--r--lib/kernel/precedence.ml22
-rw-r--r--lib/kernel/precedence.mli31
-rw-r--r--lib/kernel/validate.ml90
-rw-r--r--lib/kernel/validate.mli26
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml333
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli121
7 files changed, 516 insertions, 120 deletions
diff --git a/lib/kernel/celebration.mli b/lib/kernel/celebration.mli
index a3c2960..85f7af4 100644
--- a/lib/kernel/celebration.mli
+++ b/lib/kernel/celebration.mli
@@ -1,7 +1,14 @@
(** Whether this celebration can be the observed day at all. The 1960 reform
- reduced many feasts to a bare commemoration; they retain a rank (RG 111
- orders admitted commemorations by dignity) but can never be observed. NOT
- a fifth rank: RG 8 fixes the classes at four. *)
+ reduced many feasts to a bare commemoration; they retain a rank but can
+ never be observed. NOT a fifth rank: RG 8 fixes the classes at four.
+
+ What the retained rank is FOR (corrected 2026-08-12): this line formerly
+ read "RG 111 orders admitted commemorations by dignity". Ordering is no
+ longer done by rank at all -- it moved to the rite's own table of
+ precedence (RG 113) in commit ea22ad2, and a bare commemoration has no
+ row in that table. The rank remains load-bearing for ADMISSION: RG 111(b)
+ reserves a II-class Sunday's single slot "de festo II classis", a floor
+ read off this rank directly. *)
type status = Feast | Commemoration_only [@@deriving sexp]
(** A celebration. Parameterised by the rite's rank type only. *)
diff --git a/lib/kernel/precedence.ml b/lib/kernel/precedence.ml
index 05ad69f..2c55817 100644
--- a/lib/kernel/precedence.ml
+++ b/lib/kernel/precedence.ml
@@ -17,8 +17,14 @@ type ('s, 'r) rules = {
disposition : winner:'r candidate -> loser:'r candidate -> disposition;
admit :
observed:'r candidate ->
- ('r candidate * privilege) list ->
+ temporal:'r candidate ->
+ ('r candidate * privilege * int) list ->
('r candidate * privilege) list;
+ (** the trailing [int] on each input triple is that candidate's own
+ [band] value, computed once by {!resolve} below (a rite's [admit]
+ has no [context] of its own to compute it with) -- see [resolve]'s
+ own comment for why this is a KERNEL-level policy, not a rite-
+ specific rule threaded in as data. *)
}
type 'r resolution = {
@@ -55,7 +61,19 @@ let resolve rules ctx ~temporal ~sanctoral =
([], [], []) losers
in
let comms = List.rev comms and deferred = List.rev deferred in
- let admitted = rules.admit ~observed comms in
+ (* RG 113 (EF; docs/research/rules-register.md §4 "Commemorations"): "in
+ admittendis et ordinandis aliis commemorationibus, servetur ordo
+ tabellae praecedentiae" -- admitting AND ordering commemorations both
+ run on the rite's own table of precedence, the same [band] already
+ used above to pick [observed]. Computed here, once, generically (a
+ rite's own [admit] has no [ctx] of its own to call [band] with) rather
+ than inside every rite's [admit] separately -- a kernel-level POLICY
+ ("commemorations are ordered by the rite's own band"), not a
+ rite-specific RULE baked into the kernel: the actual [band] function,
+ and whether a rite's [admit] even uses the value it is handed, both
+ stay entirely rite-supplied. *)
+ let comms_by_precedence = List.map (fun (c, p) -> (c, p, rules.band ctx c)) comms in
+ let admitted = rules.admit ~observed ~temporal comms_by_precedence in
let dropped =
List.filter (fun c -> not (List.exists (fun a -> fst a == fst c) admitted)) comms
in
diff --git a/lib/kernel/precedence.mli b/lib/kernel/precedence.mli
index ae054dd..09376d7 100644
--- a/lib/kernel/precedence.mli
+++ b/lib/kernel/precedence.mli
@@ -34,12 +34,41 @@ type ('s, 'r) rules = {
(** RG 92-95: the loser's fate, which depends on the loser's own rank. *)
admit :
observed:'r candidate ->
- ('r candidate * privilege) list ->
+ 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 ... }]
diff --git a/lib/kernel/validate.ml b/lib/kernel/validate.ml
index cc8bdce..ffeb89e 100644
--- a/lib/kernel/validate.ml
+++ b/lib/kernel/validate.ml
@@ -293,26 +293,96 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year =
"transfer placement did not reach a fixed point within the round guard (RG 96-98)";
(* "admission": re-offer this day's own admitted commemorations
back to [rite.rules.admit] and require the exact same set back.
- [origin] is reconstructed as [Sanctoral] uniformly:
- {!Liturgical_day.t} does not retain a commemoration's original
- origin, and the real EF [admit] (precedence_ef.ml) reads only
- rank and slug from a candidate, never [origin], so this
- reconstruction is exact for it; documented in validate.mli as
- the one place a rite whose [admit] DOES consult [origin] could
- see a false negative from this check. *)
+ [origin] is RECOVERED, not fabricated -- see the fuller note
+ below on [as_candidates]. It was formerly reconstructed as
+ [Sanctoral] uniformly, justified by the claim that the real EF
+ [admit] reads only rank and slug and never [origin]. That claim
+ is now FALSE: since ea22ad2 the EF [admit] orders by [band]
+ (RG 113), and [band] does read [origin] via [is_temporal], so a
+ temporal-origin commemoration relabelled [Sanctoral] would be
+ scored on the wrong table entry. The recovery below is exact,
+ not a heuristic: [resolve] builds exactly one temporal
+ candidate per day, so a slug match against the day's own
+ temporal office identifies it unambiguously. *)
let observed_candidate : 'r Precedence.candidate =
{ Precedence.cel = d.Liturgical_day.observed; origin = Precedence.Sanctoral }
in
+ (* [~temporal] (fix round 1, RG16(a) task): {!Precedence.rules.admit}
+ now also takes the day's own temporal-cycle candidate, reused
+ here from {!Liturgical_day.t}'s own embedded [Temporal.t] --
+ the exact same value {!Calendar} passed to {!Precedence.resolve}
+ in the first place, so re-offering is against the SAME inputs,
+ not a reconstruction that could itself introduce a false
+ negative. [origin] is [Precedence.Temporal], genuinely (this IS
+ the temporal candidate, not a reconstructed sanctoral one). *)
+ let temporal_candidate : 'r Precedence.candidate =
+ { Precedence.cel = d.Liturgical_day.temporal.Temporal.office;
+ origin = Precedence.Temporal }
+ in
+ (* [band]'s own [context] (RG 113, docs/research/rules-register.md
+ §4 "Commemorations" -- {!Precedence.rules.admit}'s own new
+ [int] parameter, added alongside RG 113's fix: [admit] now
+ orders/selects by the rite's table-of-precedence value
+ {!resolve} attaches to each candidate, not by [Vocab.rank]
+ alone). Reconstructed from {!Liturgical_day.t}'s own embedded
+ [Temporal.t], the same source [temporal_candidate] above
+ already draws its [cel] from, so this is the exact [ctx]
+ {!Calendar} passed to {!Precedence.resolve} for this date in
+ the first place, not a re-derivation that could itself drift. *)
+ let day_ctx : 's Precedence.context =
+ { Precedence.date;
+ season = d.Liturgical_day.temporal.Temporal.season;
+ weekday = d.Liturgical_day.temporal.Temporal.weekday }
+ in
+ (* CORRECTED (Task B fix round 1, coordinator finding 5): [origin]
+ used to be reconstructed as [Sanctoral] UNCONDITIONALLY, which
+ was harmless while nothing here called [band] on the result
+ (the comment this replaces was correct about [PE.admit] itself:
+ it never reads [origin]) -- but this function ALSO now calls
+ [rite.rules.Precedence.band day_ctx cand] on every one of these
+ reconstructed candidates (the line just below), and [band] DOES
+ read [origin] (its own [is_temporal] test) to choose between its
+ temporal- and sanctoral-keyed branches. A genuinely
+ TEMPORAL-origin commemoration (a privileged Advent/Lent/
+ Passiontide feria, an Ember day, an impeded Sunday) mislabelled
+ [Sanctoral] would score the WRONG band entry (e.g. a Lent feria
+ scoring entry 23, "III-class feasts in particular calendars",
+ instead of its real entry 22) -- inert today only because
+ nothing currently asserts on the SCORE [band] returns here, only
+ on whether re-offering [admit] the same values round-trips
+ (which happens not to depend on getting [origin] right for any
+ case this codebase's data reaches -- unverified in general).
+ [Liturgical_day.t] itself still does not retain a
+ commemoration's original origin, so it is recovered the only
+ way available: a commemoration whose SLUG matches the day's own
+ temporal office is temporal-origin; every other commemoration
+ is sanctoral-origin. This is exact whenever slugs cannot
+ collide across the two streams (Task 12's own "observed" check
+ already assumes this for a different purpose), which is the
+ same assumption the rest of this codebase already leans on. *)
+ let temporal_office_slug = d.Liturgical_day.temporal.Temporal.office.Celebration.slug in
let as_candidates comms =
- List.map (fun (c, p) -> ({ Precedence.cel = c; origin = Precedence.Sanctoral }, p)) comms
+ List.map
+ (fun (c, p) ->
+ let origin =
+ if Slug.equal c.Celebration.slug temporal_office_slug then Precedence.Temporal
+ else Precedence.Sanctoral
+ in
+ let cand : 'r Precedence.candidate = { Precedence.cel = c; origin } in
+ (cand, p, rite.Rite.rules.Precedence.band day_ctx cand))
+ comms
in
let offered = as_candidates d.Liturgical_day.commemorations in
- let readmitted = rite.Rite.rules.Precedence.admit ~observed:observed_candidate offered in
+ let readmitted =
+ rite.Rite.rules.Precedence.admit ~observed:observed_candidate
+ ~temporal:temporal_candidate offered
+ in
let norm l =
List.map (fun (c, p) -> (Slug.to_string c.Precedence.cel.Celebration.slug, p)) l
|> List.sort compare
in
- if norm readmitted <> norm offered then
+ let offered_pairs = List.map (fun (c, p, _) -> (c, p)) offered in
+ if norm readmitted <> norm offered_pairs then
fail date "admission"
(Printf.sprintf
"admit is not a fixed point on this day's own commemorations: re-offering %d \
diff --git a/lib/kernel/validate.mli b/lib/kernel/validate.mli
index 5e55fc5..b46ca30 100644
--- a/lib/kernel/validate.mli
+++ b/lib/kernel/validate.mli
@@ -59,14 +59,24 @@ val failure_to_string : failure -> string
fixed point.
- ["admission"]: the rite's own [rules.admit] is a fixed point on what it
already admitted -- re-offering a day's [commemorations] back to
- [admit] (reconstructed with {!Precedence.Sanctoral} origin; the real EF
- admit reads only rank and slug, never origin, so this reconstruction is
- exact for it) must return exactly that same set. A cap-enforcing
- selector that is not idempotent on its own output has, by definition,
- admitted something its own rule would not admit if asked again -- the
- rite-agnostic form of "the admission limit was not exceeded" available
- without embedding a rite's specific numeric caps (RG 111's, for EF)
- into kernel code.
+ [admit] (with [observed]'s own origin reconstructed as
+ {!Precedence.Sanctoral}; the real EF admit reads only rank and slug
+ from [observed], never origin, so this reconstruction is exact for
+ it) must return exactly that same set. Each offered commemoration's
+ own origin -- CORRECTED, Task B fix round 1 -- is recovered by
+ comparing its slug against the day's own temporal office, not
+ reconstructed as [Sanctoral] uniformly: {!Precedence.rules.admit}
+ (since Task B) is handed each candidate's own {!Precedence.rules.band}
+ value, computed here exactly as {!Precedence.resolve} computes it, and
+ [band] DOES read a candidate's origin (temporal- vs sanctoral-keyed
+ branches) even though EF's own [admit] itself still does not -- a
+ mislabelled origin would silently score the wrong table entry for a
+ genuinely temporal-origin commemoration (e.g. a Lent feria) before
+ this fix. A cap-enforcing selector that is not idempotent on its own
+ output has, by definition, admitted something its own rule would not
+ admit if asked again -- the rite-agnostic form of "the admission limit
+ was not exceeded" available without embedding a rite's specific
+ numeric caps (RG 111's, for EF) into kernel code.
Total over the whole 1583..9999 domain, including [year] = 9999: the
liturgical year opening there continues into out-of-domain civil year
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
index db7e708..750da8b 100644
--- a/lib/rites/rite_ef/precedence_ef.ml
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -110,6 +110,7 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc
int =
let cel = c.Precedence.cel in
let rank = cel.Celebration.rank in
+ let status = cel.Celebration.status in
let subject = cel.Celebration.subject in
let layer = cel.Celebration.layer in
let slug = Slug.to_string cel.Celebration.slug in
@@ -129,8 +130,48 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc
binding, not a second literal, so the two can never drift apart. *)
let entry_15_band = 15 in
let open Vocab_ef in
- (* 1: Nativity, Easter Sunday, Pentecost Sunday (I class w/ octave). *)
- if is_temporal && rank = Class1 && ((m = 12 && d = 25) || off = 0 || off = 49) then 1
+ (* CORRECTED (Task B fix round 1, ef-rg16a): a [Commemoration_only]
+ celebration has NO row in RG 91's table at all, checked FIRST, ahead of
+ every rank-keyed branch below -- RG 91's own text enumerates only "dies
+ liturgici" (entry 24's own wording, e.g., "Festa III classis, in
+ calendario Ecclesiae universae inscripta" -- FEASTS, inscribed in the
+ calendar), and the calendarium itself marks the difference in its own
+ notation: 22 September's row reads "S. Thomae de Villanova Ep. et
+ Conf., III classis. / Commemoratio Ss. Mauritii et Soc. Mm." -- Thomas
+ gets a class number (a "festum"); Maurice gets "Commemoratio" and NO
+ class number at all, because he never had a row in the table for that
+ occasion to begin with. Before this fix, [band] read [rank] alone, so
+ a [Commemoration_only] entry silently borrowed the SAME entry number
+ as a genuine [Feast] of its own rank (a Class3 Commemoration_only
+ entry banded to 24, indistinguishable from a real Class3 universal
+ feast) -- manufacturing a "tie" at {!compare_precedence}/RG 113's own
+ admission ordering that the primary text never creates: RG 113 does
+ not run out of instruction between two same-rank commemorations;
+ [band] ran out of fidelity, handing out a table row that does not
+ exist for one of them. [unclassified] (worse than every real entry) is
+ the same value already used for a candidate this table's 28 branches
+ otherwise fail to describe, which is exactly the right answer here
+ too: "not in this table" for a different reason, same table-position
+ consequence. Confirmed by the oracle: missalemeum's own commemoration
+ id embeds a rank that AGREES with the demoted status this represents
+ (e.g. sancti:09-22o:4:r for Maurice, rank 4, against Thomas's own
+ sancti:09-22:3:w, rank 3, on the day Thomas is observed -- test_oracle
+ .ml's own [test_identity_rank_corroboration]). Restricted to [status];
+ [rank] itself is untouched, and stays load-bearing for a demoted feast:
+ RG 111(b)'s "scilicet de festo II classis" floor ({!admit} below) reads
+ [rank] directly, and it is exactly that floor which excludes these
+ Class3 entries from a II-class Sunday's single slot. Only ORDERING among
+ candidates already offered to {!admit} changes here.
+ NOTE, corrected: this comment previously cited {!Celebration.status}'s
+ own doc comment as saying a demoted feast retains its rank for RG 111's
+ admission-COUNT purposes. That misquotes it -- celebration.mli says RG
+ 111 "orders admitted commemorations by dignity", i.e. it names the
+ ORDERING use this guard removes, not a counting one. celebration.mli's
+ line is itself now stale: ordering moved from dignity to [band] in
+ ea22ad2. The guard is right; its former justification was not. *)
+ if status = Celebration.Commemoration_only then unclassified
+ (* 1: Nativity, Easter Sunday, Pentecost Sunday (I class w/ octave). *)
+ else if is_temporal && rank = Class1 && ((m = 12 && d = 25) || off = 0 || off = 49) then 1
(* 2: Sacred Triduum (Thu-Sat of Holy Week). *)
else if is_temporal && rank = Class1 && off >= -3 && off <= -1 then 2
(* 3: Epiphany, Ascension, Holy Trinity, Corpus Christi, Sacred Heart,
@@ -244,13 +285,14 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc
(* Task 8: what happens to the day's LOSING candidate (docs/research/
rules-register.md §4, "Occurrence" RG 92-95 and "Vigils" RG 33, plus RG
- 94). [band] above decides who wins; this decides the loser's fate, which
- turns on the LOSER's own rank and status (RG 95), except RG 33's vigil
- omission, which also has to read the winner. Nothing here ever returns
- [Precedence.Repose]: that disposition denotes RG 100-102's *repositio*
- (perpetual impediment from a proper/diocesan calendar), out of this
- plan's scope -- see calendar.mli's own note that nothing in the EF
- ruleset currently emits it. *)
+ 94; also §6.0/Caput III "De Dominicis" RG 16(a), below). [band] above
+ decides who wins; this decides the loser's fate, which turns on the
+ LOSER's own rank and status (RG 95), except RG 33's vigil omission and RG
+ 16(a)'s Sunday-suppression, which also have to read the winner. Nothing
+ here ever returns [Precedence.Repose]: that disposition denotes RG
+ 100-102's *repositio* (perpetual impediment from a proper/diocesan
+ calendar), out of this plan's scope -- see calendar.mli's own note that
+ nothing in the EF ruleset currently emits it. *)
(* RG 33 -- CORRECTED 2026-08-12 (Task 16, primary-source-verified against
docs/research/1962-06-23,_SS_Ioannes_XXIII,_Missale_Romanum,_LT.pdf, the
@@ -605,6 +647,91 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
register's own §4 "Vigils" entry states RG 32 only as "same pattern
[as RG 31]", not verbatim -- now closed here). *)
Precedence.Omit
+ else if
+ is_temporal
+ && cel.Celebration.rank = Class2
+ && is_sunday_slug (Slug.to_string cel.Celebration.slug)
+ && (let wcel = winner.Precedence.cel in
+ (wcel.Celebration.rank = Class1 || wcel.Celebration.rank = Class2)
+ && wcel.Celebration.subject = Subject.Lord)
+ then
+ (* RG 16(a) (docs/research/rules-register.md §6.0, Caput III "De
+ Dominicis", primary text): "Dominica II classis, in occurrentia,
+ festis II classis praefertur. Attamen: a) festum Domini I aut II
+ classis, in dominica II classis occurrens, locum tenet ipsius
+ dominicae cum omnibus iuribus et privilegiis: de dominica, proinde,
+ NULLA FIT COMMEMORATIO" -- a Feast of the Lord, I or II class,
+ occurring on a II-class Sunday, takes the Sunday's own place with all
+ its rights and privileges: OF THE SUNDAY, THEREFORE, NO
+ COMMEMORATION IS MADE. {!band} entry 14 already ranks such a feast
+ above the Sunday (RG 91 entry 14 < entry 15), so the feast is
+ correctly [observed]; this is [disposition]'s own answer for what
+ becomes of the SUNDAY once it has lost -- [Omit], not the [Commemorate]
+ every other impeded II-class Sunday gets via RG 109(a)/RG 111(b)
+ below. This is the ONE place in this function that needs the WINNER's
+ [subject], not only the loser's own fields -- like RG 33's vigil
+ omission above, not a new kind of signature: [Precedence.rules.disposition]
+ already takes [~winner], this is simply its first other reader.
+
+ Both winner-side conjuncts are load-bearing, proved by
+ [test_precedence_ef.ml]'s own paired rows (the brief's "one without
+ the other proves nothing"):
+ - [rank = Class1 || rank = Class2]: {!band} entries 11-13 admit ANY
+ I-class feast -- Lord or Saint alike, no subject test at all (entry
+ 14's own comment: unlike entry 16, entries 11-13 draw no such
+ line) -- ahead of a II-class Sunday. REACHABLE at Class1 on real
+ data: e.g. the Nativity of St John the Baptist (24 June, I class,
+ Saint) landing on a Time-after-Pentecost Sunday already wins the
+ day under {!band} alone; without this conjunct RG 16(a) would
+ wrongly fire for it too. (At Class2 this conjunct adds no further
+ cases beyond what the [subject = Lord] conjunct below already
+ requires -- entry 16's ordinary II-class feasts never outrank a
+ Sunday in {!band} in the first place, 16 > 15 -- so it is only
+ independently reachable at Class1.)
+ - [subject = Lord]: at Class2 this is what {!band} entry 14 already
+ requires of its own winners, so it adds no further restriction
+ there; at Class1 it is independently reachable and necessary, per
+ the St John Baptist example above -- dropping it would fire RG
+ 16(a) for any winning I-class feast at all, exactly the
+ over-wide branch the task brief warns against.
+
+ The SUNDAY-side conjuncts: [rank = Class2] excludes a I-class Sunday
+ (Advent/Lent/Passiontide, Low Sunday, {!band} entry 6) -- RG 16(a)'s
+ own text says "Dominica II classis", not "any Sunday", and a I-class
+ Sunday can never actually reach here as a [loser] against a
+ Class1-or-2 Lord winner in the first place, for TWO DIFFERENT reasons
+ depending on the winner's shape (CORRECTED, fix round 1: the previous
+ version of this comment claimed "entry 6's own band value (6) is
+ lower than every entry [3, 11-14]" as a single numeric argument --
+ WRONG on its face, 6 is not lower than 3, and the claim would in any
+ case prove the opposite of what it was cited for: if a band-3
+ candidate really did contest a Class1 Sunday, the LOWER number (3)
+ would win, i.e. the Sunday would LOSE, not "always win outright" as
+ claimed):
+ - Against entries 11-14 (SANCTORAL-origin Lord feasts, e.g. the
+ Transfiguration): the numeric argument genuinely holds here --
+ entry 6 (6) IS lower than 11-14, so a I-class Sunday always wins
+ outright against these.
+ - Against entry 3 (TEMPORAL-origin Lord feasts -- Epiphany,
+ Ascension, Trinity, Corpus Christi, Sacred Heart, Christ the King):
+ not a numeric argument at all, but a STRUCTURAL one -- every
+ band-3 celebration is [is_temporal], and {!Precedence.resolve}
+ takes exactly ONE temporal candidate per day ([~temporal], not a
+ list), so a band-3 Lord feast IS that date's own single temporal
+ candidate, never a SEPARATE candidate contesting an
+ independently-produced Sunday on the same date. There is no
+ collision to resolve by band comparison in the first place.
+ Either way, no I-class Sunday can reach here as a loser against a
+ Class1-or-2 Lord winner -- this is exactly why the rubric restricts
+ itself to II class. [is_sunday_slug]
+ (the same marker RG 33's [impedes_vigil] and RG 109(a)'s
+ [privilege_of] already use, with no [is_temporal] guard there either
+ -- no sanctoral slug this codebase's data produces contains
+ {!sunday_marker}) is the "Dominica" half; [is_temporal] is kept
+ alongside it anyway, the same explicit-but-not-load-bearing defence
+ the RG26 branch above gives its own [not is_vigil] guard, rather than
+ relying on that absence silently. *)
+ Precedence.Omit
else
(* RG 95's other branch: "aut commemorantur aut penitus omittuntur" --
commemorated or wholly omitted. Reached by every SANCTORAL loser
@@ -642,42 +769,105 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
classis", "dominicis II classis", "aliis diebus II classis", "diebus III
et IV classis") -- read here off [observed]'s own [rank] and, for the
Sunday/non-Sunday II-class split, the same slug marker [privilege_of] and
- RG 33's [impedes_vigil] already use ([is_sunday_slug]). No [context]
- (date/season/weekday) is available to [admit] (see precedence.mli's
- [rules.admit]) or needed: [observed] IS the day's own celebration, so its
- rank and slug already carry everything RG 111's own four categories test. *)
+ RG 33's [impedes_vigil] already use ([is_sunday_slug]).
+
+ CORRECTED (fix round 1, RG16(a) task): the previous version of this
+ comment claimed "[observed] IS the day's own celebration, so its rank and
+ slug already carry everything RG 111's own four categories test" --
+ WRONG once RG 16(a) exists. RG 16(a)'s own text says the winning Feast of
+ the Lord holds the Sunday's place "cum omnibus iuribus et privilegiis"
+ (with ALL its rights and privileges) -- the day remains a "dominica II
+ classis" for RG 111(b)'s own purposes even though [observed] is now the
+ FEAST, not the Sunday, so [observed]'s own slug is no longer a reliable
+ signal of "is this a Sunday" once something can legitimately observe in
+ the Sunday's place. Confirmed wrong for real data by the oracle: 6 August
+ falling on a Sunday shows the Transfiguration observed and NO
+ commemoration (missalemeum: `commemorations: []`, Pope Sixtus II et al.
+ `displaced`); the pre-fix code, reading Sunday-ness off [observed]'s own
+ slug ("transfiguration-of-our-lord", no Sunday marker), wrongly took the
+ [Class2, false] "other II class: one" branch below and admitted Sixtus
+ (Class3) regardless of RG 111(b)'s own "de festo II classis" rank floor.
+ Control, also oracle-confirmed: 6 August on an ordinary WEEKDAY (no
+ Sunday collision) correctly admits Sixtus -- being a Sunday is exactly
+ what excludes him, and [observed]'s own identity cannot tell the two
+ cases apart on its own.
+
+ Fixed by reading Sunday-ness off [temporal] instead -- {!Precedence.rules.admit}'s
+ own [~temporal] parameter, {!Precedence.resolve}'s [~temporal] argument
+ passed straight through, unaffected by whether it won the day. [context]
+ (date/season/weekday) is still not needed: [temporal]'s own slug already
+ carries everything this split needs, the same way [observed]'s used to
+ before a competing office could occupy the Sunday's place. *)
-(* RG 8's four-class dignity order, Class1 highest. Deliberately NOT [band]
- (RG 91's much finer 28-entry table): [band] needs a [context] [admit]
- does not have (see above), and Celebration.mli's own comment on [status]
- -- "RG 111 orders admitted commemorations by dignity" -- names [rank]
- itself as that dignity, not the finer occurrence-table entry. *)
-let dignity = function
- | Vocab_ef.Class1 -> 1
- | Vocab_ef.Class2 -> 2
- | Vocab_ef.Class3 -> 3
- | Vocab_ef.Class4 -> 4
+(* RG 113 -- CORRECTED, Task B (branch ef-rg16a): docs/research/rules-
+ register.md §4's RG 113 entry previously quoted only its FIRST sentence
+ ("commemoratio de Tempore fit primo loco"); its SECOND, load-bearing
+ sentence, primary-source-verified against two independent scans, is the
+ real rule for this function: *"In admittendis et ordinandis aliis
+ commemorationibus, servetur ordo tabellae praecedentiae"* -- in ADMITTING
+ and ORDERING the other commemorations, the order of the table of
+ precedence (RG 91, {!band}'s own 28-entry table) is to be kept.
-(* Deterministic selection order for RG 111: dignity first, then slug --
- the same tie-break {!Precedence.compare_by} uses for [band] itself (the
- brief: "break ties on slug"), so which candidate wins a shared rank never
- depends on the order [comms] arrives in. *)
-let compare_dignity (a, _) (b, _) =
- let da = dignity a.Precedence.cel.Celebration.rank
- and db = dignity b.Precedence.cel.Celebration.rank in
- if da <> db then Int.compare da db
+ This REPLACES a previous [compare_dignity], which sorted by RG 8's coarse
+ four-class [rank] ("dignity") and broke same-rank ties alphabetically by
+ slug -- an engineering convention with no rubrical warrant. Measured,
+ fix round 1 (2005-2050, a temporary pre-fix [git worktree]): a
+ same-[rank] tie existed on 599 days, most never reaching a real
+ decision; reversing the slug tie-break alone changed the ADMITTED SET on
+ 65 of them and the printed ORDER of an already-admitted pair on a
+ further 149 (docs/research/rules-register.md §6.1's full account,
+ correcting this comment's own earlier, unreproduced "66 days" claim).
+
+ CORRECTED, fix round 1 (coordinator finding 1): checking every one of
+ those 65+149 real decisions against data/ef/sanctoral.sexp found the
+ SAME underlying shape in all of them, no exceptions -- one candidate is
+ always [Cel.Commemoration_only] (e.g. "maurice-and-companions-martyrs",
+ 22 September) and the other always a genuine [Cel.Feast] or a temporal
+ office of the SAME [rank] (e.g. "thomas-of-villanova", same day; or a
+ Lent feria, {!band} entry 22, against "paul", 22 February). That is
+ {!band}'s OWN fidelity bug, fixed separately at its source (see [band]'s
+ own top-of-branch guard, RG 91's table has no row for a bare
+ commemoration at all) -- NOT a case RG 113's table-order alone resolves,
+ since a [Commemoration_only] candidate never had a real table entry to
+ compare in the first place. With that fixed, none of these 599 days'
+ real decisions any longer depend on THIS function's own slug fallback:
+ {!band} alone (a real entry vs {!unclassified}) already decides every
+ one. What remains genuinely open -- two DIFFERENT candidates landing on
+ the IDENTICAL real table entry, e.g. two different Class3 universal
+ feasts both at entry 24 -- was checked and found EMPTY across
+ 2005-2050 (register §6.1): RG 113's own table-order, once {!band} is
+ accurate, already decides every real case this codebase's current data
+ produces; the slug fallback below is exercised only between two
+ [Commemoration_only] candidates tied at {!unclassified} (138 of the 599,
+ all order/count-invisible -- RG 91 has no table position for either of
+ them to compare, so there is nothing more specific RG 113 could supply
+ here either). {!band} needs a [context] this function itself does not
+ have (date/season/weekday) -- unlike [dignity], which read [rank] alone
+ -- so {!Precedence.resolve} now computes each candidate's own [band]
+ value once, generically, and hands it to [admit] as the trailing [int]
+ on each input triple (see {!Precedence.rules.admit}'s own doc). [comms]
+ below is [(candidate * privilege * int) list], not the pair it used to
+ be. *)
+let compare_precedence (a, _, ba) (b, _, bb) =
+ if ba <> bb then Int.compare (ba : int) bb
else Slug.compare a.Precedence.cel.Celebration.slug b.Precedence.cel.Celebration.slug
let rec take n = function
| [] -> []
| x :: xs -> if n <= 0 then [] else x :: take (n - 1) xs
+(* [comms]'s own candidate/privilege pair, its [band] value dropped once a
+ selection has been made -- {!Precedence.rules.admit}'s return type is
+ still the pair, not the triple; only the INPUT carries [band]. *)
+let drop_band (c, p, (_ : int)) = (c, p)
+
let admit ~(observed : Vocab_ef.rank Precedence.candidate)
- (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) :
+ ~(temporal : Vocab_ef.rank Precedence.candidate)
+ (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list) :
(Vocab_ef.rank Precedence.candidate * Precedence.privilege) list =
- (* Sorted once, by dignity then slug (see [compare_dignity]); every branch
- below either takes a prefix of this list or filters it, so the RESULT
- is always a sub-list of [comms] with its elements untouched -- never
+ (* Sorted once, by {!band} then slug (see [compare_precedence]); every
+ branch below either takes a prefix of this list or filters it, so the
+ RESULT is always built from [comms]'s own elements, untouched -- never
rebuilt -- which matters beyond determinism: {!Precedence.resolve}'s
own [dropped] computation tells an admitted candidate from a dropped
one by physical equality (==) on the candidate value (Task 2's own
@@ -689,21 +879,29 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
celebration would surface TWICE in the same day -- once in
[commemorations] (the rebuilt copy) and once in [omitted] (the original,
which nothing admitted matches). One admission, double-reported, and no
- crash to announce it, which is exactly why this comment exists. *)
- let sorted = List.stable_sort compare_dignity comms in
- let is_privileged (_, p) = p = Precedence.Privileged in
+ crash to announce it, which is exactly why this comment exists.
+ [drop_band] only unwraps the pair back out of the triple -- it does not
+ rebuild [c] or [p] themselves, so this obligation still holds. *)
+ let sorted = List.stable_sort compare_precedence comms in
+ let is_privileged (_, p, _) = p = Precedence.Privileged in
let observed_rank = observed.Precedence.cel.Celebration.rank in
- let observed_is_sunday =
- is_sunday_slug (Slug.to_string observed.Precedence.cel.Celebration.slug)
+ (* CORRECTED (fix round 1, RG16(a) task): read off [temporal], not
+ [observed] -- see this function's own doc comment above for the full
+ argument and the oracle evidence. [temporal] is the day's own
+ temporal-cycle candidate regardless of who is [observed]; RG 16(a)
+ makes that distinction live for the first time (a Feast of the Lord
+ can now be [observed] on a day whose [temporal] candidate is a Sunday). *)
+ let day_is_sunday =
+ is_sunday_slug (Slug.to_string temporal.Precedence.cel.Celebration.slug)
in
let open Vocab_ef in
- match (observed_rank, observed_is_sunday) with
+ match (observed_rank, day_is_sunday) with
| Class1, _ ->
(* RG 111: "I class: none save one privileged." Ordinary commemorations
never get a slot at all on a I-class day, no matter how many are
- due; at most one privileged one does, the highest-dignity one if
- several are. *)
- (match List.filter is_privileged sorted with [] -> [] | best :: _ -> [ best ])
+ due; at most one privileged one does, the highest-precedence one
+ (RG 113: {!band}'s own table order) if several are. *)
+ (match List.filter is_privileged sorted with [] -> [] | best :: _ -> [ drop_band best ])
| Class2, true ->
(* RG 111(b), primary text, RE-VERIFIED word for word against the scan
(final fix wave; this sentence is the sole textual basis for the
@@ -717,17 +915,18 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
dropped if a privileged commemoration is due." Two clauses, not
one: (i) a privileged
commemoration, whenever due, categorically takes the day's one slot
- -- not by comparing its dignity against the ordinary contender's,
+ -- not by comparing its precedence against the ordinary contender's,
so an ordinary commemoration that would otherwise win on raw
- dignity is still dropped once any privileged one is also due (the
- asymmetric clause the brief and task report flag as deliberate, not
- present at "other II class" below); (ii) failing that, the slot is
- reserved SPECIFICALLY for a [Class2] candidate -- "de festo II
- classis" is a RANK restriction, not merely "whichever ordinary
- candidate has the best dignity": a III- or IV-class ordinary loser
- (a plain commemoration-only saint with no privilege of its own) has
- NO standing for this slot at all and must be entirely omitted, even
- when it is the only candidate present.
+ table order is still dropped once any privileged one is also due
+ (the asymmetric clause the brief and task report flag as
+ deliberate, not present at "other II class" below); (ii) failing
+ that, the slot is reserved SPECIFICALLY for a [Class2] candidate --
+ "de festo II classis" is a RANK restriction, not merely "whichever
+ ordinary candidate has the best table position": a III- or
+ IV-class ordinary loser (a plain commemoration-only saint with no
+ privilege of its own) has NO standing for this slot at all and
+ must be entirely omitted, even when it is the only candidate
+ present.
Fix, Task 16 (primary-source-verified + missalemeum-confirmed):
previously this fell back to "the best of [sorted], whatever its
@@ -739,21 +938,21 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
shows him "displaced" (omitted), never commemorated; the
pre-fix code admitted him regardless. *)
(match List.filter is_privileged sorted with
- | best :: _ -> [ best ]
+ | best :: _ -> [ drop_band best ]
| [] -> (
- match List.filter (fun (c, _) -> c.Precedence.cel.Celebration.rank = Class2) sorted with
+ match List.filter (fun (c, _, _) -> c.Precedence.cel.Celebration.rank = Class2) sorted with
| [] -> []
- | best :: _ -> [ best ]))
+ | best :: _ -> [ drop_band best ]))
| Class2, false ->
(* RG 111: "other II class: one" -- no privilege-override clause here,
unlike the Sunday case immediately above, so the day's one slot
- goes to whichever candidate outranks the rest by dignity alone,
- privileged or not. *)
- (match sorted with [] -> [] | best :: _ -> [ best ])
+ goes to whichever candidate outranks the rest by RG 113's own
+ table-of-precedence order ({!band}), privileged or not. *)
+ (match sorted with [] -> [] | best :: _ -> [ drop_band best ])
| (Class3 | Class4), _ ->
- (* RG 111: "III-IV class: at most two" -- by dignity, same as the
- non-Sunday II-class case, just with room for two. *)
- take 2 sorted
+ (* RG 111: "III-IV class: at most two" -- by RG 113's table order, same
+ as the non-Sunday II-class case, just with room for two. *)
+ List.map drop_band (take 2 sorted)
(* Task 11: RG 96 -- where an impeded I-class feast lands (docs/research/
rules-register.md §4, "Transfer/translation"). [band] decides who is
@@ -766,9 +965,11 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
RG 96's own text, register-transcribed: "the next following day that is
not I or II class." [is_blocking] reads that off [Vocab_ef.rank] --
- RG 96 speaks of the day's CLASS (RG 8's four-way dignity), not [band]'s
- finer 28-entry occurrence-table row, the same distinction {!admit} above
- already draws for RG 111 ({!dignity}, not [band]). *)
+ RG 96 speaks of the day's CLASS (RG 8's four-way "dignity"), not [band]'s
+ finer 28-entry occurrence-table row -- unlike {!admit} above, which (RG
+ 113, Task B/ef-rg16a) now DOES use [band] itself for its own selection
+ order; RG 96's own text has no such finer-table reading, so [is_blocking]
+ stays on [Vocab_ef.rank] alone. *)
let is_blocking (rank : Vocab_ef.rank) = rank = Vocab_ef.Class1 || rank = Vocab_ef.Class2
(* RG 96's own named exception (docs/research/rules-register.md §4,
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli
index 55f947e..b283a17 100644
--- a/lib/rites/rite_ef/precedence_ef.mli
+++ b/lib/rites/rite_ef/precedence_ef.mli
@@ -68,10 +68,21 @@ val unclassified : int
text states an exception: entry 8 (All Souls) reads "yields to an
occurring Sunday", so on a Sunday this returns a value that
loses to entry 15 rather than the literal integer 8 (see the comment on
- entry 8 in precedence_ef.ml for the exact value and why). Total over
- every candidate {!Precedence.resolve} or {!Calendar} can construct,
- including shapes the 1962 table itself does not describe (see
- {!unclassified}). *)
+ entry 8 in precedence_ef.ml for the exact value and why). ALSO
+ {!unclassified} for any [Cel.Commemoration_only] candidate, checked
+ first, ahead of every rank-keyed branch -- CORRECTED, Task B fix round
+ 1 (coordinator finding 1): RG 91's own table enumerates only "dies
+ liturgici" (entry 24's own text, "Festa III classis..." -- FEASTS), so
+ a celebration reduced to a bare commemoration was never a row in the
+ table to begin with, confirmed by the calendarium's own notation (a
+ genuine feast carries a class number on its occurrence line; a bare
+ "Commemoratio" carries none). Before this fix, a [Commemoration_only]
+ candidate silently borrowed the SAME entry number as a genuine [Feast]
+ of its own [rank] -- manufacturing ties {!Precedence.compare_by} and
+ {!compare_precedence} both then had to break some other way, that RG 91
+ itself never created. Total over every candidate {!Precedence.resolve}
+ or {!Calendar} can construct, including shapes the 1962 table itself
+ does not describe (see {!unclassified}). *)
val band : Vocab_ef.season Precedence.context -> Vocab_ef.rank Precedence.candidate -> int
(** RG 33's marker: every Sunday slug this rite's temporal cycle produces
@@ -164,33 +175,82 @@ val nativity_octave_prefix : string
so the two cannot silently drift apart. *)
val september_ember_prefix : string
-(** [admit ~observed comms]: RG 108-111 (docs/research/rules-register.md §4,
- "Commemorations"). How many of [comms] -- each already tagged with its
- real RG 109 privilege by {!disposition} -- RG 111 admits, and which,
- given the day actually observed:
+(** [admit ~observed ~temporal comms]: RG 108-111 (docs/research/rules-register.md
+ §4, "Commemorations"). How many of [comms] -- each already tagged with
+ its real RG 109 privilege by {!disposition}, and its own {!band} value
+ (RG 91's table-of-precedence entry, computed once by
+ {!Precedence.resolve} -- see {!Precedence.rules.admit}'s own doc) -- RG
+ 111 admits, and which, given the day actually observed:
- [observed] a [Class1] day: none, except at most one privileged
- commemoration (the highest-dignity one, if several are due) -- an
- ordinary one is never admitted here, no matter how many are due;
- - [observed] a [Class2] Sunday (its slug carries {!sunday_marker}): one,
- subject to TWO conditions, not one -- (i) a privileged commemoration,
- whenever due, categorically takes the day's one slot over any
- ordinary one, not by comparing dignity, so an ordinary commemoration
- that would otherwise win on dignity is still dropped; (ii) failing
- that, the slot is reserved for a [Class2] candidate SPECIFICALLY
- ("de festo II classis", RG 111(b)'s own wording -- a RANK FLOOR, not
- "whichever ordinary candidate has the best dignity"): a III- or
- IV-class ordinary loser has no standing for this slot at all and is
- admitted nothing, even when it is the only candidate due;
- - [observed] any other [Class2] day: one, by dignity alone -- no
- privilege override and no rank floor, unlike the Sunday case
- immediately above;
- - [observed] a [Class3] or [Class4] day: at most two, by dignity alone.
+ commemoration (the highest-{!band}-precedence one, if several are
+ due) -- an ordinary one is never admitted here, no matter how many
+ are due;
+ - the CIVIL DAY is a [Class2] Sunday ([temporal]'s slug carries
+ {!sunday_marker} -- CORRECTED, fix round 1, RG16(a) task: read off
+ [temporal], not [observed]; see below): one, subject to TWO
+ conditions, not one -- (i) a privileged commemoration, whenever due,
+ categorically takes the day's one slot over any ordinary one, not by
+ comparing table position, so an ordinary commemoration that would
+ otherwise win on table order is still dropped; (ii) failing that, the
+ slot is reserved for a [Class2] candidate SPECIFICALLY ("de festo II
+ classis", RG 111(b)'s own wording -- a RANK FLOOR, not "whichever
+ ordinary candidate has the best table position"): a III- or IV-class
+ ordinary loser has no standing for this slot at all and is admitted
+ nothing, even when it is the only candidate due;
+ - the civil day is any other [Class2] day: one, by {!band}'s table order
+ alone -- no privilege override and no rank floor, unlike the Sunday
+ case immediately above;
+ - [observed] a [Class3] or [Class4] day: at most two, by {!band}'s table
+ order alone.
- "Dignity" here is [Vocab_ef.rank] (RG 8's four classes), NOT {!band}'s
- finer RG 91 entry number -- {!band} needs a [context] (date/season/
- weekday) this function does not receive (see {!Precedence.rules.admit}).
- Ties break on slug, matching {!Precedence.compare_by}, so the result
- never depends on the order [comms] arrives in.
+ CORRECTED, Task B (branch ef-rg16a): this used to sort by
+ [Vocab_ef.rank] (RG 8's coarse four-class "dignity") and break same-rank
+ ties alphabetically by slug -- an engineering convention with no
+ rubrical warrant, cited nowhere. Measured, fix round 1 (2005-2050,
+ reproducible via a temporary [git worktree] at the pre-fix commit):
+ a same-[rank] tie existed in the candidate pool offered to [admit] on
+ 599 days, most never reaching a real decision (RG 111's own admission
+ count often has room for both, e.g. a III-class day admits two); of
+ those, reversing the slug tie-break ALONE actually changed the ADMITTED
+ SET on 65 days and only the printed ORDER of an already-admitted pair
+ on a further 149 (docs/research/rules-register.md §6.1's own full
+ account, superseding this comment's earlier, unreproduced "66 days"
+ claim). RG 113's own second sentence, previously uncited
+ (docs/research/rules-register.md §4's RG 113 entry), is the real rule:
+ *"in admittendis et ordinandis aliis commemorationibus, servetur ordo
+ tabellae praecedentiae"* -- admitting and ordering commemorations both
+ run on {!band}'s own finer RG 91 table, not RG 8's four classes.
+ {!band} needs a [context] (date/season/weekday) this function does not
+ receive on its own, so {!Precedence.resolve} computes it once,
+ generically, and hands each candidate its own value as the trailing
+ [int] on [comms]'s triples (see {!Precedence.rules.admit}). Ties break
+ on slug ONLY within one identical [band] value -- CORRECTED, fix round
+ 1 (coordinator finding 1): [band] itself used to hand a
+ [Cel.Commemoration_only] candidate the same table entry as a genuine
+ [Cel.Feast] of its own rank (RG 91's table has no row for a bare
+ commemoration at all), manufacturing most of the "identical [band]
+ value" ties this comment used to describe as RG 113's own residual.
+ Fixed in [band] itself; checked afterward (2005-2050 and the full
+ 1583-9999 domain, register §6.1) and found EMPTY of any remaining
+ "two different real table entries tied" case -- the slug fallback below
+ is exercised only between two [Commemoration_only] candidates both at
+ {!unclassified}, still an uncited engineering convention (register §6
+ "RG 113 tie-break"), matching {!Precedence.compare_by}'s own fallback,
+ so the result never depends on the order [comms] arrives in.
+
+ [temporal] -- CORRECTED, fix round 1 (RG16(a) task): this function used
+ to read the Sunday/non-Sunday split off [observed]'s own slug, which
+ silently assumed [observed] IS the day's temporal-cycle office. RG 16(a)
+ breaks that assumption on purpose: a Feast of the Lord holds an impeded
+ II-class Sunday's own place "cum omnibus iuribus et privilegiis"
+ (RG 16(a)'s own text), so the day still counts as a "dominica II
+ classis" for RG 111(b) even though [observed] is now the FEAST.
+ [temporal] -- {!Precedence.rules.admit}'s own parameter, the day's
+ temporal-cycle candidate regardless of who won -- is the reliable
+ signal; oracle-confirmed (6 August: Sunday excludes a Class3 saint
+ Sixtus II from the freed slot, an ordinary weekday admits him). Every
+ row of the previous behaviour where [observed] and [temporal] coincide
+ (no RG 16(a) displacement occurred) is unaffected.
Every candidate this returns is a value taken unchanged from [comms],
never rebuilt: {!Precedence.resolve}'s own [dropped]/[omitted]
@@ -202,7 +262,8 @@ val september_ember_prefix : string
itself total over an empty or arbitrarily long [comms]. *)
val admit :
observed:Vocab_ef.rank Precedence.candidate ->
- (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list ->
+ temporal:Vocab_ef.rank Precedence.candidate ->
+ (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list ->
(Vocab_ef.rank Precedence.candidate * Precedence.privilege) list
(** The Annunciation's own bootstrapped slug (data/ef/sanctoral.sexp, Task