<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/test/test_differential.ml, branch v1.0.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=v1.0.0</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=v1.0.0'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-08-22T18:55:18Z</updated>
<entry>
<title>fix(ef): ferias after the Ascension repeat its own Mass, not a Sunday's</title>
<updated>2026-08-22T18:55:18Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-22T18:55:18Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=9c96e0afc19925b651e0dfee3bc9182dee72acf0'/>
<id>urn:sha1:9c96e0afc19925b651e0dfee3bc9182dee72acf0</id>
<content type='text'>
Ascension has no octave under the 1960 rubrics -- confirmed structurally:
the Proprium de Tempore's own table of contents runs straight from "In
Ascensione Domini" to "Dominica post Ascensionem" with no per-feria
headings in between. Step 3 of the reading chain therefore fell through
to Preceding_sunday on every unoccupied weekday in that stretch, walking
each one back to the 5th or 6th Sunday after Easter's own citation.

The Missal's own Proprium de Tempore carries an explicit marginal rubric
immediately after the Ascension's Mass propers, before "Dominica post
Ascensionem" begins: "Diebus a feria VI post Ascensionem usque ad feriam
VI ante vigiliam Pentecostes inclusive, dicitur Missa de festo
Ascensionis, cum Gloria et praefatione de Ascensione, sine Credo et
Communicantes proprio" -- every feria without its own proper, from the
Friday after the Ascension through the Friday before the Vigil of
Pentecost, repeats the Ascension's own Mass. RG 490(b) corroborates
independently: it assigns the Ascension preface "tamquam de Tempore"
across that identical window, which only makes sense if the Mass
actually repeated there is the Ascension's, not a numbered Sunday's
(whose own Mass would carry no need for a preface override at all).

Witnessed directly, not only inferred from the rubric: the LMS Ordo
prints "Mass of the Ascension" literally on every affected day across
all three of its fixture windows, in BOTH the tail of Ascension's own
week and the following week -- ruling out the alternative reading (that
the second week instead resumes "Dominica post Ascensionem"'s own Mass,
which a first pass over the FIUV Ordo's own week-labelled titles could
suggest at a glance; its own [praef] field for the same two dates
independently reads "Ascensionis", resolving the apparent conflict).

Implemented as data, not logic, matching this codebase's own established
precedent for the identical RG 299 shape (the week after Trinity Sunday,
data/ef/lectionary.sexp's [trinity_week_entries]): six new hand-authored
entries, [ascension_week_entries] in tools/bootstrap_lectionary.ml,
regenerated against a git-pinned copy of lectio's own source ini (SHA-256
verified unchanged) so the regeneration carries no unrelated drift. The
window's two Saturdays are deliberately excluded -- RG 78's votive Office
of Our Lady on Saturday always wins an otherwise-unoccupied Class4
Saturday first, before step 2 (this fix's own entry point) is ever
reached, so an entry for either slug would be dead data.

Blast radius measured in-process across the full 1583-9999 domain
(throwaway probe, not committed): 16 350 days total across the six
slugs, every one resolved via step 2 (Own_slug), zero anomalies, and the
window's two Saturdays confirmed to contribute zero hits via this cause
across the whole domain, corroborating the RG78-interception reasoning
by direct sweep.

Closed L2 in data/ef/expected-divergences-lms.sexp. New cited entries:
C41 in data/ef/expected-divergences.sexp (95 of 16801 rows, 2005-2050 --
lectio has no equivalent rubric and still walks these ferias back);
M31 in data/ef/expected-divergences-missalemeum.sexp (3 rows in the
2026-2027 window, plus the same shape independently reconfirmed in the
2035 and 2038 live-capture windows) -- missalemeum, Divinum Officium's
lineage, still gives the same wrong answer colitur itself used to give,
which is why these rows used to agree with M18's shape and now escape it
(M18 373 -&gt; 370, the same mechanism M23-M27 established when the
citation axis was first built).

Golden pin: 2031-05-23, the exact date the bug report was filed against.

Mutation-tested: reverting data/ef/lectionary.sexp alone (a git-diff
capture/restore, not a source-code revert, since the fix is data) reddens
19 tests across the differential, both oracle fixtures, all three LMS
Ordo windows, the golden pin, the lectionary unit test and every Render
golden template; restored and reconfirmed green (681 tests; 683 under
COLITUR_EXHAUSTIVE_SWEEP=1, ~175s).
</content>
</entry>
<entry>
<title>feat(ef): implement the calendarium's bissextile February shift</title>
<updated>2026-08-22T14:40:24Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-22T14:40:07Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=60e87914718dd2c8e69bb89d8cc48d6bef9bfc74'/>
<id>urn:sha1:60e87914718dd2c8e69bb89d8cc48d6bef9bfc74</id>
<content type='text'>
The Missale Romanum's calendarium footnote for February (LT.txt:5011-5014,
scan-corroborated scan2.txt:3050-3058): in a leap year the sixth kalends of
March (24 February) is doubled rather than a 29th day appended, so St
Matthias moves 24-&gt;25 February and St Gabriel of Our Lady of Sorrows
27-&gt;28, with 24 February itself carrying no fixed office that year.
data/ef/expected-divergences-lms.sexp entry L3 tracked this as an open gap.

