From a4ffb0234d198ef295554e3202db15403346fbed Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 12 Aug 2026 22:24:30 +0200 Subject: test(differential): refresh the lectio fixture, close 9 entries, add C14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lectio moved 2386a45 -> 3b32c00 (seven EF calendar defects fixed, plus a sanctoral-generator root-cause fix); the committed fixture predates all of them. Regenerated with the same command the provenance note already named (`go run ./cmd/lectio-ef-dump 2005 2050`), read-only against lectio -- nothing there was touched. New SHA-256 asserted in both the provenance note and test_differential.ml's fixture_sha256. The dumper's two new name_en/name_pl columns are kept verbatim (the fixture is meant to be the exact command's raw output; the comparator already discards everything past field 7). Every C-entry's expected_rows was re-derived independently against the refreshed fixture (a Python re-implementation of diff_fields/ layer_c_reason, cross-checked against the real OCaml comparator's own failure output), not transcribed from a green run. Nine entries -- C2, C3, C4, C5, C7, C10, C11, C12, C13 -- now match zero rows and are closed: sexp row removed, layer_c_reason predicate and its now-dead helper bindings removed, citation preserved in docs/research/rules-register.md's new §3d with the specific lectio commit that fixed each one. C1 (361 -> 368) absorbs C13's former 7 rows now that lectio's own slug matches there too. C6 (138, unchanged count) narrows to a slug-only shape now that lectio's rank agrees. C8 (31, unchanged) survives untouched -- lectio still computes no Rogation days. C9's old shape ("Joseph observed ON a Lent Sunday") is also closed, but a narrower, different divergence survived under the same saint's slug: 3 years (2008, 2035, 2046) where Joseph's own congested RG96 walk collides with the Annunciation's separately-transferred "sedes propria". The old predicate's broad "any diff involving this slug" match was silently absorbing both under one citation -- split into a new id, C14, rather than folded back into C9, since the two are unrelated rules. lectio's own commit history (09a1444) documents this residual as a gap it deliberately left unresolved; colitur gives Joseph a real transferred day either way, which is why C14's verdict stands regardless of a genuinely open sub-question (recorded in the register, not resolved here) about which of the two feasts should claim the contested day first under RG98's "prius impeditum" wording. Re-triaged the full remaining 4975 raw differences: only 10 distinct field-diff signatures now, all resolving to Layer A/B noise or the four surviving citations, 0 unexplained. The crude "Sunday-rank" and "Holy Thursday" residues the task flagged both fully resolve once the real Layer A/B machinery runs (season-spelling and week-numbering artifacts, not substantive divergences) -- see the task report for the derivation. Teeth proof: perturbing ferial_rank to reproduce C4's old defect made 140 rows reappear as unexplained (the exact prior C4 count), reverted after capturing the failure message. 277 tests green (dune test) and 278 with COLITUR_EXHAUSTIVE_SWEEP=1 (the extra case is the 1583-9999 sweep itself). --- test/test_differential.ml | 196 +++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 107 deletions(-) (limited to 'test/test_differential.ml') diff --git a/test/test_differential.ml b/test/test_differential.ml index 4d6aab0..62c7a48 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -51,9 +51,20 @@ *** THE THREE-LAYER DESIGN (controller ruling, Task 15 dispatch) *** - Of 16801 day-pairs (2005-2050), 11206 already match on the seven columns. - Of the 5595 that don't, exactly 25 distinct (field-diff) signatures cover - all of them (task-15-class-summary.txt). They resolve into three strictly + REFRESHED (2026-08-12, task 2026-08-12-colitur-rg16a, branch ef-rebootstrap): + the fixture was regenerated from lectio's CURRENT HEAD (3b32c00), which has + fixed seven EF calendar defects since the fixture was first pinned (commit + 2386a45) -- see the fixture's own provenance note for the exact commits and + defect list. Of 16801 day-pairs (2005-2050), 11826 now already match on the + raw seven columns (up from roughly 11201 against the stale fixture); of the + 4975 that don't, only 10 distinct RAW (field-diff, including the + never-compared week column) signatures cover all of them, independently + recomputed against this fixture and colitur's current output, not copied + from the original task-15-class-summary.txt (which described the OLD + fixture and is stale). After Layers A and B below strip the week column and + the vocabulary/numbering artifacts, only 540 of those 4975 remain genuine + differences -- all 540 explained by Layer C (368 + 138 + 31 + 3, see each + entry's own citation), 0 unexplained. They resolve into three strictly separate layers: - Layer A (this file's [norm_season]/[norm_slug]): vocabulary. A @@ -67,7 +78,7 @@ done even where it would shorten the table. - Layer B ([strip_epiphany_index]): numbering. The ONE case in the whole - 5595 where a slug's embedded index genuinely cannot be reconciled by a + 4975 where a slug's embedded index genuinely cannot be reconciled by a literal table (Time-after-Epiphany's non-constant offset, limit 3 above) -- both sides' embedded week digit is stripped to a common family+weekday form before comparing, while rank and colour (which @@ -76,37 +87,48 @@ family would still be caught by everything except the digit itself. - Layer C (data/ef/expected-divergences.sexp, matched by - [layer_c_reason] below): the CITED allow-list. Eleven genuine liturgical - disagreements (C11 added by Task 16's oracle work, below), each citing - its RG paragraph and stating which engine is right (always colitur, - verified against the Missal/register, never against lectio's own - behaviour -- "lectio does it differently" is not itself a justification - anywhere in this file). This is the ONLY layer that may cover a - difference in rank, colour, or which celebration is observed; A and B - never do (enforced structurally below: A/B only ever - touch the season/slug fields, and Layer C's predicates each require an - exact, narrow field-diff SET, not "anything goes"). + [layer_c_reason] below): the CITED allow-list. Each entry cites its RG + paragraph and states which engine is right (always colitur, verified + against the Missal/register, never against lectio's own behaviour -- + "lectio does it differently" is not itself a justification anywhere in + this file). This is the ONLY layer that may cover a difference in rank, + colour, or which celebration is observed; A and B never do (enforced + structurally below: A/B only ever touch the season/slug fields, and + Layer C's predicates each require an exact, narrow field-diff SET, not + "anything goes"). + + REFRESHED (2026-08-12, ef-rebootstrap fixture regeneration): lectio's own + fix wave (2386a45 -> 3b32c00, provenance note has the full commit list) + independently fixed the exact defects nine of the thirteen entries this + file used to carry were about -- C2, C3, C4, C5, C7, C10, C11, C12 and C13 + all now match ZERO rows against the refreshed fixture and were CLOSED + (removed from data/ef/expected-divergences.sexp and from + [layer_c_reason] below), each with its citation preserved and the closure + recorded in docs/research/rules-register.md, not silently deleted -- see + the report for the full before/after account. C9's old shape ("St Joseph + observed ON a Lent Sunday", lectio's own defect 4) also closed the same + way, but a NARROWER, different divergence involving the same saint + survived the refresh and was re-cited as a new entry, C14 (below), not + folded back into the old C9 id (the two are different rules; conflating + them would be exactly the "count proving cardinality where identity was + required" vacuity flavour this project's harnesses are specifically + checked against). Three entries survive unchanged in kind: C1 (368 rows, + up from 361 -- the 7 rows C13 used to carve out fold back into C1's own + shape now that lectio's slug matches there too, see C1's own note), C6 + (138 rows, unchanged count but a narrower shape -- lectio fixed the RANK + this entry used to also cover, leaving only the SLUG naming difference, + see C6's own note) and C8 (31 rows, wholly unchanged -- lectio still + computes no Rogation days at all). 13 January (register §6's long-open "Baptism of the Lord" item) is - EMPIRICALLY CONFIRMED FIXED, not separately allow-listed, ONLY in the 39 - of 46 years it is NOT a Sunday: colitur's slug/rank/colour equal - lectio's fixture exactly there (the sanctoral wiring landed in Task 11's - "13 Jan now resolves to the Baptism of the Lord" review note), and the - only residual difference on those 39 rows is season (covered by Layer - C's C1, the Jan 6-13 boundary). - - CORRECTED (ef-rebootstrap fix round 1, F2): this note previously said - "in all 46 years" and "the only residual difference is season" without - qualification -- WRONG once `commemoration-of-the-baptism-of-the-lord` - became `subject = Lord` (ef-rebootstrap): on the remaining 7 years (13 - January itself falls on a Sunday: 2008, 2013, 2019, 2030, 2036, 2041, - 2047), RG16(a) now makes colitur observe the Baptism itself where it - used to observe the Sunday -- the OBSERVED SLUG differs from lectio's - fixture too, not merely colour. Allow-listed separately as C13 - (`data/ef/expected-divergences.sexp`), whose own note has the full - account, including a live cross-check against lectio's CURRENT engine - (not the committed fixture, which predates lectio's own equivalent - fix) -- see the report. *) + EMPIRICALLY CONFIRMED FIXED, not separately allow-listed, in ALL 46 years + again as of this refresh: lectio's own fix wave independently fixed its + equivalent of the same RG16(a) defect the now-closed C13 used to allow- + list (its slug now matches colitur's `commemoration-of-the-baptism-of- + the-lord` on all 7 of the Sunday years too, not only the 39 non-Sunday + ones), so the residual difference on every one of the 46 rows is season + alone -- covered by C1, the Jan 6-13 boundary, with no separate entry + needed any more. *) module Cal = Colitur_kernel.Calendar module Layer = Colitur_kernel.Layer @@ -136,7 +158,7 @@ let allow_list_path = "../data/ef/expected-divergences.sexp" deliberately, after re-running the exact command the provenance note names -- never by copying the actual value back in to make a mismatch pass, which would defeat the point of pinning it at all. *) -let fixture_sha256 = "2ca3eeeda4e7a0406c4d004c1b2003fc0df671aca9af18a1b506543a721c8bac" +let fixture_sha256 = "fc4832f1bc1bdfd322e808ae4929487628981792c23f959c1764c8eaedbfaf7f" (* Same technique tools/bootstrap_sanctoral.ml already uses for this exact purpose (that file's own comment: shelling out to the system's @@ -396,15 +418,21 @@ let diff_fields (l : row) (c : row) = let subset xs ys = List.for_all (fun x -> List.mem x ys) xs let day_of_date date = int_of_string (String.sub date 8 2) -let advent_feria_slug slug = - List.exists - (fun wk -> List.exists (fun wd -> String.equal slug (Printf.sprintf "ef-advent-%d-%s" wk wd)) weekdays) - [ 3; 4 ] - -let sunday_iclass_slugs = - [ "ef-advent-sunday-2"; "ef-advent-sunday-4"; "ef-lent-sunday-1"; "ef-lent-sunday-2"; "ef-lent-sunday-3" ] - -let rose_sunday_slugs = [ "ef-advent-sunday-3"; "ef-lent-sunday-4" ] +(* ef-rebootstrap fixture refresh (2026-08-12): C2, C3, C4, C5, C7, C10, C11, + C12 and C13's own predicates/helper bindings (sunday_iclass_slugs, + rose_sunday_slugs, advent_feria_slug, fixed_iii_class_reclassified_slugs, + jan13_lord_sunday_dates_2005_2050, and the inline C7/C10/C11 date/slug + checks) are REMOVED here, not left dead -- each matched zero rows against + the refreshed fixture (lectio's own fix wave, 2386a45 -> 3b32c00, + independently fixed the exact defects these entries were about), so their + citations moved to docs/research/rules-register.md (closure record, with + the RG paragraph preserved) and their sexp rows to nothing -- + data/ef/expected-divergences.sexp no longer declares those ids at all. + Removing a predicate strengthens this harness the same way removing its + sexp row does: if any of these nine shapes ever reappears (a regression in + colitur, or lectio moving again), it now surfaces as UNEXPLAINED instead + of being silently re-absorbed by a citation whose own divergence no + longer exists. *) (* Fix round 1, finding 1: C1 and C6 (below) originally gated on calendar date alone, with no slug/slug-family check -- unlike every other entry @@ -427,40 +455,21 @@ let jan_6_13_slug slug = let nativity_octave_day_slugs = [ "ef-nativity-octave-day-5"; "ef-nativity-octave-day-6"; "ef-nativity-octave-day-7" ] -(* C12 (ef-rebootstrap, 2026-08-12): the exact 15 slugs data/ef/sanctoral.sexp - re-bootstrapped from `rank = commemoration` to a real class-3 feast (the - source's own SHA-256 moved 6a25e634... -> 1b303ef2...) -- a literal list, - not a date-range or slug-prefix pattern, per the SAME discipline the - comment above this one already states for C1/C6 (fix round 1, finding 1): - a slug outside this exact set must fail through to [None], not be - silently absorbed by a citation about a different set of saints. Only 7 - of the 15 are ever actually reachable here (the other 8 never collide - with a plain class-4 feria anywhere in 2005-2050), but the guard is - written against the full set colitur's own data now carries, not the - narrower subset this one 46-year window happens to exercise. *) -let fixed_iii_class_reclassified_slugs = - [ "benedict"; "cyril-of-jerusalem"; "forty-holy-martyrs-of-sebaste"; "frances-rome"; - "francis-of-paola"; "gabriel-the-archangel"; "gregory-the-great"; "isidore-of-seville"; - "john-damascene"; "john-of-capistrano"; "john-of-god"; "patrick"; "sts-felicitas-perpetua"; - "thomas-aquinas"; "vincent-ferrer" ] - -(* C13 (ef-rebootstrap fix round 1, F2): the 7 years 13 January falls on a - Sunday within 2005-2050 -- independently re-derived, not copied from a - prior figure: `date -d -01-13 +%A` = Sunday in exactly these 7 - (2005-2050 is 46 years; 13 January cycles through the 7 weekdays with - the usual leap-year irregularity, landing on Sunday this many times). - A LITERAL list, not a predicate on [jan_6_13_slug] (which already, and - correctly, admits this exact slug for the other 39 non-Sunday years in - the window) -- checked BEFORE C1 below in the if/elif chain specifically - so it intercepts these 7 dates first; leaving C1's own guard as it was - would have let [jan_6_13_slug]'s pre-existing admission of this slug - silently absorb them into C1's count instead, the exact gap fix round 1 - found: C1's own [expected_rows] is a bare row COUNT, blind to WHICH of - two different diff shapes produced it, so a real shape change here - (colitur's own OBSERVED CELEBRATION on these 7 dates, not merely its - colour) could move underneath the pin without the pin ever moving. *) -let jan13_lord_sunday_dates_2005_2050 = - [ "2008-01-13"; "2013-01-13"; "2019-01-13"; "2030-01-13"; "2036-01-13"; "2041-01-13"; "2047-01-13" ] +(* C14 (ef-rebootstrap fixture refresh, 2026-08-12; replaces the closed C9, + see data/ef/expected-divergences.sexp's own C14 note for the full RG + citation): the exact 3 civil days, across the whole 2005-2050 window, + where St Joseph's (19 March, I class) RG 96 transfer walk is congested + enough by Passiontide/Holy Week/the Easter octave to cross Easter AND + collide with the Annunciation's own transferred "sedes propria" (RG 96 + Attamen (a), Monday after Low Sunday) -- independently re-derived from + this fixture and colitur's own output (`grep joseph-spouse` both sides, + filtered to rows with a real post-Layer-A/B diff), not copied from the + register's prior "2008, 2035, 2046" prose. A LITERAL date list, not a + slug predicate, per the SAME discipline C12/C13 already used (a plain + "involves this slug" predicate is exactly what let the old C9 silently + cover two textually-unrelated divergences at once -- see C14's own sexp + note for the full account of why that was wrong). *) +let joseph_annunciation_collision_dates_2005_2050 = [ "2008-04-01"; "2035-04-03"; "2046-04-03" ] (* [layer_c_reason l c diffs] returns the [data/ef/expected-divergences.sexp] [id] this row-pair's remaining (post Layer A/B) diff set belongs to, or @@ -468,50 +477,23 @@ let jan13_lord_sunday_dates_2005_2050 = let layer_c_reason (l : row) (c : row) diffs = let m = month_of_date l.date and d = day_of_date l.date in if diffs = [] then None - else if List.mem l.date jan13_lord_sunday_dates_2005_2050 && diffs = [ Season; Slug_f; Colour_f ] then - Some "C13" else if m = 1 && d >= 6 && d <= 13 && subset diffs [ Season; Colour_f; Slug_f ] && (not (List.mem Slug_f diffs) || jan_6_13_slug c.slug) then Some "C1" - else if List.mem c.slug sunday_iclass_slugs && diffs = [ Rank ] then Some "C2" - else if List.mem c.slug rose_sunday_slugs && subset diffs [ Rank; Colour_f ] then Some "C3" - else if advent_feria_slug c.slug && diffs = [ Rank ] then Some "C4" - else if starts_with ~prefix:"ef-lent-ember-" c.slug && subset diffs [ Slug_f; Rank ] then Some "C5" else if m = 12 && (d = 29 || d = 30 || d = 31) && subset diffs [ Slug_f; Rank ] && (not (List.mem Slug_f diffs) || List.mem c.slug nativity_octave_day_slugs) then Some "C6" - else if (String.equal c.slug "matthew" || String.equal c.slug "thomas") && subset diffs [ Slug_f; Colour_f ] - then Some "C7" else if (String.equal c.slug "ef-rogation-monday" || String.equal c.slug "ef-rogation-tuesday") && subset diffs [ Season; Slug_f; Colour_f ] then Some "C8" - else if - (String.equal l.slug "joseph-spouse-of-the-bl-virgin-mary" - || String.equal c.slug "joseph-spouse-of-the-bl-virgin-mary") - && subset diffs [ Season; Slug_f; Rank; Colour_f ] - then Some "C9" - else if - (String.equal l.date "2011-07-02" || String.equal l.date "2011-07-04") - && subset diffs [ Slug_f; Rank; Colour_f ] - then Some "C10" - else if String.equal c.slug "ef-passiontide-2-thursday" && diffs = [ Colour_f ] then Some "C11" - (* RG16(a) task, fix round 1 (CRITICAL finding): a C12 entry briefly lived - here, allow-listing 2 February on a Sunday against lectio, when - `data/ef/adjustments.sexp` retagged the Purification `Bvm`. REVERTED -- - the user has ruled: follow the oracle (register §6.0 has the full - account). The Purification is `Lord` again, colitur observes it - outright on those six 2-February Sundays, matching lectio's own - `class = lord` exactly -- no divergence to allow-list. That C12 was - removed, not merely unused -- superseded by the DIFFERENT C12 below, - ef-rebootstrap (2026-08-12), reusing the freed id. *) - else if List.mem c.slug fixed_iii_class_reclassified_slugs && subset diffs [ Slug_f; Rank; Colour_f ] - then Some "C12" + else if List.mem l.date joseph_annunciation_collision_dates_2005_2050 && subset diffs [ Slug_f; Rank ] then + Some "C14" else None (* ---------------------------------------------------------------------- *) @@ -591,7 +573,7 @@ let test_dates_align () = in Alcotest.(check (list (pair string string))) "no misaligned dates" [] mismatched -(* The core assertion: every one of the 5595 raw differences is either +(* The core assertion: every one of the 4975 raw differences is either normalised away (Layers A/B) or named in the cited allow-list (Layer C). Nothing else is permitted to pass silently. *) let test_no_unexplained_differences () = -- cgit v1.3