(* 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 so these properties are pinned by the test suite, not merely observable in CLI output (which is exactly what finding 3 flagged: with the overlay's one directive replaced by [()], `colitur day 2026`'s stdout is byte-identical for all 365 days, since [ef-nativity-vigil] always outranks [vigil-of-christmas] regardless -- the suppression's only observable effect is on [Liturgical_day.omitted], which no cram assertion reads). *) 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 Subject = Colitur_kernel.Subject module P = Colitur_kernel.Precedence module Comp = Colitur_kernel.Computus module V = Rite_ef.Vocab_ef module T = Rite_ef.Temporal_ef module PE = Rite_ef.Precedence_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 mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e 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 (* Finding 3: the suppression's ONLY observable effect is on 24 December's [omitted] (and, belt-and-braces, on every OTHER field of every OTHER day too -- vigil-of-christmas must not surface anywhere at all, since the overlay removes it from the layer before resolution ever runs, unlike an ordinary occurrence loss). Mutation-verified (see the task report): substituting `(directives ())` for the real overlay makes this test's first assertion fail (24 December's [omitted] gains "vigil-of-christmas"), and confirmed the whole cram suite (test/cli.t) still passes under that same mutation -- so this closes the "removing the deliverable breaks no test" gap the coordinator flagged, which cram alone structurally cannot. *) let test_vigil_of_christmas_suppressed () = let layer = real_layer () in let days = Cal.year Rite_ef.context layer 2026 in let christmas_eve = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (mk 2026 12 24) = 0) in Alcotest.(check string) "ef-nativity-vigil is still the observed day" "ef-nativity-vigil" (slug_of christmas_eve.LD.observed); Alcotest.(check (list string)) "24 December has nothing omitted -- vigil-of-christmas never enters the RG91 contest at all" [] (List.map (fun (c, _) -> slug_of c) christmas_eve.LD.omitted); let appears_anywhere = Array.to_list days |> List.exists (fun d -> let is_it c = slug_of c = "vigil-of-christmas" in is_it d.LD.observed || List.exists (fun (c, _) -> is_it c) d.LD.commemorations || List.exists (fun (c, _) -> is_it c) d.LD.omitted || (match d.LD.transferred_in with Some c -> is_it c | None -> false) || List.exists (fun (c, _) -> is_it c) d.LD.transferred_out) in Alcotest.(check bool) "vigil-of-christmas appears NOWHERE in the resolved year" false appears_anywhere (* RG16(a) task (register §6.0 + the sanctoral subject audit, this task's own report). data/ef/sanctoral.sexp is left untouched (still a faithful mirror of lectio, subject `Lord` for every entry below except `most-holy-name-of-mary` -- see test_sanctoral_ef.ml's own "explicit class = lord" spot-check, which loads that file WITHOUT applying this overlay and still passes); the one real correction lives entirely in data/ef/adjustments.sexp's single remaining [Edit] directive, the same mechanism [test_vigil_of_christmas_suppressed] above already proves for a sibling bootstrap defect. CORRECTED, fix round 1 (CRITICAL finding, item 1): this test used to also assert `purification-of-the-blessed-virgin-mary` was retagged `Bvm`. REVERTED -- the user has ruled: follow the oracle (register §6.0 has the full account, both the calendarium argument for `Bvm` and the oracle evidence against it). The Purification is `Lord` again, one of FIVE untouched entries this test now re-verifies, not four. This is the ONE test in the suite that observes the one live retag actually takes effect through the real pipeline -- without it, a typo in the directive's slug (silently a no-op diagnostic, not a hard failure -- Overlay.apply's own contract) or a reverted adjustments.sexp would leave every other test green ([test_sanctoral_ef.ml] deliberately never applies the overlay). Also re-verifies the five entries the audit confirmed correct and left untouched, so a future accidental retag of one of THEM would be caught here too, not just the one that changed. *) let test_rg16a_subject_retag_in_effect () = let layer = real_layer () in let subject_of slug = match Layer.find layer (Slug.of_string_exn slug) with | Some e -> e.Layer.cel.Cel.subject | None -> Alcotest.failf "slug %s not found in the overlaid layer" slug in Alcotest.(check bool) "Most Holy Name of Mary retagged Bvm (calendarium: \"Nominis Mariae\")" true (subject_of "most-holy-name-of-mary" = Subject.Bvm); Alcotest.(check bool) "Purification stays Lord (reverted, fix round 1 -- the user's ruling: follow the oracle)" true (subject_of "purification-of-the-blessed-virgin-mary" = Subject.Lord); Alcotest.(check bool) "Precious Blood stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true (subject_of "precious-blood-of-our-lord-jesus-christ" = Subject.Lord); Alcotest.(check bool) "Transfiguration stays Lord (calendarium: \"D. N. I. C.\") -- audit, not touched" true (subject_of "transfiguration-of-our-lord" = Subject.Lord); Alcotest.(check bool) "Exaltation of the Holy Cross stays Lord -- audit, not touched" true (subject_of "exaltation-of-the-holy-cross" = Subject.Lord); Alcotest.(check bool) "Dedication of the Archbasilica of the Most Holy Saviour stays Lord -- audit, not touched" true (subject_of "dedication-of-the-archbasilica-of-our-holy-savior" = Subject.Lord) (* 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 (* Task 11's re-review finding, closed here: [Precedence_ef.transfer_target]'s Annunciation/Easter condition (and, more generally, RG 96's whole "not I or II class" test) is correct only because [Temporal_ef] happens to make every day from Easter Sunday through Low Sunday (Easter+0..+7) blocking -- Easter itself and Low Sunday via [named], every day between via [privileged_feria]. Nothing in the type system enforces that; a future edit narrowing [privileged_feria]'s Easter-octave range would silently let the RG 96 walk land a translated feast inside the octave. This tests the CONSEQUENCE (no day in that window ever receives one), not the mechanism ([privileged_feria] itself), so it stays sensitive to any way that consequence could break, not only the one code path that currently protects it. *) let easter_offset (d : Date.t) = let easter = Comp.gregorian_easter (Date.year d) in Date.to_rata d - Date.to_rata easter let in_easter_octave d = let off = easter_offset d in off >= 0 && off <= 7 (* 2005-2050: the project's own differential-testing window (CLAUDE.md), reused here as a deterministic, non-trivial sample -- 46 liturgical years, each with several genuine transfers (All Souls onto a Sunday, impeded universal feasts, and so on; test_precedence_ef.ml's own 21k-day manual review already confirmed "all 7 slugs that ever transfer have a verified rubrical cause" over a similar span), so this is not a vacuous sweep over years where nothing ever transfers. *) let sample_years = let rec range a b = if a > b then [] else a :: range (a + 1) b in range 2005 2050 (* Property 1: no day in the resolved output, across the whole sample, is EVER a transfer's landing point inside [Easter, Easter+7] -- checked two ways. [transferred_out]'s own recorded target is what [transfer_target] itself returned (calendar.ml's [assignment], stored verbatim), so this is the more direct signal; [transferred_in] is also checked, in case some future Calendar change ever let the two disagree. *) let test_no_transfer_lands_in_easter_octave () = let layer = real_layer () in let violations = ref [] in List.iter (fun y -> let days = Cal.year Rite_ef.context layer y in Array.iter (fun (d : (V.season, V.rank) LD.t) -> (match d.LD.transferred_in with | Some c when in_easter_octave d.LD.date -> violations := (Printf.sprintf "%s transferred_in on %s (Easter+%d)" (slug_of c) (Date.to_iso8601 d.LD.date) (easter_offset d.LD.date)) :: !violations | _ -> ()); List.iter (fun (c, target) -> if in_easter_octave target then violations := Printf.sprintf "%s transferred_out from %s to %s (Easter+%d)" (slug_of c) (Date.to_iso8601 d.LD.date) (Date.to_iso8601 target) (easter_offset target) :: !violations) d.LD.transferred_out) days) sample_years; Alcotest.(check (list string)) "no day in [Easter, Easter+7] is ever a transfer's target, 2005-2050" [] (List.rev !violations) (* Property 2, and the LIVE case: [PE.transfer_target] called directly, with an origin that genuinely starts the search INSIDE Holy Week -- Holy Thursday 2026 (Easter - 3), a date no real sanctoral entry in data/ef/sanctoral.sexp occupies (Holy Week carries none), so this is deliberately constructed, not found. [occupant] is the REAL [Temporal_ef.temporal] (not a synthetic stand-in), so the search is driven by the actual blocking shape [privileged_feria] produces, not a hand-picked one -- this is genuinely live: search_from walks origin+1 (Good Friday, Easter-2) forward through every remaining day of Holy Week, all of Easter through Low Sunday (Easter+0..+7, all Class1), and only stops at Easter+8 (the Monday after Low Sunday), which [named] and [ferial_rank]/[privileged_feria] agree is Class4 -- confirmed below by checking the OCCUPANT's own rank there, not asserted blind. Without an origin inside the window itself, [search_from] could stop before ever reaching it and this test would prove nothing (the vacuity trap the task brief names explicitly) -- [test_search_genuinely_enters_the_window] pins that it does not stop early. *) let holy_week_origin_2026 = let easter_2026 = Comp.gregorian_easter 2026 in Date.add_days easter_2026 (-3) let real_occupant d = (T.temporal d).Colitur_kernel.Temporal.office let test_transfer_target_skips_the_whole_easter_octave () = let easter_2026 = Comp.gregorian_easter 2026 in let c = { P.cel = Cel.make ~slug:(Slug.of_string_exn "test-impeded-in-holy-week") ~rank:V.Class1 ~colour:Colour.White ~subject:Subject.Temporal ~layer:PE.universal_layer (); origin = P.Sanctoral } in let target = PE.transfer_target c holy_week_origin_2026 real_occupant in Alcotest.(check string) "lands on Easter + 8 (Monday after Low Sunday), past the entire octave" (Date.to_iso8601 (Date.add_days easter_2026 8)) (Date.to_iso8601 target); Alcotest.(check bool) "strictly after origin (rite.mli's own obligation)" true (Date.compare target holy_week_origin_2026 > 0); Alcotest.(check bool) "not inside [Easter, Easter+7]" false (let off = Date.to_rata target - Date.to_rata easter_2026 in off >= 0 && off <= 7) (* The vacuity check itself: proves the search genuinely walked THROUGH the window rather than [search_from] having some other reason to stop before it (e.g. an off-by-one that happened to also land past the octave). Reads the real occupant's own rank at every day from the origin through Easter+7 and requires every one of them to be blocking (Class1 or Class2) -- if any single one were not, [search_from] would have stopped there instead of at Easter+8, and the test above would be passing for the wrong reason. *) let test_search_genuinely_enters_the_window () = let easter_2026 = Comp.gregorian_easter 2026 in let rec days_from a b = if Date.compare a b > 0 then [] else a :: days_from (Date.add_days a 1) b in let walked = days_from (Date.add_days holy_week_origin_2026 1) (Date.add_days easter_2026 7) in Alcotest.(check bool) "every day from origin+1 through Easter+7 (the whole span search_from must cross) is blocking" true (List.for_all (fun d -> match (real_occupant d).Cel.rank with V.Class1 | V.Class2 -> true | V.Class3 | V.Class4 -> false) walked); Alcotest.(check bool) "the walked span is at least 8 days -- the octave alone (Easter..Easter+7), not a one-day hop" true (List.length walked >= 8) (* Task B fix round 1 (coordinator finding 1), end to end with REAL data: 22 September 2027 is a September Ember Wednesday -- the OBSERVED day is the Ember office itself ([band] entry 18, RG 91, beats both sanctoral candidates), and RG 111(c) ("other II class: one") admits exactly ONE of the two competing losers, "maurice-and-companions-martyrs" ([Commemoration_only], Class3) and "thomas-of-villanova" ([Feast], Class3). Before the fix, [Precedence_ef.band] gave Maurice the SAME table entry (24) as Thomas, so the residual slug tie-break picked Maurice ('m' < 't') and OMITTED Thomas -- contrary to the calendarium's own 22 September row ("S. Thomae de Villanova Ep. et Conf., III classis. / Commemoratio Ss. Mauritii et Soc. Mm.": Thomas is the one WITH a table entry) and to missalemeum (test/test_oracle.ml's own former M17 allow-list entry -- oracle "commemorations": Thomas, "displaced": Maurice -- deleted once this fix made the divergence disappear). This is the SAME date the oracle harness itself checks, pinned here too so a regression shows up at the OCaml level, not only via the fixture comparison. *) let test_maurice_thomas_band_fidelity_end_to_end () = let layer = real_layer () in (* [Calendar.year]'s own liturgical-year convention (Advent-anchored, see calendar.ml's [year_bounds]): the label [y] spans December of civil year [y] through November of civil year [y+1], so 22 September 2027 is reached via [Cal.year ... 2026], not 2027 -- the same [y-1]/straddling indexing test_oracle.ml's own header comment already documents for exactly this reason. *) let days = Cal.year Rite_ef.context layer 2026 in let day = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (mk 2027 9 22) = 0) in Alcotest.(check string) "the September Ember Wednesday office itself is observed (band entry 18)" "ef-september-ember-wed" (slug_of day.LD.observed); Alcotest.(check (list string)) "St Thomas of Villanova (band entry 24, a real table row) is the sole commemoration" [ "thomas-of-villanova" ] (List.map (fun (c, _) -> slug_of c) day.LD.commemorations); Alcotest.(check (list string)) "St Maurice and Companions (Commemoration_only, unclassified -- no table row) is omitted, not \ commemorated" [ "maurice-and-companions-martyrs" ] (List.map (fun (c, _) -> slug_of c) day.LD.omitted) let suite = ( "Rite_ef (real data: overlay-in-effect, domain-ceiling)", [ Alcotest.test_case "the overlay suppression is observably in effect" `Quick test_vigil_of_christmas_suppressed; Alcotest.test_case "RG16(a) task: the two subject retags are in effect, the other four untouched" `Quick test_rg16a_subject_retag_in_effect; Alcotest.test_case "RG96 search does not raise at the domain ceiling (real data)" `Quick test_transfer_search_does_not_raise_at_domain_ceiling; Alcotest.test_case "no transfer ever lands inside [Easter, Easter+7], 2005-2050" `Quick test_no_transfer_lands_in_easter_octave; Alcotest.test_case "transfer_target skips the whole Easter octave from inside Holy Week" `Quick test_transfer_target_skips_the_whole_easter_octave; Alcotest.test_case "the search genuinely enters the window (not vacuous)" `Quick test_search_genuinely_enters_the_window; Alcotest.test_case "band fidelity end-to-end: Thomas of Villanova observed, Maurice commemorated (2027-09-22)" `Quick test_maurice_thomas_band_fidelity_end_to_end ] )