Implemented as the rubric's own general mechanism (every fixed entry from
24 through 28 February shifts one civil day later in a leap year), not as
"move these two named saints": the two readings are indistinguishable on
shipped data (nothing else is fixed in that window) and diverge only for a
future --overlay entry in the same range, which the mechanism reading
covers correctly and a two-saints special case would not. This project has
already been bitten once (RG 16(a)) by a rule implemented against shipped
data's coincidental shape rather than the rubric itself.

Kernel stays rite-agnostic: Rite.t gains a fixed_key field (Date.t -&gt; (int
* int) option), the same seam easter already establishes, read only by
Layer.on_date's FIXED half -- the movable half (Easter_offset/Nth_weekday)
is untouched. Default is the identity mapping, an optional argument on
on_date, so every existing caller and every rite that supplies nothing is
byte-identical to before this field existed. Date.is_leap is exposed in
date.mli (already existed in date.ml) so the rite reads the kernel's own
single-sourced leap-year definition. The EF implementation lives in
Rite_ef.Temporal_ef.bissextile_fixed_key, fully cited, wired into
Rite_ef.context.

Blast radius, measured over the full 1583-9999 domain (Calendar.year run
twice per civil year, real fixed_key vs identity, every day diffed): all
2,041 leap years in the domain show a visible change for each saint;
6,983 individual liturgical days change total, zero unclassified, across
four shapes (Matthias vacates 24 Feb in 1,803 years / occupies 25 Feb in
1,811; Gabriel vacates 27 Feb in 1,699 / occupies 28 Feb in 1,670).
L3's own prior estimate (1,650 years, observed-outright only) is confirmed
close on the same definition (1,677 measured); the broader observed-or-
commemorated count is higher, not lower, showing the narrow estimate
undercounted visible impact rather than overcounting it.

The lectio differential (2005-2050 fixture) gains a new cited entry, C40
in data/ef/expected-divergences.sexp: lectio implements no such shift, so
every leap year in its window now diverges on the two shifted days (21
rows, not the naive 44 -- the comparator never compares commemorations, so
a side where the shifted saint has zero comparator-visible footprint on
both engines produces no row). The LMS Ordo's 2023-2024 fixture -- the
window L3 was originally found in -- now matches on the Creed comparison;
its formulary-override bucket count is corrected 182-&gt;181 (24 February
leaves the counted Proper population, becoming Own_slug-sourced). The
missalemeum oracle fixtures (2026-2027, 2038, 2035) cover no leap year and
are unaffected.

L3 is closed: converted from an active allow-list record to a prose
closure paragraph (L1's own precedent), citation preserved verbatim, fix
and measured blast radius recorded. The id-list assertion narrows to [L4]
alone, and the now-dead "2024-02-24" -&gt; "L3" date mapping is removed.

Six golden values pinned across four new test cases: Matthias in a leap
year (both the vacated and occupied sides) and a common-year control;
Gabriel likewise, deliberately choosing a leap year where he is admitted
only as an ordinary commemoration rather than winning outright, a
different shape from Matthias's.

Mutation-tested: forcing fixed_key back to identity reddens exactly five
test cases -- the differential's Layer C count-pin (C40 drops to 0 actual
vs 21 expected), the LMS Ordo's Creed and formulary-bucket checks, and
both new golden pins -- confirming the tests actually exercise the fix.
Reverted; dune test, the exhaustive sweep, and make check are all green.
</content>
</entry>
<entry>
<title>feat(ef): implement RG 33's third omission trigger</title>
<updated>2026-08-21T12:42:30Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-21T12:42:30Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=12b97761019cfa02ca0da8a5fb50ef815d07685c'/>
<id>urn:sha1:12b97761019cfa02ca0da8a5fb50ef815d07685c</id>
<content type='text'>
RG 33 omits a II/III-class vigil in three cases: it falls on a Sunday, it
falls on a I-class feast, "vel si festum cui praemittitur in alium diem
transferri aut ad commemorationem reduci contingat". Only the first two
were built; the third was recorded in precedence_ef.ml as unimplemented on
the grounds that no witness existed in the shipped data. That reasoning was
wrong, and the rule fires on 1 744 days across 1583-9999.

