<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git, branch v0.3.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.3.0</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=v0.3.0'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-08-17T20:27:11Z</updated>
<entry>
<title>test(cli): the 3 January pin is a BVM Saturday, not a feria</title>
<updated>2026-08-17T20:27:11Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T20:27:11Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=1fa4017a20dea1843e08afd257fc7798560659c4'/>
<id>urn:sha1:1fa4017a20dea1843e08afd257fc7798560659c4</id>
<content type='text'>
3 January 2026 is an unoccupied IV-class Saturday, so its office is Our
Lady's and now its Mass is too -- the second of the Missal's five, assigned
to Christmas through the Purification. The pin held the Christmastide
feria's readings, which is what colitur said before RG 309(a) was
implemented. Caught by make check on the 0.3.0 release, which is what that
gate is for.
</content>
</entry>
<entry>
<title>docs: CHANGELOG entry for 0.3.0</title>
<updated>2026-08-17T20:24:33Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T20:24:33Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=921a17583067f49ed5c9f900b649d049c2b876ec'/>
<id>urn:sha1:921a17583067f49ed5c9f900b649d049c2b876ec</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(ef): the votive Mass of Our Lady on Saturday (RG 309(a))</title>
<updated>2026-08-17T19:45:47Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T19:45:47Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=cffb879fa124c22acd400f59152a57d20cec036f'/>
<id>urn:sha1:cffb879fa124c22acd400f59152a57d20cec036f</id>
<content type='text'>
RG 78's office has been built since ef-bvm-saturday: an otherwise
unoccupied IV-class Saturday is de sancta Maria in sabbato, white, IV
class. The Mass stayed the feria's, so the day observed Our Lady and then
read Colossians on the parable of the weeds. That is M26 shape 1.

RG 309(a) is the rule -- the Masses the Missal assigns "iuxta temporum
diversitatem" -- and it prints five, each under its own season heading.
All ten citations are transcribed from the scans with line numbers in the
source. Masses II-V were confirmed twice, against the scan and against a
live missalemeum capture of 2038 which names them and agrees on both
readings. Mass I rests on the scan alone: no oracle year to hand has an
Advent Saturday carrying this office. One witness where the others have
two, and the code says so rather than blurring it.

The mapping looked like the hard part and dissolved. The Missal's ranges
are cut by civil dates and movable feasts, not by this rite's seasons --
II ends mid-season at the Purification, III spans four seasons. But
measured against what a IV-class Saturday can actually be, it reduces to
a seasonal mapping plus one date test at 2/3 February, with no Easter
arithmetic at all: III's end to IV's start leaves the Triduum unassigned,
unreachable because Holy Saturday is I class; IV's end to V's start
leaves Pentecost week unassigned, also unreachable because that week's
Saturday is an Ember Saturday. Time after Pentecost then begins exactly
at Trinity, exactly where Mass V begins. Both gaps verified by sweep --
zero IV-class Saturdays in either across 2000-2059 -- not inferred.

The office is identified structurally, never off the slug, which it
deliberately shares with the ordinary feria: subject Bvm, IV class,
Saturday.

Blast radius: 87611 reading rows over the full domain, every one a
Saturday.

Allow-lists: M26 27 to 6, shape 1 gone, leaving only the Christ-the-King
residue argued in colitur's favour last commit. The 2038 suite's
BVM-SATURDAY-MASS class is removed rather than zeroed -- an id with no
rows must not be declared. M18 355 to 376. C17 widened 397 to 445,
taking its own Saturdays back from C1, C19 and C25, since one cause
should not be filed under two ids; C31 89 to 103; C25 closed at 0 rows,
its Holy-Name-week Saturdays now answered before step 3 is reached.

Three unit tests pinned the old behaviour and are corrected rather than
re-pinned: two asserted the day "keeps its ferial Mass", which was the
honest thing to assert while this was unimplemented. The third still
tests what it always did -- that step 4 refuses a temporal office -- and
only what answers instead has changed.

