<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/data, branch v1.2.1</title>
<subtitle>deterministic OCaml engine to compute and validate liturgical calendars for multiple rites, template-driven output to year 9999</subtitle>
<id>https://git.labunix.xyz/colitur.git/atom?h=v1.2.1</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=v1.2.1'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-09-02T10:54:07Z</updated>
<entry>
<title>fix(of): correct ten reading references, regenerated from lectio</title>
<updated>2026-09-02T10:54:07Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-09-02T10:54:07Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=b14a3078c1a4db87010f5df955442bf1e440e0ec'/>
<id>urn:sha1:b14a3078c1a4db87010f5df955442bf1e440e0ec</id>
<content type='text'>
Nine ended at verse 22 where the OLM ends them at 20; the tenth read
"Matthew 17:9a,10-135", a verse Matthew 17 does not have. Every one was in a
Year II entry, or in of-advent-2-saturday, which has no year cycle. Not one
Year I entry was affected -- that distribution is what made this a
transcription fault rather than ten independent slips.

Corrected upstream in lectio's own internal/caldata/of-lectionary.ini (its
suite green) and this file regenerated from it, per the header's own
instruction not to hand-edit a generated artifact. The diff is exactly the
source SHA-256 pin plus the ten references; both pins in test_lectionary_of.ml
move with it.

The Ordinary Time invariant added in the previous commit now asserts outright
rather than pinning six exceptions: in Ordinary Time the OF weekday Gospel does
not depend on the year of the cycle, so a -i/-ii pair whose Gospel differs is a
defect, and the expected violation set is empty. Mutation-tested against the
Year II entry alone -- an earlier attempt changed both halves of the pair and
passed for the wrong reason, which is worth knowing about this shape of test.

Measured: the 20-vs-22 divergence class against litcal goes 26 -&gt; 0 over the
twelve witnessed years. 917 tests, every year 1583-9999.

Also, L6/L7 (the Sacred Heart / John the Baptist tie at an equal Tabula entry)
gains the citation its note said did not exist. Tabula I.3 enumerates
"Sollemnitates Domini, beatae Mariae Virginis, et Sanctorum" -- an order, not a
bare category, and read that way it settles the tie against colitur's current
alphabetical answer. Recorded with its counter-argument (entry 4 marks its own
internal order with explicit lettering; entry 2's list cannot be an ordering at
all) and with what would settle it. Verdict stays open, behaviour unchanged.
</content>
</entry>
<entry>
<title>fix(of): break the movable-BVM-memorial tie per a CDWDS Notification</title>
<updated>2026-08-26T22:37:45Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T22:37:45Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=6e62e499aed1c5a31a18bcf5e9eaa7f2b0c4ff2e'/>
<id>urn:sha1:6e62e499aed1c5a31a18bcf5e9eaa7f2b0c4ff2e</id>
<content type='text'>
Mary, Mother of the Church (Easter+50) and the Immaculate Heart of Mary
(Easter+69) are both movable obligatory memorials that can land on a
FIXED obligatory memorial's own date -- e.g. 2011-06-13 and 2015-06-13,
both against Anthony of Padua. Both candidates banded identically at
Tabula III.10 (100, both universal Memoria_obligatoria), a tie band
could not break, so Precedence.resolve's kernel-level alphabetical
Slug.compare tie-break silently picked instead -- 892 days for Mater
Ecclesiae and 1109 for the Immaculate Heart across 1583-9999, all one
shape (measured against the pre-fix alphabetical order directly, zero
unexplained).

Searched the corpus rather than inventing a rule. colitur's two
previously-checked primary sources really are silent: the 2018 decree
(AAS 110, 437-438) never addresses universal-calendar coincidence, and
Normae n. 14's third clause resolves only two AD-LIBITUM memorials
colliding. A third source resolves it: the CDWDS's own "Notification on
the Memorial of the Blessed Virgin Mary, Mother of the Church" (24 March
2018 -- same dicastery and signatories as the decree; published only on
vatican.va, confirmed absent from the AAS 2018 combined index) states
that when this memorial "coincides with another Memorial of a Saint or
Blessed... the Memorial of the Blessed Virgin Mary is to prevail," and
extends the identical reasoning to the Immaculate Heart of Mary by name.
This is a rubric, not colitur's own reasoned choice.

