summaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef/precedence_ef.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites/rite_ef/precedence_ef.ml')
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml120
1 files changed, 82 insertions, 38 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
index c488596..cd0c53b 100644
--- a/lib/rites/rite_ef/precedence_ef.ml
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -1125,37 +1125,73 @@ let rg110_find_companion comms companion_slug =
(* RG 110's own uncapped addition, applied to [normal] -- the day's ALREADY-
COMPUTED, ORDINARILY-CAPPED admission result (every branch of {!admit}
below, unchanged otherwise). Two triggers, covering RG 110(a)/(b) and
- RG 110(c) respectively, in this function's own comment above:
+ RG 110(c) respectively, in this function's own comment above -- and,
+ fix round 1, TWO DIFFERENT ORDERINGS, not one, because the two shapes'
+ own primary texts say different things about where the companion goes:
+
- [observed] itself is one of the three trigger slugs (shape (a)/(b):
Peter's or Paul's own Office/Mass IS today's day) -- the companion is
- looked up directly and appended, uncapped.
+ looked up directly and PREPENDED to [normal]. (a)/(b)'s own text,
+ "oratio alterius Apostoli additur ... orationi diei" (the OTHER
+ Apostle's oration is added ... TO THE DAY'S OWN oration), has nothing
+ in [normal] to order the companion AGAINST in the first place: the
+ day's own oration is [observed], not a member of this list, so
+ leading the list is simply where an item with nothing to be "before"
+ or "after" inside it goes -- not itself a citation for a POSITION
+ the primary text does not describe a list position for at all.
- one of [normal]'s OWN members is a trigger slug (shape (c): Peter's or
Paul's own office lost the day outright but is itself being admitted
- as an ordinary/privileged commemoration of whatever else won) -- same
- lookup, same append.
- Both can never fire together on one call ({!Precedence.resolve} only
- ever calls {!Precedence.rules.admit} once per date, and all three
- trigger slugs are distinct FIXED calendar dates -- 25 January, 22
- February, 30 June -- so at most one of the two conditions is ever true
- for a given [observed]/[normal] pair; [List.filter_map] over both
- sources together is simply the shortest way to express "check every
- place a trigger slug could legitimately appear", not evidence they are
- expected to co-occur).
+ as an ordinary/privileged commemoration of whatever else won) -- the
+ companion is looked up the same way, but SPLICED IN IMMEDIATELY AFTER
+ its own trigger, not prepended to the whole list. RG 110(c)'s own
+ text, word for word: "quoties vero oratio unius Apostoli addenda est
+ ad modum commemorationis, HUIC ORATIONI ADDITUR ALTERA immediate, ante
+ omnes alias commemorationes" -- "huic orationi" (dative, "to THIS
+ oration") refers back to "oratio unius Apostoli" (the trigger's OWN
+ oration, the one just named as "addenda... ad modum commemorationis")
+ -- so "additur altera" (the OTHER is added) means the companion is
+ added TO the trigger's own oration, i.e. FOLLOWS it, not precedes it;
+ the pair as a BLOCK then precedes "omnes ALIAS commemorationes"
+ ("alias" = OTHER, unrelated ones -- cannot include the pair's own
+ first member). CORRECTED, fix round 1: the original version of this
+ function prepended in BOTH shapes uniformly, citing (c)'s own "ante
+ omnes alias" for shape (a)/(b) too by analogy -- which is fine for
+ WHERE the pair sits relative to unrelated commemorations, but wrongly
+ also reordered the PAIR'S OWN internal order in shape (c), producing
+ `paul, chair-of-st-peter` where the primary text requires
+ `chair-of-st-peter, paul` (verified against BOTH photographic scans,
+ word for word, no divergence to adjudicate). Confirmed live: every one
+ of the 593 shape-(c) days measured in this task's own report emitted
+ the companion FIRST before this fix; RG 111's own admission branches
+ never build a second, unrelated commemoration alongside a shape-(c)
+ trigger in today's data (RG 111(b) admits exactly one candidate on a
+ II-class Sunday, the only day shape (c) ever occurs), so this
+ splice-after-trigger behaviour is Total the same way {!take}/
+ {!drop_band} are, but has no OTHER live witness to also prove the
+ "ante omnes alias" half of (c) against.
- PREPENDED, not appended, per RG 110(c)'s own explicit ordering directive
- ("ante omnes alias commemorationes") -- the one clause in this rule that
- actually specifies WHERE, restated here for shape (a)/(b) too rather than
- drawing an unwarranted distinction between the two: no live data in this
- codebase ever has a THIRD, unrelated commemoration alongside a RG 110
- pair to make the ordering choice visible in real output (the companion is
- always the only OTHER item, or the only item, in [normal] already), so
- this is closer to a defensive convention than a tested outcome -- flagged
- honestly rather than claimed as independently verified.
+ Both shapes can never fire together on one call ({!Precedence.resolve}
+ only ever calls {!Precedence.rules.admit} once per date, and all three
+ trigger slugs are distinct FIXED calendar dates -- 25 January, 22
+ February, 30 June -- so at most one of [observed]/[normal]'s own members
+ is ever a trigger for a given call).
A companion already present in [normal] on its own merits (structurally
unreachable today -- {!unclassified}'s own comment above -- but not
provably so for every future rite/data shape) is not added a second
- time: [List.exists] guards each append. *)
+ time: [List.exists] guards both branches.
+
+ NEW BLIND SPOT, fix round 1: no validation layer in this codebase
+ compares commemoration ORDER at all -- the lectio differential does not
+ compare commemorations (its own "limit 1"); the oracle's own
+ [identity_diff] sorts both sides into a multiset before comparing;
+ {!Colitur_kernel.Record}/`describe` (test_golden.ml) both sort their own
+ comms field too, for the same reason presence/identity checks should not
+ be order-sensitive by accident. This function's own [admit_cases] unit
+ test (test_precedence_ef.ml) is therefore the ONLY place in this
+ codebase's test suite where commemoration order is asserted at all --
+ flagged here, and in CLAUDE.md's own "know what each layer cannot see"
+ section, as a genuine, permanent limit, not merely this fix's own gap. *)
let rg110_additions (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list)
~(observed : Vocab_ef.rank Precedence.candidate)
(normal : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) :
@@ -1163,20 +1199,28 @@ let rg110_additions (comms : (Vocab_ef.rank Precedence.candidate * Precedence.pr
let already_has slug =
List.exists (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug = slug) normal
in
- let trigger_slugs =
- Slug.to_string observed.Precedence.cel.Celebration.slug
- :: List.map (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug) normal
- in
- let companions =
- List.filter_map
- (fun trigger_slug ->
- match rg110_companion_slug trigger_slug with
- | None -> None
- | Some companion_slug ->
- if already_has companion_slug then None else rg110_find_companion comms companion_slug)
- trigger_slugs
- in
- companions @ normal
+ match rg110_companion_slug (Slug.to_string observed.Precedence.cel.Celebration.slug) with
+ | Some companion_slug when not (already_has companion_slug) -> (
+ (* Shape (a)/(b): prepend -- see this function's own comment above. *)
+ match rg110_find_companion comms companion_slug with
+ | Some companion -> companion :: normal
+ | None -> normal)
+ | _ ->
+ (* Shape (c): splice each trigger's companion in immediately after it
+ -- {!List.concat_map} rather than a fold, so a trigger with no
+ companion found (defensive; {!rg110_find_companion} returning
+ [None]) simply passes through unchanged, and a member that is not
+ a trigger at all ([rg110_companion_slug] returns [None]) is never
+ touched. *)
+ List.concat_map
+ (fun ((c, _) as pair) ->
+ match rg110_companion_slug (Slug.to_string c.Precedence.cel.Celebration.slug) with
+ | Some companion_slug when not (already_has companion_slug) -> (
+ match rg110_find_companion comms companion_slug with
+ | Some companion -> [ pair; companion ]
+ | None -> [ pair ])
+ | _ -> [ pair ])
+ normal
let admit ~(observed : Vocab_ef.rank Precedence.candidate)
~(temporal : Vocab_ef.rank Precedence.candidate)
@@ -1227,7 +1271,7 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
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 ->
+ | 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
shipped rank-floor fix below, and the register's own ยง4 "RG 111"
@@ -1268,7 +1312,7 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
match List.filter (fun (c, _, _) -> c.Precedence.cel.Celebration.rank = Class2) sorted with
| [] -> []
| best :: _ -> [ drop_band best ]))
- | Class2, false ->
+ | 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 RG 113's own