<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/lib/kernel/liturgical_day.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-11T19:29:13Z</updated>
<entry>
<title>kernel(calendar): fix multi-departure loss, band-order gap, off-array targets</title>
<updated>2026-08-11T19:29:13Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T19:29:13Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=1d6be4ac281e62accfb905b137b3c6b494183ee4'/>
<id>urn:sha1:1d6be4ac281e62accfb905b137b3c6b494183ee4</id>
<content type='text'>
Four findings from Task 6 review, addressed on top of f15e44d.

1. transferred_out was a single Date.t option, so when RG 97-98 collides
   three or more feasts on one date (more than one loser), only the last
   one Hashtbl.iter happened to visit survived -- a genuinely lost move,
   and which one survived depended on OCaml's hash seed
   (OCAMLRUNPARAM=R), an environment read a kernel invariant forbids.
   RG 97-98 says coinciding feasts transfer "in order" -- plural -- so
   the type was wrong, not the fixture: transferred_out is now
   (Celebration.t * Date.t) list. transferred_in stays a single option,
   deliberately: a day receives at most one arrival (RG 96 sends each
   departure to the next non-I/II-class day, and the first to arrive
   occupies it). The per-day list is canonicalised (sorted by target
   date, then slug) after accumulation, the same fix layer.ml already
   applies to its own date-bucket index and for the same reason.
   Verified clean across 15 runs under OCAMLRUNPARAM=R; disabling the
   canonicalisation step showed the raw order genuinely flip between
   seeds, confirming the fix is load-bearing.

2. Every deferred candidate in the fixture was the same rank, so
   compare_deferred's band branch was unreachable and reversing it broke
   nothing -- the RG 97-98 test was pinning slug order, not band order.
   The fixture now has three ranks (Hi1 outranks Hi2, both transfer,
   both outrank Lo), with slugs chosen so band order and slug order
   disagree. Reversing the band comparison now fails the test on
   "higher-band loser claims 2 Feb first", received the wrong slug
   instead.

3. A transfer_target free to name any date could place a candidate
   outside the liturgical year's own start/stop bounds: invisible to
   year/build_day, so it would be observed nowhere and, since its
   origin's re-resolution would report it as settled, omitted nowhere
   either -- genuinely gone, contradicting calendar.mli's "never
   silently dropped". place_transfers now checks the range on every
   placement and routes an out-of-range one to a permanent-exclusion
   table instead of assignment, with its own cited omitted reason.

4. Precedence.resolve folds Transfer and Repose into one deferred case,
   and place_transfers routed all of it through transfer_target (RG
   96's search), which is only correct for Transfer. Repose is RG
   100-102's repositio, a distinct rubric this module does not
   implement. Documented rather than split into a second mechanism:
   nothing in the EF ruleset returns Repose (design spec section 1.3,
   "declared, not exercised"), so the gap is latent, not a live bug.

Two new tests (origin records every departure; transfer target outside
year is recorded not lost); the RG 97-98 test's fixture and assertions
rewritten for finding 2.
</content>
</entry>
<entry>
<title>kernel: carry omitted celebrations on Liturgical_day.t, reason and all</title>
<updated>2026-08-11T18:07:55Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T18:07:55Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=953427d8d1e3a34994be53b60e18662ec26fef4e'/>
<id>urn:sha1:953427d8d1e3a34994be53b60e18662ec26fef4e</id>
<content type='text'>
Precedence.resolution already tracked what happened to every losing
candidate -- commemorated, deferred, or omitted with a reason -- but
Liturgical_day.t had nowhere for the deferred and omitted buckets to land,
so Calendar dropped them at the door. Task 12's no-celebration-lost
invariant needs to read that accounting off the day result itself, not
re-resolve every day to reconstruct it, so a reason recorded nowhere is not
recorded.

Add Liturgical_day.omitted : ('r Celebration.t * string) list, after
transferred_out and before citations. Calendar.resolve_day now folds
resolution.omitted (Precedence's own native omissions, reasons intact) and
resolution.deferred (mapped to "deferred: transfer placement not yet
implemented (Task 6)") into it.

Adds a full-day accounting test against the whole Calendar pipeline: four
colliding sanctoral entries plus the day's feria, checked as a slug set
(matching test_precedence.ml's own nothing-silently-lost test) so a
candidate silently dropped or duplicated into two buckets would fail it,
plus an identity check that the deferred and admission-limit reasons don't
get swapped.
</content>
</entry>
<entry>
<title>kernel: the LiturgicalDay result schema</title>
<updated>2026-08-11T17:30:34Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-11T17:30:34Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=9df1aab700ce4454c26c7d0ede5b51c0f0c96c66'/>
<id>urn:sha1:9df1aab700ce4454c26c7d0ede5b51c0f0c96c66</id>
<content type='text'>
Temporal is embedded rather than flattened, so season/week/weekday have one
home and cannot disagree with themselves. transferred_in/out make transfers
visible in the result -- an ordo must print 'transferred from the 25th', and
the nothing-lost invariant reads these fields. citations exists and is empty
until Plan 4; adding it later would widen a type every consumer matches on.
</content>
</entry>
</feed>
