<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/test/test_colitur.ml, branch v0.1.0</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=v0.1.0</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=v0.1.0'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-08-17T14:00:23Z</updated>
<entry>
<title>test(oracle): a second oracle year, 2038, closing step 4's blind spot</title>
<updated>2026-08-17T14:00:23Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T14:00:23Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=f03567a04b68ab19b5f16f0b6d56d9c08b14c818'/>
<id>urn:sha1:f03567a04b68ab19b5f16f0b6d56d9c08b14c818</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>kernel+ef: resolve readings, chain steps 1 and 2</title>
<updated>2026-08-14T22:16:35Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-14T22:16:35Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=124d7e2261c721c4a49f7203efc276088c7cb217'/>
<id>urn:sha1:124d7e2261c721c4a49f7203efc276088c7cb217</id>
<content type='text'>
Liturgical_day.citations has read "always empty until Plan 4" since Plan 3;
it is now filled. Rite.t gains a readings function, rite-supplied for the
same reason transfer_target is: what a day with no proper falls back to is a
rubric, not a universal. Calendar calls it and passes its own temporal
function as the callback the rite needs to reach another date.

Steps 1 and 2 only: the observed celebration's own proper, else the day's own
temporal slug. Nothing encodes "Lent has daily propers" -- the presence of an
entry is the discriminator.
</content>
</entry>
<entry>
<title>kernel(lectionary): slug-keyed reading citations</title>
<updated>2026-08-14T14:43:00Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-14T14:43:00Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=bca1a206589bc41bbcb367a7ae9b217c02f0e892'/>
<id>urn:sha1:bca1a206589bc41bbcb367a7ae9b217c02f0e892</id>
<content type='text'>
Data only, the same shape and discipline as Layer: slug-canonical,
duplicates rejected at construction naming the offending slug, sexp
round-trips. Which slug a day falls back to is a rubric and belongs to the
rite module, so nothing here knows about ferias or Sundays.
</content>
</entry>
<entry>
<title>test: golden pins for the known-tricky years</title>
<updated>2026-08-12T04:13:14Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-12T04:13:14Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=b8e1e2376de17feeb4d0b331f1febbc4ffe478e5'/>
<id>urn:sha1:b8e1e2376de17feeb4d0b331f1febbc4ffe478e5</id>
<content type='text'>
Validation layer 5: fourteen hand-verified pins in test/test_golden.ml,
wired into the suite via test/test_colitur.ml. Each pinned day was computed
and checked against its RG citation before being written down, never
transcribed from `colitur day` output:

- Easter extremes 1598 (earliest, 22 Mar), 1666 (latest, 25 Apr) and 2038
  (late-modern instance of the same latest date) -- all three independently
  computed by hand via the Gauss/Meeus Gregorian Easter algorithm, not read
  off Computus.gregorian_easter. extreme_years in test_validate.ml already
  established 1598/1666 as the true 1583-2500 extremes (correcting a stale
  1818/2038 comment); this file pins the resolved DAY there, not just the
  date.
- Annunciation transfer, 25 March inside Holy Week (2016: 25 March is
  literally Good Friday) and the double transfer with St Joseph (2008:
  RG96's Attamen(a) claims Easter+8 for the Annunciation first, Joseph's
  own RG96 walk continues past it to the next day).
- The RG96 Attamen(a) exception's own CONDITION pinned on both sides: 2057,
  2007, 2012 (general walk suffices, lands before Easter, no exception) vs
  2016/2024 (walk would cross Easter, Easter+8 fires).
- 2011-07-04, the Precious Blood transfer (Sacred Heart outranks it
  outright on 1 July; the RG96 walk skips Visitation and a Sunday before
  landing on 4 July).
- All Souls falling on a Sunday (2025, RG96 Attamen(b)) and Christmas
  falling on a Sunday (2022, RG91 entry 1 -- no contest, since 25 December
  is never an ordinary Sunday candidate in the EF temporal cycle).
- Holy Thursday's white amid violet Passiontide (2026, RG128(b)/RG122) --
  the case colitur and lectio previously agreed was violet, so the
  differential could never have caught it; only a golden pin or the
  missalemeum oracle can.
- Advent/Lent Ember ferias commemorated when impeded (1900, 1902, RG24 +
  RG109(e)) contrasted with IV-class ferias never commemorated (2026,
  RG26) and RG111(b)'s Sunday rank floor (2009, 2026) -- all three assert
  the actual displaced/excluded candidate is present in `omitted`, not just
  that `commemorations` is empty, so a day with no losing candidate at all
  could not pass vacuously.

2038 is deliberately NOT pinned day-by-day: register item F4 confirms
2038-03-06/08/09 are wrong (a lectio bootstrap-generator defect, not
fixable here). Only the Easter-week days, untouched by that bug, are
pinned; the exclusion is stated in the test's own comment, not silent.

