aboutsummaryrefslogtreecommitdiff
path: root/test/test_differential.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 01:42:00 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 01:42:00 +0200
commit254cfd7d9111b0f23f9c44e82d3e31d5817dc5a4 (patch)
treea14a1e43e95d5bb3b03cd9832776461ea9440605 /test/test_differential.ml
parenta2654a1e4d8987a251bcefbad2b0f0b867a6c6e6 (diff)
parent0f6454c123f1514c1cf255faed8d9b8c752c6347 (diff)
downloadcolitur-254cfd7d9111b0f23f9c44e82d3e31d5817dc5a4.tar.gz
colitur-254cfd7d9111b0f23f9c44e82d3e31d5817dc5a4.zip
Merge branch 'ef-rebootstrap': re-bootstrap the sanctoral, refresh the differential
Re-bootstraps data/ef/sanctoral.sexp from lectio's corrected generator (322 -> 327 entries), propagating fixes colitur had inherited wholesale: 15 entries wrongly marked commemoration-only that are really III-class feasts, four entries missing entirely, six commemorations an electronic transcription had hidden, and two class corrections. 38639 days change across 1583-9999, in three shapes with no remainder: 2245 where a III-class feast now correctly beats a class-4 feria, 35174 where one of five restored commemorations appears, and 1220 on 13 January. All five restored entries were verified against a photographic scan -- real, correctly ranked, and confined to their own dates. 13 January is NOT fixed by this. It moves from one wrong answer to a different one, because RG 112 and a Holy Family office are both unbuilt. Recorded as its own allow-list entry with a citation and a count, rather than left inside a season-boundary entry's cardinality pin, and deliberately not pinned by a golden test. Then refreshes the lectio differential fixture, which predated the seven EF defects fixed in that project. Nine allow-list entries close -- each was a real divergence adjudicated in colitur's favour, and each closes because lectio was fixed, with its RG citation preserved. The list goes from twelve entries to four; the remaining 540 differences are fully attributed with none unexplained.
Diffstat (limited to 'test/test_differential.ml')
-rw-r--r--test/test_differential.ml170
1 files changed, 113 insertions, 57 deletions
diff --git a/test/test_differential.ml b/test/test_differential.ml
index e12cd9a..89e6873 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,23 +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 allow-listed: colitur's slug/rank/colour
- for 13 January already equal lectio's exactly, in all 46 years (the
- sanctoral wiring landed in Task 11's "13 Jan now resolves to the Baptism
- of the Lord" review note). The only residual difference there is season
- (covered by Layer C's C1, the Jan 6-13 boundary) -- 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
@@ -122,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
@@ -382,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
@@ -413,6 +455,22 @@ 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" ]
+(* 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
[None] if nothing here explains it (a genuine, uncovered failure). *)
@@ -424,42 +482,40 @@ let layer_c_reason (l : row) (c : row) diffs =
&& 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)
+ (* NARROWED, fixture-refresh review: this was [subset diffs [ Slug_f; Rank ]].
+ Since lectio's own Christmas-octave rank was corrected (3b32c00) all 138
+ rows diff on [Slug_f] ALONE, and the note below says so -- but leaving
+ [Rank] in the accepted set did not merely over-permit, it made the entry
+ BLIND: every row already carries a Slug_f diff, so an added Rank diff
+ changed neither the diff-set membership nor the count. Demonstrated by
+ the reviewer: dropping the RG 91 entry 17 elevation in temporal_ef
+ (Class2 -> Class4 on octave days 5-7) left BOTH differential tests green
+ with C6 still reporting 138. The slug guard below already pins identity;
+ [Rank] only removed the ability to notice a rank regression. *)
+ && diffs = [ Slug_f ]
+ && 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. Left
- unimplemented here deliberately, not merely absent by oversight: this
- comment is the record that C12 existed and was removed, should a
- future reader wonder why the numbering skips it. *)
+ List.mem l.date joseph_annunciation_collision_dates_2005_2050
+ && subset diffs [ Slug_f; Rank ]
+ (* Colitur-slug guard, added by the fixture-refresh review. Fix round 1's
+ own finding 1 established that a Layer C predicate must pin WHICH
+ celebration the citation is about, not merely the date -- C1, C6 and C8
+ all carry that guard; C14 was written after that ruling and omitted it.
+ Demonstrated live: with [Calendar.compare_deferred] switched to RG 98
+ origin order, the observed celebration on all three dates flips to the
+ Annunciation, and C14 still matched exactly 3 rows with its count pin
+ green. The suite only reddened because the SIBLING dates fell outside
+ every predicate -- geometry, not this pin. *)
+ && String.equal c.slug "joseph-spouse-of-the-bl-virgin-mary"
+ then Some "C14"
else None
(* ---------------------------------------------------------------------- *)
@@ -539,7 +595,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 () =