aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 13:48:08 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 13:48:08 +0200
commit94ad73cf74d7f1cf02913e2f462c01028a8ef4b4 (patch)
tree6b184dc0563f8eb43867ca4f8060230f21dedc47
parentff7fa965d68631b5caac771d6db3a0f2110cb5dd (diff)
downloadcolitur-94ad73cf74d7f1cf02913e2f462c01028a8ef4b4.tar.gz
colitur-94ad73cf74d7f1cf02913e2f462c01028a8ef4b4.zip
fix(kernel): Mass_formulary.t.said is honestly optional -- was false for Votive
The .mli promised said is "the slug whose Mass is said". For Votive (RG 78/309(a), the Saturday votive Mass of Our Lady) it was set to the day's own ferial slug -- whose Mass is exactly the one NOT said. A consumer joining rubrics to readings on that slug would silently get the wrong Mass: 2026-01-03 reports ef-christmas-1-saturday, which has zero entries in data/ef/lectionary.sexp, because the citations actually come from bvm_saturday_citations, a season-keyed function with no slug of its own anywhere in the shipped data. Chose the type-honest fix over the interim documentation one: said is now Slug.t option, None exactly for Votive, because there is genuinely no slug in the shipped data this field could report for that one source. Adding real ids for the five seasonal BVM Masses (the reviewer's first option) is out of scope -- a data restructuring this round explicitly does not carry. Threading the office slug through a second field was considered and rejected as redundant: the day's own office is already available on the same Liturgical_day.t via observed.slug, which every caller already has in scope regardless of via, so said does not need to duplicate it. colitur rubrics stays byte-identical: rubrics_line already has d.observed in scope and falls back to its slug when said is None, printing the exact value it always printed for a Votive row (verified directly, diffed against pre-fix output across four years). colitur day/readings are unaffected (neither reads Mass_formulary at all). colitur emit --format sexp's pretty-printed line count for 2027 moved 9011 -> 9025: every day's formulary record widened by said's own extra option wrapping, and to_string_hum wraps by column width. Cosmetic only, diffed line by line to confirm every change is this shape or a consequent wrap shift; recorded in test/cli.t alongside the 476(f) note it now sits next to.
-rw-r--r--bin/main.ml18
-rw-r--r--lib/kernel/mass_formulary.ml4
-rw-r--r--lib/kernel/mass_formulary.mli29
-rw-r--r--lib/rites/rite_ef/lectionary_ef.ml30
-rw-r--r--lib/rites/rite_ef/lectionary_ef.mli14
-rw-r--r--test/cli.t36
-rw-r--r--test/test_lectionary_ef.ml40
-rw-r--r--test/test_lms_ordo.ml15
-rw-r--r--test/test_mass_formulary.ml16
-rw-r--r--test/test_validate.ml2
10 files changed, 153 insertions, 51 deletions
diff --git a/bin/main.ml b/bin/main.ml
index f957190..582c776 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -397,13 +397,27 @@ let readings_line ~lang ~sigla (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.r
least surprising choice for a machine-readable field). Unlike
[formulary], [d.creed] is a plain [bool] with no [option] to guard: a
rite that has not implemented the rule answers [false] outright, so
- there is no third "unknown" state this column could ever need to print. *)
+ there is no third "unknown" state this column could ever need to print.
+
+ Whole-branch review fix round: {!Colitur_kernel.Mass_formulary.t.said}
+ itself gained an [option] (its own citation has the full account --
+ [None] exactly for [Votive], where the shipped data genuinely names no
+ slug for the Mass actually said). This column's own OUTPUT does not
+ change for that reason: when [said] is [None] it falls back to
+ [d.observed]'s own slug -- the SAME value this column always printed
+ for a [Votive] day before [said] became honest, and it is a value this
+ function already has in scope regardless of [via]. So this is not
+ "print a placeholder for the missing case", it is "the value was
+ already available from a different field, and still is". *)
let rubrics_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t)
=
let said, via =
match d.Colitur_kernel.Liturgical_day.formulary with
| Some f ->
- ( Colitur_kernel.Slug.to_string f.Colitur_kernel.Mass_formulary.said,
+ ( Colitur_kernel.Slug.to_string
+ (match f.Colitur_kernel.Mass_formulary.said with
+ | Some s -> s
+ | None -> d.Colitur_kernel.Liturgical_day.observed.Colitur_kernel.Celebration.slug),
Colitur_kernel.Mass_formulary.source_to_string f.Colitur_kernel.Mass_formulary.via )
| None -> ("-", "-")
in
diff --git a/lib/kernel/mass_formulary.ml b/lib/kernel/mass_formulary.ml
index cb3532b..60f9f1c 100644
--- a/lib/kernel/mass_formulary.ml
+++ b/lib/kernel/mass_formulary.ml
@@ -1,5 +1,7 @@
+open Sexplib0.Sexp_conv
+
type source = Proper | Own_slug | Preceding_sunday | Common | Votive [@@deriving sexp]
-type t = { said : Slug.t; via : source } [@@deriving sexp]
+type t = { said : Slug.t option; via : source } [@@deriving sexp]
let source_to_string = function
| Proper -> "proper"
diff --git a/lib/kernel/mass_formulary.mli b/lib/kernel/mass_formulary.mli
index a2faf64..b990e69 100644
--- a/lib/kernel/mass_formulary.mli
+++ b/lib/kernel/mass_formulary.mli
@@ -27,10 +27,31 @@ type source =
is the RG 78/309(a) Saturday Mass of Our Lady. *)
[@@deriving sexp]
-type t = { said : Slug.t; via : source } [@@deriving sexp]
+type t = { said : Slug.t option; via : source } [@@deriving sexp]
-(** The slug whose Mass is said. For {!Proper}, {!Own_slug} and {!Votive}
- this is the day's own; for {!Preceding_sunday} it is that Sunday's
- TEMPORAL slug; for {!Common} it is the Common's own id. *)
+(** The slug whose CITATIONS were actually used to build the day's
+ readings, when the shipped data can name one.
+
+ [Some] for four of the five sources: {!Proper} and {!Own_slug} each
+ carry the day's own slug; {!Preceding_sunday} carries that Sunday's
+ TEMPORAL slug; {!Common} carries the Common's own id.
+
+ [None] for {!Votive}, and ONLY for {!Votive} (whole-branch review fix
+ round, celebrant-rubrics-phase1): RG 309(a)'s five seasonal "Missae de
+ sancta Maria in sabbato" carry no slug of their own anywhere in the
+ shipped data -- their citations come from a season-keyed function
+ ({!Rite_ef.Lectionary_ef.bvm_saturday_citations}), never from a
+ [Lectionary.find] against any slug, so there is genuinely no slug this
+ field could honestly report. An earlier version of this type set
+ [said] to the day's own OFFICE slug for {!Votive} too (RG 78's Office
+ of Our Lady, kept unchanged under the votive Mass) -- readable at the
+ call site as "this is the slug whose Mass is said", which is false for
+ exactly this one case: that slug's own [Lectionary] entry, if it has
+ one at all, is NOT what the day's citations came from. [None] says so
+ directly instead of silently mis-naming a Mass. The day's own OFFICE
+ is not lost by this change -- it is still on the very same
+ {!Liturgical_day.t} this formulary lives on, via [observed.slug],
+ which every caller already has in scope regardless of [via]; this
+ field does not need to duplicate it. *)
val source_to_string : source -> string
diff --git a/lib/rites/rite_ef/lectionary_ef.ml b/lib/rites/rite_ef/lectionary_ef.ml
index 1fba3de..b996412 100644
--- a/lib/rites/rite_ef/lectionary_ef.ml
+++ b/lib/rites/rite_ef/lectionary_ef.ml
@@ -260,7 +260,7 @@ let is_bvm_saturday_office (observed : Vocab_ef.rank Celebration.t)
let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
match observed.Celebration.citations with
| _ :: _ as cs ->
- (Some { Mass_formulary.said = observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
+ (Some { Mass_formulary.said = Some observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
| [] -> (
(* Step 4: a saint who is the day's observed office and has no proper
says his assigned Common. The assignment is explicit, never
@@ -357,7 +357,7 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
if sanctoral_office then commons_for ~commons observed.Celebration.slug else None
with
| Some (common_id, cs) ->
- (Some { Mass_formulary.said = common_id; via = Mass_formulary.Common }, cs)
+ (Some { Mass_formulary.said = Some common_id; via = Mass_formulary.Common }, cs)
| None -> (
(* The votive Mass of Our Lady on Saturday (RG 309(a)) runs HERE:
after the proper (step 1) and the Common (step 4), which answer
@@ -407,23 +407,29 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
[bvm_saturday_citations]'s own season-keyed SELECTION, not
merely that some BVM Mass was chosen.
- [said] is UNCHANGED by this correction and stays the day's own
- temporal slug: [is_bvm_saturday_office] only ever fires when
- [sanctoral_office] above is false, i.e. the observed celebration
- already IS the day's own temporal office (the office
+ [said] used to be set HERE to the day's own temporal slug
+ (reasoning: [is_bvm_saturday_office] only ever fires when
+ [sanctoral_office] above is false, i.e. the observed
+ celebration already IS the day's own temporal office, which
deliberately reuses the ordinary ferial slug, [Temporal_ef]'s
- own [bvm_saturday_names]) -- only [via] needed correcting, the
- office/Mass split [Votive] exists to name. *)
+ own [bvm_saturday_names]). CORRECTED (whole-branch review fix
+ round): that value is the OFFICE's slug, not the slug whose
+ Mass is actually said -- the five seasonal Masses RG 309(a)
+ names have no slug of their own anywhere in the shipped data,
+ so [said] is [None] here, honestly, rather than silently
+ naming the wrong thing. See {!Colitur_kernel.Mass_formulary.t}'s
+ own [said] citation for the full account; the office itself is
+ not lost, it is still [observed.slug] on the very same
+ {!Colitur_kernel.Liturgical_day.t} this formulary lives on. *)
if is_bvm_saturday_office observed temporal then
- let said = temporal.Temporal.office.Celebration.slug in
- ( Some { Mass_formulary.said; via = Mass_formulary.Votive },
+ ( Some { Mass_formulary.said = None; via = Mass_formulary.Votive },
bvm_saturday_citations temporal.Temporal.season ~month:(Date.month date)
~day:(Date.day date) )
else
match Lectionary.find lectionary temporal.Temporal.office.Celebration.slug with
| Some cs ->
( Some
- { Mass_formulary.said = temporal.Temporal.office.Celebration.slug;
+ { Mass_formulary.said = Some temporal.Temporal.office.Celebration.slug;
via = Mass_formulary.Own_slug },
cs )
| None -> (
@@ -478,5 +484,5 @@ let readings ~lectionary ~commons ~observed ~temporal ~date ~temporal_at =
with
| Some cs ->
let said = sunday_temporal.Temporal.office.Celebration.slug in
- (Some { Mass_formulary.said; via = Mass_formulary.Preceding_sunday }, cs)
+ (Some { Mass_formulary.said = Some said; via = Mass_formulary.Preceding_sunday }, cs)
| None -> (None, []))))
diff --git a/lib/rites/rite_ef/lectionary_ef.mli b/lib/rites/rite_ef/lectionary_ef.mli
index 0797c0d..715e9c2 100644
--- a/lib/rites/rite_ef/lectionary_ef.mli
+++ b/lib/rites/rite_ef/lectionary_ef.mli
@@ -123,11 +123,15 @@ val commons_for : commons:Commons.t -> Slug.t -> (Slug.t * Citation.t list) opti
naming which of the five seasonal Masses is said, not a marker of the
Mass's kind; see the implementation comment on this branch for the
full account and the validation use this correction still leaves
- available for Task 6.) [said] is still that slug: only the source
- constructor differs from an ordinary Step 2 lookup, because the guard
- that reaches this branch only ever fires when the observed celebration
- already IS the day's own temporal office -- see the implementation
- comment on that branch.
+ available for Task 6.) [said] is [None] here (whole-branch review fix
+ round, celebrant-rubrics-phase1): the five seasonal Masses RG 309(a)
+ names carry no slug of their own anywhere in the shipped data, so
+ there is no slug this field could honestly report -- see
+ {!Colitur_kernel.Mass_formulary.t}'s own [said] citation for the full
+ account. The day's own OFFICE (the guard that reaches this branch
+ only ever fires when the observed celebration already IS the day's
+ own temporal office) is unaffected and is still [observed.slug] on
+ the same {!Colitur_kernel.Liturgical_day.t}.
- {b Step 3} -- for a weekday whose own slug has no entry, the preceding
Sunday's temporal slug (never its observed one; a Sunday is guarded
out because it has no PRECEDING Sunday to resume, not because
diff --git a/test/cli.t b/test/cli.t
index e2d2eb5..971e442 100644
--- a/test/cli.t
+++ b/test/cli.t
@@ -534,20 +534,32 @@ CSV run rather than one per year:
$ colitur emit --format csv --from 2027 --to 2028 | wc -l
732
-sexp and xml are also available. This line count moved 9010 -> 9011
-(whole-branch review fix round, RG 476(f)): [emit --format sexp] pretty-
-prints with [Sexplib.Sexp.to_string_hum], a column-width wrapping printer,
-not a fixed-shape one -- 2027's All Souls' Day (2 November) record grew a
-single wrapped line when its own [creed] field's value changed from
-[true] to [false] (RG 476(f), the Creed is never said at a Requiem Mass;
-see rubrics_ef.ml), because "false" is one character longer than "true"
-and pushed that one line's rendered width over to_string_hum's own wrap
-threshold. Purely cosmetic -- the record's DATA is unchanged in every
-other field, and this is not a claim that [emit]'s FORMAT changed, only
-that one record's pretty-printed SHAPE did:
+sexp and xml are also available. This line count moved twice in the same
+whole-branch review fix round, for two different reasons, both cosmetic:
+[emit --format sexp] pretty-prints with [Sexplib.Sexp.to_string_hum], a
+column-width wrapping printer, not a fixed-shape one, so any change to a
+value's own rendered WIDTH can shift where it wraps.
+
+9010 -> 9011 (RG 476(f)): 2027's All Souls' Day (2 November) record grew
+a single wrapped line when its own [creed] field's value changed from
+[true] to [false] (the Creed is never said at a Requiem Mass; see
+rubrics_ef.ml), because "false" is one character longer than "true" and
+pushed that one line's rendered width over the wrap threshold.
+
+9011 -> 9025 (Mass_formulary.t.said honesty, Fix 3): [said] gained an
+[option] (its own .mli has the full account) -- every day's formulary
+record now prints [(said (<slug>))] instead of [(said <slug>)], one
+character wider, and a [Votive] day (the RG 78 Saturday Mass of Our
+Lady) prints [(said ())] instead of naming a slug at all, since the
+data genuinely names none for the Mass actually said. Both changes ripple
+across many lines' own wrap points, not just the days whose DATA changed
+-- confirmed directly (diffed the full sexp output line by line): every
+difference is exactly this [said] shape change or a consequent wrap
+shift, nothing else. Not a claim that [emit]'s FORMAT changed, only that
+individual records' pretty-printed SHAPE did:
$ colitur emit --format sexp --from 2027 --to 2027 | wc -l
- 9011
+ 9025
$ colitur emit --format xml --from 2027 --to 2027 | head -2
<?xml version="1.0" encoding="UTF-8"?>
diff --git a/test/test_lectionary_ef.ml b/test/test_lectionary_ef.ml
index 04076c7..3e0880e 100644
--- a/test/test_lectionary_ef.ml
+++ b/test/test_lectionary_ef.ml
@@ -591,22 +591,22 @@ let test_commons_load_rejects_bad_data () =
let formulary_cases =
[ (* step 1: a saint with his own proper -- same date as
[test_step1_proper_beats_any_common_john_of_god]. *)
- (2038, 3, 8, "john-of-god", Colitur_kernel.Mass_formulary.Proper);
+ (2038, 3, 8, Some "john-of-god", Colitur_kernel.Mass_formulary.Proper);
(* step 2: the day's own temporal slug -- same date as
[test_step2_lenten_feria_has_its_own], Monday of Lent I. *)
- (2026, 2, 23, "ef-lent-1-monday", Colitur_kernel.Mass_formulary.Own_slug);
+ (2026, 2, 23, Some "ef-lent-1-monday", Colitur_kernel.Mass_formulary.Own_slug);
(* step 3: a feria resuming the preceding Sunday. The task brief's own
snippet pinned this date against week 9 ("ef-time-after-pentecost-
sunday-9"); running the real resolver against 2026 shows 3 August
2026 is Monday of week 10, resuming 2 August's "...sunday-10" --
corrected per the brief's own "find the dates by running the current
binary if they drift" instruction. *)
- (2026, 8, 3, "ef-time-after-pentecost-sunday-10",
+ (2026, 8, 3, Some "ef-time-after-pentecost-sunday-10",
Colitur_kernel.Mass_formulary.Preceding_sunday);
(* step 4: a saint sent to a Common -- same date as
[test_step4_commons_perpetua_and_felicity]; [said] is the Common's
OWN id (data/ef/commons.sexp), not the saint's slug. *)
- (2038, 3, 6, "common-of-non-virgins-1", Colitur_kernel.Mass_formulary.Common);
+ (2038, 3, 6, Some "common-of-non-virgins-1", Colitur_kernel.Mass_formulary.Common);
(* Fix round 1 (coordinator review): the RG 309(a)/RG 78 Saturday votive
Mass of Our Lady, structurally reached between steps 4 and 2 (see
[readings]' own implementation comment) but tagged [Votive], not
@@ -623,9 +623,16 @@ let formulary_cases =
2026 verified directly against the real resolver (`colitur day
2026`), not trusted from a supplied date, given this session's own
drifted-pin history: a IV-class Saturday, "Officium sanctae Mariae in
- sabbato", temporal slug [ef-time-after-pentecost-9-saturday]. [said]
- is unaffected by the retag and stays that same (reused ferial) slug. *)
- (2026, 8, 1, "ef-time-after-pentecost-9-saturday", Colitur_kernel.Mass_formulary.Votive) ]
+ sabbato", temporal slug [ef-time-after-pentecost-9-saturday].
+ (CORRECTED, whole-branch review fix round: [said] used to be
+ claimed "unaffected by the retag" and pinned to that same reused
+ ferial slug -- that was the defect this round fixed. [said] is
+ [None] here: the shipped data names no slug for the votive Mass
+ actually said, only for the office it replaces. The office slug
+ itself is checked separately, below, via [observed.slug], not
+ through [said] -- see {!Colitur_kernel.Mass_formulary.t}'s own
+ citation for why the two are no longer conflated.) *)
+ (2026, 8, 1, None, Colitur_kernel.Mass_formulary.Votive) ]
let test_formulary_reports_its_source () =
List.iter
@@ -634,10 +641,10 @@ let test_formulary_reports_its_source () =
match day.Colitur_kernel.Liturgical_day.formulary with
| None -> Alcotest.failf "%04d-%02d-%02d: no formulary" y m d
| Some f ->
- Alcotest.(check string)
+ Alcotest.(check (option string))
(Printf.sprintf "%04d-%02d-%02d slug" y m d)
expected_slug
- (Colitur_kernel.Slug.to_string f.Colitur_kernel.Mass_formulary.said);
+ (Option.map Colitur_kernel.Slug.to_string f.Colitur_kernel.Mass_formulary.said);
Alcotest.(check string)
(Printf.sprintf "%04d-%02d-%02d source" y m d)
(Colitur_kernel.Mass_formulary.source_to_string expected_via)
@@ -645,6 +652,17 @@ let test_formulary_reports_its_source () =
f.Colitur_kernel.Mass_formulary.via))
formulary_cases
+(* [said = None] on the Votive day above does not mean the office is lost --
+ {!Colitur_kernel.Mass_formulary.t}'s own citation says a caller reads it
+ off [observed.slug] instead, on the very same {!Colitur_kernel.
+ Liturgical_day.t}. Checked directly, not merely asserted: the same
+ 1 August 2026 date, same expected slug the old (pre-fix) [said] field
+ used to carry. *)
+let test_votive_office_slug_still_available_via_observed () =
+ let d = day 2026 8 1 in
+ Alcotest.(check string) "2026-08-01 observed slug" "ef-time-after-pentecost-9-saturday"
+ (Colitur_kernel.Slug.to_string d.Colitur_kernel.Liturgical_day.observed.Colitur_kernel.Celebration.slug)
+
let suite =
[ ("step 1: sanctoral proper", `Quick, test_step1_sanctoral_proper);
("step 2: own temporal proper", `Quick, test_step2_lenten_feria_has_its_own);
@@ -685,4 +703,6 @@ let suite =
("Commons.load rejects the four silent-degradation defects", `Quick,
test_commons_load_rejects_bad_data);
("the formulary reports its own source, one day per step", `Quick,
- test_formulary_reports_its_source) ]
+ test_formulary_reports_its_source);
+ ("the Votive office slug is still available via observed, not said", `Quick,
+ test_votive_office_slug_still_available_via_observed) ]
diff --git a/test/test_lms_ordo.ml b/test/test_lms_ordo.ml
index cff4b3d..a728cbc 100644
--- a/test/test_lms_ordo.ml
+++ b/test/test_lms_ordo.ml
@@ -302,7 +302,9 @@ let describe_creed_mismatch (o : ordo_row) (c : colitur_row) =
Printf.sprintf "%s %S: colitur creed=%b, Ordo creed=%b (formulary=%s)" o.date o.title c.c_creed
(Option.get o.creed)
(match c.c_formulary with
- | Some f -> Colitur_kernel.Slug.to_string f.MF.said
+ | Some { MF.said = Some s; _ } -> Colitur_kernel.Slug.to_string s
+ | Some { MF.said = None; via = MF.Votive } -> "votive (said unnamed in the data)"
+ | Some { MF.said = None; _ } -> "NONE (said, unexpectedly outside Votive)"
| None -> "NONE")
(* The core assertion: every Creed divergence, over all 400 days (Good
@@ -587,7 +589,16 @@ let test_formulary_override_matches () =
:: !bad
| Some { MF.via = MF.Preceding_sunday; said } -> (
bump "preceding_sunday";
- let slug = Colitur_kernel.Slug.to_string said in
+ (* [said] is [Some] for every constructor except [Votive] (see
+ {!Colitur_kernel.Mass_formulary.t}'s own citation) -- a bare
+ [Option.get] here would raise an unhelpful exception if that
+ ever stopped being true; [Alcotest.failf] names the day
+ instead. *)
+ let slug =
+ match said with
+ | Some s -> Colitur_kernel.Slug.to_string s
+ | None -> Alcotest.failf "%s: Preceding_sunday day with said = None (should be impossible)" o.date
+ in
let ordo_says = Printf.sprintf "Mass of %s" in
(* Try the generic [ef-<season>-sunday-<n>] shape first; fall back
to the one NAMED Sunday that also reaches this population
diff --git a/test/test_mass_formulary.ml b/test/test_mass_formulary.ml
index 824e0c7..1316e47 100644
--- a/test/test_mass_formulary.ml
+++ b/test/test_mass_formulary.ml
@@ -4,9 +4,19 @@ module Slug = Colitur_kernel.Slug
let slug s = Slug.of_string_exn s
let test_round_trips_through_sexp () =
- let f = { MF.said = slug "ef-time-after-pentecost-sunday-9"; via = MF.Preceding_sunday } in
+ let f = { MF.said = Some (slug "ef-time-after-pentecost-sunday-9"); via = MF.Preceding_sunday } in
let f' = MF.t_of_sexp (MF.sexp_of_t f) in
- Alcotest.(check string) "slug survives" (Slug.to_string f.MF.said) (Slug.to_string f'.MF.said);
+ Alcotest.(check (option string)) "slug survives"
+ (Option.map Slug.to_string f.MF.said) (Option.map Slug.to_string f'.MF.said);
+ Alcotest.(check bool) "source survives" true (f.MF.via = f'.MF.via)
+
+(* [said] is [None] exactly for {!MF.Votive} -- see the .mli's own citation.
+ Round-tripped separately so the [None] case has its own witness, not only
+ inferred from the [Some] case above. *)
+let test_none_said_round_trips_through_sexp () =
+ let f = { MF.said = None; via = MF.Votive } in
+ let f' = MF.t_of_sexp (MF.sexp_of_t f) in
+ Alcotest.(check bool) "said stays None" true (f'.MF.said = None);
Alcotest.(check bool) "source survives" true (f.MF.via = f'.MF.via)
(* [to_string] is what an ordo line shows. It names the SOURCE, not the slug,
@@ -25,5 +35,7 @@ let test_to_string_names_the_source () =
let suite =
( "Mass_formulary",
[ Alcotest.test_case "sexp round-trips" `Quick test_round_trips_through_sexp;
+ Alcotest.test_case "sexp round-trips, said = None (Votive)" `Quick
+ test_none_said_round_trips_through_sexp;
Alcotest.test_case "source_to_string names each source" `Quick
test_to_string_names_the_source ] )
diff --git a/test/test_validate.ml b/test/test_validate.ml
index ea8fc0f..1ceaca2 100644
--- a/test/test_validate.ml
+++ b/test/test_validate.ml
@@ -321,7 +321,7 @@ module Synthetic = struct
(* The formulary equivalent of [well_formed_citations] above -- shape only,
never rubrically meaningful content. *)
let well_formed_formulary =
- { Colitur_kernel.Mass_formulary.said = Slug.of_string_exn "syn-formulary";
+ { Colitur_kernel.Mass_formulary.said = Some (Slug.of_string_exn "syn-formulary");
via = Colitur_kernel.Mass_formulary.Own_slug }
(* No fixture here exercises the Creed rubric -- a rite that has not