From bd7a41b5b83a5fc18e6169327c9176eee2d54a2b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 13 Aug 2026 08:07:17 +0200 Subject: test(oracle): compare the observed day's identity, not only rank/colour Layer 4 (test_oracle.ml, vs missalemeum) compared the observed celebration's rank and colour and stopped there -- it never checked whether the observed day is actually the RIGHT one. Holy Family (11 January 2026: oracle title "The Holy Family: Jesus, Mary & Joseph") is rank 2/white on both sides purely by coincidence -- an ordinary, unnamed Sunday and Holy Family share both -- so this layer has been silently blind to the missing feast since it was built. Adds Observed_identity_mismatch/Observed_identity_unresolved, following the shape and honesty of yesterday's commemoration-identity comparison (Comm_identity_mismatch/Comm_identity_unresolved): colitur identifies its own observed day by slug, missalemeum by an English title, and the bridge is Celebration.t.names, read directly off LD.observed. That name is only ever set for a SANCTORAL-origin celebration -- a TEMPORAL-origin one (an ordinary Sunday, a feria, a movable named feast) carries none, so most of the 730 days in the fixture window land in Observed_identity_unresolved, not a clean match. That is counted and reported, never silently skipped -- the same discipline this file already applies to an unresolvable commemoration. WIP, expected red: 399 days now show observed-identity-unresolved with nothing in the allow-list naming it, and five of the six existing M-series entries whose own diff shape is checked by exact list equality (M1, M2, M3, M13, M16) lose their match now that this axis widens their diff set. Landed on its own, ahead of any production fix, so it is the regression net the following commits are checked against rather than a change bundled in alongside the fix they enable. The next commits widen those five entries' own diff shapes and add the new bucket's own cited allow-list entry, then build Holy Family itself. --- test/test_oracle.ml | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) (limited to 'test/test_oracle.ml') diff --git a/test/test_oracle.ml b/test/test_oracle.ml index a86fd76..ae5c396 100644 --- a/test/test_oracle.ml +++ b/test/test_oracle.ml @@ -3,6 +3,20 @@ layer 4 of the design spec's five (colitur CLAUDE.md "Validation" section; layer 3, the lectio differential, is test_differential.ml, already green). + *** WIP, 2026-08-13, branch ef-rg112-rg110: this file is EXPECTED RED + right now. [Observed_identity_mismatch]/[Observed_identity_unresolved] + (below) are new -- this layer used to compare the observed day's rank and + colour and stop there, never whether it is actually the RIGHT day (Holy + Family, 11 January 2026, is rank 2/white on both sides purely by + coincidence -- an ordinary unnamed Sunday and Holy Family share both). + Landing the strengthened comparator FIRST, red, before touching any + production code, is the point: it is the regression net the rest of this + task's fix commits are checked against, not a change bundled in alongside + the fix. The very next commits make it green again (data/ef/expected- + divergences-missalemeum.sexp's own M-series entries below, and this + file's own [layer_m_reason] widening) -- see that file and + docs/research/rules-register.md for the running account. + *** WHY THIS LAYER EXISTS, AND WHAT IT CAN DO THAT THE LECTIO DIFFERENTIAL CANNOT (test_differential.ml's own header comment, limit 1) *** @@ -280,6 +294,15 @@ type colitur_row = { c_date : string; c_rank : int; c_colour : char; + c_observed_slug : string; + c_observed_name : string option; + (** colitur's own resolved English name for the OBSERVED celebration -- + [None] for a TEMPORAL-origin day (an ordinary Sunday, a feria, a + movable named feast: {!Rite_ef.Temporal_ef}'s own [build] never + sets [names], the SAME gap this file's header already documents + for commemorations, see [Comm_identity_unresolved]), the same + [Names.find ... en] read [c_commemorations] below already uses, + applied to [LD.observed] instead of a commemoration candidate. *) c_commemorations : (string * int * Cel.status * string option) list; (** slug, rank, status, English name *) } @@ -321,6 +344,8 @@ let colitur_rows_2026_2027 () = { c_date = Date.to_iso8601 day.LD.date; c_rank = rank_to_int cel.Cel.rank; c_colour = colour_to_char cel.Cel.colour; + c_observed_slug = Slug.to_string cel.Cel.slug; + c_observed_name = Names.find cel.Cel.names en; c_commemorations = commemorations } :: !rows @@ -335,7 +360,15 @@ let colitur_rows_2026_2027 () = (* header for the mapping and its exact limits. *) (* ---------------------------------------------------------------------- *) -type field = Rank | Colour_f | Comm_presence | Comm_count | Comm_identity_mismatch | Comm_identity_unresolved +type field = + | Rank + | Colour_f + | Comm_presence + | Comm_count + | Comm_identity_mismatch + | Comm_identity_unresolved + | Observed_identity_mismatch + | Observed_identity_unresolved let field_name = function | Rank -> "rank" @@ -344,6 +377,40 @@ let field_name = function | Comm_count -> "commemoration-count" | Comm_identity_mismatch -> "commemoration-identity-mismatch" | Comm_identity_unresolved -> "commemoration-identity-unresolved" + | Observed_identity_mismatch -> "observed-identity-mismatch" + | Observed_identity_unresolved -> "observed-identity-unresolved" + +(* CORRECTING the gap this file's own header names (added under this task, + branch ef-rg112-rg110): the axes above all compare the day's OBSERVED + celebration's rank and colour ([Rank]/[Colour_f]) and the admitted + COMMEMORATIONS' identity ([Comm_identity_*]) -- nothing until now compared + the OBSERVED celebration's own identity, WHICH day is actually being kept, + only what class/colour it happens to carry. Holy Family (11 January 2026: + oracle title "The Holy Family: Jesus, Mary & Joseph", colitur's own + observed slug the plain "ef-time-after-epiphany-sunday-1", BOTH rank 2 and + colour white on both sides) is exactly the shape this axis exists to + catch -- rank and colour already agreed by coincidence (an ordinary, + unnamed II-class Sunday and Holy Family share both), so nothing above + ever saw a difference. Same mapping and the same honesty about its limit + as [identity_diff] just above: colitur identifies a day by SLUG, + missalemeum by an English TITLE string, and the bridge is + {!Celebration.t}.names, read directly off [LD.observed] by + [colitur_rows_2026_2027] into [c_observed_name] -- resolvable for a + SANCTORAL-origin observed day (a saint's feast winning the day outright), + NEVER for a TEMPORAL-origin one (an ordinary Sunday, a feria, a movable + named feast -- {!Rite_ef.Temporal_ef}'s own [build] never sets [names]). + [Observed_identity_unresolved] is that TEMPORAL-origin case: counted and + reported, never silently skipped, the same discipline + [Comm_identity_unresolved] already established and the brief demands + again here ("a day whose observed identity cannot be resolved must be a + counted, allow-listed outcome, never a silent skip"). Unlike commemoration + identity, this axis needs no presence/count gate first -- there is always + exactly one observed day on each side, no list-length ambiguity to + resolve before a title comparison is even meaningful. *) +let observed_identity_diff (c : colitur_row) (o : oracle_row) = + match c.c_observed_name with + | None -> Some Observed_identity_unresolved + | Some name -> if String.equal name o.o_title then None else Some Observed_identity_mismatch (* Identity (Task B): only even attempted once presence AND count already agree (both non-empty, same length) -- see this file's own header, @@ -378,7 +445,8 @@ let diff_fields (c : colitur_row) (o : oracle_row) = (if c_has && o_has && List.length c.c_commemorations <> List.length o.o_commemorations then Some Comm_count else None); - identity_diff c o + identity_diff c o; + observed_identity_diff c o ] (* ---------------------------------------------------------------------- *) -- cgit v1.3