Every weekday asserted was independently cross-checked against `date -d
&lt;iso&gt; +%A` (glibc, wholly outside this codebase) before being written down.
Precedence outcomes were traced against precedence_ef.ml's own band/
disposition/admit/transfer_target, not merely observed to look plausible;
where the primary text alone doesn't fully settle an outcome (the 2008
Annunciation/Joseph tie-break, both landing at RG91 table entry 11), the
test's own comment says so rather than overclaiming a citation.

Perturbation-tested: temporarily broke Holy Thursday's white-colour special
case in temporal_ef.ml, confirmed the golden test failed with a clear
day-and-field diff, reverted.
</content>
</entry>
<entry>
<title>test: oracle vs missalemeum 2026-2027; audit the sanctoral</title>
<updated>2026-08-12T02:36:36Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-12T02:36:36Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=cb200a3f95e24f67c1d08b2ceb09404bf57a1636'/>
<id>urn:sha1:cb200a3f95e24f67c1d08b2ceb09404bf57a1636</id>
<content type='text'>
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-&gt;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.
</content>
</entry>
<entry>
<title>test: differential vs lectio 2005-2050 with a cited allow-list</title>
<updated>2026-08-12T01:18:15Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-12T01:18:15Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=fcfce4ba0ab4ea93837b647deaa5292300aaad5e'/>
<id>urn:sha1:fcfce4ba0ab4ea93837b647deaa5292300aaad5e</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>rite(ef): clamp the RG96 search at the domain ceiling</title>
<updated>2026-08-11T23:16:22Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T23:16:22Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=94fc488cc9c6b4a050d90c4250f6e166b40088e7'/>
<id>urn:sha1:94fc488cc9c6b4a050d90c4250f6e166b40088e7</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>data(ef): bootstrap the 1962 sanctoral from lectio</title>
<updated>2026-08-11T22:05:57Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T22:05:57Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=4624441d0bc9c1f28dea54606ab7999041d4b323'/>
<id>urn:sha1:4624441d0bc9c1f28dea54606ab7999041d4b323</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>rite(ef): RG 91 Table of Precedence</title>
<updated>2026-08-11T20:04:59Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T20:01:24Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=436ba75e27d2aa61b1c6035a22157b40f1a9834b'/>
<id>urn:sha1:436ba75e27d2aa61b1c6035a22157b40f1a9834b</id>
<content type='text'>
Precedence_ef.band transcribes RG 91's 28-entry Table of Precedence
(rules-register.md §4) for the EF rite: given a day's context and a
candidate celebration, returns the table's own entry number, 1-28
(I class 1-13, II class 14-21, III class 22-26, IV class 27-28); lower
wins. Every branch carries its entry number and register citation in a
comment, checked in the table's own numeric order.

Two entries are transcribed as the register states them even though
they invert the pattern the rest of the table follows: at III class,
23 (particular calendars) outranks 24 (universal), the reverse of how
11/12 and 14/16/19/20 rank a universal feast ahead of a proper one at
I and II class.

Sanctoral-origin, layer-decided entries (11-13, 14/16/19/20, 23/24)
follow the brief's structural insight: a celebration whose layer is
not the universal base is an overlay -- proper, or indult if its
layer id also carries the indult prefix. Neither the universal-layer
id nor the indult prefix is an RG citation; both are colitur's own
data-modelling convention, exposed from the module so whichever task
loads the real EF sanctoral overlays can align to them.

Vigils (21, 26) are read off the temporal cycle's own -vigil slug
suffix rather than gated on origin, since a II/III-class vigil can be
either temporal-origin (Ascension, already produced by temporal_ef) or
sanctoral-origin (a saint's vigil, no task has loaded yet); Ember days
(part of entry 18) are read off temporal_ef's own ember slug prefixes
rather than re-derived, since the September anchor is independently
flagged there as one of the more contested dates in the calendar.

A candidate shape the table has no row for (e.g. a Class1 vigil that
is not Nativity or Pentecost, or a Class4 candidate marked as a vigil
-- RG 91 has no IV-class vigil either) returns a dedicated unclassified
sentinel (max_int) rather than being folded into a same-rank entry it
does not belong to.

test_precedence_ef.ml is table-driven: one Alcotest.test_case per RG
91 entry (55 rows total, several entries covered by more than one
named day so a single missed offset cannot hide behind a passing
sibling), each date computed from Computus.gregorian_easter rather
than hand-typed, so an arithmetic slip cannot pass by accident.
</content>
</entry>
<entry>
<title>kernel(calendar): the year is the primitive, the day is derived</title>
<updated>2026-08-11T18:01:54Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T18:01:31Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=8de560db7fb1b7b7d3ca93285068c6a4214e0bff'/>
<id>urn:sha1:8de560db7fb1b7b7d3ca93285068c6a4214e0bff</id>
<content type='text'>
Transfers make per-date resolution impossible to do correctly: resolving 25
March can push a feast onto 26 March, and RG 97-98 has coinciding I-class
feasts transfer in table order, which needs global knowledge. So year computes
a whole liturgical year in one pass and day indexes into it. Pure, no cache, no
mutable state.

This commit resolves each day but does not yet place deferred transfers; they
are recorded with a reason. Task 6 adds the placement pass.
</content>
</entry>
</feed>
