1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
# colitur — working context for Claude
Read this first, then skim the two authoritative docs it points to. This file
orients a fresh session; the specs hold the exhaustive detail.
## What colitur is
*computus liturgicus* + Latin `colitur` ("He is worshipped"). A **safe,
highly-tested, deterministic OCaml engine** that computes and validates
**liturgical calendars** for multiple rites and emits universal, template-driven
output. It computes the **day identity** (season, week, cycle, observed
celebration with rank/colour/flags, commemorations, transfers) and the day's
**reading citations** (references like `Jn 3:16`, never Bible text), correct all
the way to year **9999**.
Starts with the Roman **EF (1962)** and **OF** forms; the architecture generalizes
to any deterministic rite (Byzantine, Ambrosian, pre-Trent) as future modules.
**Sibling projects** (same author, `~/git/projects/`): **lectio** (Go; the shipped
OF+EF readings engine, 0-error vs references 2005–2050 — colitur bootstraps its
data from lectio and uses lectio as a differential oracle), **dlectio** (offline
Android app over lectio), **clectio** (tiny C build). colitur is a **standalone**
tool, not part of lectio.
## The authoritative docs (read these)
- **Design:** `docs/superpowers/specs/2026-07-30-colitur-design.md` — the full,
approved design (scope, kernel+rites+overlays architecture, data model, output,
the 5 validation layers, phasing, success criteria). **This is the contract.**
- **Rules register:** `docs/research/rules-register.md` — every temporal/precedence
rule the engine computes against, each citing its normative paragraph. The **EF
rubrics are primary-source-verified** against the 1962 Missal (RG 91 Table of
Precedence full 28 entries, occurrence RG 92–95, commemorations RG 108–111,
vigils/octaves/Rogations/Sunday-classes, seasons RG 71–77). OF side cites UNLYC.
- **Plans:** `docs/superpowers/plans/` — `…-plan1-computus-skeleton.md` (done).
- **Primary scans:** `docs/research/*.pdf` — the 1962 Missale Romanum (Latin) and
the *Rubricarum instructum* motu proprio. `docs/` is **gitignored** (research +
copyrighted scans stay off the public repo).
## Binding decisions (do not relitigate)
1. **Source of truth = the 1962 Missale Romanum + its Rubricae Generales.**
Divinum Officium, missalemeum, gcatholic are **comparison oracles only** — any
**divergence from a reference is flagged LOUDLY** in validation, never silently
swallowed.
2. **Scope is strictly the 1962 Missal** (1960 rubrics + 1955 Holy Week). Every
addition (2020 *Quo Magis*/*Cum Sanctissima*, any community's proper) is an
**overlay**, never core.
3. **Data is bootstrapped from lectio** (0-error vs missalemeum), then validated by
**rigorous property + differential testing over a large RANDOM sample across the
whole 1583–9999 range** — not only 2005–2050.
4. **EF and OF are peer rite modules** — a form is never an overlay of another form.
Each has its own `temporal` + `precedence` **code** and its own **data**; they
share only the kernel.
5. **Build EF end-to-end first** as the pilot vertical slice (it is the harder,
more idiosyncratic form, and its rules are already fully researched), learn from
it, *then* add OF as the second module to prove the `RITE` abstraction generalizes.
## Architecture (one screen)
Rite-agnostic **kernel** + **rite modules** (plug in via a signature) + **data
overlays**.
- **Kernel** (`lib/kernel`, pure, total, bounded 1583–9999): `Computus` (Gregorian
+ Julian Easter + anchors), `Date` (proleptic Gregorian arithmetic), `Overlay`
(ordered layer-merge algebra: field-level add/suppress/replace/edit, last-writer-
wins, `empty` = identity), `Precedence` (general resolver parameterized by a
rite's ruleset → observed day + commemorations + transfers; deterministic,
terminating), `Calendar` (orchestrator), `Validate` (the invariant/property harness).
- **Rite module** (`lib/rites/<rite>`) satisfies:
```ocaml
module type RITE = sig
val id : string
val temporal : Date.t -> Temporal.t (* season, week, cycle, movable feasts — CODE *)
val precedence : Precedence.rules (* rite ranking + resolution — CODE *)
val sanctoral : Calendar.layer (* fixed-date base calendar — DATA *)
val lectionary : Lectionary.t (* slug/day → citations — DATA *)
end
```
Temporal + precedence are **code** (auditable, property-tested); sanctoral +
lectionary are **data** (`.sexp`, bootstrapped from lectio). `Rite_ef` then `Rite_of`.
- **Result type** `LiturgicalDay` = { date; rite; season; week/cycle; observed
(slug, names, rank, colour, flags); commemorations; transfer; citations } — the
single stable schema for all output.
- **Output**: one schema → CSV / JSON / S-expression, rendered by a **logic-less
Mustache-family template engine** (user supplies the target-language template;
the engine never executes code). Unix-composable CLIs: `compute | render`, `table`.
- **Data format**: **S-expressions** (`sexplib`/`ppx_sexp_conv`) — the OCaml type
*is* the format, parse/print auto-derived, no hand-written parser.
## Validation (the "sure bet" pillar — 5 layers)
1. **Types** — illegal states unrepresentable (closed variants for ranks/colours/
seasons; dates validated at construction; resolution total).
2. **Property (QCheck), year-independent** — hold for every year 1583–9999: exactly
one observed day per date; year covered once, no gaps; seasons contiguous; Easter
a Sunday in [Mar 22, Apr 25]; movable feasts at correct Easter-offset weekday;
overlay merge deterministic + `empty` identity; sexp round-trips. **This is how
confidence extends past the oracle horizon (~2050).**
3. **Differential vs lectio** — every day 2005–2050 agrees (season, rank, observed,
citations).
4. **Oracle cross-check** — vs missalemeum (EF) / litcal (OF) in lectio's `sources/`.
5. **Golden regression** — landmark + known-tricky years pinned.
**Status: all five layers are built and green.** Layer 2 is exhaustively clean over
all 8 417 years (`COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force`, ~50 s; the default
suite samples). Layer 3 compares 16 801 days against lectio; layer 4, 730 days
against missalemeum; layer 5 pins ~30 dates.
**Know what each layer cannot see** — this is load-bearing, not a caveat:
- Layer 3 **never compares commemorations** (lectio has no RG 111 admission logic,
so its "others" are losing candidates, not the admitted set) and never compares
the week column. It also **shares colitur's own lineage**: colitur's data was
bootstrapped from lectio, so an error both inherited is invisible here. Proven:
Holy Thursday was violet in both because both were wrong.
- Layer 4 is **two years wide** and compares rank, colour-membership and
commemoration presence/count — **never celebration identity**.
- Together they sleep through **commemoration identity**: reversing `admit`'s
dignity sort, so the engine admits the *worst* commemorations, leaves all eight
differential and oracle assertions green across 17 531 days.
## Current state (Plans 1–3 DONE — verify with `git log`)
**Plans 1 + 2 are on `main` (35 commits). Plan 3 is branch `ef-plan3`, 42 commits,
259 tests green** (260 with the exhaustive sweep). The kernel, the **complete EF
temporal cycle**, the **resolution engine**, the **sanctoral data**, and **all five
validation layers** are built. `colitur day <year>` emits a full resolved year.
**Kernel** (`lib/kernel`, pure, total, 1583–9999):
- `date.ml[i]` — opaque rata-die (Hinnant civil↔days); validated `make`;
`to_iso8601`/`of_iso8601`; sexp form is an ISO-8601 atom that revalidates.
- `computus.ml[i]` — `gregorian_easter`, `julian_easter`, Easter anchors.
- Shared vocabulary: `colour` · `subject` (Lord/BVM/saint/temporal; named
`Subject` because `class` is an OCaml keyword) · `slug` · `lang` · `names`
(open lang-keyed assoc, canonically sorted) · `citation` · `date_spec`.
- Rite-parametric: `vocab` (operations record) · `celebration` · `temporal`
(+ the `RITE` module type). **`Celebration.t` takes one parameter (`'r`)**,
not two — season is contextual to the day, not intrinsic to a celebration.
- `layer` (slug-canonical, date-indexed once) · `overlay` (add/suppress/replace/
field-edit, ordered, last-writer-wins, **diagnostics not silence or failure**)
· `record` (flat all-string output view) · `validate` (the invariant harness).
- **Plan 3 additions**: `precedence` (the rite-parameterised resolver — a rite
supplies `band` / `disposition` / `admit`) · `liturgical_day` (the result
schema) · `rite` (everything a rite supplies, bundled, so mismatched assembly
is unrepresentable) · `calendar` (**year is the primitive**, day derived —
transfers need whole-year knowledge, so per-date resolution cannot be correct).
**EF rite module** (`lib/rites/rite_ef`): `vocab_ef` (8 RG-cited seasons, 4
classes) · `temporal_ef` (season boundaries RG 71–77, named feasts, Sunday slugs,
week numbering, the resumed-Sunday tail, ferias, four Ember sets, Rogations,
`anchors`) · `precedence_ef` (the full RG 91 28-entry table, occurrence RG 92–95,
commemorations RG 108–111, transfers RG 96–98) · `rite_ef` (the bundle).
**Data**: `data/ef/sanctoral.sexp` (322 entries, bootstrapped from lectio, SHA-256
in its provenance header) · `data/ef/adjustments.sexp` (overlay) · two cited
allow-lists, `expected-divergences.sexp` (11 entries, vs lectio) and
`expected-divergences-missalemeum.sexp` (14, vs the oracle). Fixtures live in
`test/fixtures/` with asserted SHA-256s.
**CLI**: `colitur easter <year>`, `temporal <year>`, `day <year>`.
`band` is **provably total** over everything the engine constructs: zero
`unclassified` across all 8 417 years, for a 28-branch hand-transcribed table.
Transfers reach a fixed point everywhere — 6 739 out, 6 739 in, zero unconverged.
Deps are `dune alcotest qcheck qcheck-alcotest sexplib ppx_sexp_conv` and are
**frozen**. A Mustache lib is still **not** added — it arrives with rendering.
**Gotcha that costs an hour if unknown:** `[@@deriving sexp]` on a type with
primitive fields fails with `Unbound value string_of_sexp` unless the `.ml`
opens `Sexplib0.Sexp_conv`. Every kernel module with primitive fields does.
Argument-less variants (`Colour`, `Subject`) don't need it. Do **not** hand-write
converters instead — that is reserved for `Slug`/`Lang`, whose `private string`
smart constructors deriving would bypass.
### Build & test
```sh
eval $(opam env) # activate the project-local switch (run from this dir)
dune build
dune test # fast suite, ~3 s
COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force # + every year 1583-9999, ~50 s
dune exec colitur -- day 2026 | head
```
## What's next
- **Plan 4 — OF rite module** (proves `RITE` generalizes) → the lectionary
bootstrap and citations → full output/rendering → hardening and a first tag.
**Open Plan 4 with the two behaviour items below**, before OF: they are the
places the five layers, taken together, currently sleep through a real error.
### Carried into Plan 4 (read before starting)
The full record — every task's outcome, every ruling, the 21-item deferred-minor
triage, and the whole-branch review — is in
`.superpowers/sdd/2026-08-11-colitur-plan3-resolution-engine/progress.md`. That
workspace is deliberately kept, because it and the register corrections exist
nowhere in git (`docs/` is gitignored).
**The two behaviour items, in order:**
1. **RG 16(a) — RESOLVED (RG16(a) task, branch `ef-rg16a`, 2026-08-12).** Was
the largest known-wrong output on the branch: a Feast of the Lord occurring
on a II-class Sunday takes the Sunday's place *"cum omnibus iuribus et
privilegiis: de dominica, proinde, **nulla fit commemoratio**"*, and colitur
used to commemorate the Sunday anyway (**5 996 wrong days over 1583–9999**,
369 of them in 1583–2100, re-confirmed exactly by this task's own
independent re-measurement). Fixed in `Rite_ef.Precedence_ef.disposition`
with **no signature change** — `disposition` already took `winner:...`
(RG 33's own vigil-omission branch already read it); the earlier note here
claiming a signature change was needed was itself wrong, corrected in
register §6.0. Landed together with a sanctoral data fix (two of six
`subject = Lord` entries were themselves wrong — the Purification and the
Most Holy Name of Mary are BVM feasts, not the Lord's) via a new
`data/ef/adjustments.sexp` overlay; see register §6.0 for the full account,
the sanctoral audit, and one further `subject` defect found but left open
(13 January, Baptism of the Lord, currently mistagged `Saint`).
2. **Commemoration identity is unasserted** outside ~3 test rows (see the layer
caveats above). Related: `compare_dignity` breaks same-rank ties
**alphabetically**, with no RG warrant, and over 2005–2050 the admission cut
falls between two same-rank candidates on **66 days**.
**Structural items, cheapest to fix before OF exists:**
3. **`Record` and `Liturgical_day` both claim to be "the single stable output
schema".** `Record` cannot express what the engine now computes (no observed
celebration, no commemorations, no transfers), has **no test file**, and is
used only by the legacy `colitur temporal` path — `day` hand-formats instead.
Plan 2's carried item (add a `cycle` field for OF's Sunday A/B/C and weekday
I/II) is still open and now costlier: it must pass through `Temporal.t`, which
is embedded in the sexp-derived `Liturgical_day.t`.
4. **`Temporal.RITE` and `Rite.t` are two competing abstractions.** The module
type still exists and `temporal_ef` still satisfies it, but it carries none of
`rules`, `anchors`, `season_runs`, `transfer_target` — satisfying it now proves
almost nothing. `Rite.t` is the load-bearing one.
5. **EF-shaped things still in "rite-agnostic" kernel code**: `validate.ml`
hardcodes Sunday as the week start; `Liturgical_day.transferred_in` is an
`option` justified by RG 96; `Precedence.privilege` is defined by RG 111;
`Repose` is EF vocabulary emitted by nothing. Each is one field short of the
remedy already applied to `season_runs`.
**Data defects traced upstream into lectio's generator** (register §6): 15 entries
wrongly marked `Commemoration_only` that are really III-class feasts, clustered
6 March – 5 April, **six of which produce a wrong observed office** in real years
(2008-04-02/04/05, 2038-03-06/08/09); four missing entries (Agnes *secundo*,
Boniface 14 May, Evaristus, Theodore); and `romanus`, which should not exist on
9 August. lectio's ini is **generated from missalemeum**, so the two are one
lineage, not two independent sources.
**Unbuilt, recorded**: the Sacred Triduum has no proper office (it uses ordinary
Passiontide ferial slugs); RG 91 entry 27's BVM Saturday Office; RG 110's
inseparable commemorations; Major Litanies (25 April); the Rogation-Wednesday
commemoration. Allow-list entries M11 and M13 are `verdict open` by design.
## How to work here
- **Superpowers workflow**: `brainstorming` → `writing-plans` → `executing-plans`
or `subagent-driven-development`. Present a design and get approval before coding.
Plans live in `docs/superpowers/plans/`, specs in `docs/superpowers/specs/`.
- **TDD**, bite-sized tasks, a commit per task on the feature branch (never straight
to `main` without consent).
- **Every temporal/precedence rule carries a source citation** (RG/UNLYC paragraph)
in a comment — grep-able, matching the register.
- **Kernel is total & deterministic**: no wall-clock, randomness, or environment
reads; fallible construction returns `result`/`option`, never raises on in-range
input; years outside 1583–9999 rejected at the boundary.
- **Commits**: conventional-commit style, subject + body only. **No AI/tool trailer
of any kind** (the author considers them noise in a public repo).
- **License header**: files may carry a short SPDX `AGPL-3.0-or-later` line.
- `docs/` is gitignored — design/research/scans stay local; only code + README +
LICENSE + this file are tracked.
|