Implemented as a sub-rank of 95, strictly between Tabula entries 9 and
10 -- the same "fits between neighbours" technique this module already
uses elsewhere -- scoped to the two named slugs rather than
[subject = Bvm], since the Notification's own reasoning is specifically
about a memorial's link to a movable solemnity of the Lord, not a
blanket Marian precedence.

Golden pin updated: test_rite_of.ml's 2011-06-13 tie now pins Mary,
Mother of the Church as observed (was Anthony of Padua, labelled "known
arbitrary"), with the header rewritten to state the outcome now rests on
the Notification, not a coin-flip. The litcal oracle allow-list gains
L10: litcal's own grade vocabulary has no representation for a 95
sub-rank (everything Memoria_obligatoria reads "Memorial"), a
representational gap in litcal, not a computational disagreement -- the
identity axis is untouched and needed no new entry.
</content>
</entry>
<entry>
<title>fix(citation): represent chapter-crossing verse ranges (W4)</title>
<updated>2026-08-26T21:53:58Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T21:53:49Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=90584d87e763789808329847c46dbaa8e22dca47'/>
<id>urn:sha1:90584d87e763789808329847c46dbaa8e22dca47</id>
<content type='text'>
Parse.verse_range's [last] endpoint gains an optional chapter
(Parse.verse_end: { chapter : int option; verse : verse_num }), so a
hyphen range whose two endpoints lie in different chapters
("1 John 1:5-2:2") can be represented at all. Rejected: a bare
[int option] living alongside [last] as a second field on
verse_range -- that would let 'a chapter with no verse' exist as a
constructible value.

parse_part now splits a part's leading "chapter:" at the FIRST
colon only (not every colon), so the verses side can itself carry a
second colon from a crossing range. parse_range detects a crossing
by checking whether the range's right-hand side contains ':', and
skips the same-chapter descending-range guard for that case (a later
chapter is always "ahead", whatever its own verse numbers are).
Handles the compound shape too -- a crossing range followed by
further, same-chapter verse references in the same comma list
("Matthew 9:35-10:1,5a,6-8") -- since those trailing pieces parse as
ordinary bare verses/ranges, unaffected by the preceding crossing.

Render's one_range renders a crossing [last] through the same
chapter_verse template one_part already uses for the part's own
leading "chapter:verses", so a style that reconfigures the
chapter/verse separator renders a crossing endpoint in that same
convention rather than a hardcoded ':'.

This closes the W4 known-wrong: 41 (now 49, after an intervening
Second-reading extraction) of the OF lectionary's citations printed
unconverted, every one this exact shape. test_citation_coverage_of.ml's
pinned residual is now empty and asserted exactly, over the full
1725-field data/of/lectionary.sexp population, including the round-trip
check (parse -&gt; render -&gt; parse structural equality). test_citation.ml
gains direct parse-suite cases for the basic crossing, the compound
shape, a mid-list crossing, a crossing with a sub-verse letter, and a
malformed-crossing rejection.

data/of/lectionary.sexp is regenerated via its own generator
(tools/bootstrap_lectionary_of.ml, whose own embedded header text is
updated to match); only comment lines change, confirmed by diff --
no lectionary entry differs. test_lectionary_of.ml's whole-file
SHA-256 pin is updated to match.

EF is unaffected: data/ef/ is untouched since v1.0.0, and a direct
byte comparison of `colitur day`/`colitur readings` for 2026, 1583
and 9999 against a git-worktree build of 1c0137d is identical on all
six outputs.
</content>
</entry>
<entry>
<title>fix(of): dispatch Holy Family's Normae n.35(a) fallback (W2)</title>
<updated>2026-08-26T21:45:16Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T21:45:16Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=9ebb06983b7a26db5564302253f2551dfbcf834e'/>
<id>urn:sha1:9ebb06983b7a26db5564302253f2551dfbcf834e</id>
<content type='text'>
temporal_of.ml's temporal only ever reached holy_family's own correct
26-31 December / 30 December fallback logic through sunday_slug, which
returns None immediately for a non-Sunday date. In a year 25 December
is itself a Sunday (26-31 December then holding no Sunday of its own),
the feast vanished entirely and fell through to an ordinary Christmas
ferial slug -- confirmed on 2022, 2033, 2039, 2044, 2050 (and roughly
1583..9999 domain-wide, one year in seven).

temporal's dispatch now consults holy_family directly, independent of
weekday, between the sunday_slug and christmas_feria_slug branches,
carrying the identical identity the Sunday case already builds
(subject Lord, holy_family_names, rank Festum). holy_family and
anchors were already correct and needed no change.

Added a direct example-based test for the fallback across five
affected years plus a control for the unaffected Sunday case, and a
domain-wide property (Holy Family is observed in every liturgical
year 1583-9999) folded into both the 200-sample QCheck run and the
exhaustive sweep -- the property that would have caught this directly.

Un-pinned the now-fixed behaviour everywhere it was recorded as
known-wrong: test_rite_of.ml's own 1583 pin (rewritten to assert the
fix), test_validate_of.ml's own independent exhaustive-sweep counter
and landmark-year filter (found via make check, not the initial
survey), and data/of/expected-divergences-litcal.sexp's L2/L3
allow-list entries against the litcal oracle (closed and removed, with
a dedicated regression test replacing the allow-list's own silence).
</content>
</entry>
<entry>
<title>fix(citation): make the well-formed reading-part set rite-supplied</title>
<updated>2026-08-26T12:28:50Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T12:28:50Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=e5cbc662227de4f3ab0089fe73f639f4f84601a4'/>
<id>urn:sha1:e5cbc662227de4f3ab0089fe73f639f4f84601a4</id>
<content type='text'>
I7 (final-review.md, 2026-08-25-colitur-of-phases-3-5): OF Sundays and
solemnities shipped without the Second reading, and the lectionary
data header called the First reading an "Epistle" -- EF vocabulary.
The sole blocker was Validate's own kernel-hardcoded assertion that a
well-formed day's sorted citation parts are exactly [First; Gospel];
Citation.part already carried Second.

Rite.t gains citation_shapes : Citation.part list list, the fourth
EF-shaped kernel misfit (after Preface.t, Mass_formulary.source,
transfer_target's strictly-later contract) and the first fixed rather
than only documented. EF supplies its one shape unchanged; OF supplies
two, [First; Gospel] on a feria/feast/memorial and [First; Second;
Gospel] on a Sunday or solemnity (OLM 1981 Praenotanda n. 66.1 vs
n. 69.1, n. 84(b)/(c), page-image verified against olm-1981.pdf).
Validate's "citations" check now matches against the rite's own
declared shapes rather than a single literal pattern, preserving all
three invariants it always enforced: zero or one complete shape, never
partial; no part outside what the rite declares; no part repeated.

bootstrap_lectionary_of.ml's cite now extracts a Second reading from
Sunday-cycle ini sections only, correctly excluding the one stray
`second` field lectio's snapshot carries on a weekday-cycle section (a
content-verified scraping artifact). The hand-authored Christmas Day
entry gains its own Second reading from the same OLM passage already
cited for its First/Gospel. Regenerated data/of/lectionary.sexp: 771
entries unchanged, citation fields 1542 -&gt; 1725 (+183, all Second
readings), SHA-256 re-pinned, header wording fixed and a new coverage
bullet added. Responsorial Psalm is deliberately still not emitted --
OLM 1981 Praenotanda n. 71 itself lists it apart from the readings
proper as a chant with its own refrain, and the finding driving this
change names only the missing Second reading.

EF output verified byte-identical to the branch start (empty
lib/rites/rite_ef + data/ef diff since v1.0.0, plus a direct binary
comparison of `day`/`readings` for 2026, 1583 and 9999 against a
worktree build of the pre-change commit).

Tests: three new fixtures in test_validate.ml prove the generalisation
holds both directions (a multi-shape rite accepts either of its
declared shapes; a rite that never declared a shape still rejects it).
test_citation_coverage_of.ml's pinned residual grows 41 -&gt; 49, all new
entries the same already-understood chapter-crossing shape.
</content>
</entry>
<entry>
<title>feat(of-lectionary): hand-author the Christmas Day Mass, close the gap</title>
<updated>2026-08-26T11:57:14Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T11:57:14Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=03d3c00ccdde318207c8b4fdc456e5e64d6b4abf'/>
<id>urn:sha1:03d3c00ccdde318207c8b4fdc456e5e64d6b4abf</id>
<content type='text'>
colitur readings --rite of printed "- | -" on 25 December every year
-- lectio's own 988 keys never included the Christmas DAY Mass at
all, only the Vigil (already correctly noted, but left unfilled, in
tools/bootstrap_lectionary_of.ml's own named_overrides comment).

Sourced, not guessed: docs/research/of/olm-1981-ocr.txt:4513-4520,
"16 Ad Missam in die", the same primary authority this module already
cites for the cycle rules. First reading and Gospel only, matching
this file's own scope everywhere else (First/Gospel is the kernel's
own boundary, I7, a separate deferred task) -- Isaiah 52:7-10 / John
1:1-18 (OLM's own LONGER form; the file's LINEAGE section already
discloses that short/long-form choices are not modelled anywhere in
this data, so this is an application of an existing limitation, not a
new one).

Injected via a new hand_authored table in the generator, merged into
entries before assert_reachable/Lectionary.of_entries -- there is no
ini section to derive this from. One new lookup_any call (readings'
own existing temporal-slug fallback, unmodified) now resolves BOTH the
formulary and the citations for 25 December from the same entry, so
both halves of the "citations-unresolved"/"formulary" gap close
together, not just the citation half.

Verified across the WHOLE domain, not assumed: test_validate_of.ml's
exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1, all 8 416 years) now
asserts the Nativity gap fires on NONE of them, down from all 8 416
before this fix -- and is kept as a live, dedicated "must never recur"
counter rather than deleted. test_rite_of.ml's landmark-year check and
test_lectionary_of.ml's 2026 coverage pins are updated the same way
(the unresolved-day set that used to be pinned as {25 December} is now
pinned as empty, not merely a count going to zero). A concrete golden
pin (test_golden_of.ml) locks the exact citations through the full
resolved pipeline. data/of/lectionary.sexp regenerated (771 entries,
was 770); its own SHA-256 re-pinned.

