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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
|
# 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.
- **Nothing anywhere compares commemoration ORDER** (found `ef-holyname-rg110`
task, fix round 1, RG 110's own shape-(c) ordering bug): layer 3 doesn't compare
commemorations at all (above); layer 4's own `identity_diff` sorts both sides
into a multiset before comparing; layer 5's own `describe` (test_golden.ml)
sorts its `comms` field too, deliberately, so a golden pin's string comparison
is not accidentally order-sensitive where nothing textual requires it to be.
The ONLY place in the whole suite that asserts commemoration order is
`test_precedence_ef.ml`'s own `admit_cases` table (`Alcotest.(check (list
string))`, unsorted) — proven by mutation: reverting RG 110(c)'s own
trigger/companion order left every layer green except that one table.
- **Layer 4's OBSERVED-identity gap is CLOSED** (2026-08-13, branch
`ef-rg112-rg110`, register §6.2): it used to compare the observed day's rank
and colour and stop there — never whether it is actually the RIGHT day. Holy
Family (RG 17(b), missing from colitur entirely until this task) was rank
2/white on both sides purely by coincidence (an ordinary, unnamed Sunday
and Holy Family share both), so this layer stayed silently green through
the whole gap's lifetime. Now compares `Celebration.t.names` against
missalemeum's own title, same mapping/limits as the commemoration-identity
fix below: resolvable only for a SANCTORAL-origin observed day; a
TEMPORAL-origin one (an ordinary Sunday, a feria, a movable named feast —
373 of 730 days in the 2026–2027 window) is `Observed_identity_unresolved`,
counted and allow-listed (`M18`), never silently skipped. Full breakdown:
331 of 730 days resolved (330 matching, 1 mismatched — Joseph vs the Seven
Sorrows, `M13`), 399 unresolved (373 in `M18`, 26 absorbed inside four
other entries' own widened subsets). The blind spot, precisely: a
TEMPORAL-origin observed day silently replaced by a DIFFERENT
temporal-origin observed day of the SAME rank and colour — exactly Holy
Family's own shape. Teeth proved and
reverted: corrupting one sanctoral saint's own English name on an OBSERVED
day (rank/colour untouched) reddened the suite immediately with an
`observed-identity-mismatch` and nothing else — the exact shape the
pre-strengthening comparator would have slept through completely.
- **Layer 4's commemoration-identity gap is CLOSED** (2026-08-12, Task B, branch
`ef-rg16a`, two fix rounds): it used to compare presence/count only, never
*which* commemoration won — reversing `admit`'s dignity sort (the engine
admitting the *worst* commemoration, an outright RG 111 violation) left all
eight differential and oracle assertions green across 17 531 days. Layer 4 now
also compares **identity** — colitur's own resolved English name
(`Celebration.t.names`, `en`) against missalemeum's title text, for every day
both streams admit the same count. The mapping resolves **every
SANCTORAL-origin commemoration** (colitur's own name field, bootstrapped from
lectio, verified to match missalemeum's titles character-for-character); it
**cannot resolve a TEMPORAL-origin one** (an impeded feria/Ember/Rogation day —
`Rite_ef.Temporal_ef` never sets a celebration name) — that case is **never
silently skipped**: it is a separate, counted, allow-listed outcome
(`Comm_identity_unresolved`, 19 of 227 non-empty-commemoration days over
2026–2027), not a silent pass. ONE genuine identity mismatch remains adjudicated
open (register §4/§6.1, `data/ef/expected-divergences-missalemeum.sexp` M16): a
known unimplemented office (the Seven Sorrows of Passion Friday). Proof of
teeth, reproduced twice: reversing `admit`'s *dignity*-based sort (the
historical defect shape) turns layer 4 red — an unexplained
`commemoration-identity-mismatch` day (colitur admitting "St. Thecla" where
both the rubric and missalemeum require "St. Linus"); separately, disabling
`band`'s own `Commemoration_only` guard (below) also turns it red, on the same
date this whole gap was originally found through — both reverted after
confirming.
- **The `admit` same-rank tie-break is RG 113, not an uncited convention** (same
task, fix round 1): RG 113's own second sentence ("in admittendis et ordinandis
aliis commemorationibus, servetur ordo tabellae praecedentiae"), previously
quoted only in its first half, is the real rule — `admit` now orders/selects by
`Precedence_ef.band` (RG 91's own table), not RG 8's coarse four-class rank.
**`band` itself had a fidelity bug this exercise surfaced**: RG 91's table
enumerates only "dies liturgici" (real feasts), so a `Celebration.t.status =
Commemoration_only` candidate has NO row in it at all — `band` used to read
`rank` alone and silently lent such a candidate the same table entry as a
genuine `Feast` of its own rank, manufacturing ties RG 113 never created (the
original "Maurice vs Thomas of Villanova, both entry 24" example was this bug,
not a real RG 91 tie). Fixed at the source: `band` now returns `unclassified`
for any `Commemoration_only` candidate, checked first. Measured, independently,
twice (`compare_precedence`'s own ordering-criterion change, then `band`'s
fidelity fix): the ORDERING-CRITERION change alone is zero-blast-radius
(byte-identical across the whole 1583–9999 domain — a correctness-of-citation
fix, not an answer-changing one); the `band`-FIDELITY fix has a real, large,
fully-classified effect, **4 451 days across the whole domain, exactly 4
verified shapes, no surprises** (register §6.1). A genuine "two different
candidates on the identical REAL table entry" residual was searched for
exhaustively across the whole domain and found EMPTY — the tie-break `admit`
still breaks alphabetically is real but narrower than first thought: it is only
ever exercised between two `Commemoration_only` candidates, neither of which has
any RG 91 table position to compare in the first place.
## 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 including
Holy Family (RG 17(b)), Sunday slugs, week numbering, the resumed-Sunday tail,
ferias, four Ember sets, Rogations, `anchors`) · `precedence_ef` (the full RG 91
28-entry table including entry 14's movable/fixed split, occurrence RG 92–95,
commemorations RG 108–112, 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 — `Add` as well
as `Suppress`/`Edit`: RG 110's own 30 June companion, `commemoration-of-st-peter`,
is genuinely missing from lectio's own source, not merely from colitur's
bootstrap, so it is hand-authored here rather than upstream) · two cited
allow-lists, `expected-divergences.sexp` (6 active entries, vs lectio — C1, C6,
C8, C14, C15, C16; several more closed and recorded in the register, not deleted)
and `expected-divergences-missalemeum.sexp` (12 active, vs the oracle — M12
closed/M19 opened this task, net count unchanged). 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.
**All four behaviour items below are now RESOLVED** (RG 16(a) and
commemoration identity, closed on branch `ef-rg16a`; Holy Family/RG 112(a)
and observed identity, closed on branch `ef-rg112-rg110`; Holy Name of
Jesus/RG 110, closed on branch `ef-holyname-rg110`) — kept here as
the record of what the five layers, taken together, used to sleep through,
and as the shape a future gap of the same kind would need to be caught by.
### 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 four behaviour items, in order:**
1. **RG 16(a) — RESOLVED (RG16(a) task, branch `ef-rg16a`, 2026-08-12; ONE
FIX ROUND of review after the first pass — see register §6.0 for the full,
corrected account).** 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, twice, independently). 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). A SECOND, related bug needed a genuine kernel signature
change: `Precedence.rules.admit` gained a `~temporal` parameter, because
RG 16(a) also breaks the assumption that `observed` IS the day's own
temporal-cycle office for RG 111(b)'s Sunday rank-floor check (an
unrelated saint could otherwise be wrongly admitted into the freed slot —
confirmed on 1 178 real days, 6 August, before this second fix).
**The sanctoral data question was more contested than the first pass
found**: the Purification (2 Feb) was FIRST retagged `Bvm` (calendarium
title argument), then REVERTED to `Lord` in fix round 1 on the user's own
ruling — follow the oracle, which treats the Purification as taking an
occurring Sunday's place outright, unlike an ordinary Marian feast (real
primary-text counter-evidence, RG 120(b)'s colour rule, remains on record
as the argument the other way). Only `most-holy-name-of-mary` stays
retagged `Bvm`. A related, unresolved primary-source finding: the Common
of the Dedication of a Church's own classification (*"Festum
Dedicationis Ecclesiae est festum Domini"*) means St Michael's Dedication
(29 Sep) may also be `Lord`, not `Saint` — measured, not applied (1 200
days domain-wide if it were). Two further open items were recorded here,
not fixed at the time: 13 January (Baptism of the Lord, mistagged
`Saint`) and RG 112, unimplemented. **Both are now RESOLVED — see item 3
below.** (The `Saint` mistag turned out to be independently fixed by the
`ef-rebootstrap` re-bootstrap, upstream of item 3's own task; RG 112 is
item 3's own work.)
2. **Commemoration identity — RESOLVED (Task B, branch `ef-rg16a`, 2026-08-12;
ONE FIX ROUND of review after the first pass — see register §6.1 for the
full, corrected account).** Was unasserted outside ~3 test rows — **the
exact gap the RG 16(a) fix round above had exploited**: the lectio
differential (layer 3) compares season/slug/rank/colour only, never
commemorations, BY DESIGN (lectio has no RG 111 admission logic of its
own) and still does not — that part of this item is unchanged and remains
the reason layer 4, not layer 3, had to close this gap. Layer 4
(missalemeum, 2026–2027) now compares commemoration IDENTITY, not only
presence/count (see the "know what each layer cannot see" section above
for the mapping and its limits). While building it, found and fixed a
SECOND, independent bug the exercise surfaced: `Precedence_ef.band` gave a
`Commemoration_only` candidate the same RG 91 table entry as a genuine
`Feast` of its own rank (RG 91's table has no row for a bare commemoration
at all) — **4 451 days wrong across the whole 1583–9999 domain**, exactly
4 verified shapes, fixed at the source. The `admit` same-rank tie-break
itself is RG 113 (previously uncited), not the alphabetical convention
this item used to describe — reconciled against the Plan-3-era "66 days"
figure: 599 is the tie POPULATION, 65 (or 67) the real ADMITTED-SET
decisions within it, 149 order-only — all now independently reproduced
(register §6.1), not merely asserted.
3. **Holy Family (RG 17(b)) + RG 112(a) + layer 4's observed-identity gap —
RESOLVED (2026-08-13, branch `ef-rg112-rg110`; see register §6.2 for the
full account).** Holy Family did not exist anywhere in colitur — a `grep`
found no trace in `lib/` or `data/`, and the day it should have observed
emitted an ordinary Sunday instead, undetected because **layer 4 compared
the observed day's rank and colour, never its identity** (Holy Family is
rank 2/white on both sides purely by coincidence — see the "know what
each layer cannot see" section above, closed first, as the regression
net, before any production code changed). Built: `Temporal_ef.temporal`'s
existing Sunday-fallback branch already computed the right slug/rank/
colour for 7-13 January by coincidence; the only silently-wrong field was
`subject` (always `Temporal`), now `Lord` on `holy_family_sunday y`
(`RG 91 entry 14`, "primum mobilia, deinde fixa") alone. RG 17(b)'s own
window can never be empty of a Sunday (unlike RG 17(a)'s Holy Name,
which carries an explicit calendarium fallback for its own narrower
window) — checked, not assumed; no fallback built. `Precedence_ef.band`
gained a movable-half priority for entry 14 — without it, Holy Family
would tie with the fixed Commemoration of the Baptism of the Lord
(13 January) and lose the kernel's alphabetical tie-break, backwards
from RG 91's own stated order. **The whole table is now scaled ×10**
(entry *n* → 10*n*), so a half-row is expressed as an ordinary position
between its neighbours. *(CORRECTED: this paragraph previously described
`entry_14_movable_band` as "negative so it can never collide with a real
table position". That was the bug, not the design — a negative sentinel
avoids **collision** but also inverts **ordering**, making a movable
II-class feast of the Lord outrank every I-class day. Unreachable on
universal data; live the moment a diocesan overlay puts a I-class proper
or indult feast, RG 91 entries 12–13, in the 7–13 January window. Do not
re-derive the sentinel approach.)*
`disposition` gained RG 112(a) (a mystery of one Divine Person excludes a
commemoration of another mystery of the SAME Divine Person). The primary
authority is **RG 95 ¶2** — *"Si vero duo festa eiusdem Divinæ Personæ…
fit de festo, quod in tabella præcedentiæ superiorem obtinet locum, et
aliud omittitur"* — an occurrence-level rule present in all three
documents; RG 112(a) and the Holy Family Mass propers' own 13-January
rubric corroborate it. *(CORRECTED: this previously called the propers'
rubric a further instance of the transcription defect. It is not. The
transcription carries RG 112 in full; RG 112 has no worked example in
either scan; and the propers' note is absent because that document is a
partial 2006 web capture containing almost no propers text — one
`Introitus` in 26 322 lines against 52 in a scan. Diagnose the cause of a
silence before invoking the rule.)*
**Blast radius, measured (`git archive` pre-change binary vs HEAD, full
1583–9999 sweep, diffed): 1 220 days, every single one the identical
shape, cross-verified against `date -d` independently (exactly 1 220
years have 13 January on a Sunday) — no anomaly, nothing outside what
was expected.** The differential's own C1 (the 6-13 January blanket) lost
exactly those 7 (of the 1 220) rows within its 2005–2050 window and they
were split into their own new cited entry (C15), not silently
re-absorbed — the same discipline the task brief demanded. Not built at
the time: RG 110 (inseparable Peter/Paul, still open, M12) — out of this
task's own dispatched scope despite the branch name. Holy Name of Jesus
(RG 17(a)) had the identical "generic-Sunday-slug masking a real named
feast" shape Holy Family had, PLUS a second, more severe gap (no RG 17(a)
fallback for its own 2–5 January window when empty of a Sunday — 3 619 of
8 417 domain years). **Both are now RESOLVED — see item 4 below.**
4. **Holy Name of Jesus (RG 17(a)) + RG 110 — RESOLVED (2026-08-13, branch
`ef-holyname-rg110`; see register §6.3 for the full account).** Holy Name
gained the `subject = Lord` tag Holy Family already had (RG 91 entry 14),
plus a genuinely new office: RG 17(a)'s own fallback, *"secus die 2
ianuarii"* — 2 January carries the feast whenever no Sunday falls 2–5
January that year, tagged and ranked identically to the Sunday shape (ONE
feast, per the Mass propers' own single heading covering both dates, both
scans). Before this fix colitur emitted no Holy Name office at all in
3 619 of 8 417 domain years — a genuine missing II-class feast, not
merely an unnamed one. A real asymmetry the fix's own synthetic
precedence tests found and kept honest rather than forced: a losing
Holy-Name-SUNDAY is RG 109(a)-privileged and survives RG 111(a)'s cap; a
losing Holy-Name-FALLBACK is not (2 January genuinely is not a Sunday, no
other RG 109 category names it) — both correct readings of RG 109/111's
own closed lists, no live witness for either today.
RG 110 (*"In Officio et Missa S. Petri semper fit commemoratio S.
Pauli, et vicissim... pro unica habeantur"*) gained a THIRD sub-clause
this register had not transcribed before, (c) — the same inseparable
commemoration also fires when one Apostle is admitted merely AS a
commemoration, not only when he is the day's own office. Three real
pairs in the 1962 calendar (25 January, 22 February, 30 June); the third
had no companion candidate anywhere — a genuine gap in lectio's own
source data AND in missalemeum's own oracle output, not only a colitur
bootstrap miss — closed via `data/ef/adjustments.sexp`'s own `Add`
directive. Built in `Precedence_ef.admit` (`rg110_additions`), layered on
AFTER `admit`'s own four RG 111 branches decide their ordinarily-capped
result, uncapped and additional, never competing for a slot.
**Blast radius, measured (`git archive` pre-change binary vs HEAD, full
1583–9999 sweep, diffed): 14 627 days, ALL FOUR predicted shapes, zero
unclassified** — 3 619 the Holy Name fallback itself; 3 533 Paul wrongly
excluded from Chair of St Peter's own day by a competing privileged
feria (RG 110 shapes (a)/(b) — supersedes an earlier 852-day estimate
that measured only a delta between two older commits, not the rule's
full scope); 593 Chair of St Peter admitted only as an ordinary
commemoration, Paul entirely absent (shape (c) — a real 7% of the
domain, not a corner case); 6 882 the new 30-June companion. The lectio
differential needed no RG 110 change at all (it does not compare
commemorations); the oracle allow-list needed M12 removed, M15 widened
by one date (a newly-exposed instance of its own pre-existing limit),
and a new M19 for the 30-June gap.
**CORRECTED, fix round 1: RG 110(c)'s own ordering was inverted on all
593 shape-(c) days.** *"Huic orationi additur altera"* (the companion is
added TO the trigger's own oration) means the trigger comes first, the
companion follows — the original build prepended the companion
uniformly in both shapes, right for (a)/(b) (nothing in the list to
order against) but backwards for (c). Fixed in `rg110_additions`
(splice the companion in immediately after its own trigger for shape
(c) only); the wrong-order unit test is re-pinned. New, permanent
blind spot found and recorded: **nothing in this suite compares
commemoration order except that one unit table** — see "know what each
layer cannot see" above. M19's own predicate was also strengthened to
check commemoration IDENTITY, not merely presence (the same C6/C14
failure mode, proven by fabricating a second companion and watching the
whole suite stay green pre-fix).
6. **`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`.
7. **`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.
8. **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**: RG 91 entry 27's BVM Saturday Office; RG 112(b)/(c)/(d)
(only (a) has a live witness this codebase's data can construct). Allow-list
entries M11 and M13 are `verdict open` by design. Holy Name of Jesus (RG 17(a))
and RG 110 (inseparable Peter/Paul commemorations) are **RESOLVED — see item 4
above.** The Sacred Triduum's own identity is **RESOLVED — see item 5 below.**
Major Litanies (25 April) and the Rogation-Wednesday commemoration are
**NARROWED, still unbuilt — see item 5 below**: both rules are now
scan-verified and register-cited, but neither is built, because both need the
SAME missing machinery (a rite-computed, Easter-relative, non-competing
commemoration candidate — no channel in `Precedence.resolve`/`calendar.ml`
constructs one; sanctoral candidates are Fixed-date only, the day's temporal
candidate is exactly one and already spoken for).
5. **The Sacred Triduum (RG 91 entry 2) — RESOLVED (2026-08-13,
`ef-triduum-litanies` task); Major Litanies (RG 80) and Rogation
Wednesday's own commemoration (RG 87-89) — established from a
photographic scan, deliberately NOT built.** Holy Thursday/Good
Friday/Holy Saturday kept their existing slugs
(`ef-passiontide-2-{thursday,friday,saturday}` — RG 91 entry 2 is
identified structurally by `Precedence_ef.band`, off rank and Easter
offset, never off the slug) and gained `Celebration.names` (Latin, both
photographic scans, corroborated by the electronic transcription's own
table-of-contents listing at the identical headings: "Feria V in Cena
Domini", "Feria VI in Passione et Morte Domini", "Sabbato Sancto") and
`subject = Lord` (verified safe: RG 112(a) only fires when both sides of
an occurrence are `Lord`, and no `Lord`-subject sanctoral entry has a
fixed date inside Holy Week's own movable range). **Full 1583-9999
blast radius, measured (`git archive` pre- vs post-change, `colitur day`
CLI output diffed): ZERO differing lines anywhere in the domain** — the
CLI prints neither `names` nor `subject`, so this whole change is
invisible to the differential (layer 3), the oracle (layer 4), and even
the exhaustive property sweep (layer 2); only `test_golden.ml`'s
`describe` (widened this task to add a `name_la` field, the same lesson
its own `subject` field was added for) and one new `test_temporal_ef.ml`
unit test see it at all. Mutation-tested: reverting `temporal_ef.ml`
alone reddens 6 tests across those two files.
Major Litanies (25 April, RG 80) and Rogation Wednesday's own
commemoration (RG 87-89, Minor Litanies) were both investigated to the
same depth and **both scan-verified and fully register-cited, but
deliberately not built**: RG 80's own transfer clause (25 April moving to
the following Tuesday whenever it coincides with Easter Sunday or Easter
Monday — **194 of 8,417 domain years, ≈2.3%, measured**, not assumed
rare) and Rogation Wednesday's own commemoration (RG 88/89, competing
only when the Ascension Vigil already occupies the day) both need a
commemoration candidate keyed to a MOVABLE, Easter-relative date that
is not the day's own temporal office — a kind of thing
`Precedence.resolve`'s architecture has no channel for: sanctoral
candidates come exclusively from `Layer.on_date`'s Fixed-`Date_spec`
lookup (`date_spec.ml`'s own header: Easter-relative forms "arrive with
the OF sanctoral" — not yet), and the day's temporal candidate is
exactly one, already spoken for. RG 110's own `rg110_additions`
mechanism (item 4 above) looks like a precedent but is not one: it only
ever RE-INCLUDES a candidate that already exists elsewhere in that
date's own candidate pool (both Peter and Paul are ordinary Fixed-date
sanctoral entries); neither the Major Litanies' Easter+2 target nor
Rogation Wednesday's own Mass commemoration has any such pre-existing
candidate to find.
**The real reason to defer the Major Litanies, corrected by the
fix-round review** — the version above was wrong in two load-bearing
ways and is retracted:
- It said the suppression half needs "a kernel signature extension
threading Easter-offset into `disposition`/`admit`". It does not.
`admit` **already** takes `~temporal` (added for exactly this class of
question during the RG 16(a) task) and `disposition` already takes
`~winner`; on 25 April in a transfer year both carry the Easter Sunday
or Easter Monday office. A rite-local slug test suppresses it with
**zero** kernel surface.
- It implied Easter Monday is unmarkable. Measured over the whole
domain: `ef-easter-1-monday` occurs **exactly 8 417 times in 8 417
years** — once every year, never displaced, since it is an I-class
octave day — making it precisely as reliable a marker as
`ef-easter-sunday`. The stated asymmetry between the two trigger
conditions does not exist.
So the **guarded** build (a `Commemoration_only` `Fixed(4,25)` entry, RG
109(f) wired, plus slug-based suppression on the two Easter slugs) is a
strict improvement, not a regression: 8 223 years newly correct, 194
unchanged, zero new wrong answers. "Worse than the recorded gap" was
true only of the *unguarded* build, which is the only option that was
scored.
Deferring is still right, for a reason nobody had identified: **25 April
is St Mark, II class**, so under **RG 111(c)** a *privileged* Litanies
commemoration (RG 109(f)) would **displace** whatever ordinary
commemoration the day currently carries, in ≈97.7% of years — a live,
unmeasured blast radius straight through layers 3 and 4. That
measurement is the prerequisite, and it makes this its own task rather
than a rider on another. The 194 figure is exact and reproduced twice
(Easter = 25 April in 67 years, Easter Monday = 25 April in 127).
Full reasoning: the `ef-triduum-litanies` task report and register
(Rogations/§4, and the two narrowed open items in §6). **Rogation
Wednesday remains genuinely blocked** — Easter+38 coincides with the
Ascension Vigil by construction, so there is no `(month, day)` pair a
`Fixed` spec could anchor to and no partial build exists at all.
## 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.
|