diff options
Diffstat (limited to 'test/test_oracle.ml')
| -rw-r--r-- | test/test_oracle.ml | 167 |
1 files changed, 154 insertions, 13 deletions
diff --git a/test/test_oracle.ml b/test/test_oracle.ml index a86fd76..35d4501 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 ] (* ---------------------------------------------------------------------- *) @@ -658,25 +726,98 @@ let m16_dates = [ "2026-03-27" ] entry needed at all -- removed, not re-adjudicated to a different verdict, since there is no longer a divergence to name. *) +(* M18 -- ef-rg112-rg110 task: the gap this file's own header now names under + "COMMEMORATION IDENTITY", restated for the OBSERVED axis. colitur's own + observed celebration carries an English name ({!Celebration.t}.names) + ONLY for a SANCTORAL-origin day (a saint's feast winning outright); a + TEMPORAL-origin one -- an ordinary Sunday, a feria, a movable named feast + including Holy Family itself -- never does ({!Rite_ef.Temporal_ef}'s own + [build] has no [en "..."] string anywhere in it, this file's own header + again). CORRECTED, fix round 1 (coordinator finding 8) -- the precise + breakdown: of this window's 730 days, 331 are RESOLVED (330 matching, 1 + mismatched, [M13]); 399 are UNRESOLVED, split between this entry's own + 373 (the axis's sole disagreement) and 26 absorbed inside [M1]/[M2]/[M3]/ + [M16]'s own widened subsets, where it fires ALONGSIDE their pre-existing + citation. The BLIND SPOT this axis exists to close, stated precisely: a + TEMPORAL-origin observed day silently replaced by a DIFFERENT + temporal-origin observed day of the SAME RANK AND COLOUR -- exactly the + shape that hid Holy Family from every layer before this task. This is + the OVERWHELMING majority of days in any calendar (saints' feasts are + common, but far from every day), so this is a single STRUCTURAL + predicate -- [Observed_identity_unresolved] alone, nothing else + disagreeing -- not a literal date list the way every other entry in this + file is: at this population size, a list would be exactly the + "range/pattern that could silently widen" this file's own header + explicitly avoids elsewhere, for the OPPOSITE reason a range is normally + risky here -- the predicate itself (colitur's own name is [None]) is the + precise, falsifiable evidence, the same shape [M2]'s own title-substring + predicate already uses instead of a date list, just keyed on + presence-of-a-name rather than a title string. NOT a rubric dispute and + NOT a data gap either engine is wrong about -- a LIMIT of this comparator + itself, honestly counted rather than silently passed, per the brief's own + explicit instruction ("a day whose observed identity cannot be resolved + must be a counted, allow-listed outcome, never a silent skip"), the SAME + discipline [M15] already established for an unresolvable COMMEMORATION, + applied here for the first time to the OBSERVED day. The count pin below + ([test_layer_m_counts_match_citations]) is what stands guard against this + population growing (or shrinking) silently -- it cannot say WHICH day + moved or why, only that the total did. Fixing this at the root (giving + every temporal-cycle candidate an English name) is a data/lectionary- + bootstrap task (Plan 4), not something this comparator can do for itself + -- register ยง6 tracks it, the same open item + [M15]'s own note already points to. *) let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = if diffs = [] then None - else if List.mem c.c_date m1_dates && subset diffs [ Rank; Colour_f; Comm_presence ] then Some "M1" - else if contains_substring o.o_title ~needle:bvm_saturday_title && diffs = [ Colour_f ] then Some "M2" - else if List.mem c.c_date m3_dates && diffs = [ Colour_f ] then Some "M3" + (* M1's own subset widened (this task): colitur's observed day on both + dates is TEMPORAL-origin (the Sunday itself, RG33's own point -- the + vigil should never have displaced it), so it now ALSO carries + [Observed_identity_unresolved] -- the same root cause the file's other + widened entries below share, restated for this one's own shape. *) + else if List.mem c.c_date m1_dates + && subset diffs [ Rank; Colour_f; Comm_presence; Observed_identity_unresolved ] + then Some "M1" + (* M2's own subset widened (this task): every BVM-Saturday date's observed + celebration is temporal-origin (the ordinary Saturday feria colitur + computes, not the distinct Office of the BVM missalemeum's title + names -- M2's own gap), so [Observed_identity_unresolved] now fires + alongside [Colour_f] where it used to fire alone, AND alone on its own + on every Paschaltide occurrence M2's own comment already says never + reached this predicate before (colour already agrees there; only the + new axis has anything left to say). *) + else if contains_substring o.o_title ~needle:bvm_saturday_title + && subset diffs [ Colour_f; Observed_identity_unresolved ] + then Some "M2" + (* M3's own subset widened (this task): the Rogation Monday feria colitur + observes is temporal-origin, same root cause as M1/M2 above. *) + else if List.mem c.c_date m3_dates && subset diffs [ Colour_f; Observed_identity_unresolved ] then + Some "M3" else if List.mem c.c_date m5_dates && diffs = [ Comm_presence ] then Some "M5" else if List.mem c.c_date m8_dates && diffs = [ Comm_presence ] then Some "M8" else if List.mem c.c_date m10_dates && diffs = [ Comm_presence ] then Some "M10" else if List.mem c.c_date m11_dates && diffs = [ Comm_presence ] then Some "M11" else if List.mem c.c_date m12_dates && diffs = [ Comm_count ] then Some "M12" - (* M13's own subset widened (Task B): the same underlying gap (the Seven - Sorrows commemoration is never constructed at all) now ALSO surfaces - as [Comm_identity_unresolved] on this date, since colitur's own - admitted commemoration there is temporal-origin (no English name) -- - one root cause, one id, both axes it touches. *) - else if List.mem c.c_date m13_dates && subset diffs [ Rank; Colour_f; Comm_identity_unresolved ] then - Some "M13" + (* M13's own subset widened AGAIN (this task, following the SAME pattern + Task B's own widening comment below records): colitur's observed + celebration here is Joseph, SANCTORAL-origin, so its name IS + resolvable -- and disagrees with missalemeum's own title outright + ([Observed_identity_mismatch], not [_unresolved]), the SAME underlying + gap (the Seven Sorrows commemoration is never constructed, so colitur + has no candidate to observe OR commemorate in its place) now visible + on a THIRD axis. *) + else if List.mem c.c_date m13_dates + && subset diffs [ Rank; Colour_f; Comm_identity_unresolved; Observed_identity_mismatch ] + then Some "M13" else if List.mem c.c_date m15_dates && diffs = [ Comm_identity_unresolved ] then Some "M15" - else if List.mem c.c_date m16_dates && diffs = [ Comm_identity_mismatch ] then Some "M16" + (* M16's own subset widened (this task): colitur's observed celebration + here is the temporal Passiontide feria (John Damascene is only a + COMMEMORATION on this Feast-status-losing day -- band picks the + temporal candidate outright at Class3 rank, register's own account of + this date), so [Observed_identity_unresolved] now fires alongside the + pre-existing [Comm_identity_mismatch]. *) + else if List.mem c.c_date m16_dates + && subset diffs [ Comm_identity_mismatch; Observed_identity_unresolved ] + then Some "M16" + else if diffs = [ Observed_identity_unresolved ] then Some "M18" else None (* ---------------------------------------------------------------------- *) |