Both halves of the clause reduce to one observable question -- is the feast
the OBSERVED office on the following day (RG 34 puts it there) -- so the
kernel asks it once per candidate, after place_transfers has settled the
year. No fixed point is needed: a vigil is a candidate only on its own day,
never on its feast's, so suppressing it cannot change what the next day
observes. Precedence.rules gains vigil_feast, which returns the slug of the
feast a vigil precedes; the kernel cannot infer that itself, because only
two of the five vigil/feast pairs share a slug stem.

Blast radius, measured pre-change binary vs HEAD over the whole domain and
classified: 1 744 days, three shapes, zero unexplained. 1 199 are the feast
reduced to a commemoration (10 August on a Sunday, St Lawrence); 478 and 67
are the feast transferred under RG 96 after the Sacred Heart or Corpus
Christi takes its day. The Assumption's and the Ascension's vigils never
qualify -- their I-class feasts always keep their own day.

Independently witnessed, which is unusual here. The published Ordo -- the
only witness outside the Divinum Officium -&gt; missalemeum -&gt; lectio lineage
-- omits St Lawrence's vigil on 2025-08-09, agreeing with colitur against
both engines. That date had been read earlier as an Ordo gap; the Ordo was
right, and correcting the misreading is what surfaced this clause. On
2027-08-09 the feast does keep its day and the Ordo omits a vigil colitur
correctly keeps, which is a genuine Ordo gap.

Allow-lists: C39 (lectio, 10 rows) and a 2038 oracle class citing the
register, the 2026-2027 window having no instance. The golden pin asserting
St Lawrence's vigil is violet moved 2025 -&gt; 2027; its own comment had
reasoned about the vigil's weekday and missed that RG 33 also looks at the
feast's. Two new pins cover both shapes of the clause.

The vigil/feast table is built with Slug.of_string_exn: mutation testing
showed that of_string plus Result.to_option turns a typo into None, which
this hook's contract reads as "not a vigil", switching the rule off in
silence. Two tests assert the table against the shipped data in both
directions.
</content>
</entry>
<entry>
<title>test(differential): C14 and C15 closed -- only slug vocabulary remains</title>
<updated>2026-08-18T15:35:53Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T15:35:53Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=897c274fd28402159ca6d45eedc1257b1ce98696'/>
<id>urn:sha1:897c274fd28402159ca6d45eedc1257b1ce98696</id>
<content type='text'>
lectio adopted the last two Missal corrections colitur argued:

  C14  3 -&gt; 0   RG 96(a)/97/98. lectio resolved each impeded I-class feast's
                transfer independently, so in 2008, 2035 and 2046 St Joseph and
                the Annunciation both walked onto the Monday after Low Sunday
                and Joseph, losing there, was observed on no day of those years
                at all. It now resolves the year's transfers as a set: the
                Annunciation takes the Monday as its sedes propria, Joseph the
                Tuesday.
  C15  7 -&gt; 0   The Holy Family propers' own 13-January rubric, "sine
                commemoratione Baptismatis D.N.I.C.". lectio kept the Baptism
                as the observed office on all seven such years in 2005-2050.

Those were the last two BEHAVIOUR classes. What is left is C1 (138) and C6
(119), both pure slug vocabulary.

Measured across the whole fixture, applying only the season normalisation the
test already applies: 579 of 16801 days differ, and every one of them differs
on the SLUG ALONE. Zero days differ on season, rank, colour, Epistle or Gospel.
(The test's own norm_slug table already maps 322 of the 579; the 257 left are
C1 and C6. The field profile is the same either way.)

So the two engines now agree on every liturgically meaningful field on every
day of 2005-2050. The residue is what the two projects call things --
ef-christmas-2-friday against ef-time-after-epiphany-1-friday for the same day,
same Mass. Aligning it means renaming lectio's slugs, and those are keys: its
lectionary, clectio's generated tables and any user overlay are built on them.
Left as vocabulary rather than forced.

