diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-17 17:22:43 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-17 17:22:43 +0200 |
| commit | 2b9f8133a22ce6dc309644a98bbe61507f574b38 (patch) | |
| tree | edb24a59d092f56f00dcbca654224a0622725d5e /test | |
| parent | f2a0660a9b60b70af32f1157dc78cdfcb298c9f7 (diff) | |
| download | colitur-2b9f8133a22ce6dc309644a98bbe61507f574b38.tar.gz colitur-2b9f8133a22ce6dc309644a98bbe61507f574b38.zip | |
feat(kernel): movable Date_spec variants, and Rogation Wednesday
Date_spec carried one variant, Fixed(month, day), and its own header said
Sunday- and Easter-relative forms would arrive with the OF sanctoral. They
arrive early because two things needed them at once: a user-supplied
overlay carrying a local movable feast had no way to express "the first
Sunday of October", and Rogation Wednesday's commemoration has been
recorded as architecturally blocked since 2026-08-13.
That blockage turns out to have been a premise, not a fact. The register
concluded the Rogation "is not Commemoration_only sanctoral data by
nature... it needs a genuinely THIRD kind of thing this architecture has
no name for" -- but the reasoning rested on there being "no civil (month,
day) pair to anchor a Fixed entry to". That is a statement about
Date_spec's expressiveness. With Easter_offset the entity is ordinary
sanctoral data, the identical shape Add major-litanies already ships,
differing only in how the date is written. No third channel was needed.
The register is corrected rather than left contradicting the code.
Two variants: Easter_offset of int, and Nth_weekday of {month; nth;
weekday} with negative nth counting from the end. Fixed's sexp
representation is untouched, so all 327 sanctoral entries and the whole
overlay parse unchanged. Both are re-validated in t_of_sexp through their
smart constructors, extending the idiom Fixed already used -- the failure
that guards against is invisible, a spec deserialising into something
that silently never resolves and a celebration vanishing with no
diagnostic.
Easter is supplied by the rite, not computed in the kernel. Computus
ships Gregorian and Julian both, and picking one here would hard-code a
Roman assumption into rite-agnostic code and be silently wrong for a
Julian-reckoning rite. Rite.t gains an easter field; Rite_ef supplies the
Gregorian one.
Layer keeps a split index. Fixed entries stay in the year-independent
(month, day) table, which preserves the fast path and the "30 November
counted twice in a 371-day span" behaviour validate.mli documents for St
Andrew. Movable entries have no year-independent key by construction --
the same spec lands on a different (month, day) each year -- so they
resolve per civil year into a rata-die table. index_by_date is removed
rather than kept as an alias: two ways to index a layer, one of which
silently ignores movable entries, is the trap this change exists to
avoid.
Two domain-edge defects, both found by the suite rather than reasoning. A
liturgical year is Advent-anchored, so resolving civil year y names y +/-
1, and at the edges those are 1582 and 10000, which Computus correctly
refuses by raising. The ceiling surfaced through the domain-ceiling test,
the floor through `colitur day 1583` failing outright. Fixed once, in
Layer.index, the single point that calls the rite's easter -- a clamp
repeated per caller would have been two places to get wrong a third time.
Rogation Wednesday: RG 87 assigns the Litaniae minores to the Monday,
Tuesday AND Wednesday before Ascension (scan1:691); colitur built the
first two from the start and never the third. RG 88 keeps it out of the
Office, so the Ascension Vigil remains observed. RG 89 routes it through
the Major Litanies' rules, but RG 109's closed list names only maiores,
so by RG 107 it is an ORDINARY commemoration -- confirmed in the output,
not assumed. Name from the Missal's own "feria IV" (scan1:20495-20497),
violet to match the Monday and Tuesday offices.
Blast radius, full 1583-9999 sweep against the pre-change binary: 2257
days, every one the same shape, zero unclassified, line counts equal both
sides. The complement is the informative half -- in the other 6160 years
the Rogation does not appear at all, because RG 111 admits one
commemoration on a II-class day and an impeded feast takes it under RG
113, a Commemoration_only candidate having no row in RG 91's table. Both
shapes are pinned.
Allow-lists unmoved: layer 3 compares no commemorations and is blind by
construction, and layer 4's 2026-2027 window has a competing feast on
both Wednesdays, so the Rogation is capped out in exactly the years the
oracle can see. C8 is unchanged, asserted by its own count pin.
Not built: Sunday-relative specs, which nothing needs yet, and the
--overlay plumbing that would let a user actually supply a local movable
feast. Nth_weekday therefore ships with synthetic and property coverage
only; inventing a shipped entry to exercise it would be fabricating
calendar data.
Register: section 6.10.
Diffstat (limited to 'test')
| -rw-r--r-- | test/test_calendar.ml | 5 | ||||
| -rw-r--r-- | test/test_golden.ml | 40 | ||||
| -rw-r--r-- | test/test_names.ml | 137 | ||||
| -rw-r--r-- | test/test_overlay.ml | 13 | ||||
| -rw-r--r-- | test/test_rite_ef.ml | 2 | ||||
| -rw-r--r-- | test/test_sanctoral_ef.ml | 2 | ||||
| -rw-r--r-- | test/test_validate.ml | 5 |
7 files changed, 192 insertions, 12 deletions
diff --git a/test/test_calendar.ml b/test/test_calendar.ml index a533ab2..59624aa 100644 --- a/test/test_calendar.ml +++ b/test/test_calendar.ml @@ -105,6 +105,11 @@ module Fixture = struct let rite : (season, rank) Rite.t = { Rite.id = "synthetic-calendar"; vocab; year_start; temporal; anchors = (fun _ -> []); + (* Not a Roman rite, but a Rite.t must supply SOME Easter now that + movable Date_spec variants exist. The Gregorian one is as good as + any for a fixture; nothing here is Easter-relative, so the value + is never actually read. *) + easter = Colitur_kernel.Computus.gregorian_easter; rules; season_runs = [ A; B ]; transfer_target; readings } let entry ~month ~day ~slug ~rank = diff --git a/test/test_golden.ml b/test/test_golden.ml index e9b3ec5..a073c52 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -1281,6 +1281,40 @@ let test_readings_displaced_sunday_feria_known_divergent () = 2026 10 26 "2026-10-26 slug=ef-time-after-pentecost-22-monday first=Col 1:12-20. gospel=John 18:33-37" + +(* ---- Rogation Wednesday (RG 87/88/89), movable-date-specs task ---- + + RG 87, verbatim (scan1.txt:691): "Litaniae minores seu Rogationes, per se, + assignantur feriis II, III et IV ante festum Ascensionis Domini" -- Monday, + Tuesday AND Wednesday. colitur built the first two as temporal offices from + the start; the Wednesday had no channel at all until Date_spec grew + [Easter_offset], because Easter+38 IS the Ascension Vigil by construction + and there is no (month, day) a Fixed spec could anchor to. + + RG 88 keeps it out of the Office entirely, so the Vigil remains the + observed day and this is only ever a commemoration. RG 89 routes it through + the Major Litanies' rules -- but RG 109's closed list of privileged + commemorations names only "de Litaniis MAIORIBUS", never minoribus, so by + RG 107's closing rule it is ORDINARY. That asymmetry is the whole point of + pinning two years rather than one: + + 2024 -- nothing else competes, so the single commemoration RG 111 allows on + a II-class day is the Rogation itself. + 2026 -- St Robert Bellarmine (III class) is impeded onto the same day and + takes that one slot instead: RG 113 orders admissions by RG 91's table, and + a Commemoration_only candidate has no row in it at all (band returns + [unclassified]), so a genuine feast outranks it. + + Both weekdays independently checked with `date -d`. *) +let test_rogation_wednesday_admitted_2024 () = + check ~msg:"2024: Rogation Wednesday is the one admitted commemoration" 2024 5 8 + "2024-05-08 wednesday season=paschaltide week=6 slug=ef-ascension-vigil rank=class-2 colour=white subject=temporal name_la=- comms=[rogation-wednesday:ordinary] in=- out=[]" + +let test_rogation_wednesday_yields_to_a_feast_2026 () = + check ~msg:"2026: an impeded III-class feast takes the single slot instead" 2026 5 13 + "2026-05-13 wednesday season=paschaltide week=6 slug=ef-ascension-vigil rank=class-2 colour=white \ + subject=temporal name_la=- comms=[robert-bellarmine:ordinary] in=- out=[]" + let suite = ( "golden pins (known-tricky years)", [ Alcotest.test_case "Easter extreme: 1598 earliest (22 Mar, Gauss-verified)" `Quick @@ -1371,5 +1405,9 @@ let suite = test_readings_step4_common_frances; Alcotest.test_case "readings: displaced-Sunday feria pins M26 shape 2(b)'s KNOWN-WRONG walkback (2026-10-26)" `Quick - test_readings_displaced_sunday_feria_known_divergent + test_readings_displaced_sunday_feria_known_divergent; + Alcotest.test_case "RG87: Rogation Wednesday admitted (2024)" `Quick + test_rogation_wednesday_admitted_2024; + Alcotest.test_case "RG87/113: Rogation Wednesday yields to an impeded feast (2026)" `Quick + test_rogation_wednesday_yields_to_a_feast_2026 ] ) diff --git a/test/test_names.ml b/test/test_names.ml index cef7357..f3cdc54 100644 --- a/test/test_names.ml +++ b/test/test_names.ml @@ -2,6 +2,11 @@ module N = Colitur_kernel.Names module L = Colitur_kernel.Lang module C = Colitur_kernel.Citation module DS = Colitur_kernel.Date_spec + +(* Task: Date_spec.resolve now takes the rite's own Easter (movable specs). + These fixtures are all [Fixed], so the value is irrelevant to them -- but it + must be supplied, and the Gregorian one is the honest choice here. *) +let easter_of y = Colitur_kernel.Computus.gregorian_easter y module D = Colitur_kernel.Date let lang s = L.of_string_exn s @@ -52,15 +57,15 @@ let test_citation () = let test_date_spec () = (match DS.fixed ~month:3 ~day:25 with | Ok ds -> ( - match DS.resolve ds ~year:2026 with + match DS.resolve ds ~year:2026 ~easter:(easter_of 2026) with | Some d -> Alcotest.(check string) "resolve" "2026-03-25" (D.to_iso8601 d) | None -> Alcotest.fail "resolve returned None") | Error e -> Alcotest.failf "fixed: %s" e); (* Feb 29 is a legitimate fixed date that simply does not occur every year. *) (match DS.fixed ~month:2 ~day:29 with | Ok ds -> - Alcotest.(check bool) "Feb 29 resolves in 2024" true (DS.resolve ds ~year:2024 <> None); - Alcotest.(check bool) "Feb 29 absent in 2026" true (DS.resolve ds ~year:2026 = None) + Alcotest.(check bool) "Feb 29 resolves in 2024" true (DS.resolve ds ~year:2024 ~easter:(easter_of 2024) <> None); + Alcotest.(check bool) "Feb 29 absent in 2026" true (DS.resolve ds ~year:2026 ~easter:(easter_of 2026) = None) | Error e -> Alcotest.failf "Feb 29 must be constructible: %s" e); Alcotest.(check bool) "reject Feb 30" true (Result.is_error (DS.fixed ~month:2 ~day:30)); Alcotest.(check bool) "reject Apr 31" true (Result.is_error (DS.fixed ~month:4 ~day:31)); @@ -176,6 +181,118 @@ let test_record () = [ "2026-04-05"; "ef"; "ordinary"; "1"; "sunday"; "ef-easter-sunday"; "high"; "white"; "lord" ] (Rec.to_row r) + +(* ---- movable Date_spec variants (2026-08-17) ---- + Every date literal below was checked against `date -d` before being typed, + the same rule test_golden.ml states for itself. Easter 2026 is 5 April. *) + +let easter_2026 = easter_of 2026 + +let test_easter_offset_resolves () = + let ds = match DS.easter_offset 38 with Ok d -> d | Error e -> failwith e in + (* Easter 2026 = 5 April; +38 = 13 May, the Wednesday before Ascension -- + the offset Rogation Wednesday needs (RG 87). *) + Alcotest.(check string) "Easter+38 in 2026" "2026-05-13" + (match DS.resolve ds ~year:2026 ~easter:easter_2026 with Some d -> D.to_iso8601 d | None -> "none") + +let test_easter_offset_zero_is_easter () = + let ds = match DS.easter_offset 0 with Ok d -> d | Error e -> failwith e in + Alcotest.(check string) "Easter+0 is Easter itself" "2026-04-05" + (match DS.resolve ds ~year:2026 ~easter:easter_2026 with Some d -> D.to_iso8601 d | None -> "none") + +let test_easter_offset_negative () = + let ds = match DS.easter_offset (-46) with Ok d -> d | Error e -> failwith e in + (* Ash Wednesday 2026 is 18 February, Easter-46. *) + Alcotest.(check string) "Easter-46 is Ash Wednesday" "2026-02-18" + (match DS.resolve ds ~year:2026 ~easter:easter_2026 with Some d -> D.to_iso8601 d | None -> "none") + +let test_nth_weekday_first_sunday_october () = + let ds = match DS.nth_weekday ~month:10 ~nth:1 ~weekday:D.Sun with Ok d -> d | Error e -> failwith e in + (* 1 October 2026 is a Thursday, so the first Sunday is the 4th. *) + Alcotest.(check string) "first Sunday of October 2026" "2026-10-04" + (match DS.resolve ds ~year:2026 ~easter:easter_2026 with Some d -> D.to_iso8601 d | None -> "none") + +let test_nth_weekday_last_sunday_october () = + let ds = match DS.nth_weekday ~month:10 ~nth:(-1) ~weekday:D.Sun with Ok d -> d | Error e -> failwith e in + Alcotest.(check string) "last Sunday of October 2026" "2026-10-25" + (match DS.resolve ds ~year:2026 ~easter:easter_2026 with Some d -> D.to_iso8601 d | None -> "none") + +let test_nth_weekday_absent_fifth () = + let ds = match DS.nth_weekday ~month:10 ~nth:5 ~weekday:D.Sun with Ok d -> d | Error e -> failwith e in + (* October 2026 has four Sundays (4, 11, 18, 25). A fifth is not an error -- + it simply does not occur, the same contract 29 February has. *) + Alcotest.(check bool) "a fifth Sunday the month lacks resolves to None" true + (DS.resolve ds ~year:2026 ~easter:easter_2026 = None) + +let test_nth_weekday_february_leap_edge () = + let ds = match DS.nth_weekday ~month:2 ~nth:(-1) ~weekday:D.Sat with Ok d -> d | Error e -> failwith e in + (* 2024 is a leap year; 29 February is a Thursday, so the last Saturday is + the 24th. Exercises the month-length probe against Date.make rather than + a duplicated leap rule. *) + Alcotest.(check string) "last Saturday of February 2024" "2024-02-24" + (match DS.resolve ds ~year:2024 ~easter:(easter_of 2024) with Some d -> D.to_iso8601 d | None -> "none") + +let test_movable_constructors_reject_nonsense () = + Alcotest.(check bool) "nth = 0 rejected" true (Result.is_error (DS.nth_weekday ~month:10 ~nth:0 ~weekday:D.Sun)); + Alcotest.(check bool) "nth = 6 rejected" true (Result.is_error (DS.nth_weekday ~month:10 ~nth:6 ~weekday:D.Sun)); + Alcotest.(check bool) "nth = -6 rejected" true (Result.is_error (DS.nth_weekday ~month:10 ~nth:(-6) ~weekday:D.Sun)); + Alcotest.(check bool) "month 13 rejected" true (Result.is_error (DS.nth_weekday ~month:13 ~nth:1 ~weekday:D.Sun)); + Alcotest.(check bool) "offset 400 rejected" true (Result.is_error (DS.easter_offset 400)); + Alcotest.(check bool) "offset -400 rejected" true (Result.is_error (DS.easter_offset (-400))) + +(* The failure this guards is INVISIBLE: an unvalidated spec deserialises into + something that silently never resolves, and a celebration vanishes with no + diagnostic anywhere. Date_spec quarantines the derived parser in [Repr] and + re-validates in a hand-written [t_of_sexp] for exactly that reason; the two + new variants are held to it too, or the guarantee is only partial. *) +let test_movable_sexp_parser_rejects_invalid () = + List.iter + (fun s -> + Alcotest.(check bool) + (Printf.sprintf "rejects %s" s) + true + (try + ignore (DS.t_of_sexp (Sexplib.Sexp.of_string s)); + false + with _ -> true)) + [ "(Nth_weekday (month 10) (nth 0) (weekday Sun))"; + "(Nth_weekday (month 13) (nth 1) (weekday Sun))"; + "(Nth_weekday (month 10) (nth 9) (weekday Sun))"; + "(Easter_offset 100000)"; + "(Easter_offset -100000)" ] + +let test_movable_sexp_roundtrip () = + List.iter + (fun ds -> Alcotest.(check bool) "sexp round-trips" true (DS.t_of_sexp (DS.sexp_of_t ds) = ds)) + [ (match DS.fixed ~month:6 ~day:30 with Ok d -> d | Error e -> failwith e); + (match DS.easter_offset 38 with Ok d -> d | Error e -> failwith e); + (match DS.easter_offset (-63) with Ok d -> d | Error e -> failwith e); + (match DS.nth_weekday ~month:10 ~nth:1 ~weekday:D.Sun with Ok d -> d | Error e -> failwith e); + (match DS.nth_weekday ~month:10 ~nth:(-1) ~weekday:D.Sat with Ok d -> d | Error e -> failwith e) ] + +(* Year-independent properties: the confidence-past-2050 mechanism. *) +let prop_nth_weekday_lands_correctly = + QCheck.Test.make ~count:500 ~name:"Nth_weekday resolves into its own month with its own weekday" + QCheck.(triple (int_range 1583 9998) (int_range 1 12) (int_range 1 5)) + (fun (year, month, nth) -> + match DS.nth_weekday ~month ~nth ~weekday:D.Sun with + | Error _ -> false + | Ok ds -> ( + match DS.resolve ds ~year ~easter:(easter_of year) with + | None -> true (* a 5th Sunday the month lacks: legitimate *) + | Some d -> D.month d = month && D.weekday d = D.Sun)) + +let prop_easter_offset_is_exactly_that_offset = + QCheck.Test.make ~count:500 ~name:"Easter_offset n resolves exactly n days from Easter" + QCheck.(pair (int_range 1600 9900) (int_range (-60) 200)) + (fun (year, n) -> + match DS.easter_offset n with + | Error _ -> false + | Ok ds -> ( + match DS.resolve ds ~year ~easter:(easter_of year) with + | None -> false + | Some d -> D.to_rata d - D.to_rata (easter_of year) = n)) + let suite = ( "Names/Citation/DateSpec", [ Alcotest.test_case "names basics" `Quick test_names_basics; @@ -187,6 +304,18 @@ let suite = Alcotest.test_case "date_spec" `Quick test_date_spec; Alcotest.test_case "date_spec sexp roundtrip" `Quick test_date_spec_sexp_roundtrip; Alcotest.test_case "date_spec sexp validates" `Quick test_date_spec_sexp_validates; + Alcotest.test_case "Easter_offset resolves" `Quick test_easter_offset_resolves; + Alcotest.test_case "Easter_offset 0 is Easter" `Quick test_easter_offset_zero_is_easter; + Alcotest.test_case "Easter_offset negative" `Quick test_easter_offset_negative; + Alcotest.test_case "Nth_weekday first Sunday" `Quick test_nth_weekday_first_sunday_october; + Alcotest.test_case "Nth_weekday last Sunday" `Quick test_nth_weekday_last_sunday_october; + Alcotest.test_case "Nth_weekday absent fifth" `Quick test_nth_weekday_absent_fifth; + Alcotest.test_case "Nth_weekday February leap edge" `Quick test_nth_weekday_february_leap_edge; + Alcotest.test_case "movable constructors reject nonsense" `Quick test_movable_constructors_reject_nonsense; + Alcotest.test_case "movable sexp parser rejects invalid" `Quick test_movable_sexp_parser_rejects_invalid; + Alcotest.test_case "movable sexp roundtrip" `Quick test_movable_sexp_roundtrip; Alcotest.test_case "celebration" `Quick test_celebration; Alcotest.test_case "celebration status" `Quick test_celebration_status; - Alcotest.test_case "record" `Quick test_record ] ) + Alcotest.test_case "record" `Quick test_record ] + @ List.map QCheck_alcotest.to_alcotest + [ prop_nth_weekday_lands_correctly; prop_easter_offset_is_exactly_that_offset ] ) diff --git a/test/test_overlay.ml b/test/test_overlay.ml index b5edd5a..f3d1aee 100644 --- a/test/test_overlay.ml +++ b/test/test_overlay.ml @@ -1,4 +1,7 @@ module L = Colitur_kernel.Layer + +let mkdate y m d = + match Colitur_kernel.Date.make ~year:y ~month:m ~day:d with Ok x -> x | Error e -> failwith e module Cel = Colitur_kernel.Celebration module S = Colitur_kernel.Slug module DS = Colitur_kernel.Date_spec @@ -47,9 +50,9 @@ let test_layer_basics () = (List.map (fun e -> S.to_string e.L.cel.Cel.slug) l.L.entries) let test_layer_index () = - let idx = L.index_by_date (base ()) in - Alcotest.(check int) "Jan 14 has one" 1 (List.length (L.on_date idx ~month:1 ~day:14)); - Alcotest.(check int) "Jan 20 has none" 0 (List.length (L.on_date idx ~month:1 ~day:20)) + let idx = L.index (base ()) ~easter:Colitur_kernel.Computus.gregorian_easter ~years:[ 2026 ] in + Alcotest.(check int) "Jan 14 has one" 1 (List.length (L.on_date idx (mkdate 2026 1 14))); + Alcotest.(check int) "Jan 20 has none" 0 (List.length (L.on_date idx (mkdate 2026 1 20))) (* Three entries sharing a date, inserted deliberately out of canonical (by-slug) order: this is what actually pins accumulation (the bucket must @@ -61,8 +64,8 @@ let test_layer_index_same_date () = L.of_entries ~id:"base" ~name:"Test same-date" [ entry ~month:3 ~day:17 "zeta"; entry ~month:3 ~day:17 "alpha"; entry ~month:3 ~day:17 "mu" ] in - let idx = L.index_by_date l in - let bucket = L.on_date idx ~month:3 ~day:17 in + let idx = L.index l ~easter:Colitur_kernel.Computus.gregorian_easter ~years:[ 2026 ] in + let bucket = L.on_date idx (mkdate 2026 3 17) in Alcotest.(check int) "three entries share Mar 17" 3 (List.length bucket); Alcotest.(check (list string)) "bucket sorted by slug" [ "alpha"; "mu"; "zeta" ] (List.map (fun e -> S.to_string e.L.cel.Cel.slug) bucket) diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml index a014977..4da6d73 100644 --- a/test/test_rite_ef.ml +++ b/test/test_rite_ef.ml @@ -261,7 +261,7 @@ let test_barbara_added () = | None -> Alcotest.fail "slug barbara not found in the overlaid layer (ef-sanctoral-audit Add missing?)" | Some e -> Alcotest.(check bool) "barbara: 4 December" true - (match e.Layer.date with Date_spec.Fixed { month = 12; day = 4 } -> true | Date_spec.Fixed _ -> false); + (match e.Layer.date with Date_spec.Fixed { month = 12; day = 4 } -> true | _ -> false); Alcotest.(check bool) "barbara: Commemoration_only (no separate feast row in the calendarium, a bare \ \"Commemoratio\" nested under Peter Chrysologus)" true (e.Layer.cel.Cel.status = Cel.Commemoration_only); diff --git a/test/test_sanctoral_ef.ml b/test/test_sanctoral_ef.ml index 2024dc2..ebf96dc 100644 --- a/test/test_sanctoral_ef.ml +++ b/test/test_sanctoral_ef.ml @@ -91,7 +91,7 @@ let test_load_and_counts () = leap year (2028: divisible by 4, not by 100). *) List.iter (fun e -> - match DS.resolve e.L.date ~year:2028 with + match DS.resolve e.L.date ~year:2028 ~easter:(Colitur_kernel.Computus.gregorian_easter 2028) with | Some _ -> () | None -> Alcotest.failf "slug %s: date does not resolve in leap year 2028" (S.to_string e.L.cel.Cel.slug)) l.L.entries diff --git a/test/test_validate.ml b/test/test_validate.ml index 1a960df..4d5458f 100644 --- a/test/test_validate.ml +++ b/test/test_validate.ml @@ -321,6 +321,11 @@ module Synthetic = struct ?(transfer_target = fun _ origin _ -> origin) ?(readings = readings) temporal : (season, rank) Rite.t = { Rite.id = "synthetic"; vocab; year_start; temporal; anchors; rules; season_runs; + (* Not a Roman rite, but a Rite.t must supply SOME Easter now that + movable Date_spec variants exist. The Gregorian one is as good as + any for a fixture; nothing here is Easter-relative, so the value + is never actually read. *) + easter = Colitur_kernel.Computus.gregorian_easter; transfer_target; readings } (* Empty by default: every check built before Task 12 exercises the |