Register: section 6.13.
</content>
</entry>
<entry>
<title>feat(ef): the Seven Sorrows of Passion Friday, and M16 reversed</title>
<updated>2026-08-17T19:23:57Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T19:23:57Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=6e42b2f524b283f43e05a967295e244e4083725f'/>
<id>urn:sha1:6e42b2f524b283f43e05a967295e244e4083725f</id>
<content type='text'>
Not the II-class feast of 15 September, which colitur has had since the
bootstrap, but the other Seven Sorrows: the movable commemoration of
Passion Friday, of which colitur had no trace at all.

The 1962 calendarium lists it at the foot of March, below the fixed
dates, exactly where a movable entry belongs (LT.txt:5056): "Feria VI
post dominicam I Passionis: Commemoratio septem Dolorum B. Mariae Virg."
Note the word -- Commemoratio. Under the 1960 rubrics it is a
commemoration, not a feast, so it can never win the day.

Friday after Passion Sunday I is Easter-9, confirmed independently by
both dates the oracle allow-list already disputed: Easter 2026 is 5 April
and M16's date is 27 March; Easter 2027 is 28 March and M13's is 19
March. It could not be built before Date_spec grew Easter_offset -- the
same wall Rogation Wednesday hit, and the second entity that work has now
unblocked.

M16 is re-adjudicated and its verdict REVERSED, from missalemeum to
colitur. The entry existed because colitur admitted St John Damascene
where missalemeum showed the Seven Sorrows, colitur having no candidate
to admit. Both streams now name the Seven Sorrows and that mismatch is
gone. What remains runs the other way, and the rubric is explicit: RG
111(d) admits TWO commemorations on a III-class day, RG 113 puts the
seasonal one first, and RG 114 omits only what exceeds the number. 27
March 2026 is a III-class privileged Passiontide feria; colitur admits
exactly two, the Seven Sorrows and John Damascene, a real III-class
universal feast genuinely falling that day. missalemeum admits one and
reports John Damascene as displaced, dropping a commemoration the rubric
allows.

That makes four divergences adjudicated colitur where missalemeum drops
or mis-assigns something -- Monica's Epistle, Perpetua's Common, Christ
the King's week, and this. Recorded as a pattern rather than four
coincidences: the oracle stays a comparison instrument, but its error
rate on adjudicated cases is no longer negligible.

M13 is untouched and stays open: 19 March 2027 is a different dispute,
whether Joseph is displaced from Passion Friday at all, and on a I-class
day RG 111(a) admits one privileged commemoration which the feria takes.

Blast radius, full 1583-9999 sweep against the pre-change binary: 7874
days, every one carrying the new commemoration, zero unclassified, line
counts equal both sides.

Register: section 6.12.
</content>
</entry>
<entry>
<title>fix(lectionary): the week after Trinity says its own assigned Mass</title>
<updated>2026-08-17T19:12:35Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T19:12:35Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=dcce3a5936c2232aaabbb2e8552be24ea7decdbf'/>
<id>urn:sha1:dcce3a5936c2232aaabbb2e8552be24ea7decdbf</id>
<content type='text'>
RG 299 located (scan1:1096-1098), which does two things at once.

First it closes a standing gap: the ferial-Mass selection rule was
recorded in the register as unconfirmed against the primary source, and
step 3 said so in its own comment -- "lectio's own observed behaviour,
not a confirmed Missal citation". It is confirmed now. "In reliquis
feriis dicitur Missa dominicae praecedentis, nisi a rubricis aliter
provisum sit", restated in the propers as "...nisi propria Missa
assignetur".

Second, that escape clause splits M26 shape 2 in half, which is why the
shape was never resolvable as one thing.

Shape 2(a), the week after Trinity, is fixed. Trinity is the temporal
identity of its own Sunday, so a plain walkback lands on Trinity's Mass
-- but the Missal prints a named formula for the following week, "Missa
dominicae I post Pentecosten" (scan1:21758), precisely because the feast
has taken that Sunday's own. Those ferias now resolve at step 2 from it
and never reach the walkback.

