diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 09:59:48 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 09:59:48 +0200 |
| commit | d8f4bece5480cfb94088fae62fa875a66b5b4e7e (patch) | |
| tree | a546fc4f2468a9e80da62e29c1524acc928e3d1d /test/test_oracle.ml | |
| parent | 051716cdee0197351fa966af1e13738c443045c4 (diff) | |
| download | colitur-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.
Diffstat (limited to 'test/test_oracle.ml')
| -rw-r--r-- | test/test_oracle.ml | 121 |
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 |
