aboutsummaryrefslogtreecommitdiff
path: root/test/test_fiuv_ordo.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 17:56:42 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 17:56:42 +0200
commitf4cc032d7e812d716ff6b5df8192f79a2560e8f0 (patch)
tree65c8c20456ebe66fd1ff2253428357b5b027ec0a /test/test_fiuv_ordo.ml
parent60e87914718dd2c8e69bb89d8cc48d6bef9bfc74 (diff)
downloadcolitur-f4cc032d7e812d716ff6b5df8192f79a2560e8f0.tar.gz
colitur-f4cc032d7e812d716ff6b5df8192f79a2560e8f0.zip
feat(ef): the Gloria in excelsis, RG 431-432, deferring to Breviary 237-238
Phase 2 of celebrant-rubrics-phase1: colitur rubrics gains a fifth column, whether the Gloria is said. Follows the Creed's own seam exactly -- Rite.t.gloria, Liturgical_day.t.gloria, wired through calendar.ml the same way. RG 431(a)/432(a) defer the Gloria to the Breviary's own Te Deum rule (nn. 237-238), so te_deum is implemented as its own named predicate, cited clause by clause, not collapsed into a colour heuristic. 431(c) (Holy Thursday, the Easter Vigil Mass) and 432(b)/(d) (violet; a Requiem) are independent overrides checked ahead of the Te Deum-derived answer. Every clause this engine has no dimension to model (votive Mass classes, the wider n.302 "Missa festiva" categories) is stated as N/A with its own reasoning, not silently dropped. Validated against the FIUV universal Ordo (Gloria and Te Deum) and all three LMS editions (Gloria). A first pass over-trusted a clean-looking 15-for-15 FIUV contradiction of 237(b)'s own Septuagesima exception and replaced it with a blanket "every Sunday" rule; the evidence was itself corrupted -- the FIUV extractor recognised only one of the source's two Te Deum negations ("non dicitur", not "sine"), so every "sine Te Deum" Sunday read wrongly true. Fixed in tools/extract_fiuv_ordo.ml, fixture re-extracted, and the literal 237(b) reading restored once the corrected data confirmed it. A second bug surfaced alongside it (Palm/ Passion Sunday wrongly reading true via Temporal_ef.named's own table membership, then Christ the King wrongly reading false from an over-broad fix) is closed with an explicit two-slug exclusion. Domain-wide 1583-9999: every violet or Rose day is gloria=false except the Easter Vigil (RG 431(c) lex specialis), every Requiem is gloria=false, both measured exhaustively, zero exceptions. Mutation- proved: disabling 431(c) reddens 8 tests including all four oracle comparisons; disabling 238(c)'s feria-I-classis exclusion reddens exactly the dedicated Ash Wednesday unit test, a genuine blind spot in both oracle layers, reported rather than hidden. Two open, cited findings, neither fixed here (out of this task's "follow creed's exact seam" scope): a privileged Lenten/Passiontide feria carrying one commemoration reads Gloria=true in the LMS Ordo but Te-Deum=true/Gloria=false in FIUV -- the two oracles disagree with each other, not merely with colitur (data/ef/expected-divergences-lms.sexp L5, expected-divergences-fiuv.sexp F3); and a pre-existing, uncited Colour.Violet bug on Rogation Monday/Tuesday in Temporal_ef.temporal, surfaced by this comparison but root-caused as a separate defect (L6). day/readings verified byte-identical against a build from the branch tip before this task (v0.10.1's own tag predates an already-landed bissextile fix that legitimately changed both, so it is not the right baseline). 671 tests green (dune test); 678 with the exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, ~104s).
Diffstat (limited to 'test/test_fiuv_ordo.ml')
-rw-r--r--test/test_fiuv_ordo.ml169
1 files changed, 165 insertions, 4 deletions
diff --git a/test/test_fiuv_ordo.ml b/test/test_fiuv_ordo.ml
index 8b3e942..24eeed0 100644
--- a/test/test_fiuv_ordo.ml
+++ b/test/test_fiuv_ordo.ml
@@ -68,7 +68,7 @@ let sha256_of_file path =
| Some i -> String.sub line 0 i
| None -> Alcotest.failf "unexpected sha256sum output for %s: %S" path line)
-let fixture_sha256 = "becadaad43b3a42c4eb820cb4e93d68f9b1c07a2c8e5273167cd599758af5c67"
+let fixture_sha256 = "a52cc4dae32ce4d07446c9daa86287cb99796f6584b3d571e3a3cbdc93fca396"
let real_layer () =
let layer =
@@ -132,7 +132,7 @@ let window_last = "2026-12-31"
(* late). *)
(* ---------------------------------------------------------------------- *)
-type colitur_row = { c_date : string; c_creed : bool }
+type colitur_row = { c_date : string; c_creed : bool; c_gloria : bool }
let colitur_rows () =
let layer = real_layer () in
@@ -148,7 +148,50 @@ let colitur_rows () =
let stop = mk window_last in
while Date.compare !d stop <= 0 do
(match Hashtbl.find_opt by_rata (Date.to_rata !d) with
- | Some day -> rows := { c_date = Date.to_iso8601 day.LD.date; c_creed = day.LD.creed } :: !rows
+ | Some day ->
+ rows :=
+ { c_date = Date.to_iso8601 day.LD.date; c_creed = day.LD.creed; c_gloria = day.LD.gloria } :: !rows
+ | None -> Alcotest.failf "no colitur day resolved for %s" (Date.to_iso8601 !d));
+ d := Date.add_days !d 1
+ done;
+ List.rev !rows
+
+(* Te Deum has no colitur-side [Liturgical_day.t] field of its own -- it is
+ a Breviary fact {!Rite_ef.Rubrics_ef.gloria} reads internally, not a Mass
+ part {!Colitur_kernel.Rite.t} exposes. Resolved separately, straight from
+ {!Rite_ef.Rubrics_ef.te_deum}, over the identical window/day set. *)
+type colitur_te_deum_row = {
+ t_date : string;
+ t_te_deum : bool;
+ t_rank : V.rank;
+ t_slug : string;
+ t_has_commemoration : bool;
+}
+
+let colitur_te_deum_rows () =
+ let layer = real_layer () in
+ let rite = Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ()) in
+ let by_rata : (int, (V.season, V.rank) LD.t) Hashtbl.t = Hashtbl.create 800 in
+ for y = 2024 to 2027 do
+ let days = Cal.year rite layer y in
+ Array.iter (fun (d : (V.season, V.rank) LD.t) -> Hashtbl.replace by_rata (Date.to_rata d.LD.date) d) days
+ done;
+ let mk s = match Date.of_iso8601 s with Ok d -> d | Error e -> Alcotest.failf "%s: %s" s e in
+ let rows = ref [] in
+ let d = ref (mk window_first) in
+ let stop = mk window_last in
+ while Date.compare !d stop <= 0 do
+ (match Hashtbl.find_opt by_rata (Date.to_rata !d) with
+ | Some day ->
+ let te_deum =
+ Rite_ef.Rubrics_ef.te_deum ~temporal:day.LD.temporal ~observed:day.LD.observed ~date:day.LD.date
+ in
+ rows :=
+ { t_date = Date.to_iso8601 day.LD.date; t_te_deum = te_deum;
+ t_rank = day.LD.observed.Colitur_kernel.Celebration.rank;
+ t_slug = Colitur_kernel.Slug.to_string day.LD.observed.Colitur_kernel.Celebration.slug;
+ t_has_commemoration = day.LD.commemorations <> [] }
+ :: !rows
| None -> Alcotest.failf "no colitur day resolved for %s" (Date.to_iso8601 !d));
d := Date.add_days !d 1
done;
@@ -258,11 +301,129 @@ let test_creed_matches_or_is_explained () =
Alcotest.failf "allow-list entry %s is declared but never matched a real divergence" e.id)
allow_list
+(* ---------------------------------------------------------------------- *)
+(* Gloria (RG 431-432, {!Rite_ef.Rubrics_ef.gloria}) -- Phase 2. Same shape *)
+(* as the Creed comparison above, over the identical 400-row window. *)
+(* ---------------------------------------------------------------------- *)
+
+let test_gloria_coverage () =
+ let ordo = ordo_rows () in
+ let no_gloria = List.filter (fun o -> o.gloria = None) ordo in
+ Alcotest.(check int) "exactly one day has no Ordo Gloria marker" 1 (List.length no_gloria)
+
+let describe_gloria_mismatch (o : ordo_row) (c : colitur_row) =
+ Printf.sprintf "%s %S: colitur gloria=%b, Ordo gloria=%b" o.date o.title c.c_gloria (Option.get o.gloria)
+
+(* F1 -- OPEN, adjudicated FOR colitur. The single Gloria mismatch found:
+ Good Friday (2026-04-03). See data/ef/expected-divergences-fiuv.sexp's
+ own F1 for the full citation and the raw source text this was checked
+ against directly (docs/research/ordo/fiuv-ordo-2025-2026.pdf, page 46). *)
+let is_f1_good_friday (o : ordo_row) = String.equal o.date "2026-04-03"
+
+let test_gloria_matches_or_is_explained () =
+ let ordo = ordo_rows () in
+ let colitur = colitur_rows () in
+ let unexplained = ref [] in
+ let f1_count = ref 0 in
+ List.iter2
+ (fun (o : ordo_row) (c : colitur_row) ->
+ if not (String.equal o.date c.c_date) then Alcotest.failf "misaligned: ordo %s vs colitur %s" o.date c.c_date;
+ match o.gloria with
+ | None -> ()
+ | Some ogloria ->
+ if Bool.equal ogloria c.c_gloria then ()
+ else if is_f1_good_friday o then incr f1_count
+ else unexplained := describe_gloria_mismatch o c :: !unexplained)
+ ordo colitur;
+ Alcotest.(check (list string)) "every Gloria mismatch is named in the allow-list -- none unexplained" []
+ (List.rev !unexplained);
+ Alcotest.(check int) "F1 (Good Friday) count" 1 !f1_count
+
+(* ---------------------------------------------------------------------- *)
+(* Te Deum (Breviary 237-238, {!Rite_ef.Rubrics_ef.te_deum}) -- Phase 2, *)
+(* the mitigation the task brief names for this source's own stated *)
+(* weakness (a single, not yet scan-verified, web transcription). A *)
+(* SEPARATE colitur-side resolution ({!colitur_te_deum_rows}), since Te *)
+(* Deum has no {!Colitur_kernel.Liturgical_day.t} field of its own. *)
+(* *)
+(* The SUNDAY shape this same comparison originally found (237(b)'s own *)
+(* literal Septuagesima/Sexagesima/Quinquagesima exception, contradicted *)
+(* 15/15) is FIXED at the source ({!Rite_ef.Rubrics_ef.te_deum}'s own *)
+(* header has the correction and its full citation) -- not allow-listed, *)
+(* because it no longer diverges. Three OTHER shapes remain OPEN, *)
+(* single-witnessed (never captured by the LMS fixtures, which do not *)
+(* record Te Deum at all) -- see data/ef/expected-divergences-fiuv.sexp's *)
+(* own F2/F3/F4 for the full citations. Matched by PREDICATE, the same *)
+(* "varies by shape, not by a fixed date list" reasoning L5/L6 already *)
+(* establish for the LMS suite. *)
+(* ---------------------------------------------------------------------- *)
+
+let test_te_deum_coverage () =
+ let ordo = ordo_rows () in
+ let no_te_deum = List.filter (fun o -> o.te_deum = None) ordo in
+ Alcotest.(check int) "the unresolved-Te-Deum population matches this fixture's own measured figure" 127
+ (List.length no_te_deum)
+
+let describe_te_deum_mismatch (o : ordo_row) (t : colitur_te_deum_row) =
+ Printf.sprintf "%s %S: colitur te_deum=%b, Ordo te_deum=%b (rank=%s commemoration=%b)" o.date o.title t.t_te_deum
+ (Option.get o.te_deum) (V.rank_to_string t.t_rank) t.t_has_commemoration
+
+(* F2 (every I-class and omissible vigil) and F4 (the three September
+ Ember days) were BOTH found against the FIRST, buggy extraction (see
+ the fixture's own provenance header, "RE-EXTRACTED" note, and
+ {!Rite_ef.Rubrics_ef.te_deum}'s own 237(a) comment for the full
+ account of the extractor bug and its fix) -- re-run against the
+ corrected data, NEITHER fires any more: colitur's own answer already
+ matched the CORRECTED Ordo on every one of those 7 dates, all along.
+ Removed rather than kept as dead code with an [expected 0] pin, the
+ same "an entry that stops firing is a real change, not silently
+ absorbed" discipline data/ef/expected-divergences-lms.sexp's own L1/L3
+ closures already establish -- see that file's own history for the
+ precedent this follows.
+
+ F3, the privileged-Lenten/Passiontide-feria-plus-commemoration shape
+ L5 (data/ef/expected-divergences-lms.sexp) already found for Gloria,
+ REMAINS: 6 instances, unaffected by the extractor fix (none of the six
+ raw source lines used "sine Te Deum" at all -- confirmed directly
+ against the pdftotext dump, see F3's own citation). CONTRADICTS L5,
+ not corroborates it: FIUV's own [gloria] is [false] on all six dates
+ (agreeing with colitur), while its [te_deum] is [true] on the same six
+ -- see F3's own citation in data/ef/expected-divergences-fiuv.sexp for
+ the full account of why Gloria and Te Deum diverge on the identical
+ day. *)
+let is_f3_lenten_commemoration (_ : ordo_row) (t : colitur_te_deum_row) =
+ t.t_has_commemoration && t.t_rank = V.Class3
+
+let test_te_deum_matches_or_is_explained () =
+ let ordo = ordo_rows () in
+ let colitur = colitur_te_deum_rows () in
+ let unexplained = ref [] in
+ let f3_count = ref 0 in
+ List.iter2
+ (fun (o : ordo_row) (t : colitur_te_deum_row) ->
+ if not (String.equal o.date t.t_date) then Alcotest.failf "misaligned: ordo %s vs colitur %s" o.date t.t_date;
+ match o.te_deum with
+ | None -> ()
+ | Some otd ->
+ if Bool.equal otd t.t_te_deum then ()
+ else if is_f3_lenten_commemoration o t then incr f3_count
+ else unexplained := describe_te_deum_mismatch o t :: !unexplained)
+ ordo colitur;
+ Alcotest.(check (list string)) "every Te Deum mismatch is named in the allow-list -- none unexplained" []
+ (List.rev !unexplained);
+ Alcotest.(check int) "F3 (Lenten privileged feria + commemoration) count" 6 !f3_count
+
let suite =
( "fiuv-ordo",
[ Alcotest.test_case "fixture SHA-256 matches its provenance note" `Quick test_fixture_checksum;
Alcotest.test_case "streams are 400 rows each, dates aligned 1:1" `Quick test_dates_align;
Alcotest.test_case "only Holy Saturday has no Ordo Creed marker" `Quick test_creed_coverage;
Alcotest.test_case "every Creed difference is named in the cited allow-list -- none unexplained" `Quick
- test_creed_matches_or_is_explained
+ test_creed_matches_or_is_explained;
+ Alcotest.test_case "Ordo Gloria coverage matches the measured figure" `Quick test_gloria_coverage;
+ Alcotest.test_case "every Gloria difference is named in the cited allow-list -- none unexplained" `Quick
+ test_gloria_matches_or_is_explained;
+ Alcotest.test_case "Ordo Te Deum coverage matches the measured figure" `Quick test_te_deum_coverage;
+ Alcotest.test_case "every Te Deum difference is named in the cited allow-list -- none unexplained" `Quick
+ test_te_deum_matches_or_is_explained
] )