summaryrefslogtreecommitdiff
path: root/test/test_lms_ordo.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 16:40:07 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 16:40:24 +0200
commit60e87914718dd2c8e69bb89d8cc48d6bef9bfc74 (patch)
tree814f451ac97e61d8ab83a2e24877b1b5223734db /test/test_lms_ordo.ml
parent219c0ca6884a634e83293c62b27cca990fa25854 (diff)
downloadcolitur-60e87914718dd2c8e69bb89d8cc48d6bef9bfc74.tar.gz
colitur-60e87914718dd2c8e69bb89d8cc48d6bef9bfc74.zip
feat(ef): implement the calendarium's bissextile February shift
The Missale Romanum's calendarium footnote for February (LT.txt:5011-5014, scan-corroborated scan2.txt:3050-3058): in a leap year the sixth kalends of March (24 February) is doubled rather than a 29th day appended, so St Matthias moves 24->25 February and St Gabriel of Our Lady of Sorrows 27->28, with 24 February itself carrying no fixed office that year. data/ef/expected-divergences-lms.sexp entry L3 tracked this as an open gap. Implemented as the rubric's own general mechanism (every fixed entry from 24 through 28 February shifts one civil day later in a leap year), not as "move these two named saints": the two readings are indistinguishable on shipped data (nothing else is fixed in that window) and diverge only for a future --overlay entry in the same range, which the mechanism reading covers correctly and a two-saints special case would not. This project has already been bitten once (RG 16(a)) by a rule implemented against shipped data's coincidental shape rather than the rubric itself. Kernel stays rite-agnostic: Rite.t gains a fixed_key field (Date.t -> (int * int) option), the same seam easter already establishes, read only by Layer.on_date's FIXED half -- the movable half (Easter_offset/Nth_weekday) is untouched. Default is the identity mapping, an optional argument on on_date, so every existing caller and every rite that supplies nothing is byte-identical to before this field existed. Date.is_leap is exposed in date.mli (already existed in date.ml) so the rite reads the kernel's own single-sourced leap-year definition. The EF implementation lives in Rite_ef.Temporal_ef.bissextile_fixed_key, fully cited, wired into Rite_ef.context. Blast radius, measured over the full 1583-9999 domain (Calendar.year run twice per civil year, real fixed_key vs identity, every day diffed): all 2,041 leap years in the domain show a visible change for each saint; 6,983 individual liturgical days change total, zero unclassified, across four shapes (Matthias vacates 24 Feb in 1,803 years / occupies 25 Feb in 1,811; Gabriel vacates 27 Feb in 1,699 / occupies 28 Feb in 1,670). L3's own prior estimate (1,650 years, observed-outright only) is confirmed close on the same definition (1,677 measured); the broader observed-or- commemorated count is higher, not lower, showing the narrow estimate undercounted visible impact rather than overcounting it. The lectio differential (2005-2050 fixture) gains a new cited entry, C40 in data/ef/expected-divergences.sexp: lectio implements no such shift, so every leap year in its window now diverges on the two shifted days (21 rows, not the naive 44 -- the comparator never compares commemorations, so a side where the shifted saint has zero comparator-visible footprint on both engines produces no row). The LMS Ordo's 2023-2024 fixture -- the window L3 was originally found in -- now matches on the Creed comparison; its formulary-override bucket count is corrected 182->181 (24 February leaves the counted Proper population, becoming Own_slug-sourced). The missalemeum oracle fixtures (2026-2027, 2038, 2035) cover no leap year and are unaffected. L3 is closed: converted from an active allow-list record to a prose closure paragraph (L1's own precedent), citation preserved verbatim, fix and measured blast radius recorded. The id-list assertion narrows to [L4] alone, and the now-dead "2024-02-24" -> "L3" date mapping is removed. Six golden values pinned across four new test cases: Matthias in a leap year (both the vacated and occupied sides) and a common-year control; Gabriel likewise, deliberately choosing a leap year where he is admitted only as an ordinary commemoration rather than winning outright, a different shape from Matthias's. Mutation-tested: forcing fixed_key back to identity reddens exactly five test cases -- the differential's Layer C count-pin (C40 drops to 0 actual vs 21 expected), the LMS Ordo's Creed and formulary-bucket checks, and both new golden pins -- confirming the tests actually exercise the fix. Reverted; dune test, the exhaustive sweep, and make check are all green.
Diffstat (limited to 'test/test_lms_ordo.ml')
-rw-r--r--test/test_lms_ordo.ml36
1 files changed, 25 insertions, 11 deletions
diff --git a/test/test_lms_ordo.ml b/test/test_lms_ordo.ml
index 84c4776..219e51f 100644
--- a/test/test_lms_ordo.ml
+++ b/test/test_lms_ordo.ml
@@ -502,13 +502,15 @@ let make_suite ~label ~fixture_path ~fixture_sha256 ~window_first ~window_last ~
this function declared [by_id]/[explained_counts] but never actually
populated the latter (the mechanism this comment sits in) -- L1 was
already CLOSED by the time that task shipped, so it had nothing real
- to explain and the gap went unexercised. L3/L4 (Witnesses task,
- 2026-08-22, both 2023-2024-only) are the first live tests of it. *)
- let allow_list_id_for_date d =
- if String.equal d "2024-02-24" then Some "L3"
- else if String.equal d "2023-12-24" then Some "L4"
- else None
- in
+ to explain and the gap went unexercised. L4 (Witnesses task,
+ 2026-08-22, 2023-2024-only) is the first live test of it. L3's own
+ "2024-02-24" branch (bissextile-shift task, 2026-08-22) is REMOVED,
+ not left dead: the fix makes that date's Creed match outright, so the
+ branch would never fire again, and a stale date->id mapping for a
+ now-closed entry is exactly the kind of thing this file's own
+ "must be visible as a real change" discipline (this comment's own
+ opening sentence) argues against leaving in place. *)
+ let allow_list_id_for_date d = if String.equal d "2023-12-24" then Some "L4" else None in
let test_creed_matches_or_is_explained () =
let ordo = ordo_rows fixture_path in
let colitur = colitur_rows ~year_lo ~year_hi ~window_first ~window_last in
@@ -651,17 +653,29 @@ let allow_list_path = "../data/ef/expected-divergences-lms.sexp"
ever looks at its own explained_counts. Fixed to the two ids this task
added (L3, L4) -- L1 is CLOSED/removed and L2 is deliberately prose-only
(see the sexp file's own header on why), so neither is expected here. *)
-let test_allow_list_ids_are_exactly_l3_l4 () =
+(* L3 CLOSED (bissextile-shift task, 2026-08-22): removed from the active
+ sexp record set -- see its own now-prose closure note in
+ data/ef/expected-divergences-lms.sexp for the fix and the measured
+ blast radius. Only L4 remains active. *)
+let test_allow_list_ids_are_exactly_l4 () =
let ids = List.sort String.compare (List.map (fun e -> e.id) (load_allow_list allow_list_path)) in
- Alcotest.(check (list string)) "the LMS allow-list declares exactly L3 and L4" [ "L3"; "L4" ] ids
+ Alcotest.(check (list string)) "the LMS allow-list declares exactly L4" [ "L4" ] ids
-let suite_allow_list = ("lms-ordo-allow-list", [ Alcotest.test_case "declares exactly L3 and L4" `Quick test_allow_list_ids_are_exactly_l3_l4 ])
+let suite_allow_list = ("lms-ordo-allow-list", [ Alcotest.test_case "declares exactly L4" `Quick test_allow_list_ids_are_exactly_l4 ])
let suite_2023_2024 =
make_suite ~label:"lms-ordo-2023-2024" ~fixture_path:"fixtures/lms-ordo-2023-2024.sexp"
~fixture_sha256:"c8d9d4b790f6b4438e932f70dad8ebe9a0316662f1eb02c26248e04c7be1a62b" ~window_first:"2023-12-01"
~window_last:"2024-12-31" ~year_lo:2022 ~year_hi:2025 ~allow_list_path ~expected_rows:397
- ~expected_bvm_votive:12 ~expected_proper:182 ~expected_common:2 ~expected_preceding_sunday:61
+ (* expected_proper 182 -> 181 (bissextile-shift task, 2026-08-22): 24
+ February 2024, previously Matthias's own Proper Mass, is now the
+ Ember Saturday of Lent's own temporal proper -- Own_slug-sourced, not
+ Proper, so it leaves this bucket's population rather than merely
+ changing which slug's proper it is (see this file's own header on why
+ Own_slug is excluded). Matthias himself moves to 25 February, an
+ I-class Sunday that admits him not even as a commemoration (RG16(a)),
+ so his own Mass is not said anywhere in this window any more. *)
+ ~expected_bvm_votive:12 ~expected_proper:181 ~expected_common:2 ~expected_preceding_sunday:61
~expected_ascension_week:1
let suite_2024_2025 =