| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
35% of OF citation fields (259/730 on colitur readings --rite of 2026)
printed unconverted -- 1 John renders on 2 January but not 3 January.
Two independent causes, both in the citation/siglum path, neither in
the OF data itself:
1. Book.table only ever surveyed the three EF citation-bearing files,
so 345 references citing a book no EF file happens to use (Job,
Ruth, Judges, 1/2 Samuel, 1/2 Chronicles, 1/2 Maccabees, Baruch,
Ecclesiastes, Habakkuk, Haggai, Nahum, Zechariah, Zephaniah,
Deuteronomy, Amos, Micah, Lamentations, Ezra, Joshua, 2/3 John,
Jude, Philemon, plus "Isaiah"/"Jeremiah"/"Ezekiel"/"Malachi"/"Mat"/
"The Acts"/"Tobit"/"Song of Solomon" spelling variants of books EF
already knows) failed as "unknown book". Added as a new, separate
of_lectionary_table rather than folded into the EF-surveyed table:
none of these 25 new books is attested in the EF's own scans, and
lang/la.ini's own header refuses to fabricate an uncited Latin
title, so they are resolvable (parse + render, falling back to
their own English spelling) but deliberately excluded from Book.all
-- test_lang_coverage.ml's la.ini-completeness promise is preserved
exactly for the ids it already covered, not silently weakened.
2. Parse's grammar could not read a verse number carrying a lectionary
sub-verse letter ("11a", "1bcde") at all -- the dominant remaining
failure shape once (1) was fixed. verse_range now carries a
verse_num { n; suffix } on each boundary, PRESERVED through
rendering rather than dropped (dropping would silently lose real
precision the source text carries). Chapter numbers are untouched
(nothing in the data ever attaches a letter to one).
A third, subtler bug surfaced by (1): registering "jude"/"philemon"/
"2 John"/"3 John" exposed Parse's existing "leading comma-number is a
chapter" heuristic misreading a single-chapter book's bare verse list
("Jude 17,20b-25") as chapter 17 -- a wrong PARSE, worse than the
previous safe "unknown book" failure. Book.is_single_chapter now tells
Parse to skip that heuristic for the four one-chapter books and default
to chapter 1.
Residual, honestly enumerated rather than forced to zero: 41 distinct
references (of 1540) are hyphenated ranges crossing a chapter boundary
("2:29-3:6") -- a Parse.t shape verse_range/part do not represent, a
type restructuring deliberately not attempted this task. Pinned exactly
by the new test_citation_coverage_of.ml, both directions (a new failure
or one of these 41 starting to convert both go red), and disclosed in
data/of/lectionary.sexp's own regenerated provenance header (tools/
bootstrap_lectionary_of.ml now runs the same parser at generation time
and names the count and the set).
Verified EF-unaffected: git diff v1.0.0..HEAD -- lib/kernel/
lib/rites/rite_ef/ data/ef/ is empty, and `colitur day`/`readings`
output for 2027 is byte-identical against the pre-fix binary.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
golden pins
Task 6 (2026-08-25-colitur-of-phases-3-5), the final task of OF Phases 3-5.
Layer 2 (test/test_validate_of.ml, new): Colitur_kernel.Validate.run
against the real, assembled Rite_of.context -- landmark years, the 9999
domain edge, a 200-year QCheck sample, and the full 8417-year exhaustive
sweep under COLITUR_EXHAUSTIVE_SWEEP=1, all filtered through the same
three pinned-not-fixed gaps test_rite_of.ml already names (Normae n.35(a)
Holy Family fallback, the 25-December lectionary gap, St Joseph/Palm
Sunday), duplicated verbatim rather than shared so this file's own
filtering cannot drift from Task 5's silently. Two dedicated 200-sample
properties confirm Ordinary Time weeks are 1..34 with the final week
always 34, and Calendar.year covers its own liturgical year exactly once
with no gaps, both through the resolved pipeline (a genuinely different
code path from Phase 1's own already-exhaustive Temporal_of-level
property). An early version folded a second Calendar.year pass into the
exhaustive loop for these two properties too and measured ~365s for that
alone (a scratch timing harness, not committed); dropped in favour of one
Val.run-only exhaustive loop (~200s), matching the EF harness's shape
(one property, one sweep) rather than tripling it.
test/test_litcal_of.ml extended beyond season/week to GRADE (litcal's own
grade_lcl bucketed against the real Tabula band Precedence_of.band
assigns the day's observed office) and IDENTITY (observed slug against a
hand-verified event_key -> slug table for the closed 46-entity FIXED/
NAMED set -- deliberately not the numbered Sunday/weekday series, a
stated, counted scope limit). Both follow the counted-and-allow-listed
discipline: 1800/4346 rows compared for grade (1763 match, 37 allow-
listed across 5 new entries), 508/4346 for identity (505 match, 3 allow-
listed across 3 new entries), zero unexplained either way. Real
divergences found and adjudicated, not suppressed: litcal's own grade
vocabulary conflates Tabula I.2/I.3 for Trinity/Corpus Christi (verdict
colitur); litcal's Mary Magdalene data predates the 2016 CDW decree
(verdict litcal); a genuine, unadjudicated Sacred-Heart/Nativity-of-John-
the-Baptist tie-break at equal band, found live at the one Easter date in
this fixture's window that creates it (verdict open, no citation found
either way); a second instance of precedence_of.mli's own documented
forward-only transfer_target limitation, on a date shape the mli's own
worked example does not cover (verdict colitur, known limitation).
test/test_golden_of.ml (new, 7 pins): a year Ordinary Time skips a week
(2026) plus Mary Mother of the Church same date; Advent IV on Christmas
Eve in two independent years (2028, 2034); the Annunciation transferred
out of Holy Week to Easter+8 (2027, both ends of the transfer pinned);
All Souls; the Sacred Heart; 17 December's date-keyed reading in two
civil years/weekday-cycle letters.
Every new layer mutation-proved: reverting the Advent-IV fix reddens two
golden pins; corrupting Precedence_of.band's Tabula I.3 value reddens the
litcal grade comparator across every Solemnity in the fixture; renaming
one temporal slug reddens the litcal identity comparator; dropping
Ordinary Time's second season_runs entry reddens both the landmark-years
test and the sampled property. Each mutation applied to lib/rites/rite_of/,
confirmed red, then reverted -- git diff --stat lib/ is empty.
make check (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force): exit 0, 890
tests, 363.5s. EF byte-identity holds: git diff --stat v1.0.0..HEAD --
lib/rites/rite_ef/ lib/kernel/ data/ef/ is empty.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Task 5 of Phases 3-5 (the payoff task): bundles Tasks 1-4's separately
built pieces (Temporal_of, Precedence_of, Rubrics_of, Lectionary_of)
into a (Vocab_of.season, Vocab_of.rank) Colitur_kernel.Rite.t, and
wires colitur day/readings --rite of <year> into the CLI. Default
stays ef; every pre-existing invocation is byte-identical (asserted
directly in test/cli.t and against a real v1.0.0 build for three
years, plus git diff --stat v1.0.0..HEAD over lib/rites/rite_ef/,
lib/kernel/, data/ef/ is empty).
No kernel change was needed, confirming the spec's central claim.
season_runs needed real thought rather than the brief's own suggested
Vocab_of.seasons: Temporal_of.season's own 'first run'/'second run'
comments confirm Ordinary Time is genuinely interrupted by Lent and
Easter over one liturgical year, so the expected run-length-compressed
sequence names it twice, six runs over five seasons -- exactly the
case Rite.t.season_runs's own doc comment already names as its
illustration for why this field is rite-supplied rather than derived.
easter/fixed_key are as specified, each carrying its own citation
(Gregorian computus; the 2002 Missal's one bissextilis occurrence is a
leap-year table entry, not a kalends-doubling rule, so fixed_key is
identity).
Follows rite_ef.ml's template and its lectionary-parameter lesson
exactly: lectionary is caller-supplied, not closed over.
test/test_rite_of.ml resolves the real assembled rite (calendar-2002
.sexp + all 13 amendment overlays + lectionary.sexp) through Calendar
and Validate.run, the same discipline test_validate.ml/test_rite_ef.ml
already apply to EF. This is the first time rite_of has been checked
this way, and it surfaced two new, real, cited gaps outside this
task's own scope to fix: Temporal_of never reaches Normae n.35(a)'s
30-December Holy Family fallback in a year with no Sunday in 26-31
December (first hit 1583, the domain floor), and Temporal_of.anchors
disagrees with temporal on 24 December when that date is itself a
Sunday (Advent IV outranks the Vigil, first hit 2006). Both pinned as
known-wrong, not fixed, the same treatment the brief's own pre-known
St Joseph/Palm Sunday gap (Normae n.56(f)) gets, pinned on 2062. A
third gap was found in data/of/lectionary.sexp's own coverage (audited
against one civil year only, so the other weekday-cycle letter is
unaudited and at least one pairing is asymmetric on shipped data) --
that file is excluded from this task, so the wide 2005-2050 sweep
checks structural invariants only and leaves the citation-chain checks
to the small, individually-audited landmark-year set.
make check (COLITUR_EXHAUSTIVE_SWEEP=1): exit 0, 867 tests, ~316s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Task 4 of Phases 3-5: the last Rite.t field OF still needed, `readings`.
The cycle rules are CODE, cited to OLM (Ordo Lectionum Missae) 1981
Praenotanda -- verified against the real page image
(docs/research/of/olm-1981.pdf pp.32-33/"XXXII-XXXIII"), not the
document's own unreliable OCR text layer, per the design spec's own
caveat. n. 66 (with its footnote 102's worked example, 1980=C/1981=A/
1982=B/1983=C) gives the three-year Sunday cycle; n. 69 point 4 gives
the two-year weekday cycle, Year I in odd label years, Year II in
even. Both derive the "label year" the same way: one more than the
civil year Advent I opened in, per n. 66's footnote 102 itself
("nempe a prima hebdomada Adventus, quae cadit in anno civili
praecedente") -- so the cycle turns at Advent I, not 1 January, exactly
where the task brief said to get it right. The design spec's own
citation for the weekday rule, "Praenotanda n. 649", does not survive
a direct check against the primary source (search finds "649" only in
unrelated index/page-number entries); corrected here to n. 69, the
paragraph the rule's own Latin text actually sits under, confirmed by
the page image's own printed margin number.
The data is bootstrapped from lectio's of-lectionary.ini (988 keys,
niedziela.pl, harvest 2020-2025) by tools/bootstrap_lectionary_of.ml
-- OCaml, not the brief's own suggested .py: tools/bootstrap_lectionary
.ml already established this exact shape for EF, including a real
safety net ([assert_reachable], sweeping Rite_of.Temporal_of.temporal
AND the real merged sanctoral layer directly) that a second-language
reimplementation would either duplicate or lose. Handles two real
data anomalies found while building it: one malformed ini line (a
Polish Septuagint-numbering annotation for Sirach 3, "Syr 3,2-6.12-14"
against OLM/CEI/USA's shared Nova Vulgata, matching the design spec's
own sec5 finding); and two Sunday-cycle-labelled bases (Annunciation,
Immaculate Conception) where a scraping-year artifact left one of
three cycle letters carrying a different day's Mass entirely --
resolved by a general, mechanical 2-of-3 majority rule, not a
per-saint hand edit, and logged.
Coverage measured in both directions, per the brief's own Step 1:
- 1 of 365 days in civil year 2026 (25 December, the Christmas DAY
Mass) has no Temporal_of-slug match anywhere in lectio's 988 keys --
only the Vigil is present. Named in the data file's own header.
- 0 of 438 distinct lectio bases map to no colitur slug, after 19 are
deliberately excluded (late-Advent/Christmas-season DATE-keyed
duplicates lectio also carries in a WEEKDAY-keyed form colitur's own
slugs actually match -- Lectionary.t has no date-keyed lookup at
all, so the date-keyed family is transcription-inert, not silently
dropped: named explicitly, not a residual).
Lectionary_of.readings is three steps (no Commons indirection, unlike
EF -- out of this task's scope): the observed celebration's own
embedded citations; else, for a real sanctoral winner, its own slug
looked up directly (flat, then both cycle letters); else the day's
own temporal slug, same lookup. No preceding-Sunday fallback: OF's own
two-year cycle assigns every Ordinary Time weekday its own reading by
design, and no OF norm was found instructing otherwise.
Tests (test/test_lectionary_of.ml, 12 cases): a cycle-arithmetic table
straddling Advent 1980/1981/1982/2025 inside single civil years,
reproducing OLM n.66's own worked example directly; SHA-256 pins on
both the emitted file and lectio's own source (embedded in the
provenance header); a pinned, NAMED 2026 coverage result (364 resolve,
exactly {25 December} does not -- never a bare count); two resolution-
chain spot checks (a plain ferial, and a saint's day beating the
ferial it would otherwise fall on); and a real subprocess test that a
missing lectio source file fails the generator loudly, non-zero exit,
no output written -- the exact bug Task 1's own generator shipped and
needed a review round to fix.
dune test: exit 0, 856 tests. make check (COLITUR_EXHAUSTIVE_SWEEP=1,
full 1583-9999 sweep): exit 0, 862 tests, ~342s, both run unpiped in
the foreground. git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/
lib/kernel/ data/ef/ still empty.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Task 3 of OF Phases 3-5: the celebrant-rubrics module for the OF rite,
supplying three of Rite.t's four remaining fields.
creed/gloria implement IGMR n. 53 (Gloria) and n. 67-68 (Symbolum),
both quoted in full in the module header. temporal is read, not
observed, for both Sunday clauses: Temporal_of.temporal tags every
Sunday of Advent/Lent/Easter Sollemnitas (the privileged-season
convention), so an observed.rank test alone would wrongly grant Gloria
on an Advent or Lent Sunday, which n. 53's own "extra tempus Adventus
et Quadragesimae" explicitly withholds. Credo is deliberately narrower
than Gloria: n. 68 has no "et festis" clause, so an ordinary Festum
does not get the Creed, unlike EF's RG 475(b)/(c). Two Missale-proper
exceptions (Holy Thursday evening Mass, the Easter Vigil Mass) are
cited from the Missal's own "Dicitur Gloria in excelsis" rubric, not
the IGMR, which is silent on both. The Requiem question is likewise
IGMR-silent (nn. 379-385 say nothing about Gloria/Credo); flagged as
an inference from the Missal's own consistent marker pattern, applied
only to the one Requiem-shaped slug the shipped OF data can construct
(all-souls), the same closed-population discipline Rubrics_ef's own
Colour.Black proxy uses.
preface returns None unconditionally, a measured verdict rather than
a stub. The module header transcribes the 2002 Missal's own "INDEX
PRAEFATIONUM" in full: nine of Preface.t's 15 EF-cited constructors
name an OF occasion with multiple distinct texts (Nativity x3, Lent
x4-9, Easter x5, Ascension x2, Holy_spirit x2, Bvm x5, Apostles x2,
Common split across two independent 7/8-member families, Requiem x5)
-- structurally lossy to collapse into one constructor. The other six
(Epiphany, Trinity, Sacred_heart, Christ_the_king, St_joseph,
Holy_cross) match cardinality with a single OF text apiece, and were
checked and rejected anyway: Preface.t's own constructors are RG-cited
by construction, and IGMR n. 364's own "quibus Missale Romanum
ditatur" (prefaces the Missal is ENRICHED with) is direct textual
evidence the 2002 collection is not a bare carry-over -- corroborated
directly for Sacred Heart, whose OF text was read and found to be a
different composition from its RG 491 namesake. Reusing any of the
six on cardinality and theme alone would be the exact "sounds similar"
trap the task brief warns against.
test/test_rubrics_of.ml: 26 table-driven cases. Every temporal-origin
row builds observed via Temporal_of.temporal on a real date; every
sanctoral-origin row (a solemnity in Lent, an ordinary Festum, a
weekday obligatory memorial, All Souls) pairs a real Temporal_of.temporal
result with an observed record copied verbatim from
data/of/calendar-2002.sexp, since no full OF Rite.t/Calendar pipeline
exists yet to derive one. Includes same-date Gloria/Credo pairs that
prove the two rules genuinely diverge (a Sunday of Advent, an ordinary
Festum) rather than one silently mirroring the other.
dune test: exit 0, 845 tests. make check (COLITUR_EXHAUSTIVE_SWEEP=1,
full 1583-9999 sweep): exit 0, 851 tests, 303.6s, both run unpiped in
the foreground. git diff --stat v1.0.0..HEAD -- lib/rites/rite_ef/
lib/kernel/ data/ef/ is empty.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds data/of/amendments/*.sexp: 13 overlay files, one per decree,
carrying the General Roman Calendar's amendments since the 2002 typical
edition (docs/research/of/PROVENANCE-amendments.md's own 15-item
inventory, items 10-12 sharing one decree; items 2 and 3, though issued
the same day, carry different protocol numbers and are counted as two
decrees). 14 Add directives (Padre Pio, Juan Diego, Guadalupe, John
XXIII, John Paul II, Mary Mother of the Church, Paul VI, Loreto,
Faustina Kowalska, Gregory of Narek, John of Avila, Hildegard of Bingen,
Teresa of Calcutta, John Henry Newman) and 2 Edit directives (Mary
Magdalene's rank memoria->festum; Martha/Mary/Lazarus's name widened,
same slug, same date and rank -- a continuation of the existing
memorial per the decree's own text, not a suppression and replacement).
Mary, Mother of the Church is the one movable entry (Easter_offset 50,
the Monday after Pentecost) -- verified against the decree's own text
and against Colitur_kernel.Computus.gregorian_easter in three real
years, no kernel change needed.
data/of/calendar-2002.sexp is untouched, per spec sec4.1.
Adds test/test_amendments_of.ml: every file parses, each has a pinned
SHA-256, applying all 13 in decree order yields 222 entries (208 base +
14 Add) with an empty Overlay.merge diagnostics list, and Mary Mother of
the Church resolves to the Monday after Pentecost in three real years.
Two Precedence_of interactions are recorded, not fixed (out of scope for
a data-only task): a movable Memoria_obligatoria can land on an existing
fixed one (verified for 13 June 2011), a genuine band tie Normae n. 14
does not address and Precedence_of.ml does not implement; and adding
Hildegard of Bingen changes which of two ad-libitum memorials on 17
September Precedence.resolve reports as observed, by alphabetical
tie-break alone.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Extracted from the Latin typical edition, which is the authority; lectio's
roman-calendar.ini is a cross-check only and every divergence is adjudicated
against the Missal in the provenance header.
A blank grade column means Memoria ad libitum, per the calendar's own
footnote, rather than a missing value. 206 entries; 1 January, 6 January and
25 December are deliberately excluded because Temporal_of.named already
computes them, and shipping them here too would create a duplicate
candidate for the same day.
Colour is derived from IGMR n. 346, cited per class in the extractor; the
lectio cross-check surfaces one rank divergence (22 July, Mary Magdalene --
lectio reflects the 2016 post-2002 elevation decree, out of scope here) and
17 colour divergences, all adjudicated in the provenance header.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The Tabula dierum liturgicorum's entries 1-4, one band branch each, scaled
x10 as Precedence_ef's own table is. The Triduum is identified structurally
by Easter offset because Temporal_of emits Good Friday and Holy Saturday as
ordinary Lent week-6 ferias, leaving no slug to key on.
Holy Thursday sits at entry 2, not entry 1: the Tabula's own entry 2 runs
'a feria II ad feriam V inclusive' and the Triduum opens with an evening
Mass inside that civil day. Same granularity decision the litcal layer's L1
records on the season axis.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds a sixth-lineage external validation layer for the OF rite module's
Phase 1 temporal cycle, targeting the single highest-risk function that
report named: the two-block Ordinary Time week arithmetic (Normae n. 44),
which has no external witness anywhere in Phase 1's own suite and whose
output selects the Mass formulary, not merely a label.
Witness: litcal (Liturgical Calendar API), Apache-2.0, already archived
and SHA-256-pinned by the sibling lectio project. AELF was considered and
correctly excluded earlier (its CGU forbids redistribution) -- litcal is
what it was replaced with.
- tools/extract_litcal_ordo.py verifies all twelve archived
calendar-YYYY-en.json files (2024-2035; the task brief said eleven,
but twelve are actually pinned in lectio's manifest -- all twelve are
used) against lectio's own manifest.tsv, then builds one fixture
spanning the contiguous 4382-day range those twelve liturgical-year
files cover.
- test/fixtures/litcal-temporal-2024-2035.sexp: one row per day, season
and (where witnessed) Ordinary Time week, with full provenance,
licence, per-year Easter dates and the row-selection rules in its own
header.
- test/test_litcal_of.ml calls Temporal_of.temporal directly (no Rite.t)
and compares 1:1 against the fixture: zero Ordinary Time week
mismatches on all 1876 witnessed days (zero tolerance, no allow-list);
the only season divergence class is the Sacred Triduum, which litcal
tags with a season value colitur's vocabulary deliberately has none of
-- one cited allow-list entry, data/of/expected-divergences-litcal.sexp,
36 rows, exact.
- Mutation-tested: dropping the backward-from-34 adjustment reddens this
layer (1511 of 1876 days) plus three pre-existing Phase 1 tests; the
mutation was not committed.
Does not touch lib/rites/rite_ef/, data/ef/, or lib/rites/rite_of/ --
this validates Phase 1, it does not change it. Full suite: 786 tests via
dune test, 791 via make check, exit 0 both; lib/bin diff empty throughout.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The first code for a second rite. lib/rites/rite_of/vocab_of implements
the OF's five-way rank vocabulary (Sollemnitas/Festum/Memoria_obligatoria/
Memoria_ad_libitum/Feria) and five-season vocabulary (Advent/Christmas/
Lent/Easter/Ordinary_time), each constructor cited against the 2002
Missale Romanum's Normae universales and the Tabula dierum liturgicorum.
temporal_of implements season boundaries, week numbering — including
Ordinary Time's own two-block resumption, derived from the Missale's own
"HEBDOMADA I/XXXIV 'per annum'" headings and empirically verified across
the whole 1583-9999 domain — named temporal days, and Sunday/ferial
slugs. Both satisfy Colitur_kernel.Temporal.RITE with no kernel change.
No Rite.t is assembled and no CLI wiring is added (Phase 2's own scope);
lib/rites/rite_ef and data/ef are untouched, verified byte-identical
against the v1.0.0 tag across a domain-spanning sample.
test/test_temporal_of.ml adds unit tests for every boundary and named day
plus seven QCheck properties (seasons contiguous, Ordinary Time weeks in
1..34, Advent always four Sundays, slug uniqueness, weekday agreement,
determinism, consecutive liturgical years partition civil time), with a
committed exhaustive sweep mirroring test_validate.ml's own convention.
Full account, including the Ordinary Time derivation's evidence and a
list of EF concepts that do not carry over, in
.superpowers/sdd/of-phase1-report.md (untracked, gitignored).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Extends the preface comparison (RG 482-499) with a third, independent
publisher, extraordinaryform.org's three annual Ordo editions
(2024-2025, 2025-2026, 2026-2027), previously declined for two reasons
that are now resolved:
- "Advent" is not a preface: RG 494(b) grants the Trinity preface on
every Advent SUNDAY, RG 498 the residual Common on every Advent
FERIA; this source's own "Advent" label covers both indiscriminately
(confirmed live on all 11 non-impeded Advent Sundays in the corpus).
Classified by the day's own civil weekday, not a static table.
- The known Vigil-of-St-Lawrence gap is reconfirmed, with a nuance not
present in the earlier audits: colitur's own RG33-third-trigger now
omits the vigil in some years too, so the corpus's gap and colitur's
answer coincide in 2025 (a full match, not a coincidence) while 2027
still shows the classic identity-wrong-but-preface-invisible shape.
Re-derives the known menu-trap office filter (Requiem-skip, "and "-chain)
against the live JSON and finds a second, general pattern: "Saturday of
Our Lady" (RG78's BVM Saturday Office), whenever listed, is
unconditionally the office -- checked exhaustively across all 38
instances in the corpus, zero counter-examples. A third, generic
"absorption" rule was deliberately not built: it would resolve 2 of the
11 residual divergences but wrongly misfires on a structurally identical
but semantically different shape (an optional Rogation-day Mass option).
The JSON's own extraction carries a genuine classifier gap: parse_ordo.py's
hardcoded PREFACES set omits "Christ the King", misfiling it into comms;
recovered in the generator, not left blank.
11 of 1088 comparable rows diverge, resolving to 7 adjudicated root
causes (E1-E7): two Commemoration_only Marian titles reduced to
commemorations (Mt Carmel, Ransom), two genuine sanctoral data gaps
cross-confirmed absent by the FIUV/LMS witnesses (Miraculous Medal,
St John before the Latin Gate), two single-witnessed efdotorg outliers
against FIUV/LMS agreement (St Michael's Dedication, All Saints on a
Sunday), and one isolated source labelling slip.
Mutation-tested: corrupting the RG495 BVM-Saturday clause reddens 9
tests across this new layer and both existing Ordo witnesses.
Per-edition agreement: 358/363 (2024-2025), 358/363 (2025-2026),
361/362 (2026-2027).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Wires in the FIUV (Foederatio Internationalis Una Voce) Ordo for
2025-2026 as validation layer seven -- the first UNIVERSAL (not
diocesan) source in this project, and the first compiled by someone
other than the LMS editions' own Peter Day-Milne (Joseph Shaw, FIUV
President).
tools/extract_fiuv_ordo.ml hand-parses pdftotext's -layout dump of the
Latin-language ordo (no Str/regex, frozen deps). Three real parsing
traps were found and fixed by tracing actual false results against the
raw source, not assumed in advance:
- "Gloria" is also the first word of "Gloria Patri" (the psalm
doxology), which can appear INSIDE the very Mass clause being scanned
(Good Friday: "...omittuntur ps. Iudica me et Gloria Patri), Gloria,
sine Credo..."). Guarded by rejecting any "Gloria" hit whose next
token is "Patri".
- Christmas Day's own rubric uses the PLURAL "Missae"/"Missae" ("tres
Missae celebrantur..."), never the singular "Missa" every other real
day anchors on -- silently produced gloria=None/credo=None on the one
most doctrinally unambiguous day in the calendar until traced and
fixed, guarded in turn against "Missae defunctorum" (an unrelated
Requiem-prohibition notice appearing at the end of many day-blocks).
- The Vespers boundary (bounding the primary Mass option before a
second "Vel Missa" alternative or before Vespers information) missed
the ligature spelling "VESPERÆ", the dominant form in this source,
letting a whole day's trailing prose leak into the captured [praef]
field on the task brief's own worked example day (19 September).
A distinct second stop-marker bug: the malformed trailing entry this
source shares structurally with the LMS PDFs (a stray, mislabelled
duplicate of 1 January) was initially over-matched by a generic
"first token is 4 digits" heuristic, which false-positived on an
indulgence-decree date citation wrapped mid-sentence in October
("...20 augusti / 1885, 26 augusti 1886..."), truncating the extraction
to 308 of 400 real days. Fixed by anchoring on the tail entry's own
distinctive "1st Jan" opening instead.
Five opposite-prediction Creed probes are re-verified by the tool
itself against its own extracted rows (a failed probe is a hard exit,
not a printed claim) before the fixture is written.
test/test_fiuv_ordo.ml compares the Creed against colitur across all
399 comparable days (Holy Saturday excluded, no Mass that day) --
CLEAN, zero unexplained divergences, a genuine second confirmation of
{!Rite_ef.Rubrics_ef.creed} independent of the LMS lineage. [praef],
[te_deum] and [class_] (including the FIUV ordo's own "III cl." vs
"III cl. (Priv.)" Cum Sanctissima convention, stated on its title
page) are captured into the fixture but deliberately not compared --
colitur has no Gloria/preface predicate yet, and its default
configuration models no Cum Sanctissima overlay to compare the class
convention against.
All 634 tests green (dune test), 635 with the exhaustive sweep and
make check. No lib/ file touched.
Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Generalises tools/extract_lms_ordo.ml's start-of-body detection: the
hardcoded "End of November 2024" sentinel assumed every edition opens
with an "End of November" stub section. The 2023-2024 edition has none
(Advent Sunday 2023 fell on 3 December, so the compiler folded the two
tail days into the "December 2023" header directly) -- the generalised
detector anchors on the body's own "ORDO" title line instead, verified
unique in all three known editions and distinct from the Table of
Contents' lowercase "Ordo" entries.
Extracts test/fixtures/lms-ordo-2023-2024.sexp (397 rows) and
lms-ordo-2025-2026.sexp (399 rows), each with a provenance header
carrying its own SHA-256, source citation, and a per-edition
characterisation record: five opposite-prediction Creed probes,
independently re-verified against this run's own extracted rows (a
failed probe is now a hard die, not a printed claim). The 2025-2026
edition needed a different sharp II-class pairing than 2024-2025's
Joachim/Stephen one, because Joachim (16 August) is impeded by a Sunday
that year -- recorded in the fixture header, not silently swapped.
test/test_lms_ordo.ml is refactored into a parameterised make_suite,
instantiated once per edition; every count Task 6 hardcoded against the
single 2024-2025 window is now a per-edition parameter, several of which
turned out to differ once measured (BVM-Saturday population 12/14/13,
Ascension-week exclusion 1/3/2). Two mapping gaps surfaced only by the
new windows and fixed here (test-side, not colitur): Low Sunday reached
via Preceding_sunday for the first time (absent from 2024-2025's own
window), and the Ascension-week exclusion was wrongly keyed off week
number alone -- Rogation Monday also resumes week 6's Sunday but falls
BEFORE Ascension, not after, and the Ordo prints the ordinary Sunday
text there. Re-bounded by date, against Ascension and Pentecost.
Two genuine divergences surfaced in the 2023-2024 window and are
allow-listed (L3, L4) rather than fixed, per this task's no-lib-changes
scope:
- L3 (verdict ordo): colitur has no bissextile calendar rule. The 1962
Missal's own calendarium moves St Matthias from 24 to 25 February in
leap years (docs/research/LT.txt:5011); colitur keeps him on the 24th
every year. Domain-wide upper bound measured with a throwaway sweep:
1,650 of 2,041 leap years in 1583-9999 currently observe him there.
- L4 (verdict open): Christmas Eve falling on the 4th Sunday of Advent
(2023-12-24, roughly 1 year in 7). colitur says the Creed is said,
reading RG 475(a)'s Sunday clause; the Ordo says it is not. RG 30(a)'s
own "locum tenet ... nulla fit commemoratio" for this exact vigil
reads like RG 16(a)'s Feast-of-the-Lord-on-Sunday shape, which colitur
already treats as Creed-preserving -- but RG 475(a) names "festo"
specifically, and a vigil is its own liturgical-day category (RG 35),
so the counter-reading is real too. Left open rather than adjudicated.
All 630 tests green (dune test), 631 with the exhaustive sweep.
Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
An independent lineage -- neither Divinum Officium, missalemeum nor
lectio -- covering the Mass formulary and the Creed. Characterised
before use, per the discipline the extraordinaryform.org Ordo's
blanket St Lawrence vigil gap taught this session: five opposite-
prediction Creed probes (Sunday/feria, octave-override vs plain
II-class, I-class) all confirmed against the raw extracted text
before a single divergence was adjudicated, recorded in the
fixture's own provenance header.
tools/extract_lms_ordo.ml hand-parses pdftotext -layout output (no
Str/regex, frozen deps) into test/fixtures/lms-ordo-2024-2025.sexp,
400 day-rows (2024-11-27..2025-12-31). Gl/Cr print per Mass-option,
not per day; the extractor takes the first pair in reading order,
which the source's own layout guarantees is the universal entry's,
since a diocesan variant always follows it, never precedes it. The
Ordo's I-V roman numeral ("Missae de sancta Maria in sabbato", RG
309(a)) is matched whole-line only, never by substring -- "V Mass of
BVM" is a substring of "IV Mass of BVM".
test/test_lms_ordo.ml compares two axes 1:1 by date:
- the Creed (RG 475-476): 399 of 400 days comparable (Good Friday
has no Mass at all, asserted as the only such day); one genuine
divergence, adjudicated to the ORDO, not colitur -- All Souls'
Day 2025-11-03 is a Requiem Mass, and colitur's own creed
predicate has no notion of one, a pre-existing scope gap
rubrics_ef.ml already documented in its own header before this
task existed.
- the BVM-Saturday seasonal Mass selection, the addendum's own
"sharper check": all 14 Votive days in the window match their
season's own numeral exactly, both directions, independently
re-derived from RG 309(a) rather than calling colitur's private
function.
- the formulary override, for the three sources it can honestly
discriminate (Proper 179, Preceding_sunday 66, Common 2, all
clean); Own_slug (139) is excluded with evidence, not silently --
several ferial slugs carry citations byte-identical to their
Sunday's own, so the Ordo's override line cannot distinguish the
two representations.
data/ef/expected-divergences-lms.sexp carries the one adjudicated
entry (L1), cited to RG 476(f).
Mutation-tested: inverting RG 475(a)'s Sunday clause reddens 10
existing Rubrics_ef unit tests AND this new layer's own comparator,
independently, on a data source none of the other five layers touch.
Reverted after confirming.
No lib/ changes. day/readings/rubrics output re-confirmed byte-
identical (1583, 2026, 2038, 9999) after this task; all three gates
green (dune test, exhaustive sweep, make check).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Whether the Creed is said at Mass. New Rubrics_ef.creed, reached through
a new Rite.t.creed field and a new Liturgical_day.t.creed bool (false,
not an option, for a rite that has not implemented the rule); colitur
rubrics gains a fourth TAB-separated column.
475(a) reads the TEMPORAL cycle's own weekday, not the observed day: a
Sunday impeded by a Feast of the Lord (RG 16a) still says the Creed.
475(d)'s three octaves (Nativity, Easter, Pentecost) are pure date/
Easter-offset windows, checked first, since a saint's feast winning the
day inside one of them still says the Creed ("etiam in festis
occurrentibus") -- St Stephen, 26 December, is the live witness.
RG 23 classifies Ash Wednesday and every feria of Holy Week (including
the Sacred Triduum) as feriae, not festa, however high their rank, so
475(b)/(c)'s "in festis" never reaches them; this single check subsumes
476(a)'s own naming of the Chrism/Lord's-Supper Mass and the Easter
Vigil. RG 28-34 vigils are a third liturgical-day category, also
excluded from "in festis" regardless of rank -- reuses the already-
exported Precedence_ef.is_vigil rather than a new list.
475(e)'s Apostle/Evangelist natalicia list (creed_apostle_slugs) was
derived by grepping data/ef/sanctoral.sexp directly and checking each
candidate's own date against whether it is that saint's dies natalis --
not copied from any list supplied with the task. The Conversion of St
Paul and the 30 June Commemoration of St Paul are excluded (neither is
a natalicium); the Chair of St Peter and St Barnabas are included only
because the clause names them explicitly, which is exactly why it has
to: neither is a natalicium either.
475(c)'s BVM half reuses Precedence_ef.marian_slugs (newly exported)
rather than the subject field alone: checked against the shipped data,
almost every Marian sanctoral entry ships subject=Saint, not Bvm.
man/colitur.1's rubrics section is updated to match the new column;
test/cli.t repinned via dune promote for the same reason. Verified
day/readings output byte-identical to v0.10.1 across the whole
1583-9999 domain (both binaries' concatenated day+readings output,
6,148,492 lines each, zero diff). Domain-wide: 882,996 days say the
Creed, 2,191,250 do not; every one of the domain's 439,178 Sundays says
it, zero exceptions.
|
| |
|
|
|
|
|
| |
The lectionary's four-step chain already decides whether a day says its own
proper, its own slug's entry, the preceding Sunday's Mass or a Common, and
then discards that decision once the citations are out. An ordo needs to
print it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
format never raises and returns an unparseable citation unchanged, so a
gap degrades to today's behaviour rather than to a crash. The coverage
test asserts separately that no shipped citation takes that path.
verbatim is what --raw uses: an identity name table would still reformat
punctuation and renumber, which would break byte-exact diffing against
lectio.
The existing citation-coverage walk (1970-2070) now also drives a
round-trip check in the same pass: parse -> render -> parse must reach
the same structure, using Book.default_spelling for names (Book.to_string
returns the internal id, which is not a registered token and cannot be
read back). Proved with a mutation: changing the default style's
part_sep to a separator the parser does not accept reddened the
round-trip case on 31 multi-part citations and nothing else; reverted.
|
| |
|
|
|
|
|
|
|
| |
A style is a set of format strings, so punctuation convention is data.
Values are unquoted here rather than in Overlay_ini: that parser trims
every value and is shared with overlays and [defaults], so teaching it
about quotes would change behaviour this feature has no business
changing.
|
| |
|
|
|
|
|
|
|
| |
Walks 1970-2070 and parses every citation the engine emits. Nothing
validated citation spellings before this, which is why seven books
carried two spellings unnoticed.
Mutation-proved: removing the 'Isa.' spelling from the book table
reddens this test naming that citation.
|
| |
|
|
|
|
|
|
|
| |
The parsed form is a book and a LIST of chapter-parts: the data cites
across chapters and lists disjoint verse ranges within one.
Two rules the shipped data forces and that are not obvious: a
semicolon-separated part may inherit the previous chapter rather than
restate it, and a chapter may be separated from its verses by a comma.
|
| |
|
|
|
|
|
|
|
| |
Seven books arrive in two spellings, inherited from lectio's ini and
ultimately from Divinum Officium. Collapse them onto one id here rather
than editing generated data.
Naming and renumbering are kept apart: a tradition decides which book an
id denotes, a language file decides what it is called.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every entry is transcribed from the 1962 Missal's own propers headings in
docs/research/LT.txt and cites where it came from; names constructed by
following a neighbouring pattern are marked as such, so a reader can tell
transcription from inference.
The coverage test is the point of this commit. It walks every day of
2020-2045 and fails naming any slug with no Latin name -- the test that
would have caught the original defect, where a printed booklet said
ef-septuagesima-sunday-2 because nothing asserted that names exist.
The three Triduum names reuse the exact strings temporal_ef.ml already
carries, so the engine and the language file cannot disagree.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Owns precedence and provenance and nothing else, and never reads the
filesystem, so it is as testable as the language table.
resolve returns the value AND its source, because a setting that silently
comes from a file the user forgot about is worse than no setting at all --
config --show can then say where each effective value came from.
overlay accumulates rather than last-wins: a user has more than one.
An unknown key is reported, never fatal. A config written for a newer
colitur must still work on an older one, but silently dropping a line the
user wrote is how a typo becomes invisible.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Maps strings to strings and nothing else -- no calendars, no dates, no
filesystem. That is what lets every command use it without the kernel
learning about presentation.
Every lookup is total, and a miss returns THE KEY rather than the empty
string. A partial translation is therefore usable from its first line,
and the fully-degraded case is exactly today's output (bare slugs) rather
than a blank page.
--raw is a real identity table, not a special case threaded through every
call site: one value the whole program passes around.
Reuses Overlay_ini's INI reader rather than growing a second one that
would drift in its comment, quoting and trimming rules; parse_sections is
exposed in the .mli for that, with no behaviour change.
Fixes one defect found while running the brief's own tests rather than
transcribing them blind: weekday's internal lookup key is an English
day-name word (month's is already the numeral string), so on a miss it
echoed that word instead of the documented numeral, breaking both the
0=Sunday convention and Lang.raw's own identity contract for weekday.
weekday/month now fall back to string_of_int n directly on a miss
instead of through get's generic echo-the-search-key path; month is
byte-identical since its key already equals string_of_int n.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
LaTeX and groff are the print paths; HTML carries a print stylesheet;
AsciiDoc, Markdown and plain text are the plain-consumer paths.
AsciiDoc and Markdown use flavour none, and say so in a comment: their
metacharacters are context-dependent and escaping them aggressively
produces worse output than not escaping. The consequence is real and
documented -- a feast name containing * renders as emphasis.
Golden tests pin all six byte-for-byte for 2027. They prove the
templates RENDER, not that they TYPESET; compiling needs TeX and groff,
which is Task 13's opt-in make check-templates.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Not a template job: folding, escaping, exclusive DTEND and stable UIDs
are rules a logic-less template cannot enforce, and each fails silently
in a subscriber's client rather than loudly at generation.
DTEND is EXCLUSIVE for an all-day event (section 3.6.1). Wrong here
shows every event a day short, everywhere.
UIDs are YYYYMMDD-<rite>@colitur and stable across regenerations
(section 3.8.4.7). Wrong here duplicates the whole year in every
subscriber's phone, months later.
Every line is CRLF-terminated and folded at 75 octets (section 3.1).
No RRULE: a liturgical calendar is not a recurrence rule. Asserted, so
nobody optimises it later.
DTSTAMP is a parameter, not a clock read. RFC 5545 requires it and the
obvious implementation reads the wall clock -- which violates the
kernel's determinism rule and would make two feeds from identical data
differ byte-for-byte, defeating reproducible builds and any reviewable
diff on a published tree.
Corrected one test literal against real engine output: DTSTAMP is a
per-VEVENT property (section 3.8.7.2), not calendar-level, so the
default-value line count is 365 (every event), not 1.
Mutation-tested: a non-exclusive DTEND reddens the suite.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Element-per-field; attributes carry identity only and there is no mixed
content, so a consumer's XPath never has to distinguish the two.
Schema validation is an opt-in make check-schema via xmllint, not an
in-suite assertion: validating XSD needs an XML library and the
dependency list is frozen. It prints SKIPPED loudly when xmllint is
absent, because a silent skip reads as a pass. The suite asserts
well-formedness properties directly instead.
This corrects the design spec, which claimed in-test validation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Both consume the VIEW, not the kernel, so every emitter and every
template describe exactly the same fields -- there is one vocabulary,
not five.
CSV is RFC 4180: a field with a comma is quoted. That is live on real
data, not hypothetical -- 'St. Joseph, Spouse of the Bl. Virgin Mary'
would otherwise split into two columns.
JSON is hand-rolled because the dependency list is frozen and escaping
is the only subtlety. Control characters below 0x20 are \u-escaped per
RFC 8259 section 7.
There are no numbers in the view, deliberately: a consumer never has to
guess whether week is 2 or "2".
schema/day-v1.json pins the shape. Once a phone subscribes or a site
fetches this, it is a promise to strangers -- adding a field is minor,
renaming one means /v2/.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Shapes a civil year of resolved days into the value a template renders
against. This layer is why the engine can stay logic-less: a month grid
needs leading blank cells, week bucketing and an in-month test, and a
logic-less template can compute none of it.
Both weeks and days are offered at every level -- the booklet walks
days, the grid walks weeks -- so the two artefacts cannot drift.
Colours are six booleans, not hex: hex bakes a presentation policy into
the engine, and LaTeX, groff and HTML each want a different colour
expression. Asserted: exactly one of the six is true on every day of a
whole year, so a template keying off them can never get none or two.
Padding cells carry every field a real day carries, empty, so a template
never hits a missing key mid-grid.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every interpolated value is escaped for the template's flavour; the
template's own literal text never is, because that is the author's
markup. There is no raw form, so a template cannot opt out.
Scope is a stack with outward fallback, so a grid template can reach the
year number from inside a week without the view duplicating it into
every cell.
A missing key renders empty -- the one deliberate silence, so a template
survives a rite that does not set every optional field.
Mutation-tested: dropping the Escape.apply call reddens the
data-cannot-escape-flavour case.
|
| |
|
|
|
|
|
|
|
|
|
| |
Placeholders, sections, inverted sections, comments. Nothing else: no
partials, no lambdas, no expression evaluation, no raw form. A template
is data, never a program, which is what keeps an untrusted template safe.
Errors rather than silence on a malformed template: an unterminated tag,
an unclosed section, a mismatched close and a partial all return Error.
Swallowing '{{name' as text is how a typo becomes invisible missing
output in a printed booklet.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Six flavours: latex, groff, html, xml, ics, none. Markdown, AsciiDoc and
plain text map to none deliberately -- their metacharacters are
context-dependent and escaping them aggressively produces worse output
than not escaping.
An unrecognised extension returns None rather than falling back to none:
guessing the flavour wrong produces malformed output that looks fine
until it does not.
Folding backs off to a non-continuation byte, so a fold never splits a
UTF-8 sequence -- the failure mode that would corrupt Polish and Latin
names in a published feed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A convenience format for calendars that add a few local feasts and drop one
or two universal entries. Section names are slugs, a [overlay] section carries
the id, and status/subject/layer default so the common case -- an ordinary
local saint's feast -- says only what distinguishes it.
It is a FRONT DOOR, not a second data model. It parses to exactly the
Overlay.t the S-expression form parses to, and everything downstream is the
same code on the same values; a test asserts an INI overlay and its
hand-written sexp equivalent produce identical Overlay.t values. It is also
deliberately less expressive -- Add, Suppress and single-field Edit only --
and refuses Replace, multi-field edits and citation edits BY NAME rather than
dropping them silently. Anything it cannot say is a reason to write sexp.
Little of this is new machinery: tools/bootstrap_sanctoral.ml has parsed INI
and mapped it to celebrations since the sanctoral was bootstrapped from
lectio. The dates needed extending, since that mapping handled only MM-DD;
the flat forms are easter+N/easter-N and mon/day/nth, with nth negative to
count from the end.
`colitur convert` is a separate step rather than --overlay sniffing the
extension, so the author can read what their INI became. When a date form was
mistyped, "what did the engine actually get" is the question, and an invisible
transpile cannot answer it.
The conversion verifies its own output: the emitted text is parsed back with
the same function that loads an overlay and must equal what the INI denoted,
or nothing is written. That is the point of the module. A transpiler emitting
valid-but-wrong sexp is the failure a convenience format invites, and `colitur
check` could never catch it -- the output would parse cleanly and mean
something else.
That check was WRONG on the first attempt, in exactly the way it exists to
prevent. It re-serialised the parsed value instead of parsing the text being
returned, so it verified t -> sexp -> t, which is true by construction and
proves nothing. Found by mutation: corrupting the renderer to emit a different
overlay id sailed through and exited 0. It now parses the returned text, the
mutation is caught with exit 2, and two tests fail under it where none did
before.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Step 4's blind spot is now closed as far as the method allows. 2038
witnessed two of the five Common-routed saints; isidore-of-seville is the
third and the last that any year in 2005-2050 can reach, being observed
only in 2008, 2035 and 2046. He is also the only saint routing through
common-of-doctors, so that Common had never been compared against any
external source in any window.
Three independent agreements on it: the scan (Commune Doctorum,
scan1:41878ff, 2 Tim. 4, 1-8 / Mt. 5, 13-19), colitur, and missalemeum's
own 2035-04-04 row. Asserted by date in its own test rather than folded
into an aggregate count, so a regression there cannot hide.
The remaining two saints are unreachable by construction, not for want of
a fixture: gregory-the-great and patrick both sit in March and are
impeded by Lent's privileged ferias in every one of the 46 years the
differential covers. The blind spot is closed to its limit, not closed
absolutely, and the register says so.
The comparator is generalised rather than copied -- compare_live takes a
fixture and a year -- so a fourth window would cost a fixture and a count
list.
2035 differs on 3 days, both families pre-existing and already cited.
2035-04-02/03 are the Joseph/Annunciation transfer pair landing in the
opposite order from missalemeum's: neither stream loses a feast, the
sequence differs, which is C14's own RG 96 collision finally getting an
oracle window. 2035-12-30 is the Sunday within the Christmas Octave
against colitur's numbered octave-day slug, C6's family. Both are
recognised by slug rather than by date, so a year exhibiting only half
the shape would surface as unexplained rather than be quietly absorbed.
393 tests green with the exhaustive sweep.
Register: section 6.15.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Register section 6.7 recorded that step 4 of the reading chain -- the
Common route -- had no external witness of any kind, and proved it by
mutation: corrupting a Common citation left both the 16801-day
differential and the 730-day oracle green. This closes that.
2038 is the only year in 2005-2050 in which two of the five
Common-routed saints are the observed office (Perpetua and Felicitas on
6 March, Frances of Rome on 9 March). 365 days were captured live, one
request per day, zero failures.
A separate fixture, not more rows on the existing one. sources.md
already records that the live endpoint has drifted from lectio's
archived 2026-2027 snapshot; merging two versions of one source behind a
single set of expectations would make any future disagreement
unattributable -- calendar or drift, no way to tell. Nothing in the 2038
comparison is checked against the older fixture, and its provenance file
says so, along with the fact that a live capture is not reproducible
byte-for-byte on demand the way a snapshot-derived one is.
338 of 365 days match. 27 differ, every one of them in a named,
already-adjudicated class, none unexplained: 13 the BVM Saturday votive
Mass (M26 shape 1), 7 colour (M21), 2 Judith (M28), 2 Christ the King's
week (M26 shape 2b), 1 Christmas multi-Mass (M27), 1 Perpetua's Common,
1 new. Each was decided in a different year, so 2038 re-confirms them
independently -- the point of a second window is not new rulings but
evidence the old ones are not artefacts of their own year.
Two findings came out of it. Perpetua and Felicitas: missalemeum serves
the Common of Virgins and calls them "Virgins and Martyrs" in its own
oration, where the Missal directs "Missa Me exspectaverunt, de Communi
non Virginum I loco" (scan1:27634-27635). Both Commons share the Introit
Ps 118:95-96, which is exactly why the calendarium qualifies its
direction, and both women were mothers. Verdict colitur; the mechanism
is located on missalemeum's side, not merely asserted. Passion Tuesday:
the Missal prints "Dan. 14, 27 et 28-42" (scan1:11106) and colitur
reproduces that two-part form where missalemeum collapses it to
27-42. Same verses; the convention is deliberate, appearing also in the
Seven Sorrows and the Common of Non-Virgins. Verdict colitur, cosmetic.
Citations are notation-normalised before comparing, as layer 3 already
does: Ecclus/Sir, Joann/John, Luc/Luke, Matth/Matt. Each pair was added
because a real row needed it, and no target contains its own source as a
substring, so the set is idempotent.
Not compared, stated rather than left to be found: commemorations and
observed-identity. That machinery is built around a date-literal
28-entry allow-list specific to 2026-2027, and re-deriving it for a
second year is its own task. 2038 compares rank, colour, Epistle, Gospel.
The extractor's day count was hardcoded to 730, which silently forbade
any other window. It is now a parameter defaulting to 730, so the
existing documented command keeps its guard and a partial fetch still
fails loudly instead of producing a short fixture that passes a
comparison it never ran.
Teeth, by re-running section 6.7's own experiment: corrupting the Common
of Non-Virgins II now reddens four tests, two of them external-oracle,
where the same mutation previously reddened neither oracle layer.
Residual: isidore-of-seville is still unwitnessed and needs 2035 or
2046; gregory-the-great and patrick are never the observed office in any
year 2005-2050, so no fixture in that range can reach them.
Register section 6.8 and 6.9.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Liturgical_day.citations has read "always empty until Plan 4" since Plan 3;
it is now filled. Rite.t gains a readings function, rite-supplied for the
same reason transfer_target is: what a day with no proper falls back to is a
rubric, not a universal. Calendar calls it and passes its own temporal
function as the callback the rite needs to reach another date.
Steps 1 and 2 only: the observed celebration's own proper, else the day's own
temporal slug. Nothing encodes "Lent has daily propers" -- the presence of an
entry is the discriminator.
|
| |
|
|
|
|
|
| |
Data only, the same shape and discipline as Layer: slug-canonical,
duplicates rejected at construction naming the offending slug, sexp
round-trips. Which slug a day falls back to is a rubric and belongs to the
rite module, so nothing here knows about ferias or Sundays.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Validation layer 5: fourteen hand-verified pins in test/test_golden.ml,
wired into the suite via test/test_colitur.ml. Each pinned day was computed
and checked against its RG citation before being written down, never
transcribed from `colitur day` output:
- Easter extremes 1598 (earliest, 22 Mar), 1666 (latest, 25 Apr) and 2038
(late-modern instance of the same latest date) -- all three independently
computed by hand via the Gauss/Meeus Gregorian Easter algorithm, not read
off Computus.gregorian_easter. extreme_years in test_validate.ml already
established 1598/1666 as the true 1583-2500 extremes (correcting a stale
1818/2038 comment); this file pins the resolved DAY there, not just the
date.
- Annunciation transfer, 25 March inside Holy Week (2016: 25 March is
literally Good Friday) and the double transfer with St Joseph (2008:
RG96's Attamen(a) claims Easter+8 for the Annunciation first, Joseph's
own RG96 walk continues past it to the next day).
- The RG96 Attamen(a) exception's own CONDITION pinned on both sides: 2057,
2007, 2012 (general walk suffices, lands before Easter, no exception) vs
2016/2024 (walk would cross Easter, Easter+8 fires).
- 2011-07-04, the Precious Blood transfer (Sacred Heart outranks it
outright on 1 July; the RG96 walk skips Visitation and a Sunday before
landing on 4 July).
- All Souls falling on a Sunday (2025, RG96 Attamen(b)) and Christmas
falling on a Sunday (2022, RG91 entry 1 -- no contest, since 25 December
is never an ordinary Sunday candidate in the EF temporal cycle).
- Holy Thursday's white amid violet Passiontide (2026, RG128(b)/RG122) --
the case colitur and lectio previously agreed was violet, so the
differential could never have caught it; only a golden pin or the
missalemeum oracle can.
- Advent/Lent Ember ferias commemorated when impeded (1900, 1902, RG24 +
RG109(e)) contrasted with IV-class ferias never commemorated (2026,
RG26) and RG111(b)'s Sunday rank floor (2009, 2026) -- all three assert
the actual displaced/excluded candidate is present in `omitted`, not just
that `commemorations` is empty, so a day with no losing candidate at all
could not pass vacuously.
2038 is deliberately NOT pinned day-by-day: register item F4 confirms
2038-03-06/08/09 are wrong (a lectio bootstrap-generator defect, not
fixable here). Only the Easter-week days, untouched by that bug, are
pinned; the exclusion is stated in the test's own comment, not silent.
Every weekday asserted was independently cross-checked against `date -d
<iso> +%A` (glibc, wholly outside this codebase) before being written down.
Precedence outcomes were traced against precedence_ef.ml's own band/
disposition/admit/transfer_target, not merely observed to look plausible;
where the primary text alone doesn't fully settle an outcome (the 2008
Annunciation/Joseph tie-break, both landing at RG91 table entry 11), the
test's own comment says so rather than overclaiming a citation.
Perturbation-tested: temporarily broke Holy Thursday's white-colour special
case in temporal_ef.ml, confirmed the golden test failed with a clear
day-and-field diff, reverted.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Validation layer 4: an oracle harness against missalemeum (Divinum
Officium data), independent of the lectio bootstrap chain colitur's
own sanctoral data comes from -- the only layer that can catch an
error inherited from that bootstrap, and the only one that can
validate commemorations at all (the lectio differential explicitly
excludes them, per its own header comment).
tools/extract_missalemeum_oracle.py shapes the fixture from lectio's
sources/snapshot.tar.gz outside the test (no JSON library in this
project's frozen deps, same reasoning test_differential.ml's own
fixture already documents). test/fixtures/missalemeum-ef-2026-2027
.txt (730 days, SHA-256 pinned and asserted) + its own .provenance
note record exactly how to regenerate it.
test_oracle.ml compares three axes the oracle actually supports:
rank, colour (SET MEMBERSHIP -- 14 of 730 days carry two colours,
e.g. rose+violet on Gaudete/Laetare, which independently vindicates
this project's own rose reading against lectio's violet-only one,
recorded in the register), and commemoration presence/count. Slug
identity is deliberately out of scope (needs a title->slug mapping,
the data audit's own business, not the automated comparator's).
Of 730 days, 688 matched cleanly outright. The remaining 42 are all
named in data/ef/expected-divergences-missalemeum.sexp (14 cited
entries, M1-M14): most are genuine primary-source-confirmed findings
this task adjudicated and fixed in the two preceding commits (RG 33,
RG 109/111, Holy Thursday's colour); the rest are real, cited,
deferred feature/data gaps (RG 91 entry 27's BVM-Saturday office, RG
110's inseparable Peter/Paul commemoration, four sanctoral entries
missing from lectio's own source) or genuine oracle-side artifacts
-- honestly verdicted against whichever side this task's own
primary-source research actually backs, never defaulted to colitur.
One entry (M13, St Joseph vs the Friday of Passion Week 2027) is
verdict open: adjudicated as unresolved after real search effort,
not guessed past.
The data audit: every sanctoral entry the comparison flagged was
hand-checked against the 1962 calendarium, plus a 20-entry
deterministic random control sample (seed 20260812) drawn
independently of the flagged set. The control sample caught two
entries (benedict, frances-rome) marked Commemoration_only in the
bootstrapped data when the primary calendarium lists them as plain
III-class feasts with their own Office -- traced to lectio's own
source, not fixable here, and reported as a signal (10% of a random
sample) rather than a blanket claim. Coverage recorded honestly in
docs/research/rules-register.md's own three buckets: confirmed by
oracle (200/322), confirmed by hand (23/322, 2 of them wrong),
unverified (115/322) -- the unverified bucket stated explicitly
rather than left implicit.
Harness teeth demonstrated and reverted (not committed): a fixture
rank/colour edit on a previously-clean day fails both the checksum
pin and the no-unexplained-differences assertion independently; an
expected_rows drift on the allow-list fails the citation-count
assertion. Both captured with their exact failure messages, both
reverted before this commit.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Validation layer 3 (design spec's five): compares colitur's real day-by-day
EF output against lectio (sibling project, Go), 2005-2050, one line per
civil day. Of 16801 day-pairs, 11206 already agree on the seven leading
columns; the 5595 that don't resolve into exactly 25 distinct field-diff
signatures, all triaged.
Three strictly separate layers, per the controller's ruling (the brief's
single flat allow-list assumed a handful of differences, not 5595):
- Layer A (test_differential.ml, norm_season/norm_slug): vocabulary. An
explicit, closed table of naming synonyms with no liturgical substance
(lectio's easter/christmas vs colitur's paschaltide/christmastide; a
handful of slugs that are two names for the identical office). No
wildcards -- every entry is a literal string pair.
- Layer B (strip_epiphany_index): numbering. The one slug family whose
lectio/colitur index offset is not a constant (Time-after-Epiphany week
numbering, register 3c#5) has its embedded digit stripped to a common
form on both sides before comparing; rank and colour stay fully compared.
- Layer C (data/ef/expected-divergences.sexp): the cited allow-list. Ten
genuine liturgical disagreements, each citing its RG paragraph and
naming which engine is right (always colitur, verified against the
Missal/register, never against lectio's own behaviour). This is the
only layer permitted to cover a difference in rank, colour, or which
celebration is observed. Five extend or restate register 3c's already-
documented divergences (season boundary, Sunday I-class, Advent Ember
ferias, Rogations); five are new, found and adjudicated in this task
(Lent Ember days, the Nativity Octave, Ember-day-vs-saint precedence,
the St Joseph transfer off a Lent Sunday, and the 2011 Sacred Heart /
Precious Blood / Visitation collision). expected_rows on each entry is
an exact regression pin, asserted by the test, not documentation.
13 January (register 6's long-open "Baptism of the Lord" item) is
confirmed empirically fixed already -- Task 11's sanctoral wiring closed
it before this task started -- so it is not allow-listed; the only
residual difference there is the season boundary already covered by C1.
Two stated limits carried from the brief (commemorations are not
comparable; lectio's own EF oracle asserts season only, 2025-2026 only,
so a rank/colour difference is not presumptive evidence against colitur)
plus a third found during this task (the week column is a display
convention on both sides, not a liturgical fact, and is not compared at
all) are documented in the test file's own doc comment.
Fixture: test/fixtures/lectio-ef-2005-2050.txt, committed as plain text
(1.4 MB), generated by lectio commit 2386a45; provenance recorded in the
sibling .provenance file. Colitur's side is recomputed fresh from the
library on every run, through the same Calendar/Rite_ef pipeline
`colitur day` uses, not the compiled binary.
Proved the harness has teeth by two reverted perturbations: a genuine
colour difference injected into a fully-covered fixture row fails the
"no unexplained differences" check with the exact mismatched row printed;
a one-row drift in an allow-list entry's expected_rows fails the count
check independently, showing it is not merely a duplicate of the first
assertion. 236/236 tests green, clean-build verified, deterministic
across OCAMLRUNPARAM=R.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
search_from could walk up to 400 days past origin before Calendar's own
~start ~stop clamp is ever consulted, and nothing stopped it probing
occupant on a date past 31 December 9999 -- occupant chains through the
real EF rite's temporal, which calls Computus.gregorian_easter, not
total outside 1583..9999 (it Date.makes and failwiths on Error).
Not reachable with the shipped sanctoral data alone, but reachable
through the project's own primary extension path: an overlay adding an
I-class feast on 25 December leaves nothing but Class2 Nativity-octave
days for the rest of civil year 9999, so the unguarded search reached 1
January of year 10000 and crashed there with 'computus: year 10000 out
of range 1583..9999'. 9999 is an in-range year and the kernel's contract
is 'never raises on in-range input'.
search_from now also stops, without probing occupant again, once it
passes Date's own domain ceiling -- the same 'return a finite date, let
Calendar's own out-of-range handling record it, never pretend to have
found something admissible' contract the existing step-count guard
already follows.
Two new tests, both mutation-verified to actually reproduce the crash
when the guard is removed (see the task report): a precedence_ef.ml unit
test using the real Temporal_ef.temporal as occupant (a synthetic
occupant can never discriminate this, since it never calls Computus
itself), and a Calendar-level integration test reproducing the exact
overlay-based scenario the review found.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Convert lectio's tridentine-calendar.ini (322 entries) into
data/ef/sanctoral.sexp via a validating OCaml converter, tools/
bootstrap_sanctoral.ml, rather than a hand-written script: every field is
built through Slug.of_string, Colour.of_string and Vocab_ef.rank_of_string,
so the emitted sexp is valid by construction.
Two conversion decisions, both documented rather than buried:
- subject defaults to Subject.Saint, overriding Celebration.make's kernel
default of Subject.Temporal, for the 316 entries with no explicit class;
- rank = commemoration maps to status = Commemoration_only with an inferred
Class3 (not a citation -- it is what the 1960 reform reduced most simple
feasts from), recorded as an open item in the rules register for the
oracle to adjudicate.
Every celebration is tagged layer = Precedence_ef.universal_layer, the
provenance id RG 91's band classifier reads to tell the universal calendar
from proper/indult data.
The generated file carries a provenance header: source path, its SHA-256,
and the UTC conversion date, so re-bootstrapping against a newer lectio is
reproducible and diffable. Output is byte-identical across runs.
test/test_sanctoral_ef.ml loads the file through Layer.load and checks the
counts independently derived from the source INI (322 entries, 114
Commemoration_only, 12 Class1, no Subject.Temporal, every date resolves in
a leap year), plus two named spot-checks against the INI's own text -- one
entry with an explicit class field, one commemoration -- so a passing count
cannot hide the wrong 322 entries having been converted.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Precedence_ef.band transcribes RG 91's 28-entry Table of Precedence
(rules-register.md §4) for the EF rite: given a day's context and a
candidate celebration, returns the table's own entry number, 1-28
(I class 1-13, II class 14-21, III class 22-26, IV class 27-28); lower
wins. Every branch carries its entry number and register citation in a
comment, checked in the table's own numeric order.
Two entries are transcribed as the register states them even though
they invert the pattern the rest of the table follows: at III class,
23 (particular calendars) outranks 24 (universal), the reverse of how
11/12 and 14/16/19/20 rank a universal feast ahead of a proper one at
I and II class.
Sanctoral-origin, layer-decided entries (11-13, 14/16/19/20, 23/24)
follow the brief's structural insight: a celebration whose layer is
not the universal base is an overlay -- proper, or indult if its
layer id also carries the indult prefix. Neither the universal-layer
id nor the indult prefix is an RG citation; both are colitur's own
data-modelling convention, exposed from the module so whichever task
loads the real EF sanctoral overlays can align to them.
Vigils (21, 26) are read off the temporal cycle's own -vigil slug
suffix rather than gated on origin, since a II/III-class vigil can be
either temporal-origin (Ascension, already produced by temporal_ef) or
sanctoral-origin (a saint's vigil, no task has loaded yet); Ember days
(part of entry 18) are read off temporal_ef's own ember slug prefixes
rather than re-derived, since the September anchor is independently
flagged there as one of the more contested dates in the calendar.
A candidate shape the table has no row for (e.g. a Class1 vigil that
is not Nativity or Pentecost, or a Class4 candidate marked as a vigil
-- RG 91 has no IV-class vigil either) returns a dedicated unclassified
sentinel (max_int) rather than being folded into a same-rank entry it
does not belong to.
test_precedence_ef.ml is table-driven: one Alcotest.test_case per RG
91 entry (55 rows total, several entries covered by more than one
named day so a single missed offset cannot hide behind a passing
sibling), each date computed from Computus.gregorian_easter rather
than hand-typed, so an arithmetic slip cannot pass by accident.
|
| |
|
|
|
|
|
|
|
|
|
| |
Transfers make per-date resolution impossible to do correctly: resolving 25
March can push a feast onto 26 March, and RG 97-98 has coinciding I-class
feasts transfer in table order, which needs global knowledge. So year computes
a whole liturgical year in one pass and day indexes into it. Pure, no cache, no
mutable state.
This commit resolves each day but does not yet place deferred transfers; they
are recorded with a reason. Task 6 adds the placement pass.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Three rite-supplied functions, not one: band (who wins, RG 91), disposition
(what happens to the loser, RG 92-95) and admit (how many commemorations are
admitted, RG 111). The loser's fate depends on the loser's own rank, so
conflating them would resist extension.
resolve takes the temporal candidate separately from the sanctoral list, which
makes it total by construction. Every candidate lands in exactly one of
observed, commemorations, deferred or omitted -- nothing is dropped silently,
which is what makes the no-celebration-lost invariant checkable.
|
| |
|
|
|
|
|
|
|
|
|
| |
Coverage, season contiguity and completeness, Sunday-aligned week numbering,
slug well-formedness, weekday agreement and vocabulary closure.
Checks run over a liturgical year rather than a civil one, since Christmastide
straddles January and would otherwise appear to recur.
Run against EF temporal for landmark years, both Easter extremes, and 200
random years across 1583..9998 -- the property layer is how confidence reaches
past the oracle horizon.
|
| |
|
|
|
|
|
| |
Eight seasons in canonical liturgical-year order, each carrying its RG
citation, and the four classes. season_slug_word is deliberately distinct from
season_to_string: slugs are lectionary keys adopted verbatim from lectio,
which calls Paschaltide 'easter' and Christmastide 'christmas'.
|
| |
|
|
|
|
|
| |
Entries sort by slug so equal layers serialise identically. The by-date index
is built once per layer rather than per year, since fixed dates are
year-independent; a full-domain sweep would otherwise rescan every entry for
every day. load turns parse and validation failures into result.
|
| |
|
|
|
|
|
| |
Names is an open language-keyed assoc kept in canonical order so equal name
sets serialise identically. Citation carries references only, never text.
Date_spec ships the one form the EF sanctoral needs; 29 February is
constructible and resolves to None in common years.
|
| |
|
|
|
|
| |
Both parse through a smart constructor returning result, and both hand-write
t_of_sexp so a malformed value in a data file is rejected at load rather than
silently accepted -- deriving the converter would have bypassed validation.
|