<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/lib/kernel/date_spec.ml, branch v0.8.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.8.0</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=v0.8.0'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-08-17T15:22:43Z</updated>
<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>kernel: loaders never escape as an exception, date-spec validates on load</title>
<updated>2026-08-11T14:21:46Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T14:21:46Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=745ec8f8edb32ee7ef0a8c12ab84833216296f73'/>
<id>urn:sha1:745ec8f8edb32ee7ef0a8c12ab84833216296f73</id>
<content type='text'>
Layer.load narrowed its catch to Sexplib0.Sexp_conv_error.Of_sexp_error,
but rank_of_sexp is caller-supplied and may raise anything -- e.g. a
hand-written rank parser that calls invalid_arg. Overlay.load already
catches every exception from the equivalent call; mirror that here so
layer.mli's "never as an exception" promise actually holds.

Date_spec.t derived its sexp converters with plain ppx_sexp_conv, unlike
Slug and Lang, which hand-write validating parsers specifically so
malformed data is rejected at load. (Fixed (month 13) (day 40)) used to
deserialise cleanly into a spec that simply never resolves -- a saint
quietly vanishing with no diagnostic. t_of_sexp now re-runs the value
through the existing fixed validator, the same shape Slug and Lang
already use.

Covering tests: a Layer.load case where rank_of_sexp raises
Invalid_argument instead of Of_sexp_error (would have escaped
uncaught before this fix); two Date_spec.t_of_sexp cases (month 13,
31 April) that must raise Of_sexp_error rather than silently
constructing an unresolvable spec.
</content>
</entry>
<entry>
<title>kernel: fix sexp deriving, add missing tests, canonicalize remove</title>
<updated>2026-08-11T09:54:16Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T09:54:16Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=8b6c0036cb6c7f0e48a9370f53be25fd2bb243f4'/>
<id>urn:sha1:8b6c0036cb6c7f0e48a9370f53be25fd2bb243f4</id>
<content type='text'>
Use [@@deriving sexp] with open Sexplib0.Sexp_conv instead of hand-rolling
converters (fixes non-standard Citation shape and missing field validation).
Names.t_of_sexp wraps derived version to enforce canonical sort on load.

Add tests:
- Names of_list duplicate handling
- Names sexp canonical sort guarantee (identical serialization)
- Names and Date_spec sexp roundtrips

Fix Names.remove to canonicalize output (defensive against non-canonical input).
</content>
</entry>
<entry>
<title>kernel: Names, Citation and Date_spec</title>
<updated>2026-08-11T09:37:32Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T09:37:32Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=57322e766a0ece579dafcddcfe2572676a98fca8'/>
<id>urn:sha1:57322e766a0ece579dafcddcfe2572676a98fca8</id>
<content type='text'>
Names is an open language-keyed assoc kept in canonical order so equal name
sets serialise identically. Citation carries references only, never text.
Date_spec ships the one form the EF sanctoral needs; 29 February is
constructible and resolves to None in common years.
</content>
</entry>
</feed>