Fixture refreshed against lectio e713da2; 13 rows changed, colitur agrees with
all of them. 24 divergence classes at the start of this work, 2 now.
</content>
</entry>
<entry>
<title>test(differential): C31 closed -- 4 classes left, 257 of 267 rows are naming</title>
<updated>2026-08-18T15:18:16Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T15:18:16Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=5773d603a2936638654f8e6bd8e595240de4203a'/>
<id>urn:sha1:5773d603a2936638654f8e6bd8e595240de4203a</id>
<content type='text'>
The ferias between Epiphany and the first Sunday after it repeat Epiphany's
own Mass in lectio now; they had been taking the Mass of the Sunday that
follows them.

  C31  98 -&gt; 0
  C1   40 -&gt; 138   grew, and changed character

C1 is now SLUG VOCABULARY ONLY, and its note says so. Season, rank, colour and
both citations agree with colitur on every day of the January window; what
differs is the identifier -- ef-christmas-2-thursday against
ef-time-after-epiphany-1-thursday for the same day with the same Mass. C6 is
the same shape.

That is 257 of the 267 remaining rows. Aligning the vocabularies would mean
renaming lectio's slugs, and those are keys: its own lectionary, clectio's
generated tables and any user overlay are built on them. Recorded as
vocabulary rather than closed, because the rows do differ -- just not in
anything a reader of either engine's output sees.

Measured for 2026: season, rank and colour agree on all 365 days; 14 days
differ and every one differs on the slug alone.

5 classes -&gt; 4, agreement 98.4%.
</content>
</entry>
<entry>
<title>test(differential): C20 and C38 closed -- agreement 98.4%</title>
<updated>2026-08-18T15:13:03Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T15:13:03Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=60db67eb2246d748d1e8e55047cef7d3d7843c4e'/>
<id>urn:sha1:60db67eb2246d748d1e8e55047cef7d3d7843c4e</id>
<content type='text'>
lectio carries the readings of the nine Common-routed saints and of three more
that had proper Masses but no reading at all. Every day of 2005-2050 now
resolves a reading there; four days used to come back empty.

C38's own note said it could not close without lectio gaining a Commons
concept. The readings arrived instead of the abstraction, which was enough:
each Common resolves to one first/gospel pair for the saint that names it, so
a table plus assignment layer would have bought indirection and nothing else.

  C20  14 -&gt; 0
  C38  70 -&gt; 0

7 classes -&gt; 5, 351 rows -&gt; 267, agreement 97.9% -&gt; 98.4%.
</content>
</entry>
<entry>
<title>test(differential): seven classes closed -- agreement 93.9% to 97.1%</title>
<updated>2026-08-18T14:51:45Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T14:51:45Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=3c770951011d2fd4911cd98793683bef69ac2d27'/>
<id>urn:sha1:3c770951011d2fd4911cd98793683bef69ac2d27</id>
<content type='text'>
lectio gained the Passiontide week split and thirteen lectionary entries.

  C23  230 -&gt; 0   Holy Week's own six Masses
  C32   99 -&gt; 0
  C35   60 -&gt; 0   the first weeks after Epiphany and Pentecost (RG 299)
  C33   46 -&gt; 0   Corpus Christi
  C34   46 -&gt; 0   the Sacred Heart
  C26   43 -&gt; 0   Passion Tuesday
  C19    7 -&gt; 0   the Holy Family week-index residue

  C1    75 -&gt; 40
  C31   63 -&gt; 98  GREW

The Passiontide split is the one that carried most of this. efWeek had no
Passiontide case, so both weeks numbered 0 and every day of Holy Week took the
slug of its Passion-week namesake -- one set of readings for two weeks with
entirely different Masses. Good Friday was reading Passion Friday's.

C31 growing is the same bookkeeping as C1's earlier growth and is recorded in
its own note: C32 and C35 closed, and rows that carried several causes now
carry only this one. Nothing new disagrees.

17 classes -&gt; 10, 1019 rows -&gt; 488, agreement 93.9% -&gt; 97.1%.

