aboutsummaryrefslogtreecommitdiff
path: root/test/test_efdotorg_ordo.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
commit8a5da8756cf6b57aa36c01b71ce893f7967d3a29 (patch)
tree10873a692400fa25ce54c67ac9fc34d46af86272 /test/test_efdotorg_ordo.ml
parent4f87cbde4ee79ac96aa7ebfed105b3a5ec02be43 (diff)
parent73b15551804bb63ee0081005e2869d36afb54be2 (diff)
downloadcolitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.tar.gz
colitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.zip
merge: celebrant Mass rubrics, and four rubrical corrections
colitur now prints what a real ordo prints for the Mass: which formulary is said and how it was reached, the Gloria, the Creed, the preface, and the commemorations with their Low-Mass/sung distinction. colitur rubrics joins day and readings. Four defects were found and fixed on the way, each by an external witness rather than by inspection: the Creed said at Requiem Masses (RG 476(f)), the missing bissextile shift of St Matthias and St Gabriel (2 041 leap years), Rogation Monday and Tuesday coloured violet in Paschaltide (RG 119(b)), and the ferias after the Ascension resuming the wrong Sunday's Mass rather than the Ascension's. Validation gained a sixth layer and then some: the preface is checked against three independent publishers over seven witness-years (FIUV, three LMS editions, three extraordinaryform.org editions), none of which shares the Divinum Officium -> missalemeum -> lectio lineage the older layers all descend from.
Diffstat (limited to 'test/test_efdotorg_ordo.ml')
-rw-r--r--test/test_efdotorg_ordo.ml403
1 files changed, 403 insertions, 0 deletions
diff --git a/test/test_efdotorg_ordo.ml b/test/test_efdotorg_ordo.ml
new file mode 100644
index 0000000..6200cd9
--- /dev/null
+++ b/test/test_efdotorg_ordo.ml
@@ -0,0 +1,403 @@
+(* efdotorg-preface task (2026-08-24-colitur-celebrant-rubrics-phase1): the
+ THIRD independent preface witness, extraordinaryform.org's own three
+ annual Ordo editions (2024-2025, 2025-2026, 2026-2027). A prior task
+ (2026-08-21-colitur-celebrant-rubrics-phase1, see
+ .superpowers/sdd/2026-08-21-colitur-celebrant-rubrics-phase1/preface-
+ witnesses-report.md) DECLINED to wire this source in, for two reasons:
+ an unexplained "Advent" label in the preface column, and this corpus's
+ own known Vigil-of-St-Lawrence gap. Both are now characterisation work,
+ not blockers -- see tools/extract_efdotorg_ordo.py's own header for the
+ full account this file's own comparator relies on.
+
+ WHY THIS LAYER IS DIFFERENT FROM test_fiuv_ordo.ml/test_lms_ordo.ml:
+ this source prints ONE preface value per day (unlike the LMS Ordo's own
+ option lists), already reduced to the day's own real OFFICE by the
+ menu-trap filter baked into the generator (see that script's own
+ header, finding 1) -- so the comparison here is a single equality
+ check per day, not a membership check. It is also the only one of the
+ three witnesses whose own JSON, not a fresh pdftotext dump, is the
+ input -- the JSON is already fidelity-audited
+ (.superpowers/ordo-fidelity-report.md, 1091/1091 against an
+ independently-written parser) and is treated as the source; this file
+ does not re-parse the PDFs.
+
+ SCOPE: preface only (RG 482-499, {!Rite_ef.Rubrics_ef.preface}) -- this
+ source carries no Creed/Gloria/Te Deum data at all
+ (docs/research/ordo/PROVENANCE-ordo-corpus.md: "The extraordinaryform.org
+ editions carry NO Gloria or Creed"), so there is nothing else to
+ compare it against. *)
+
+module Cal = Colitur_kernel.Calendar
+module Layer = Colitur_kernel.Layer
+module Overlay = Colitur_kernel.Overlay
+module LD = Colitur_kernel.Liturgical_day
+module Date = Colitur_kernel.Date
+module V = Rite_ef.Vocab_ef
+module Preface = Colitur_kernel.Preface
+
+let sanctoral_path = "../data/ef/sanctoral.sexp"
+let adjustments_path = "../data/ef/adjustments.sexp"
+
+(* Duplicated, not shared -- test_fiuv_ordo.ml/test_lms_ordo.ml's own
+ identical function carries the same "no shared .mli" reasoning. *)
+let sha256_of_file path =
+ let tmp = Filename.temp_file "colitur_efdotorg_ordo_sha256" ".txt" in
+ Fun.protect
+ ~finally:(fun () -> try Sys.remove tmp with Sys_error _ -> ())
+ (fun () ->
+ let cmd = Printf.sprintf "sha256sum %s > %s" (Filename.quote path) (Filename.quote tmp) in
+ let rc = Sys.command cmd in
+ if rc <> 0 then Alcotest.failf "sha256sum exited %d for %s (is it on PATH?)" rc path;
+ let ic = open_in tmp in
+ let line =
+ try input_line ic
+ with End_of_file ->
+ close_in ic;
+ Alcotest.failf "sha256sum produced no output for %s" path
+ in
+ close_in ic;
+ match String.index_opt line ' ' with
+ | Some i -> String.sub line 0 i
+ | None -> Alcotest.failf "unexpected sha256sum output for %s: %S" path line)
+
+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 real_lectionary () =
+ match Colitur_kernel.Lectionary.load "../data/ef/lectionary.sexp" with
+ | Ok l -> l
+ | Error e -> Alcotest.failf "../data/ef/lectionary.sexp: failed to load: %s" e
+
+let real_commons () =
+ match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with
+ | Ok c -> c
+ | Error e -> Alcotest.failf "../data/ef/commons.sexp: failed to load: %s" e
+
+(* ---------------------------------------------------------------------- *)
+(* The Ordo side: the sexp row, mirrored from tools/extract_efdotorg_ordo.py's *)
+(* own row shape. *)
+(* ---------------------------------------------------------------------- *)
+
+open Sexplib0.Sexp_conv
+
+type ordo_row = { date : string; mass : string option; cls : int option; colour : string option; praef : string option }
+[@@deriving sexp]
+
+let ordo_rows fixture_path =
+ let sexp =
+ try Sexplib.Sexp.load_sexp fixture_path
+ with e -> Alcotest.failf "%s: failed to load: %s" fixture_path (Printexc.to_string e)
+ in
+ list_of_sexp ordo_row_of_sexp sexp
+
+(* ---------------------------------------------------------------------- *)
+(* The colitur side, over the same window -- mirrors test_fiuv_ordo.ml's *)
+(* own [colitur_rows]. *)
+(* ---------------------------------------------------------------------- *)
+
+type colitur_row = { c_date : string; c_weekday : Date.weekday; c_preface : Preface.t option }
+
+let colitur_rows ~year_lo ~year_hi ~window_first ~window_last =
+ 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 = year_lo to year_hi 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 ->
+ rows :=
+ { c_date = Date.to_iso8601 day.LD.date; c_weekday = Date.weekday day.LD.date; c_preface = day.LD.preface }
+ :: !rows
+ | None -> Alcotest.failf "no colitur day resolved for %s" (Date.to_iso8601 !d));
+ d := Date.add_days !d 1
+ done;
+ List.rev !rows
+
+(* ---------------------------------------------------------------------- *)
+(* Classification -- see tools/extract_efdotorg_ordo.py's own header, *)
+(* finding 3, for why "Advent" needs the day's own weekday rather than a *)
+(* static table entry: RG 494(b) grants the TRINITY preface on every *)
+(* Advent SUNDAY, RG 498 the residual COMMON on every Advent FERIA, and *)
+(* this source's own "Advent" label does not distinguish the two. Every *)
+(* other raw value maps 1:1 onto exactly one {!Preface.t} constructor -- *)
+(* verified exhaustive over the whole 1091-day office-selected population *)
+(* while building this comparison (16 distinct raw values: these 14 fixed *)
+(* mappings, "Advent" itself, and [None]) -- unlike the FIUV/LMS sources, *)
+(* nothing here is genuinely unclassifiable. *)
+(* ---------------------------------------------------------------------- *)
+
+let classify_praef ~(weekday : Date.weekday) (raw : string) : Preface.t option =
+ match raw with
+ | "Common" -> Some Preface.Common
+ | "For the Dead" -> Some Preface.Requiem
+ | "Easter" -> Some Preface.Easter
+ | "Lent" -> Some Preface.Lent
+ | "BVM" -> Some Preface.Bvm
+ | "Holy Trinity" -> Some Preface.Trinity
+ | "Holy Cross" -> Some Preface.Holy_cross
+ | "Nativity" -> Some Preface.Nativity
+ | "Apostles" -> Some Preface.Apostles
+ | "Ascension" -> Some Preface.Ascension
+ | "Holy Spirit" -> Some Preface.Holy_spirit
+ | "Epiphany" -> Some Preface.Epiphany
+ | "St. Joseph" -> Some Preface.St_joseph
+ | "Sacred Heart" -> Some Preface.Sacred_heart
+ | "Christ the King" -> Some Preface.Christ_the_king
+ | "Advent" -> Some (if weekday = Date.Sun then Preface.Trinity else Preface.Common)
+ | _ -> None
+
+(* ---------------------------------------------------------------------- *)
+(* data/ef/expected-divergences-efdotorg.sexp -- same shape as the other *)
+(* Ordo allow-lists' own [allow_entry]. A SEPARATE file: this Ordo is a *)
+(* FIFTH lineage (lectio/missalemeum, LMS, FIUV, and now this one), a *)
+(* different publisher and a different compiler from all three others. *)
+(* ---------------------------------------------------------------------- *)
+
+type allow_entry = { id : string; citation : string; verdict : string; note : string; expected_rows : int }
+[@@deriving sexp]
+
+let load_allow_list allow_list_path =
+ let sexps =
+ try Sexplib.Sexp.load_sexps allow_list_path
+ with e -> Alcotest.failf "%s: failed to load: %s" allow_list_path (Printexc.to_string e)
+ in
+ List.map allow_entry_of_sexp sexps
+
+let allow_list_path = "../data/ef/expected-divergences-efdotorg.sexp"
+
+(* ---------------------------------------------------------------------- *)
+(* [make_suite]: one edition's worth of tests, parameterised -- mirrors *)
+(* test_lms_ordo.ml's own shape. *)
+(* ---------------------------------------------------------------------- *)
+
+let make_suite ~label ~fixture_path ~fixture_sha256 ~window_first ~window_last ~year_lo ~year_hi ~expected_rows
+ ~expected_no_data ~allow_list_id_for_date =
+ let test_fixture_checksum () =
+ Alcotest.(check string) "fixture SHA-256 matches its provenance note" fixture_sha256
+ (sha256_of_file fixture_path)
+ in
+ let test_dates_align () =
+ let ordo = ordo_rows fixture_path in
+ let colitur = colitur_rows ~year_lo ~year_hi ~window_first ~window_last in
+ Alcotest.(check int) (Printf.sprintf "the Ordo fixture has %d rows (%s..%s)" expected_rows window_first window_last)
+ expected_rows (List.length ordo);
+ Alcotest.(check int) "colitur resolved the same number of days" (List.length ordo) (List.length colitur);
+ (match ordo with o :: _ -> Alcotest.(check string) "first date" window_first o.date | [] -> Alcotest.fail "empty");
+ (match List.rev ordo with
+ | o :: _ -> Alcotest.(check string) "last date" window_last o.date
+ | [] -> Alcotest.fail "empty");
+ let mismatched =
+ List.filter_map
+ (fun (o, c) -> if String.equal o.date c.c_date then None else Some (o.date, c.c_date))
+ (List.combine ordo colitur)
+ in
+ Alcotest.(check (list (pair string string))) "no misaligned dates" [] mismatched
+ in
+ (* This window's own Good Friday(s), RE-DERIVED from
+ {!Colitur_kernel.Computus.gregorian_easter} for every civil year the
+ window touches (never hardcoded), plus this edition's own share of
+ the single known SOURCE GAP (2027-03-03) -- the only two shapes a row
+ can have no [praef] at all for (see tools/extract_efdotorg_ordo.py's
+ own header, findings 2 and 5). *)
+ let window_no_praef () =
+ let first_year = int_of_string (String.sub window_first 0 4) in
+ let last_year = int_of_string (String.sub window_last 0 4) in
+ let good_fridays =
+ List.map
+ (fun y -> Date.to_iso8601 (Date.add_days (Colitur_kernel.Computus.gregorian_easter y) (-2)))
+ (List.init (last_year - first_year + 1) (fun i -> first_year + i))
+ in
+ let candidates = good_fridays @ [ "2027-03-03" ] in
+ List.filter (fun d -> String.compare d window_first >= 0 && String.compare d window_last <= 0) candidates
+ |> List.sort_uniq String.compare
+ in
+ let test_praef_coverage () =
+ let ordo = ordo_rows fixture_path in
+ let no_praef = List.filter (fun o -> o.praef = None) ordo in
+ let expected = window_no_praef () in
+ Alcotest.(check int) "the no-praef population matches this window's own Good-Friday-plus-source-gap count"
+ expected_no_data (List.length no_praef);
+ Alcotest.(check (list string)) "every no-praef row is either this window's own Good Friday or the known source gap"
+ expected (List.map (fun o -> o.date) no_praef)
+ in
+ let describe_praef_mismatch (o : ordo_row) (c : colitur_row) (expected : Preface.t) =
+ Printf.sprintf "%s %S: colitur preface=%s, %s praef=%S (classified %s, weekday %s)" o.date
+ (Option.value o.mass ~default:"?")
+ (match c.c_preface with Some p -> Preface.to_string p | None -> "NONE")
+ label (Option.get o.praef) (Preface.to_string expected) (Date.weekday_to_string c.c_weekday)
+ in
+ let test_praef_matches_or_is_explained () =
+ let ordo = ordo_rows fixture_path in
+ let colitur = colitur_rows ~year_lo ~year_hi ~window_first ~window_last in
+ let allow_list = load_allow_list allow_list_path in
+ let by_id = List.map (fun e -> (e.id, e)) allow_list in
+ let unexplained = ref [] in
+ let unclassifiable = ref [] in
+ let explained_counts = Hashtbl.create 8 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.praef with
+ | None -> ()
+ | Some raw -> (
+ match classify_praef ~weekday:c.c_weekday raw with
+ | None -> unclassifiable := o.date :: !unclassifiable
+ | Some expected ->
+ if c.c_preface = Some expected then ()
+ else
+ match allow_list_id_for_date o.date with
+ | Some id ->
+ Hashtbl.replace explained_counts id (1 + try Hashtbl.find explained_counts id with Not_found -> 0)
+ | None -> unexplained := describe_praef_mismatch o c expected :: !unexplained))
+ ordo colitur;
+ Alcotest.(check (list string))
+ (Printf.sprintf "[%s] every preface mismatch is named in the allow-list -- none unexplained" label)
+ [] (List.rev !unexplained);
+ Alcotest.(check (list string))
+ (Printf.sprintf "[%s] every raw praef value classifies -- none dropped" label)
+ [] (List.rev !unclassifiable);
+ Hashtbl.iter
+ (fun id n ->
+ match List.assoc_opt id by_id with
+ | None -> Alcotest.failf "allow-list entry %s used by the comparator but not declared in %s" id allow_list_path
+ | Some e -> Alcotest.(check int) (Printf.sprintf "%s: expected_rows matches the actual count" id) e.expected_rows n)
+ explained_counts
+ in
+ (* The known Vigil-of-St-Lawrence corpus gap (tools/extract_efdotorg_ordo.py's
+ own header, finding 4; docs/research/ordo/PROVENANCE-ordo-corpus.md;
+ .superpowers/ordo-class-report.md) is RECONFIRMED present, but its
+ effect on THIS axis now differs by year -- checked per date, not
+ assumed uniform, because colitur's own CURRENT behaviour (RG 33's
+ third trigger, which landed after the reports above were written --
+ CLAUDE.md's own warning that `day`/`readings` output is not stable
+ across commits is exactly this) already omits the vigil in SOME of
+ these years for its own, entirely independent reason:
+ - 2025-08-09 (Saturday): colitur's own RG33-third-trigger ALSO
+ omits the vigil this year (RG 78's BVM Saturday Office wins
+ outright), and Rule A (this generator's own finding 6) picks the
+ SAME office from the corpus's own listing -- a full, direct
+ match (office identity AND preface), not a lucky coincidence
+ limited to preface alone.
+ - 2027-08-09 (Monday): colitur's own vigil IS genuinely observed
+ this year (RG33's trigger does not fire); the corpus still shows
+ no trace of it at all -- STILL the known gap, reconfirmed, naming
+ St Romanus instead -- but both the true Vigil (Class3, no
+ title-preface trigger) and the corpus's own substitute fall
+ through RG 482's chain to the identical Common preface, so the
+ identity error stays invisible to this one axis. Asserted
+ POSITIVELY (not merely "no failure occurred") so a future change
+ giving either office a real trigger would surface here.
+ - 2026-08-09 (Sunday): the vigil is impeded on every source (a
+ Sunday outranks a Class3 vigil outright); no special case is
+ needed and none is asserted here. *)
+ let test_known_vigil_of_st_lawrence_shape () =
+ let ordo = ordo_rows fixture_path in
+ let colitur = colitur_rows ~year_lo ~year_hi ~window_first ~window_last in
+ let by_date = List.combine ordo colitur in
+ let find d = List.find_opt (fun ((o : ordo_row), _) -> String.equal o.date d) by_date in
+ let in_window d = String.compare d window_first >= 0 && String.compare d window_last <= 0 in
+ (match find "2025-08-09" with
+ | Some (o, c) when in_window "2025-08-09" ->
+ Alcotest.(check (option string)) "2025-08-09: Rule A selects the BVM Saturday Office directly" (Some "BVM")
+ o.praef;
+ Alcotest.(check (option string))
+ "2025-08-09: colitur's own answer is the same office (RG33 3rd trigger + RG78)" (Some "bvm")
+ (Option.map Preface.to_string c.c_preface)
+ | _ -> ());
+ match find "2027-08-09" with
+ | Some (o, c) when in_window "2027-08-09" ->
+ Alcotest.(check (option string))
+ "2027-08-09: the known gap is reconfirmed -- the corpus still shows no Vigil of St Lawrence"
+ (Some "Common") o.praef;
+ Alcotest.(check (option string))
+ "2027-08-09: colitur's own real Vigil office also reads Common -- the identity error stays invisible here"
+ (Some "common") (Option.map Preface.to_string c.c_preface)
+ | _ -> ()
+ in
+ ( label,
+ [ Alcotest.test_case "fixture SHA-256 matches its provenance note" `Quick test_fixture_checksum;
+ Alcotest.test_case "streams are N rows each, dates aligned 1:1" `Quick test_dates_align;
+ Alcotest.test_case "no-praef population matches Good Friday plus the known source gap" `Quick
+ test_praef_coverage;
+ Alcotest.test_case "every preface mismatch is named in the allow-list -- none unexplained" `Quick
+ test_praef_matches_or_is_explained;
+ Alcotest.test_case "the known Vigil-of-St-Lawrence gap: reconfirmed in 2027, resolved by Rule A in 2025" `Quick
+ test_known_vigil_of_st_lawrence_shape
+ ] )
+
+(* The seven adjudicated root causes behind this comparison's own 11 row-
+ level divergences (three editions combined) -- see
+ data/ef/expected-divergences-efdotorg.sexp for the full citation of
+ each. Date-keyed, the same discipline test_lms_ordo.ml's own
+ [allow_list_id_for_date] and test_fiuv_ordo.ml's own
+ [fiuv_allow_list_id_for_date] use, and for the identical reason: an
+ entry that starts firing on an unexpected new date must be visible as
+ a real change, not silently absorbed. Shared across all three editions
+ (unlike the LMS file's per-window function) because every id here is
+ adjudicated against colitur's own DATA or RULES, not against a single
+ edition's own printing quirk -- E1/E3/E4 each recur, unchanged, in
+ more than one edition. *)
+let allow_list_id_for_date = function
+ | "2025-07-16" | "2026-07-16" | "2027-07-16" -> Some "E1"
+ | "2026-09-24" -> Some "E2"
+ | "2025-11-27" | "2026-11-27" -> Some "E3"
+ | "2025-05-06" | "2026-05-06" -> Some "E4"
+ | "2025-09-29" -> Some "E5"
+ | "2026-11-01" -> Some "E6"
+ | "2025-11-29" -> Some "E7"
+ | _ -> None
+
+let suite_2024_2025 =
+ make_suite ~label:"efdotorg-ordo-2024-2025" ~fixture_path:"fixtures/efdotorg-ordo-2024-2025.sexp"
+ ~fixture_sha256:"7de633e292fdb85415dd3ad87fcf9414fbb8f34c42772663b78e107e68c6e571" ~window_first:"2024-12-01"
+ ~window_last:"2025-11-29" ~year_lo:2023 ~year_hi:2026 ~expected_rows:364 ~expected_no_data:1
+ ~allow_list_id_for_date
+
+let suite_2025_2026 =
+ make_suite ~label:"efdotorg-ordo-2025-2026" ~fixture_path:"fixtures/efdotorg-ordo-2025-2026.sexp"
+ ~fixture_sha256:"c810ec437cae9fde785f6b3453f77e667644fb82f465b3652040bb2a3b888b9b" ~window_first:"2025-11-30"
+ ~window_last:"2026-11-28" ~year_lo:2024 ~year_hi:2027 ~expected_rows:364 ~expected_no_data:1
+ ~allow_list_id_for_date
+
+let suite_2026_2027 =
+ make_suite ~label:"efdotorg-ordo-2026-2027" ~fixture_path:"fixtures/efdotorg-ordo-2026-2027.sexp"
+ ~fixture_sha256:"19c35b45d7964dd357f92a871e81423af8217dff6c40178f3a95d3ec9c4091ea" ~window_first:"2026-11-29"
+ ~window_last:"2027-11-27" ~year_lo:2025 ~year_hi:2028 ~expected_rows:364 ~expected_no_data:2
+ ~allow_list_id_for_date
+
+(* Guards against a stale allow-list entry, the same discipline
+ test_lms_ordo.ml's own [test_allow_list_ids_are_exactly_l4] uses: a
+ STATIC check that the sexp file's own declared ids are exactly this
+ set -- not a cross-edition "did every id actually fire" proof (neither
+ test_lms_ordo.ml nor test_fiuv_ordo.ml's own analogous guard attempts
+ that either; each edition's own [test_praef_matches_or_is_explained]
+ already fails loudly if an id fires the WRONG number of times, or if a
+ real divergence names an id that does not exist at all). *)
+let test_allow_list_ids_are_exactly_e1_to_e7 () =
+ let ids = List.sort String.compare (List.map (fun e -> e.id) (load_allow_list allow_list_path)) in
+ Alcotest.(check (list string)) "the efdotorg allow-list declares exactly E1..E7"
+ [ "E1"; "E2"; "E3"; "E4"; "E5"; "E6"; "E7" ] ids
+
+let suite_allow_list =
+ ( "efdotorg-ordo-allow-list",
+ [ Alcotest.test_case "declares exactly E1..E7" `Quick test_allow_list_ids_are_exactly_e1_to_e7 ] )