Shape 2(b), Christ the King's week, turns out not to be a bug at all. No
proper Mass is assigned there -- checked directly, no rubric precedes
that Mass heading (scan1:39163ff) -- so RG 299's default governs and "the
Mass of the preceding Sunday" genuinely is Christ the King's own, which
is what colitur emits and missalemeum does not. Kept open only because no
rubric specific to that week was located either way; the verdict is now
argued from the primary text rather than resting on oracle disagreement.

No Thursday in the slug list: Corpus Christi is Easter+60, which IS that
week's Thursday every year, so the ferial slug never exists. Found by the
bootstrap's own reachability guard refusing to emit a key no Temporal_ef
slug can match, not reasoned out in advance.

Allow-lists: C35 opened (69 rows, verdict colitur -- lectio still walks
back); C17 narrowed 406 to 397, nine Saturdays moving to C35 because they
carry both a citation and a colour difference, with C35 gated to require
the citation so a colour-only Saturday still falls to C17; M26 narrowed
30 to 27; M18 widened 352 to 355, the same three days keeping only their
observed-identity difference.

Register: section 6.11.
</content>
</entry>
<entry>
<title>test(cli): pin the version's shape, not its literal</title>
<updated>2026-08-17T19:00:17Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T19:00:17Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=d8ce8c3806111e1d1ba54d2ef99a8ae550c7567b'/>
<id>urn:sha1:d8ce8c3806111e1d1ba54d2ef99a8ae550c7567b</id>
<content type='text'>
The release target bumps bin/main.ml and dune-project, but cli.t pinned
the literal too, so cutting 0.2.0 failed `make check` on a cram diff that
was purely the bump working correctly. Every release would have had to
edit a test for no gain.

The shape is asserted here instead. The value is already checked where it
can actually drift: the release target requires the freshly built binary
to report the version it just wrote.
</content>
</entry>
<entry>
<title>docs: CHANGELOG entry for 0.2.0</title>
<updated>2026-08-17T18:57:22Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T18:57:22Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=301b9abd8e47e487dc5159d49d8e2432bc62488c'/>
<id>urn:sha1:301b9abd8e47e487dc5159d49d8e2432bc62488c</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(cli): --overlay FILE, so a local calendar can be supplied</title>
<updated>2026-08-17T15:46:31Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T15:46:31Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=0a4bb4e6c3cbcafb302962d9d1e70b5a0bb554b8'/>
<id>urn:sha1:0a4bb4e6c3cbcafb302962d9d1e70b5a0bb554b8</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(kernel): movable Date_spec variants, and Rogation Wednesday</title>
<updated>2026-08-17T15:22:43Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T15:22:43Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=2b9f8133a22ce6dc309644a98bbe61507f574b38'/>
<id>urn:sha1:2b9f8133a22ce6dc309644a98bbe61507f574b38</id>
<content type='text'>
Date_spec carried one variant, Fixed(month, day), and its own header said
Sunday- and Easter-relative forms would arrive with the OF sanctoral. They
arrive early because two things needed them at once: a user-supplied
overlay carrying a local movable feast had no way to express "the first
Sunday of October", and Rogation Wednesday's commemoration has been
recorded as architecturally blocked since 2026-08-13.

That blockage turns out to have been a premise, not a fact. The register
concluded the Rogation "is not Commemoration_only sanctoral data by
nature... it needs a genuinely THIRD kind of thing this architecture has
no name for" -- but the reasoning rested on there being "no civil (month,
day) pair to anchor a Fixed entry to". That is a statement about
Date_spec's expressiveness. With Easter_offset the entity is ordinary
sanctoral data, the identical shape Add major-litanies already ships,
differing only in how the date is written. No third channel was needed.
The register is corrected rather than left contradicting the code.

