diff options
Diffstat (limited to 'lib/kernel/validate.ml')
| -rw-r--r-- | lib/kernel/validate.ml | 60 |
1 files changed, 59 insertions, 1 deletions
diff --git a/lib/kernel/validate.ml b/lib/kernel/validate.ml index ffeb89e..1a0248b 100644 --- a/lib/kernel/validate.ml +++ b/lib/kernel/validate.ml @@ -388,5 +388,63 @@ let run (rite : ('s, 'r) Rite.t) (layer : 'r Layer.t) ~year = "admit is not a fixed point on this day's own commemorations: re-offering %d \ admitted %d back" (List.length offered) (List.length readmitted))) - resolved); + resolved; + (* ---- Citation invariants (the lectionary, Plan 4) ---- + + RITE-AGNOSTIC BY CONSTRUCTION, and this gating is the whole reason + these can live in the kernel beside the temporal checks rather than + in EF's own tests: a rite whose lectionary is not built yet returns + [] from {!Rite.readings} on every day, so [year_has_citations] is + false and not one of these fires. A rite that computes readings AT + ALL is held to all of them, on every day of its year. That is also + why test_validate.ml's own synthetic rite ([readings] a constant + [], "a harmless placeholder" in its own words) stays unaffected -- + checked, not assumed: its negative-path fixtures would otherwise + fail on every day of the year rather than on the one invariant each + is built to violate. + + The single [match] below expresses THREE invariants at once, which + is why it is a match on the sorted part list rather than three + separate tests: + + - zero or two, never one -- an Epistle without a Gospel, or the + reverse, is a malformed Mass, not a partial one; + - only [First] and [Gospel] ever appear -- the chants (Psalm, + Second, Tract, Alleluia, Sequence) are deliberately unbuilt, + with no source and no oracle, so a citation carrying one is a + defect and not a feature arriving early; + - no part appears twice -- two Epistles and no Gospel has length + two and would slip past a bare cardinality test. + + [Citation.part]'s own constructor order puts [First] before + [Gospel], so the sorted well-formed list is literally + [[First; Gospel]] and nothing else. *) + let year_has_citations = + Array.exists + (fun (d : ('s, 'r) Liturgical_day.t) -> d.Liturgical_day.citations <> []) + resolved + in + if year_has_citations then + Array.iter + (fun (d : ('s, 'r) Liturgical_day.t) -> + let date = d.Liturgical_day.date in + let parts = + List.map (fun (c : Citation.t) -> c.Citation.part) d.Liturgical_day.citations + in + match List.sort compare parts with + | [ Citation.First; Citation.Gospel ] -> () + | [] -> + (* Separate check name from the malformed case below on + purpose: this is the lectionary chain falling through + every one of its steps and resolving nothing, a COVERAGE + gap, where the other is a WELL-FORMEDNESS one. They want + different fixes and should be countable apart. *) + fail date "citations-unresolved" + "no reading citations resolved for this day: the lectionary chain fell through \ + every step" + | sorted -> + fail date "citations" + (Printf.sprintf "expected exactly one First and one Gospel, got [%s]" + (String.concat "," (List.map Citation.part_to_string sorted)))) + resolved); List.rev !failures |
