aboutsummaryrefslogtreecommitdiff
path: root/test/test_rite_ef.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 01:16:22 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 01:16:22 +0200
commit94fc488cc9c6b4a050d90c4250f6e166b40088e7 (patch)
tree18f68c393a704c225c0124f6c9a30bdd96e444ae /test/test_rite_ef.ml
parent9725195fc4a1050ded151854f6653459dacc35b0 (diff)
downloadcolitur-94fc488cc9c6b4a050d90c4250f6e166b40088e7.tar.gz
colitur-94fc488cc9c6b4a050d90c4250f6e166b40088e7.zip
rite(ef): clamp the RG96 search at the domain ceiling
search_from could walk up to 400 days past origin before Calendar's own ~start ~stop clamp is ever consulted, and nothing stopped it probing occupant on a date past 31 December 9999 -- occupant chains through the real EF rite's temporal, which calls Computus.gregorian_easter, not total outside 1583..9999 (it Date.makes and failwiths on Error). Not reachable with the shipped sanctoral data alone, but reachable through the project's own primary extension path: an overlay adding an I-class feast on 25 December leaves nothing but Class2 Nativity-octave days for the rest of civil year 9999, so the unguarded search reached 1 January of year 10000 and crashed there with 'computus: year 10000 out of range 1583..9999'. 9999 is an in-range year and the kernel's contract is 'never raises on in-range input'. search_from now also stops, without probing occupant again, once it passes Date's own domain ceiling -- the same 'return a finite date, let Calendar's own out-of-range handling record it, never pretend to have found something admissible' contract the existing step-count guard already follows. Two new tests, both mutation-verified to actually reproduce the crash when the guard is removed (see the task report): a precedence_ef.ml unit test using the real Temporal_ef.temporal as occupant (a synthetic occupant can never discriminate this, since it never calls Computus itself), and a Calendar-level integration test reproducing the exact overlay-based scenario the review found.
Diffstat (limited to 'test/test_rite_ef.ml')
-rw-r--r--test/test_rite_ef.ml81
1 files changed, 81 insertions, 0 deletions
diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml
new file mode 100644
index 0000000..7b60718
--- /dev/null
+++ b/test/test_rite_ef.ml
@@ -0,0 +1,81 @@
+(* Coordinator review (Task 11 fix round): integration tests wiring
+ [Rite_ef.context] together with the REAL data/ef/sanctoral.sexp +
+ data/ef/adjustments.sexp through [Colitur_kernel.Calendar] -- the same
+ pipeline `colitur day` uses, proven here at the OCaml level. *)
+
+module Cal = Colitur_kernel.Calendar
+module Layer = Colitur_kernel.Layer
+module Overlay = Colitur_kernel.Overlay
+module LD = Colitur_kernel.Liturgical_day
+module Slug = Colitur_kernel.Slug
+module Date = Colitur_kernel.Date
+module Date_spec = Colitur_kernel.Date_spec
+module Cel = Colitur_kernel.Celebration
+module Colour = Colitur_kernel.Colour
+module V = Rite_ef.Vocab_ef
+
+(* Relative to this test's own build directory (_build/default/test/), same
+ convention test_sanctoral_ef.ml uses -- test/dune declares both as deps
+ of the (test ...) stanza. *)
+let sanctoral_path = "../data/ef/sanctoral.sexp"
+let adjustments_path = "../data/ef/adjustments.sexp"
+
+let real_layer () =
+ let layer =
+ match Layer.load V.rank_of_sexp sanctoral_path with
+ | Ok l -> l
+ | Error e -> Alcotest.failf "%s: failed to load: %s" sanctoral_path e
+ in
+ let overlay =
+ match Overlay.load V.rank_of_sexp adjustments_path with
+ | Ok o -> o
+ | Error e -> Alcotest.failf "%s: failed to load: %s" adjustments_path e
+ in
+ let layer, diagnostics = Overlay.apply layer overlay in
+ Alcotest.(check (list string)) "the committed overlay applies cleanly, no diagnostics" []
+ (List.map Overlay.diagnostic_to_string diagnostics);
+ layer
+
+let slug_of (c : V.rank Cel.t) = Slug.to_string c.Cel.slug
+
+(* Coordinator review, finding 2, reproduced through the project's OWN
+ extension path (an overlay), the same way the reviewer found it: adding
+ an I-class feast on 25 December (competing against, and losing to, the
+ real Nativity) forces an RG 96 search starting 26 December -- which, with
+ the real sanctoral data (Stephen/John/the Innocents, all II class) plus
+ the temporal cycle's own Nativity-octave-day entries (29-31 Dec, also II
+ class), is blocking every single day through 31 December 9999. Before the
+ domain-ceiling fix this raised (Computus: year 10000 out of range);
+ confirmed by mutation-testing at the precedence_ef.ml unit level (see the
+ task report) -- this is the same defect reproduced end to end, through
+ Calendar, with real data, exactly as the review found it. *)
+let test_transfer_search_does_not_raise_at_domain_ceiling () =
+ let layer = real_layer () in
+ let impeding_entry : V.rank Layer.entry =
+ { Layer.date = (match Date_spec.fixed ~month:12 ~day:25 with Ok d -> d | Error e -> failwith e);
+ cel =
+ Cel.make ~slug:(Slug.of_string_exn "test-domain-ceiling-impeder") ~rank:V.Class1
+ ~colour:Colour.White ~layer:Rite_ef.Precedence_ef.universal_layer () }
+ in
+ let overlay : V.rank Overlay.t =
+ { Overlay.id = "test-domain-ceiling"; directives = [ Overlay.Add impeding_entry ] }
+ in
+ let layer, _diagnostics = Overlay.apply layer overlay in
+ (* Must not raise -- the whole point of the fix. *)
+ let days = Cal.year Rite_ef.context layer 9999 in
+ Alcotest.(check bool) "year 9999 resolves without raising, even with an impeded Christmas Day"
+ true (Array.length days > 0);
+ let impeder_placed_or_recorded =
+ Array.to_list days
+ |> List.exists (fun d ->
+ slug_of d.LD.observed = "test-domain-ceiling-impeder"
+ || List.exists (fun (c, _) -> slug_of c = "test-domain-ceiling-impeder") d.LD.omitted)
+ in
+ Alcotest.(check bool) "the impeding candidate is accounted for (observed somewhere, or omitted \
+ with a recorded reason) -- never silently dropped"
+ true impeder_placed_or_recorded
+
+let suite =
+ ( "Rite_ef (real data: overlay-in-effect, domain-ceiling)",
+ [ Alcotest.test_case "RG96 search does not raise at the domain ceiling (real data)" `Quick
+ test_transfer_search_does_not_raise_at_domain_ceiling ] )