| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
It printed 374 lines: every output format, the RFC 4180 quoting rule, the
template scope hazard, the publish manifest semantics. All of that is real
documentation and all of it is already in colitur(1), installed beside the
binary. A help text nobody reads at the terminal is not more helpful than a
short one, it is less -- the reader stops looking.
Now 42 lines: the commands, the flags most runs need, the year domain, the
exit status, and where the rest lives. Everything removed is in the man
pages, which is checked rather than assumed -- each dropped section has a
counterpart section there.
One thing moved rather than went. cli.t pinned `rubrics`' documented field
list against what `rubrics` actually prints, because that field count drifted
once already and stayed wrong for a whole review round. Deleting the prose
would have quietly retired the check, so it now reads the worked example out
of man/colitur.1 instead, and test/dune's cram stanza gains the man page as a
dep -- a sandbox holds only what that stanza names. Mutation-tested: changing
the documented example reddens the suite.
The tagline also stopped saying "Roman rite, 1962" -- the OF has shipped since
v1.1.0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Which book a reference denotes does not vary by language, so it lives in
its own file rather than in la.ini beside [weekday].
Six mappings, not four: Osee/Jonas are the same question as 3 Kings in
transliteration form.
The install rule is added in the same commit deliberately -- lang/ once
shipped without one, so the feature worked from the source tree and was
broken once installed.
bin/dune gains colitur_citation as a linked library, needed for the new
loader; lang_list's directory scan now excludes traditions.ini, which is
not a language file and does not parse as one.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds templates/ef/ordo.typ (A5, one week per page, each day framed with
a colour swatch, real names, every fixed string through term.*) and
templates/ef/grid.typ (A4 landscape, one month per page, 7 columns,
colour-filled cells, Epistle/Gospel sigla), matching what ordo.tex and
grid.tex already do.
Typst resolves its own table of contents inside a single compile: no
pageref/aux-file dance, unlike pdflatex's own two-pass need for
ordo.tex. ordo.typ's TOC is one flat heading per week (Month . Week N)
rather than ordo.tex's own month-once/weeks-nested layout, since the
view model has no first-of-month flag for a template to test and
inventing one would be new engine state for a cosmetic grouping.
grid.typ deliberately diverges from grid.tex's own partial-fill
behaviour for a 5-week month: Typst's row-size list repeats its own
last entry for every remaining row, so a two-entry rows spec fills the
page completely regardless of week count, where LaTeX's fixed cellh
leaves a 5-week month's bottom sixth blank on purpose. Both divergences
are documented in the templates' own headers.
Two bugs found and fixed while building these against the real typst
0.14.2 binary: two stray hash characters in grid.typ's header comment
that were not actually inside a line-comment (would have been parsed
as Typst code, not prose); and two lines merging into one wherever they
were separated only by a bare newline (still the same soft-wrapped
paragraph in Typst, unlike LaTeX's own explicit paragraph break) --
fixed with a trailing backslash to force a real line break without
adding inter-paragraph spacing, verified against a full-year render
(zero typst warnings across all 365 days of 2027, in both templates).
Goldens (test/golden/ordo-2027.typ, test/golden/grid-2027.typ) were
generated through the harness's own View.of_days/Template.render_string
path, via a throwaway generator, NOT through the CLI: the CLI's default
language table differs from the harness's own English-chained-to-Latin
table, confirmed live (diffing CLI output against the harness's own
render showed exactly the expected language divergence and nothing
else) -- the same trap this project has hit before.
make check-templates now typesets both templates with the typst binary,
using the same SKIPPED-and-exit-0 shape the pdflatex/groff blocks
already use when their own tool is absent, verified in both directions:
absent (PATH without typst) skips loudly and exits 0, and a
deliberately corrupted template fails the target with a non-zero exit
and typst's own error text.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Sanctoral names are transcribed from the Missal's own calendarium and
kept in the GENITIVE, as the Missal prints them -- noted in the file so
nobody corrects them to the nominative.
English reuses the 327 names already in data/ef/sanctoral.sexp rather
than retyping them, and declares fallback = la, so an untranslated day in
an English booklet shows Latin rather than a slug. The test asserts the
FALLBACK works rather than that en.ini is exhaustive: that is what makes
a partial translation shippable from its first line.
Coverage now demands a name for every slug the engine can emit across
2020-2045, temporal and sanctoral alike.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Writes ef/<year>.{json,csv,xml,ics}, one JSON per day, the schema and a
generated index. That tree is the API: any web server or git repo serves
it, and nothing runs at request time.
Deterministic: publishing twice is byte-identical, asserted in cli.t.
That is what makes publishing into a git repo safe -- the diff shows
only real change, and you review it before pushing.
Non-destructive: a manifest records exactly the files this tool wrote,
so --prune can only remove files a previous run created. A file you put
in the output directory yourself is never touched, with or without
--prune. Asserted in both directions.
Pruning a stale file also removes any directory it leaves empty behind
it (e.g. an old year's own ef/<year>/ tree), stopping at --out itself --
without this, a pruned year's own directory would survive empty and
test -d would still see it.
schema/day-v1.json is resolved the same prefix-relative way data/ef's
own sexp files are (installed vs build-tree, probed rather than
assumed), never from cwd, and a missing schema fails with one line on
stderr before anything is written rather than emitting an empty file.
Needed schema/day-v1.json wired into the root dune file's default alias
and into test/dune's cram deps -- unlike data/ and templates/, nothing
made dune mirror schema/ into the build tree before this.
unix is added to bin/dune's libraries for mkdir_p; it ships with the
compiler, so colitur.opam and dune-project are unchanged.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three flavours, not six: a month grid in Markdown or plain text is a
worse artefact than the booklet already is, and shipping a template we
would not use ourselves is maintenance with no reader.
These are the first templates to use weeks and in_month, so this is
where the view model earns its keep -- the booklet and the grid come
from one model with no second code path.
Every cell carries a per-cell 'last' boolean (already in the view). A
table row needs a separator BETWEEN cells and the engine deliberately
has no 'unless last' construct; the rule is shape the data, not the
template. Without it the LaTeX grid emits eight columns for seven
cells and pdflatex rejects the file.
Day cells resolve their label as {{#name}}{{la}}{{^la}}{{slug}}{{/la}}
{{/name}}, never a bare {{name.la}}: the enclosing month object has
its own name.la, and the engine's scope lookup falls back outward, so
a bare dotted lookup would render the month's own Latin name on every
day lacking one -- a wall calendar where every day reads "January".
Verified on the goldens: Ianuarius appears exactly once per file, the
month heading, never as a day label.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Poland and the Benedictines, in the flat INI form, installed beside the
invented diocesan example.
poland.ini -- 17 entries from the Calendarium Perpetuum pro Dioecesium
Poloniae (1964), promulgated under Rubricarum instructum and applying to the
1962 Missal. Transcribed from a published Polish EF ordo that names that same
calendar, cross-checked against missalemeum's supplement page for the two
formularies 1964 added (13 and 15 July). Date, Latin name and class were read
from the source for every entry; three classes could not be recovered from
the PDF's column layout and are marked INFERRED where they appear, with the
reasoning. The header carries an edition warning that is a real trap here:
the Proprium Poloniae of 1921 and 1934 is still bound into many missals and
carries an outdated arrangement of dioceses and ranks. It is not this
calendar, and it is exactly the kind of plausible wrong-edition source that
has cost this project time before.
benedictine.ini -- two entries, and the comment explaining why is the point of
shipping it. Counted across the Norcia ordo: 86 "I cl.", 43 "II cl.", ZERO
"III cl." and ZERO "IV cl.", against 103 "Semidup." and ~96 "Dup.". The
monastic rite uses the Roman classes at the top and the older Duplex /
Semiduplex grades below, exactly where the Roman calendar has III and IV
class. colitur's rank vocabulary cannot express those, so most Benedictine
propers cannot be written here at all -- a mismatch of vocabularies between
related rites, not a gap in the data. Rather than invent a Duplex -> Class3
mapping the source never states, that file ships only what its ordo gives in
Roman terms and lists roughly two dozen excluded feasts BY NAME, so the
omission is visible instead of silent.
Both headers say plainly that they are examples and not authorities: they are
transcriptions from published ordines, none of the five test layers can vouch
for either, and both should be checked against the reader's own ordo. The
cram test asserts only what we control -- that they parse, convert and apply
-- and says so.
They also demonstrate the precedence engine on real data: the Benedictine
Transitus is I class and takes 21 March with the Lenten feria commemorated,
and Maurus is II class and takes 15 January with Paul the First Hermit
commemorated.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`--overlay` shipped last week with nothing to point at. The only example
was a test fixture, explicitly labelled invented and living where no user
would find it, so "colitur supports local calendars" was true but
undemonstrated.
data/ef/examples/diocesan-example.sexp is installed to
<prefix>/share/colitur/examples/ and is runnable documentation: every
celebration in it is invented, with example- slugs that cannot collide,
and its header explains the four directives, the three date shapes, and
the field vocabularies before showing any of them.
It demonstrates the range deliberately rather than the minimum: a
fixed-date local patron with its own propers; a movable dedication on the
first Sunday of October, I class because a church's own dedication
anniversary is I class in that church and at III it would lose to the
Sunday every year; an Easter-relative commemoration; an Edit raising a
universal feast for local use; and a Suppress shown commented out,
because suppressing a universal feast is a real liturgical act and an
example should not invite it casually.
Two things the header says plainly, since this is the one file that lets
someone change what colitur computes: an overlay is applied ON TOP of the
shipped calendar and never instead of it, and an overlay is applied, NOT
validated -- the five test layers assert things about the shipped data
and cannot vouch for a user's file.
A test asserts it loads against the real EF rank vocabulary, still
demonstrates at least four directives, and applies with NO diagnostics.
The last matters most: a directive naming a slug the universal calendar
lacks would warn, and an example that warns teaches the wrong lesson. It
deliberately does not pin which days it produces -- those celebrations
are invented, and pinning their dates would make an illustrative file
behave like calendar data.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Overlay algebra has been built and tested since Plan 2, and
Date_spec grew movable variants last commit, but there was still no way
to hand colitur a file: bin/main.ml applied exactly one overlay, the
shipped data/ef/adjustments.sexp, with the path hardcoded. This is the
plumbing.
--overlay is repeatable and ordered, and applies ON TOP of the shipped
adjustments rather than instead of them. That ordering is the whole
point: adjustments.sexp carries RG 110's own 30 June companion, the Major
Litanies, St Barbara and Rogation Wednesday, and a user file that
replaced it would silently drop all four while appearing merely to add a
local feast. Overlay.merge is last-writer-wins, so a local calendar can
still override a universal entry deliberately, by naming its slug.
The dispatch needed real argument parsing. It matched Sys.argv as an
exact array, which does not survive a repeatable flag -- two --overlay
arguments are a different array shape from one, and each further flag
would multiply the patterns again. Flags are now stripped first and the
remaining words matched as command plus year. Hand-rolled, because the
dependency list is frozen and this is fifteen lines. One defect found by
the cram suite on the first run: the unknown-option guard rejected
--help and --version, having excluded only their short forms.
Refused, not ignored, on easter and temporal. Neither reads sanctoral
data -- temporal deliberately runs the cycle before any layer exists --
so accepting the flag there and silently doing nothing is the failure
mode this project refuses everywhere else.
A file that fails to load is fatal, exactly as the shipped overlay is; a
directive naming a slug that does not exist warns on stderr and the run
continues. The second matters more for a user file than for ours: a typo
in a diocesan calendar should say so rather than quietly do nothing.
test/fixtures/overlay-example-diocesan.sexp is a worked example and is
labelled as invented, not calendar data. It carries both shapes a real
local calendar needs: a fixed-date patron, and a dedication on "the first
Sunday of October" -- which is the case the Date_spec work existed to
unlock and which lands on 4 October in 2026, 3 October in 2027, 1 October
in 2028. The dedication is I class because a church's own dedication
anniversary is I class in that church; at III class it lost to the
II-class Sunday every year, which a first draft demonstrated correctly
and uselessly.
The man page gains an OVERLAYS section saying plainly that an overlay is
applied, NOT validated: the five test layers assert things about the
shipped calendar and none of them can vouch for a file the user supplies.
This is the first feature that lets someone change what colitur computes,
and that distinction should not have to be inferred.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fifteen class-3 saints had no readings anywhere in colitur -- the 6 March -
5 April cluster whose RANKS lectio's generator fix of 2026-08-12 restored
without backfilling readings, and lectio has no Commons concept to have
backfilled them from. They are exactly the fifteen `status Feast` entries in
data/ef/sanctoral.sexp carrying `(citations ())`.
The Missal splits them eight/seven, and NOT as the plan predicted. It
expected propers for Benedict, Patrick, Thomas Aquinas and Gregory the
Great; only Thomas Aquinas has one. Benedict ("Missa Os iusti, de Communi
Abbatum"), Patrick ("Missa Statuit, de Communi Confessoris Pontificis I
loco") and Gregory the Great ("Missa Si diligis me, de Communi unius aut
plurium Summorum Pontificum") are all sent to a Common, with only Oratio,
Secreta and Postcommunio printed at the date. All three reversals verified
on the page images (pp. 488, 492) as well as in both OCR text layers.
PROPERS (data/ef/adjustments.sexp, `Edit ... Set_citation`):
thomas-aquinas 7 Mar Sap 7:7-14 / Mt 5:13-19
john-of-god 8 Mar Eccli 31:8-11 / Mt 22:34-46
forty-holy-martyrs 10 Mar Hebr 11:33-39 / Luc 6:17-23
cyril-of-jerusalem 18 Mar Eccli 39:6-14 / Mt 10:23-28
gabriel-the-archangel 24 Mar Dan 9:21-26 / Luc 1:26-38
john-damascene 27 Mar Sap 10:10-17 / Luc 6:6-11
john-of-capistrano 28 Mar Sap 10:10-14 / Luc 9:1-6
francis-of-paola 2 Apr Philipp 3:7-12 / Luc 12:32-34
COMMONS (data/ef/commons.sexp), each quoting the Missal's own instruction at
the saint's date:
sts-felicitas-perpetua 6 Mar non Virginum I Eccli 51:1-8,12 / Mt 13:44-52
frances-rome 9 Mar non Virginum II Prov 31:10-31 / Mt 13:44-52
gregory-the-great 12 Mar Summ. Pontificum 1 Petri 5:1-4,10-11 / Mt 16:13-19
patrick 17 Mar Conf. Pont. I Eccli 44:16-27;45:3-20 / Mt 25:14-23
benedict 21 Mar Abbatum Eccli 45:1-6 / Mt 19:27-29
isidore-of-seville 4 Apr Doctorum 2 Tim 4:1-8 / Mt 5:13-19
vincent-ferrer 5 Apr Conf. non Pont. I Eccli 31:8-11 / Luc 12:35-40
Every value read in three independent places -- both OCR text layers (two
DIFFERENT printings, whose Commune Sanctorum pagination differs by 1-2, so
the data keys on heading plus Mass incipit and never on the bracketed page
number) and the rendered page images. That caught a genuine typographical
error in one printing: it gives St Gabriel's Gospel as "Luc. 2, 26-38" (on
the page image, so not an OCR artefact). Luke 1:26-38 is settled by the
other printing, by the pericope text in both, and by the SAME edition citing
the identical pericope correctly two pages later at the Annunciation.
Step 4 EXECUTES SECOND (order 1, 4, 2, 3), not last as the plan sketched.
Measured: every day on which one of the fifteen is actually the observed
office already has a non-empty step-2 or step-3 answer waiting, so a step 4
placed after the ferial fallback is unreachable on every date in 1583-9999 --
and wrong where it would fire (2038-03-06 would emit Septuagesima II
Saturday's Mass on a day whose office is Sts Perpetua and Felicity). Step 4
is also the only step in the chain with a direct primary-source warrant: the
Missal names the Mass at each such saint's own date, while steps 2 and 3 rest
only on lectio's observed behaviour. Guarded so it can never divert a day
whose observed office IS its temporal office -- ferias, Sundays, the Triduum
and the RG 78 Saturday Office of the BVM (which deliberately shares the
ferial slug) are structurally excluded. Step NUMBERS are unchanged so every
"step 3" already written down still names the same branch.
The Commons are caller-supplied, like the lectionary: `Rite_ef.context` gains
a REQUIRED `~commons`, not an optional one -- a silently-defaulted empty
table would give a rite whose class-3 saints quietly lose their Mass, and
nothing in layers 3-5 compares citations, so that loss would be invisible.
`Commons.load` rejects the four defects indistinguishable downstream from
"this saint has no Common": duplicate common id, duplicate assignment, empty
formulary, assignment naming a common that does not exist.
KNOWN LIMITS, recorded rather than papered over. There is no oracle: lectio
has no readings for these feasts and missalemeum's 2026-2027 window never
observes one of the fifteen. Eight of the fifteen are NEVER the observed
office anywhere in the domain (10-28 March always falls to a Lenten or
Passiontide feria that outranks a III-class feast), so they have no
end-to-end witness and are covered by dedicated table tests instead. The
Commune Sanctorum's own opening rubric permits any Epistle or Gospel of a
Common in any Mass of that Common unless one is assigned, so what is emitted
is the reading PRINTED WITH the named formulary, not the only lawful one;
labelled alternatives (e.g. the widow's Epistle 1 Tim 5:3-10, squarely
available to St Frances of Rome) are noted in the data and deliberately not
encoded. The BVM Common is NOT authored: the RG 78 Saturday Office shares the
ordinary ferial slug, so assigning a Common to it would hand the BVM Mass to
every feria sharing that slug; routing it needs a subject-keyed lookup and a
season-keyed choice among the Missal's five "Missae de sancta Maria in
sabbato" -- new behaviour, not new data. It currently emits its feria's Mass,
now pinned as such.
Blast radius, measured (git archive HEAD binary vs HEAD, full 1583-9999
`colitur day` sweep, diffed): ZERO differing lines, 3 074 246 lines each
side, byte-identical -- the CLI prints no citations, so this change is
invisible to layers 2-5 by construction, and the sanctoral overlay perturbed
nothing else.
357 tests green (344 before), 358 with the exhaustive sweep. Mutation-tested
six ways, all reverted after confirming: relegating step 4 to last reddens
exactly the four step-4 pins and nothing else; corrupting a Common's citation,
dropping an assignment, dropping a proper Edit, and corrupting either an
unreachable proper or an unreachable Common each redden their own pins. The
load-bearing assertion is stated over the loaded layer rather than a
hard-coded list: every sanctoral entry that can ever BE the observed office
now has either a proper or an assigned Common -- fifteen failures before,
zero after, and a future re-bootstrap adding a readingless class-3 feast
fails there instead of silently emitting its feria's Mass.
|
| |
|
|
|
|
|
| |
119 entries, Epistle + Gospel only. The generator refuses a section carrying
exactly one of the two: an Epistle without a Gospel is malformed and wants
investigating, not shipping. SHA-256 of the source INI is in the provenance
header, as with sanctoral.sexp.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Assembles Rite_ef.context (lib/rites/rite_ef/rite_ef.ml[i]): temporal,
anchors and vocab from Temporal_ef; rules from Precedence_ef's band,
disposition and admit; season_runs = Vocab_ef.seasons; transfer_target
newly implemented here.
transfer_target (RG 96): the next following day that is not I or II
class, with the Annunciation's own exception (Monday after Low Sunday).
Terminates by a structural step bound on its internal search, independent
of Calendar's own round guard, which bounds rounds across a year, not one
call's walk; documented as an obligation on rite.mli's transfer_target
field, which did not previously state it.
Fixes the vigil-naming mismatch Task 7's review predicted: the sanctoral
bootstrap names its vigils with a vigil-of-X prefix (lectio's own
convention), while Precedence_ef's is_vigil only recognised the temporal
cycle's own X-vigil suffix. Both are now recognised, fixing RG 91 entries
21/26 and RG 33's vigil omission for the four affected celebrations.
Verified by unit test and by mutation-testing the fix (reverting it fails
exactly the new rows) and against real output across several years.
Suppresses data/ef/sanctoral.sexp's vigil-of-christmas via a new overlay,
data/ef/adjustments.sexp: it is the same celebration as the temporal
cycle's own ef-nativity-vigil, both dated 24 December.
colitur day <year>: one line per civil-year day, temporal and sanctoral
fully resolved through Layer, Overlay, Precedence_ef and Calendar -- the
first CLI path exercising the whole Plan 3 pipeline against real data.
Verified the All Souls transfer chain (Tasks 7-8-11) end to end against
real output for both a Sunday year (2025, lands on 3 Nov) and a
non-Sunday year (2026, observed directly on 2 Nov).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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'.
|
| |
|
|
|
|
|
|
| |
Rite specificity is carried by type parameters plus a vocab record of
operations rather than by functors: the same guarantee that a rite cannot name
another rite's season, without threading module plumbing through every kernel
module. Celebration takes only the rank parameter, since it has no season
field and OCaml rejects a type variable that appears in no field.
|
| |
|
|
|
|
|
| |
Adds sexplib and ppx_sexp_conv to the project and wires the ppx into the
kernel library. Date's sexp form is an ISO-8601 atom rather than the opaque
rata die, so data files stay human-editable and parsing revalidates the
1583..9999 domain.
|
| |
|
|
|
| |
Argv-parsed subcommand; validates the 1583..9999 domain (exit 2 otherwise);
prints 'name YYYY-MM-DD' lines. Cram test covers the output and the exit codes.
|
|
|
OCaml 5.2.0 local switch; colitur_kernel library, a colitur executable stub,
and an alcotest+qcheck test runner. AGPL LICENSE, README, generated colitur.opam.
|