What remains is almost entirely NAMING rather than disagreement: C6 (119) is
colitur's ef-nativity-octave-day-N against lectio's ef-christmas-N-weekday on
days where both now read the same Mass, and C1 (40) is the same shape in the
January window.
</content>
</entry>
<entry>
<title>test(differential): C8 closed, C6 narrowed to naming</title>
<updated>2026-08-18T14:38:15Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T14:38:15Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=71054def597178f8143ed121e33460558456cce6'/>
<id>urn:sha1:71054def597178f8143ed121e33460558456cce6</id>
<content type='text'>
lectio gained Rogation Monday and Tuesday (RG 87, violet under RG 128(d)) and
the Missal's own Mass for the weekdays within the Octave of the Nativity
("Diebus infra octavam Nativitatis Domini", Titus 3:4-7 / Luke 2:15-20).

  C8   26 -&gt; 0    CLOSED
  C6  119 -&gt; 119  narrowed: the cause halved, the count did not

C6 is worth reading carefully rather than skimming the number. Its rows no
longer differ on First_f or Gospel_f at all -- the two engines now read the
SAME Mass on 29-31 December, where lectio previously served the Sunday's. What
is left is Slug_f alone, colitur's ef-nativity-octave-day-N against lectio's
ef-christmas-N-weekday. That is vocabulary, not disagreement. The count did not
move because the entry's gate already admitted a Slug_f-only subset, which is
exactly the case where a row count is a misleading summary.

17 classes, 1019 rows, agreement 93.9%.
</content>
</entry>
<entry>
<title>test(differential): C17 closed -- the largest divergence class is gone</title>
<updated>2026-08-18T14:29:22Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T14:29:22Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=1528d6533bbce035f78b92128fd9b2bb13e05db4'/>
<id>urn:sha1:1528d6533bbce035f78b92128fd9b2bb13e05db4</id>
<content type='text'>
lectio built the votive Office of Our Lady on Saturday (RG 78) and then said
RG 309(a)'s own Mass for it. Both halves were needed: RG 78 keeps the office
and gives it white, RG 309(a) picks among the Missal's five seasonal
formularies. Fixing only the first leaves the day announcing Our Lady and
reading the feria's Mass -- the bug colitur itself shipped briefly and fixed
in its v0.3.0.

  C17  439 -&gt; 0     CLOSED -- the largest class between the two engines
  C31  103 -&gt; 63    Saturdays that carried both causes; only this one left
  C35   69 -&gt; 60    same
  C1    35 -&gt; 75    GREW

C1 growing is not a regression and is recorded as such in its own note: the
January Saturdays C17 used to absorb now differ only on the slug vocabulary
C1 already covers, so they land there instead. Rows moved between entries as
the larger cause was removed.

Also in this refresh, from the same session: weeks after Epiphany now count
from the first Sunday after it rather than from 6 January, which took C19
from 35 to 7 -- what remains there is the Holy Family interaction, a different
cause needing RG 17(b) in lectio.

19 classes -&gt; 18, 1493 rows -&gt; 1045, agreement 91.1% -&gt; 93.8%.
</content>
</entry>
<entry>
<title>test(differential): refresh the fixture -- three more corrections adopted</title>
<updated>2026-08-18T14:05:28Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-18T14:05:28Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=80096d1227ea7406a88a774bf258bc3ac603eb8f'/>
<id>urn:sha1:80096d1227ea7406a88a774bf258bc3ac603eb8f</id>
<content type='text'>
lectio has taken three further corrections colitur argued from the Missal: the
twelve RG 124 colours (red is for Apostles, Evangelists and Martyrs, and
missalemeum had the rule inverted in both directions), Good Friday's black
under RG 128(b) with RG 132, and RG 72's Christmas Time boundary at 13 January
inclusive.

864 rows changed. Two classes closed outright and two moved:

  C18  450 -&gt; 0    the RG 124 colours -- the LARGEST single class in this file
  C36   46 -&gt; 0    Good Friday's colour
  C1   159 -&gt; 35   the season boundary stopped differing
  C23  184 -&gt; 230  Good Friday's rows fold back, the split no longer needed

C36 is closed rather than kept at zero because the split it represented has
dissolved: it existed only to hold Good Friday apart while its diff set had a
third member, and with the colour agreeing the rows are the exact pair C23 has
always gated on. Restoring 230 is not a regression, it is the number returning
to what it was before the split.

Cited classes 24 -&gt; 19, divergent rows 2252 -&gt; 1521, agreement 86.6% -&gt; 90.9%.

The percentage is again the least interesting part. C18 alone was 450 days on
which the two engines agreed about nothing and now agree because both apply
RG 124 the same way round.
</content>
</entry>
</feed>