make check (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, unpiped,
foreground): exit 0, 895 tests, ~536s.
</content>
</entry>
<entry>
<title>feat(of-lectionary): disclose the citation-conversion residual in the header</title>
<updated>2026-08-26T11:30:09Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T11:30:09Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=9b8821f7b914c94f5b2c8eeda05407ee921b3d5a'/>
<id>urn:sha1:9b8821f7b914c94f5b2c8eeda05407ee921b3d5a</id>
<content type='text'>
Fix option (c) from the I2 review finding, done alongside the parser
fix itself rather than deferred: tools/bootstrap_lectionary_of.ml now
runs every emitted reference through Colitur_citation.Parse.parse at
generation time and adds a fourth COVERAGE item plus a named listing
to data/of/lectionary.sexp's own header, so the 41-reference residual
(all chapter-crossing hyphen ranges, see the citation-fix commit) is
disclosed in the artifact a reader actually opens, not only in a task
report or test file.

Regenerated against the pinned lectio snapshot (dune exec
tools/bootstrap_lectionary_of.exe -- ../lectio/internal/caldata/
of-lectionary.ini data/of/lectionary.sexp); diffed against the
pre-change file to confirm the change is header-only, zero lines
removed, the sexp payload byte-identical. Re-pins the file's own
SHA-256 in test_lectionary_of.ml accordingly (re-derived with
sha256sum, not transcribed from generator stdout, matching that
test's own stated discipline).
</content>
</entry>
<entry>
<title>fix(install): ship the OF runtime data files, guard the installed layout</title>
<updated>2026-08-26T10:55:23Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T10:55:23Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=f893a2019e7ad57a57154926c8e0e01aa99aa473'/>
<id>urn:sha1:f893a2019e7ad57a57154926c8e0e01aa99aa473</id>
<content type='text'>
data/dune's install stanza named only the four EF runtime files, so a
`dune install`ed colitur exited 2 on any --rite of invocation
(`failed to load .../data/of/calendar-2002.sexp`) -- the branch's
headline feature was dead on an installed binary. Add all 15 OF
files (calendar-2002.sexp, the 13 amendments/*.sexp, lectionary.sexp)
to the (files ...) stanza, following the existing ef/ entries' form
and the stanza's own stated rule (deliberately not the litcal
allow-list, a test fixture, same reasoning already given for the EF
pair).

Add a regression guard for the class of bug, not just this instance:
`make check-install` performs a real `dune install --prefix &lt;scratch&gt;`
and runs both rites against the INSTALLED binary, asserting exit 0
and the expected day count. test/cli.t's cram sandbox binds
%{bin:colitur} inside the build tree, where data_dir()/of_data_dir()'s
build-tree fallback candidate resolves even when the installed one is
missing entirely -- so no cram test could have caught this. Wired
into `make check` (unlike check-schema/check-templates/check-citations,
which skip when an optional external tool is absent) since installing
needs nothing beyond what building already needs. Verified the guard
has teeth: reverted data/dune to the pre-fix version and confirmed
check-install fails loudly naming the missing file.
</content>
</entry>
<entry>
<title>test(of): validation layers -- Layer 2 properties, litcal grade/identity, golden pins</title>
<updated>2026-08-26T10:29:17Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T10:29:17Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=e6cb5937be7f009eab11f3cf85205d1fde25eb48'/>
<id>urn:sha1:e6cb5937be7f009eab11f3cf85205d1fde25eb48</id>
<content type='text'>
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 -&gt; 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.
</content>
</entry>
<entry>
<title>fix(of-lectionary): stop serving drifting readings for O-Antiphon and Christmas-season dates</title>
<updated>2026-08-26T08:11:47Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T08:11:47Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=b6d0eba04d1da51515dad70127112898b500c1bc'/>
<id>urn:sha1:b6d0eba04d1da51515dad70127112898b500c1bc</id>
<content type='text'>
The bootstrap excluded 19 lectio bases as "date-keyed duplicates", but
17 of them were not duplicates at all: the 8 O-Antiphon days
(17-24 December) and 8 further Christmas-season dates (29-31 December,
2-5 and 7 January) each carry unique per-date content found nowhere else
among the emitted entries, verified against lectio's own ini directly.
17 December is Gen 49:2,8-10 / Matthew 1:1-17, but colitur was falling
through to that year's unrelated weekday-keyed "advent-3-mon" content
instead.

OLM n. 69.3 explains why: those ferias are fixed by civil date, unlike
every other Advent/Christmastide/Paschaltide feria, which is merely
non-alternating within a weekday slot. Temporal_of's own ferial slugs
for these dates are weekday-keyed, so looking one of those up served
whichever OTHER date happened to share that year's weekday alignment --
a citation that silently drifted year to year on ~16 real civil dates
(6 January stays excluded: Temporal_of fixes Epiphany there
unconditionally, so that date can never reach the ferial path at all;
two more, easter-6-thu and advent-4-sat, stay excluded for their own,
different structural reasons).

Fixed with a date-keyed lookup route (Lectionary_of.date_keyed_slug),
tried before the weekday-keyed one in readings' own step 3.
Temporal_of's slugs are unchanged -- only which lectionary key resolves
a day's citations. tools/bootstrap_lectionary_of.ml now maps the 17
bases to synthetic date-keyed slugs instead of excluding them, and its
own reachability sweep calls date_keyed_slug directly so the two can
never drift apart. Re-emitted data/of/lectionary.sexp (754 -&gt; 770
entries) and corrected its provenance header, which previously
described these dates as excluded duplicates.

Also states, for the first time, a Minor finding from the same review:
190 of 222 shipped sanctoral slugs have no dedicated lectionary entry
and correctly fall through to the ferial per OLM norms -- expected
behaviour, not a gap, but never said in the coverage report before.

New tests pin 17 December (Gen 49:2,8-10 / Matthew 1:1-17) and 2 January
(1 John 2:22-28 / John 1:19-28) in two years with different weekday
alignments, proving neither reading drifts, plus direct boundary
coverage of date_keyed_slug itself (both windows, the 6 January
exclusion, and the Sunday guard).
</content>
</entry>
</feed>
