summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-18 09:59:48 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-18 09:59:48 +0200
commitd8f4bece5480cfb94088fae62fa875a66b5b4e7e (patch)
treea546fc4f2468a9e80da62e29c1524acc928e3d1d
parent051716cdee0197351fa966af1e13738c443045c4 (diff)
downloadcolitur-d8f4bece5480cfb94088fae62fa875a66b5b4e7e.tar.gz
colitur-d8f4bece5480cfb94088fae62fa875a66b5b4e7e.zip
test(oracle): compare commemorations in the live windows
Commemorations were checked against an oracle in the 2026-2027 fixture alone -- 227 days out of roughly 968 000 commemoration-bearing days in the domain, about 0.02%, and by some distance the thinnest axis in the project. Layer 3's 16 801 days compare none of them and never can: lectio's trailing "+slug" tokens are its losing candidates, not an RG 111 admitted set. About a third of all days carry a commemoration and admit is the most intricate code in the engine, so the gap was worth closing. 2035 and 2038 now compare presence, count and identity, reusing the 2026-2027 comparator's own identity_diff with the same limits: a sanctoral-origin commemoration resolves by colitur's English name, a temporal-origin one cannot and is reported as a counted, classified outcome rather than a silent pass. Observed-identity stays out deliberately -- that is M18's separate limit and folding it in would flood these windows with a class that says nothing about commemorations. 36 differing rows across 730 days, every one classified. The great majority of commemoration days agree, which is the headline. Four rows are not calendar differences at all but name forms -- colitur's "The Seven Sorrows of the Blessed Virgin Mary" against the oracle's "For Our Lady of the Seven Sorrows", "The Major Litanies" against "Pro rogationibus" -- which is this month's new entities appearing correctly in windows that had never seen them. One finding is genuinely new. On 2038-06-02 the Ascension Vigil admits one commemoration under RG 111(c) and two candidates compete: rogation-wednesday and sts-marcellinus-peter-erasmus. Both are Commemoration_only, so neither has a row in RG 91's table, band returns unclassified for both, RG 113 has nothing to order by, and admit falls through to its alphabetical slug tie-break -- which decides the day. missalemeum keeps the saints. That tie-break was already documented as existing only between two Commemoration_only candidates; this is its first live, externally-witnessed instance, and the decision is made by slug alphabet, which is arbitrary and not rubrical. There may be a real rule going unused: RG 113's first sentence is "Commemoratio de Tempore fit primo loco", and a Rogation is arguably de Tempore -- which turns on whether the entry should carry subject Temporal rather than the Saint it inherited from the Major Litanies precedent, a question the movable-date-specs spec deferred as behaviourally inert and which demonstrably is not. Not decided here. Adopting whichever answer matches the oracle is exactly the reasoning this project refuses, so it is recorded and cited for a task that can weigh it on its own terms. Register: section 6.16.
-rw-r--r--test/test_oracle.ml121
1 files changed, 114 insertions, 7 deletions
diff --git a/test/test_oracle.ml b/test/test_oracle.ml
index ae82b31..df56bcc 100644
--- a/test/test_oracle.ml
+++ b/test/test_oracle.ml
@@ -1805,6 +1805,12 @@ let norm_citation s =
let m21_2038_slugs = m21_colour_slugs
let classify_2038 (c : colitur_row) (o : oracle_row) diffs =
+ let has_comm slug = List.exists (fun (s, _, _, _) -> s = slug) c.c_commemorations in
+ let has_comm_prefix p =
+ List.exists
+ (fun (s, _, _, _) -> String.length s >= String.length p && String.sub s 0 (String.length p) = p)
+ c.c_commemorations
+ in
let title = o.o_title in
let contains hay needle =
let nh = String.length needle and lh = String.length hay in
@@ -1839,12 +1845,106 @@ let classify_2038 (c : colitur_row) (o : oracle_row) diffs =
String.length c.c_observed_slug > 19
&& String.sub c.c_observed_slug 0 19 = "ef-nativity-octave-"
then Some "NATIVITY-OCTAVE-SUNDAY (C6's family, RG 69)"
+ (* ---- commemoration classes (2026-08-18) ------------------------------
+ Every one below is an already-cited family from the 2026-2027
+ allow-list, re-appearing in a live window now that these compare
+ commemorations. None is a new adjudication; each points at the entry
+ that decided it. *)
+ else if has_comm "commemoration-of-the-seven-sorrows" || has_comm "major-litanies" then
+ (* Same entity, different English name form: colitur's own resolved name
+ ("The Seven Sorrows of the Blessed Virgin Mary", "The Major Litanies")
+ against missalemeum's ("For Our Lady of the Seven Sorrows", "Pro
+ rogationibus"). Both streams commemorate the SAME thing. Counted as a
+ divergence because the comparator matches on name text, which is the
+ only identity signal available -- not because the calendars differ. *)
+ Some "COMM-NAME-FORM (same entity, different English name)"
+ else if has_comm "commemoration-of-st-peter" then
+ Some "PETER-COMPANION-ABSENT-UPSTREAM (M19)"
+ else if has_comm "barbara" then Some "BARBARA-ABSENT-UPSTREAM (M22)"
+ else if has_comm_prefix "ef-advent-" then Some "ADVENT-FERIA-COMM (M10)"
+ else if
+ List.mem Comm_presence diffs && c.c_commemorations = []
+ && String.length c.c_observed_slug > 3
+ then Some "NATIVITY-OCTAVE-COMM-MISSING (M11, colitur's own gap, verdict open)"
+ (* THE ONE GENUINELY NEW FINDING of this extension, and it is not a naming
+ artefact. 2038-06-02: the Ascension Vigil (II class) admits ONE
+ commemoration under RG 111(c), and TWO candidates compete --
+ `rogation-wednesday` and `sts-marcellinus-peter-erasmus`. BOTH are
+ Commemoration_only, so neither has a row in RG 91's table and [band]
+ returns [unclassified] for both; RG 113's "servetur ordo tabellae
+ praecedentiae" has nothing to order by, and [admit] falls through to its
+ alphabetical slug tie-break, which picks the Rogation. missalemeum picks
+ the saints.
+
+ This is the tie-break CLAUDE.md records as "only ever exercised between
+ two Commemoration_only candidates, neither of which has any RG 91 table
+ position" -- and this is its FIRST live, externally-witnessed instance.
+ The decision is currently made by slug alphabet, which is arbitrary and
+ not rubrical.
+
+ There may be a real rule: RG 113's own FIRST sentence is "Commemoratio de
+ Tempore fit primo loco", and a Rogation is arguably de Tempore, which
+ would make colitur right for a reason it is not currently using. That
+ turns on whether the entry should carry subject Temporal rather than the
+ Saint it inherited from the Major Litanies precedent -- a question the
+ movable-date-specs spec explicitly deferred as "behaviourally inert",
+ which it no longer is. NOT decided here: recorded, cited, and left for a
+ dedicated task rather than settled by whichever answer happens to match
+ the oracle. *)
+ else if has_comm "rogation-wednesday" then
+ Some "COMM-ONLY-TIE-BREAK (alphabetical, arbitrary -- see register 6.16)"
+ (* The documented comparator LIMIT, not a calendar disagreement: colitur
+ resolves a commemoration's identity through its own English name, and a
+ TEMPORAL-origin commemoration (an impeded feria, an Ember or Rogation
+ day) has none -- Temporal_ef never sets one. M15 carries the same shape
+ in the 2026-2027 window. Counted and named rather than skipped, so the
+ population is visible instead of silently passing. *)
+ (* M8's own shape: a fixed I-class feast landing on an ordinary Sunday. RG
+ 109(a) makes "of a Sunday" always privileged and RG 111(a) admits exactly
+ one privileged commemoration on a I-class day, so the impeded Sunday IS
+ that one. missalemeum shows no commemoration at all. M8 names All Saints
+ and the Assumption in the 2026-2027 window; this is the same rule
+ reaching a different feast. *)
+ else if has_comm_prefix "ef-" && List.mem Comm_presence diffs && c.c_rank = 1 then
+ Some "IMPEDED-SUNDAY-COMM (M8's rule, RG 109(a)/111(a))"
+ else if List.mem Comm_identity_unresolved diffs then
+ Some "COMM-IDENTITY-UNRESOLVED (temporal-origin, no English name -- M15's limit)"
else None
+(* COMMEMORATIONS join the live-window comparison (2026-08-18).
+
+ Until now the live windows compared rank, colour and the two citations
+ only, and commemorations were checked against an oracle in the 2026-2027
+ fixture alone -- 227 days out of the ~968 000 commemoration-bearing days in
+ the domain, about 0.02%. That was by some distance the thinnest axis in the
+ project, and the reason is worth restating: layer 3 (16 801 days) compares
+ NO commemorations at all and never can, because lectio's own trailing
+ "+slug" tokens are its LOSING candidates rather than an RG 111 admitted
+ set, so the largest evidence source is structurally silent here. Roughly a
+ third of all days carry at least one commemoration, and `admit` is the most
+ intricate code in the engine, so the gap mattered.
+
+ Presence, count and IDENTITY are compared, reusing [identity_diff] --
+ the same function the 2026-2027 comparator uses, with the same limits: it
+ resolves a SANCTORAL-origin commemoration by colitur's own English name and
+ returns [Comm_identity_unresolved] for a temporal-origin one, which carries
+ no name to match. Unresolved is a counted, classified outcome, never a
+ silent pass.
+
+ OBSERVED-identity is deliberately still NOT compared in the live windows.
+ That axis is a separate, already-understood limit (M18's own shape: a
+ temporal-origin observed day has no English name either), and folding it in
+ here would flood these windows with a divergence class that says nothing
+ about commemorations. *)
let diffs_2038 (c : colitur_row) (o : oracle_row) =
let d = ref [] in
if c.c_rank <> o.o_rank then d := Rank :: !d;
if not (List.mem c.c_colour o.o_colours) then d := Colour_f :: !d;
+ let c_has = c.c_commemorations <> [] and o_has = o.o_commemorations <> [] in
+ if c_has <> o_has then d := Comm_presence :: !d;
+ if c_has && o_has && List.length c.c_commemorations <> List.length o.o_commemorations then
+ d := Comm_count :: !d;
+ (match identity_diff c o with Some f -> d := f :: !d | None -> ());
(match (c.c_first, o.o_first) with
| Some a, Some b when norm_citation a <> norm_citation b -> d := First_mismatch :: !d
| _ -> ());
@@ -1923,13 +2023,20 @@ let test_2038_class_counts () =
those 13 days no longer differ at all. An id with no rows must not be
declared -- the count assertion rejects an unused one, which is what
keeps this list honest. *)
- [ ("CHRIST-THE-KING-WEEK (M26 shape 2b)", 2);
- ("CHRISTMAS-MULTI-MASS (M27)", 1);
- ("COLOUR (M21)", 7);
- ("JUDITH (M28, register 6.6)", 2);
- ("MISSAL-TWO-PART-CITATION (register 6.9)", 1);
- ("PERPETUA-COMMON (register 6.8)", 1)
- ]
+ [ ("ADVENT-FERIA-COMM (M10)", 10);
+ ("BARBARA-ABSENT-UPSTREAM (M22)", 1);
+ ("CHRIST-THE-KING-WEEK (M26 shape 2b)", 2);
+ ("CHRISTMAS-MULTI-MASS (M27)", 1);
+ ("COLOUR (M21)", 7);
+ ("COMM-NAME-FORM (same entity, different English name)", 2);
+ ("COMM-ONLY-TIE-BREAK (alphabetical, arbitrary -- see register 6.16)", 1);
+ ("JOSEPH-ANNUNCIATION-TRANSFER-ORDER (C14's own collision)", 2);
+ ("JUDITH (M28, register 6.6)", 2);
+ ("MISSAL-TWO-PART-CITATION (register 6.9)", 1);
+ ("NATIVITY-OCTAVE-COMM-MISSING (M11, colitur's own gap, verdict open)", 2);
+ ("PERPETUA-COMMON (register 6.8)", 1);
+ ("PETER-COMPANION-ABSENT-UPSTREAM (M19)", 1)
+ ]
|> List.sort compare
in
Alcotest.(check (list (pair string int))) "2038 divergence classes and their counts" expected actual