Two variants: Easter_offset of int, and Nth_weekday of {month; nth;
weekday} with negative nth counting from the end. Fixed's sexp
representation is untouched, so all 327 sanctoral entries and the whole
overlay parse unchanged. Both are re-validated in t_of_sexp through their
smart constructors, extending the idiom Fixed already used -- the failure
that guards against is invisible, a spec deserialising into something
that silently never resolves and a celebration vanishing with no
diagnostic.

Easter is supplied by the rite, not computed in the kernel. Computus
ships Gregorian and Julian both, and picking one here would hard-code a
Roman assumption into rite-agnostic code and be silently wrong for a
Julian-reckoning rite. Rite.t gains an easter field; Rite_ef supplies the
Gregorian one.

Layer keeps a split index. Fixed entries stay in the year-independent
(month, day) table, which preserves the fast path and the "30 November
counted twice in a 371-day span" behaviour validate.mli documents for St
Andrew. Movable entries have no year-independent key by construction --
the same spec lands on a different (month, day) each year -- so they
resolve per civil year into a rata-die table. index_by_date is removed
rather than kept as an alias: two ways to index a layer, one of which
silently ignores movable entries, is the trap this change exists to
avoid.

Two domain-edge defects, both found by the suite rather than reasoning. A
liturgical year is Advent-anchored, so resolving civil year y names y +/-
1, and at the edges those are 1582 and 10000, which Computus correctly
refuses by raising. The ceiling surfaced through the domain-ceiling test,
the floor through `colitur day 1583` failing outright. Fixed once, in
Layer.index, the single point that calls the rite's easter -- a clamp
repeated per caller would have been two places to get wrong a third time.

Rogation Wednesday: RG 87 assigns the Litaniae minores to the Monday,
Tuesday AND Wednesday before Ascension (scan1:691); colitur built the
first two from the start and never the third. RG 88 keeps it out of the
Office, so the Ascension Vigil remains observed. RG 89 routes it through
the Major Litanies' rules, but RG 109's closed list names only maiores,
so by RG 107 it is an ORDINARY commemoration -- confirmed in the output,
not assumed. Name from the Missal's own "feria IV" (scan1:20495-20497),
violet to match the Monday and Tuesday offices.

Blast radius, full 1583-9999 sweep against the pre-change binary: 2257
days, every one the same shape, zero unclassified, line counts equal both
sides. The complement is the informative half -- in the other 6160 years
the Rogation does not appear at all, because RG 111 admits one
commemoration on a II-class day and an impeded feast takes it under RG
113, a Commemoration_only candidate having no row in RG 91's table. Both
shapes are pinned.

Allow-lists unmoved: layer 3 compares no commemorations and is blind by
construction, and layer 4's 2026-2027 window has a competing feast on
both Wednesdays, so the Rogation is capped out in exactly the years the
oracle can see. C8 is unchanged, asserted by its own count pin.

Not built: Sunday-relative specs, which nothing needs yet, and the
--overlay plumbing that would let a user actually supply a local movable
feast. Nth_weekday therefore ships with synthetic and property coverage
only; inventing a shipped entry to exercise it would be fabricating
calendar data.

Register: section 6.10.
</content>
</entry>
<entry>
<title>fix(make): release must not abort when the version bump is a no-op</title>
<updated>2026-08-17T14:21:02Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-17T14:21:02Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=f2a0660a9b60b70af32f1157dc78cdfcb298c9f7'/>
<id>urn:sha1:f2a0660a9b60b70af32f1157dc78cdfcb298c9f7</id>
<content type='text'>
Cutting a version whose files already carry it -- which is how the first
tag went, and what any re-run after a failed gate looks like -- leaves
nothing staged, and git commit aborts the target on "nothing to commit"
despite everything being in order. The commit is now conditional on
something actually being staged, and an existing tag is refused
explicitly rather than surfacing as a bare git error.
</content>
</entry>
</feed>
