aboutsummaryrefslogtreecommitdiff
path: root/CLAUDE.md
blob: 5047e93b9aabc17a3638009b26fe2a735c2c0b49 (plain) (blame)
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
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
# 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**.

- **`Date_spec` carries MOVABLE dates** (2026-08-17): `Fixed(month,day)` as
  before, plus `Easter_offset of int` and `Nth_weekday of {month; nth;
  weekday}` (negative `nth` counts from the end). `Fixed`'s sexp form is
  unchanged, so all 327 sanctoral entries parse untouched. Easter comes from
  the RITE (`Rite.t`'s `easter` field) — the kernel ships both Gregorian and
  Julian and must not pick. `Layer` keeps a **split index**: fixed entries in
  the year-independent `(month,day)` table, movable ones resolved per civil
  year into a rata-die table, with the domain filter in `Layer.index` itself
  (both edges, 1582 and 10000, bit during development). This unblocked
  **Rogation Wednesday** (RG 87/88/89, register §6.10) and is what a
  user-supplied overlay needs for a local movable feast. The `--overlay` CLI plumbing
  is **built** (see below).
- **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 — 6 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.
6. **LMS Ordo cross-check** (`test_lms_ordo.ml`, added `celebrant-rubrics-phase1`) —
   the Creed (RG 475–476), the RG 78/309(a) Saturday votive Mass's own seasonal
   selection, and the reading-formulary override, each compared against a printed
   Latin Mass Society Ordo for England & Wales (Peter Day-Milne, v2.21) over one
   liturgical year, 2024-11-27–2025-12-31 (400 days). Genuinely a FOURTH lineage,
   not a re-count of layer 4 — see the very next bullet, which this layer
   supersedes for the "no independent witness" half of its own claim.

**Status: all six 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; layer 6 compares 400 days against the
LMS Ordo, full 1:1 on the Creed axis, and found a real colitur defect on its first
run (RG 476(f), closed — see the register).

**Layer 6's own blind spots, stated plainly rather than left implied**: it covers
ONE civil year, not the whole 2005–2050 differential window; the formulary-
override axis compares only 3 of {!Mass_formulary.source}'s 5 constructors
(`Own_slug` is excluded — a real, evidenced data-representation ambiguity in
colitur's own citations, not a gap in the Ordo, and `Votive` is covered by a
separate, dedicated seasonal-numeral check instead); and it is an England & Wales
DIOCESAN Ordo — universal-calendar days are compared, the diocesan variant on 156
of its 400 days is not. `docs/research/ordo/PROVENANCE-lms.md` has the full
source citation, SHA-256 and characterisation record.

**Know what each layer cannot see** — this is load-bearing, not a caveat:
- **LAYERS 3 AND 4 ARE ONE LINEAGE, not two** (register §6.20, 2026-08-18 —
  this supersedes the weaker "shares colitur's lineage" caveat below).
  missalemeum's own repo states it uses **Divinum Officium's data files via a
  git submodule**; lectio's ini is generated from missalemeum; colitur was
  bootstrapped from lectio. So **Divinum Officium → missalemeum → lectio →
  colitur**, and layer 4 is layer 3's own UPSTREAM rather than a separate
  witness. Every "colitur vs missalemeum" ruling is in substance colitur vs
  Divinum Officium, the de facto standard for this whole software space. The
  project has ONE external software witness plus the scans — not two.
  The genuinely independent witness found is the **published Ordo** (clergy-
  compiled, not software): it confirmed seven colitur positions including
  three hand-authored entries missing from the entire DO tree (Major
  Litanies, the RG 110 Peter companion, St Barbara).
  *(CORRECTED, `celebrant-rubrics-phase1`, 2026-08-22: this paragraph
  previously ended "Not wired in as a layer — its PDF columns shift between
  pages and extraction is unreliable; a structured Ordo would be the only
  way to add a fourth lineage." That is still true of THIS SPECIFIC Ordo
  (the one that confirmed the three entries above) — it remains unwired,
  for that exact reason. But its own closing prediction has since been
  fulfilled by a DIFFERENT, later-acquired published Ordo: the Latin Mass
  Society's, structurally parseable (`tools/extract_lms_ordo.ml`), now
  wired in as layer 6 — see the validation section above. Two distinct
  printed Ordos, not one: this file's own historical entries about the
  first should not be read as describing the second.)*
- **THE CHAIN IS NOW BROKEN AT ONE LINK, AND LAYER 4 IS THE OUTLIER**
  (2026-08-18). lectio has ADOPTED eight corrections colitur argued from the
  Missal — `ubaldus`/`didacus` promoted to III-class feasts, both August
  vigils recoloured violet under RG 128, and both Ember Saturdays' readings.
  So on those days the lineage no longer runs Divinum Officium → lectio:
  lectio now follows the Missal, and **missalemeum is the only party still
  carrying the inherited error.** Layer 3's fixture was regenerated, and
  **C27, C28 and C37 went to zero and are closed** (C38 narrowed to a
  readings-only divergence, which cannot close without lectio gaining a
  Commons concept). 227 of 16 801 rows changed; agreement 86.6% → 87.5%.
  Read the percentage as the *least* interesting part: those 227 days now
  agree BECAUSE BOTH ENGINES FOLLOW THE MISSAL, where before they agreed
  because both inherited the same errors. This is the first time the project
  has RESOLVED the lineage problem on a set of days rather than documenting
  it.
  **What this changes for layer 4, practically.** On those days the oracle
  now disagrees with BOTH engines, and its allow-list entries (M29, M30, and
  the Ember-reading classes) should be read as "missalemeum is the outlier",
  not as "colitur is unusual". Do not treat a fresh colitur-vs-missalemeum
  divergence as presumptively colitur's fault: the base rate has changed.
  **What it does NOT change**: everywhere else, layers 3 and 4 remain one
  lineage, and lectio's own data is still generated from missalemeum by
  `scripts/gen-sanctoral-ef.go`. That generator would silently revert all
  four calendar corrections if re-run, which is why lectio's ini headers and
  the generator's own doc comment now each carry the list.
- 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.
- **Step 4 of the reading chain (the Common route) — CLOSED** (register §6.9,
  `ef-oracle-2038`, 2026-08-17). Layer 4 gained a SECOND fixture, 2038
  (365 days, live-captured, its own provenance/SHA/suite, deliberately kept
  apart from the 2026–2027 one because the live endpoint has drifted from
  lectio's archived snapshot). 2038 is the only year in 2005–2050 covering
  two Common-routed saints as the observed office. 338/365 match; 27 differ;
  all 27 in already-adjudicated classes; zero unexplained — an independent
  year re-confirming rulings made in a different one. Proved by re-running
  §6.7's own mutation: corrupting a Common citation now reddens four tests,
  two of them external-oracle, where it previously reddened none of them.
  Residual: `isidore-of-seville` still unwitnessed (needs 2035/2046), and
  `gregory-the-great`/`patrick` are never observed in 2005–2050 at all, so no
  fixture in that range can reach them.
  **The original gap, for the record** (register §6.7, `ef-lectionary` fix
  round 2). Only five saints route
  through `data/ef/commons.sexp`, and across all of 2005–2050 they are the
  *observed* office on five days total — `isidore-of-seville` 2008/2035/2046,
  `frances-rome` and `sts-felicitas-perpetua` both 2038, while
  `gregory-the-great` and `patrick` are **never** observed in 46 years (both
  sit in March, impeded by Lent's privileged ferias every year). None falls in
  layer 4's 2026–2027 window, so no Common-routed citation has ever been
  compared against an external source. Layer 3 does not fill the gap either,
  and the distinction is exact: those days are in its range, but lectio
  resolves the literal `-`/`-` sentinel there (that is *why* C18 exists and
  what it is gated on), so layer 3 confirms only that colitur produces *a*
  citation where lectio produces none — never that it is the right one. Step 4
  rests on the scan-verified Common assignments and unit tests alone. Closing
  it means extending the oracle fixture to 2035, 2038 or 2046 (2038 covers two
  of the five at once) — a fixture-scope decision, deliberately not taken.
- **Major Litanies (RG 80/109(f), `ef-major-litanies` task): layer 3 is
  entirely BLIND to a commemoration-only entity, confirmed not merely
  argued** — lectio computes no Major Litanies at all, and its own `row`
  type carries no commemorations field in the first place (limit 1, same
  file). `data/ef/expected-divergences.sexp` needed no change; a whole new
  privileged commemoration, present or absent, present-but-displacing-a-
  saint, or transferring to a different date entirely, is genuinely
  invisible to that layer. Layer 4 (missalemeum) sees the entity and the
  RG 111(b) question (both years in its 2026-2027 window), but is ALSO
  blind to the transfer specifically, because neither year in that window
  is a trigger year — confirmed by mutation (disabling the transfer
  branch produced zero oracle failures), not merely by the calendar
  coincidence. Only golden pins see the transfer at all. Full account:
  `.superpowers/sdd/2026-08-12-colitur-rg16a/major-litanies-report.md`.
- **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 + the EF lectionary DONE — verify with `git log`)

**Plans 1 + 2 are on `main` (35 commits). Plan 3 and its follow-on fix/feature
tasks (RG 16(a), Holy Family/RG 112(a), Holy Name/RG 110, the Sacred Triduum,
the BVM Saturday Office, the Major Litanies) have landed on a chain of feature
branches since — test count keeps climbing task by task (388 tests green, 389
with the exhaustive sweep, as of the movable-date-specs task; this line is not kept in
lockstep with every task, `git log`/`dune test` are the actual source of
truth).** 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; `Add major-litanies`,
`ef-major-litanies` task, RG 80/81, same reasoning) · two cited
allow-lists, `expected-divergences.sexp` (7 active entries, vs lectio — C1, C6,
C8, C14, C15, C16, C17; several more closed and recorded in the register, not
deleted — untouched by the Major Litanies, layer 3 is blind to that entity, see
above) and `expected-divergences-missalemeum.sexp` (12 active, vs the oracle —
M2 closed/M18 widened by the `ef-bvm-saturday` task; M12 closed/M19 opened by an
earlier one; M5 corrected (a prior note had 2027's own outcome backwards) and
M20 added by the `ef-major-litanies` task, M18 394 not 395 accordingly).
Fixtures live in `test/fixtures/` with asserted SHA-256s.

**CLI**: `colitur easter <year>`, `temporal <year>`, `day <year>`,
`readings <year>`, `-h`/`--help`, `-V`/`--version`, and `--overlay FILE`
(repeatable, ordered; `day`/`readings` only). Man page in
`man/colitur.1`; `Makefile` installs binary + data + man page into `~/.local`
by default. Tagged **v0.1.0**.

**User overlays** apply ON TOP of the shipped `adjustments.sexp`, never
instead of it — replacing would silently drop RG 110's companion, the Major
Litanies, Barbara and Rogation Wednesday. `Overlay.merge` is
last-writer-wins, so a local calendar can still override a universal entry
by naming its slug. Refused on `easter`/`temporal` (they read no sanctoral
data) rather than silently ignored. **An overlay is applied, not validated**
— the six layers assert things about the SHIPPED data and cannot vouch for
a user file; a directive matching nothing warns on stderr and continues, a
file that fails to load is fatal. Worked example, both a fixed and a movable
local feast: `test/fixtures/overlay-example-diocesan.sexp`.

`--help` prints to **stdout** and exits **0**; a usage error prints one line
to **stderr** and exits **2**. The distinction is asserted in `test/cli.t`,
both directions, because it is the sort of thing that silently rots.

`readings` is a **separate command, not extra columns on `day`**, for a
mechanical reason worth not rediscovering: a citation contains spaces and
commas (`Ezech 34:11-16`, `Ecclus 51:1-8, 12`) while a `day` row is
space-separated with a variable-length `+slug` commemoration tail, so
appending them there leaves the row unsplittable by field number. `day`'s
format is therefore **byte-identical** to what it was before the lectionary
existed (asserted in `test/cli.t`). Both are a **stopgap**, not the project's
answer to output: the design still calls for one schema rendered through a
logic-less template engine — two ad-hoc formats are easier to retire than one
overloaded format with unwritten parsing rules.

`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

There is a `Makefile` now (same shape as lectio's: `PREFIX ?= $(HOME)/.local`,
`## `-comment help target). Every recipe wraps dune in `opam exec --`, so make
works from a plain shell with no `eval $(opam env)` first.

```sh
make help        # list targets
make build
make test        # fast suite, ~5 s (properties sample 200 years)
make check       # full gate, ~2 min: every year 1583-9999, not a sample
make install     # binary + calendar data + man page into ~/.local
make uninstall
```

`install` goes through `dune install`, not a hand-rolled copy, because the
binary finds its data relative to its own path (`<prefix>/share/colitur/ef`);
the man page is installed separately, to `$(PREFIX)/share/man/man1`, matching
lectio. The installed copy is a SNAPSHOT, not a link — `make reinstall` after
pulling.

Raw dune still works if preferred:
```sh
eval $(opam env)          # activate the project-local switch (run from this dir)
dune build && dune test
COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force
dune exec colitur -- day 2026 | head
```

## What's next

- **The EF lectionary is DONE** (branch `ef-lectionary`, 20 commits): the four-
  step reading-resolution chain, its data (`data/ef/lectionary.sexp`,
  `commons.sexp`, sanctoral propers), all five validation layers extended to
  citations, and `colitur readings`. Layer 2 asserts that **every day of every
  year 1583–9999 resolves exactly one Epistle and one Gospel** — measured, and
  mutation-proved live rather than silently inert. **Chants (Psalm, Second,
  Tract, Alleluia, Sequence) remain deliberately unbuilt**: no source, no
  oracle, and `Validate`'s own `citations` check now *rejects* any part outside
  First/Gospel, so one appearing would be a defect rather than a feature
  arriving early.
- **Plan 4 — OF rite module** (proves `RITE` generalizes) → full output/
  rendering → hardening and a first tag. (The lectionary bootstrap and
  citations this line used to defer to Plan 4 landed early, on
  `ef-lectionary`; what remains here is OF's own lectionary, not the
  mechanism, which is now built and rite-agnostic.)
  **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 112(b)/(c)/(d, non-BVM half). *(2026-08-18: the
reason is now measured and written down, register §6.29, rather than left as
a bare "unbuilt".)* **(c) cannot fire at all** — `Temporal_ef.temporal`
returns one office per day, so two *de Tempore* candidates never coexist;
0 days domain-wide. **(b) is already produced by RG 16(a)** — 0 days either
direction across 1583–9999, because the feast takes the Sunday's place
*"nulla fit commemoratio"* and no Class1 Sunday shares a date with any of the
6 Lord-subject entries. **(d)'s saint half has no candidate pair** — the only
genuine one, `agnes`/`agnes-secundo`, is 7 days apart and co-occurs 0 times.
So (b) and (d) are DATA-unreachable, not architecture-unreachable, and an
`--overlay` can make either live — the `entry_14_movable_band` shape. Whether
to build defensively against that is an open judgement call, not an oversight. 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.** RG 91 entry 27's BVM Saturday Office is **RESOLVED — see
item 9 below.** The Major Litanies (25 April, RG 80/109(f)) are **RESOLVED
— see item 5 below.**
Rogation Wednesday's own commemoration (RG 87-89) **remains genuinely
unbuilt** — see item 5 below: the Major Litanies' own "no third channel"
blocker turned out to be dissolved by REUSING the existing RG 96 transfer
machinery rather than by adding the missing channel, but that reuse is not
available to Rogation Wednesday, whose trigger is not a fixed civil date at
all (it is the day's own temporal identity, Easter+38, which coincides
structurally with the Ascension Vigil) — confirmed still blocked for the
original, distinct architectural reason.

5. **The Sacred Triduum (RG 91 entry 2) — RESOLVED (2026-08-13,
   `ef-triduum-litanies` task); Major Litanies (RG 80/81/109(f)) —
   RESOLVED (2026-08-13, `ef-major-litanies` task); Rogation Wednesday's
   own commemoration (RG 87-89) — RESOLVED, see below.** 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/81/109(f)) — RESOLVED (2026-08-13,
   `ef-major-litanies` task).** The "no channel for a movable,
   Easter-relative commemoration candidate" blocker this entry previously
   recorded (and the item-5 header used to describe as blocking BOTH the
   Litanies and Rogation Wednesday) turned out to be dissolved by a
   DIFFERENT design, not by adding the missing channel: RG 80's own
   transfer is structurally the SAME operation RG 96 already performs for
   an impeded I-class feast (a losing candidate relocated to a named later
   date), so it is built by REUSING `Precedence.disposition`'s existing
   `Transfer` constructor and `Calendar`'s existing placement machinery,
   with a fixed target (Easter+2) instead of a searched one — no new
   `Date_spec` variant, no third candidate stream. Entity:
   `Commemoration_only`, `Fixed(4,25)`, `data/ef/adjustments.sexp`'s `Add
   major-litanies`. **The genuine reason to defer, correctly identified by
   an earlier fix-round review** (25 April is St Mark, II class, so RG
   111(b) — not (c), a citation this task corrected — makes a privileged
   Litanies commemoration DISPLACE Mark's own ordinary one whenever both
   compete) **is now measured and adjudicated**: full domain blast radius
   (1583-9999, zero unclassified findings) is 7 394 years the Litanies
   simply appear, 829 years they displace Mark (4 of them, 2010/2021/
   2027/2032, in the 2005-2050 window), 194 origin departures + 194
   target arrivals for the transfer (the same 194 figure this entry
   already had, now independently re-derived through the real
   `Calendar`/`Precedence` pipeline). The Sunday-displacement question
   itself (RG 111(b): does a privileged commemoration categorically
   override an ordinary II-class one, or does missalemeum's own
   divergent data mean otherwise?) is ADJUDICATED colitur, honestly
   flagged as the first real (non-synthetic) test of that specific admit
   clause — see the "know what each layer cannot see" section above and
   the task's own full report for the reasoning and the correction this
   task made to a PRE-EXISTING allow-list note (M5) that had 2027's own
   oracle outcome backwards. A genuine kernel bug was found and fixed
   along the way, kept rite-agnostic: `calendar.ml`'s `build_day` used to
   decide "did a transfer settle" by checking ONLY whether the candidate
   became `observed` at its target — impossible by construction for a
   `Commemoration_only` candidate (RG 81), caught by `prop_invariants`'
   SAMPLED 200-year property (the default `dune test` run), NOT the
   committed exhaustive sweep (which walks in order and would have found
   it deterministically at year 1638, not the later, seed-dependent year
   the sample happened to draw) — attribution corrected in fix round 1
   (F4). Fix round 1 also found and closed a THIRD settlement channel
   `settled_at` still missed (a transferred candidate capped out by
   admission limits AT its own target, F1) — unreachable on shipped data,
   caught by the same sampled property while mutation-testing the RG 109(f)
   privilege. Full account: `.superpowers/sdd/2026-08-12-colitur-rg16a/major-
   litanies-report.md`.

   **Rogation Wednesday — RESOLVED (2026-08-17, the movable-date-specs
   task, v0.2.0).** *(CORRECTED 2026-08-18: this paragraph previously read
   "remains genuinely blocked" and was still being quoted as an open item
   a release later. It was accurate when written and was superseded by the
   very next task.)* The blocker it describes was exact — "there is no
   `(month, day)` pair a `Fixed` spec could ever anchor to" — and it names
   its own remedy: the missing channel is a date spec that can express an
   Easter offset. `Date_spec` gained `Easter_offset` precisely then, so
   the entity is now an ordinary `Add` in `data/ef/adjustments.sexp`
   (`Easter_offset 38`, `Class4`, `Commemoration_only`, RG 87 verified at
   scan1.txt:691 — "Litaniae minores seu Rogationes, per se, assignantur
   feriis II, III et IV ante festum Ascensionis Domini", Monday, Tuesday
   AND Wednesday). Live: 1 981 days across 1583–9999 carry it, and in the
   remainder an impeded feast takes the single slot RG 111 allows. Two
   golden pins cover both outcomes.

   The lesson worth keeping: a blocker stated as an architectural
   impossibility was really a statement about one type's expressiveness,
   and it dissolved the moment that type grew a constructor. Read such a
   claim as "what would have to change", not "this cannot be done".

9. **RG 91 entry 27, the votive Office of the BVM on Saturday — RESOLVED
   (2026-08-13, `ef-bvm-saturday` task).** `Precedence_ef.band` already
   routed a plain IV-class Saturday feria to entry 27's own band value
   (312 966 times domain-wide), but `Temporal_ef.temporal` never
   constructed the office itself. Caput IX of the Rubricae Generales, both
   photographic scans and the electronic transcription, word for word (no
   scan-vs-transcription conflict — RG 78/79 are General Rubrics prose, not
   the Mass-propers body text the transcription is missing): *"78. In
   sabbatis, in quibus occurrit Officium de feria IV classis, fit de
   sancta Maria in sabbato."* RG 78's own protasis IS "otherwise unoccupied
   IV-class Saturday" — decided entirely by the existing occurrence
   machinery (`band`'s own entry-27 branch already reads
   `rank = Class4 && weekday = Sat` unconditionally and only wins when
   nothing outranks it), so **`band` and `admit` needed no change** — rank
   stays Class4 either way, and neither reads slug/colour/name to decide
   anything (`disposition` DID need one, in a fix round — see below, RG
   112(d)). Colour is white, unconditionally — the tighter chain, found in
   a fix round (RG 431(e), the Missal's own classification of this exact
   Mass as a "Missa votiva IV classis... de B. Maria Virg.", → RG 121(a),
   votive Masses take the colour of the feast-type they "respondent" →
   RG 120(b), BVM feasts are white — see below), not RG 120(b) alone
   (a stretch: this Office is not itself a *festum*, RG 120(b)'s own
   "de festis") and never RG 119/127/128's seasonal rules either way. The slug is deliberately
   UNCHANGED (reused from the ordinary `<season>-<week>-<weekday>` ferial
   fallback), the same precedent the Sacred Triduum (item 5) already set —
   identified structurally, never off the slug — and for a second, load-
   bearing reason found while building it: a bespoke uniform slug would
   have broken `Validate`'s own slug-uniqueness-per-liturgical-year
   invariant (asserted with zero exceptions since Plan 2), since the
   office recurs many times a year. Subject is tagged `Bvm` (a real
   `Subject.t` variant that existed, unused, since the kernel's vocabulary
   was designed — no kernel change needed) and the name is Latin only
   ("Officium sanctae Mariae in sabbato", RG 91 entry 27's own table title
   and RG 79's own heading), the same zero-circularity discipline items
   3–5 already established. The I–V numbered "Missae de sancta Maria in
   sabbato" (both scans) are a Mass-propers selection detail (RG 309(a):
   *"iuxta temporum diversitatem"*) governing which readings are said, not
   which office is kept — out of scope until Plan 4's lectionary.
   **Blast radius, measured (`git archive` pre-change binary vs HEAD, full
   1583–9999 `Calendar`-resolved sweep, diffed): 75 853 days, every single
   one the identical single-field shape (`colour` alone, always on a
   Saturday, always `class-4` on both sides) — no anomaly.** Reconciled
   against the full 312 930-day eligible population on the same sweep
   domain: 102 144 are actually observed (the office wins), split by
   season — 63 195 Time after Pentecost + 7 643 Septuagesima + 5 015 Time
   after Epiphany (75 853 total, all VISIBLE, green/violet → white) and
   14 213 Paschaltide + 12 078 Christmastide (26 291, all INVISIBLE — RG
   119 already made those seasons white); the remaining 210 786 are
   impeded by a real sanctoral winner and genuinely unaffected. Both
   allow-lists moved: the lectio differential gained a new cited entry
   (C17, colitur, 416 rows — lectio builds no equivalent office); the
   missalemeum oracle's own M2 (previously `verdict missalemeum`, "colitur
   is missing a whole office") is **CLOSED, REMOVED** — the colour
   divergence it named no longer occurs on any of its 22 dates, and what
   is left (`Observed_identity_unresolved` alone, the office is temporal-
   origin and deliberately unnamed in English) is exactly `M18`'s own
   shape, not a distinct citation any more; `M18` widens 373 → 395
   accordingly. Mutation-tested: reverting the office to a constant-false
   guard reddens the dedicated unit test, an end-to-end resolve test, a
   golden pin, and both allow-list count pins (in both directions — the
   fix present with M2 still declared fails identically to the fix absent
   with M2 removed), while a second end-to-end test/golden pin (the office
   losing to a real competing feast, 12 September, the one live data
   witness that also carries `subject = Bvm`) is correctly untouched by
   the mutation, proving RG 26's rank-keyed omission fires before any
   subject-keyed rule ever could.

   **Fix round 1 (2026-08-13, coordinator review) — one blocking finding,
   fixed.** RG 112(d) (Caput XVI): the Office, itself "de B. Maria Virg.",
   excludes another commemoration invoking the SAME BVM's intercession —
   violated live: `our-lady-of-mt-carmel` (16 July, `Commemoration_only`)
   was wrongly commemorated on every 16-July-Saturday. Fixed in
   `Precedence_ef.disposition` (`marian_slugs` ∪ `subject = Bvm` on both
   sides of the collision — the disjunction matters, neither signal alone
   identifies both real sides). Checked exhaustively, not merely for
   Mt Carmel: only `Commemoration_only`-status Marian entries can ever
   reach this live, and the ONLY other one, `our-lady-of-ransom` (24
   September), is PROVABLY unreachable (forces the September Ember
   Saturday every time, by construction). Re-measured: the original
   75,853-day blast radius is unchanged in total, splitting into 74,633
   `colour`-only + 1,220 `colour+comms`. Mutation-tested: exactly 3 tests
   redden. Six further ride-along findings closed the same round: a
   test pin corrected (26 December can never hold the office, St Stephen
   always wins there — 3 January used instead); C17 gained a `subject`-
   based identity guard; `band`'s own entry-27 comment gained its RG 78
   citation; a tighter RG 431(e) → RG 121(a) → RG 120(b) colour chain
   (conclusion unchanged); and the oracle's own coverage recorded
   precisely — of 26 office days in the 2026-2027 fixture only 17 are
   colour-discriminating, and **Time after Epiphany (5,015 observed days
   domain-wide) has ZERO oracle witnesses**, resting on the scan and the
   dedicated unit test alone. Full account:
   `.superpowers/sdd/2026-08-12-colitur-rg16a/bvm-saturday-report.md`.

10. **OF W1 — Normae n. 56(f), St Joseph anticipated onto Palm Sunday —
    RESOLVED (`of-normae-56f`, 2026-08-26), and the kernel contract change
    it forced.** `Rite.t.transfer_target`'s own obligation used to require
    the result be **strictly later** than the impeded date, justified
    purely from the EF's RG 96 (rite.mli's own comment cited it, "the same
    class of item CLAUDE.md tracks under 'EF-shaped things still in
    rite-agnostic kernel code'" — item 8 above). This is the FIFTH
    EF-shaped kernel misfit found while building OF (after `Preface.t`,
    `Mass_formulary.source`, `transfer_target`'s own strictly-later
    contract, and `citation_shapes`) and the SECOND fixed rather than only
    documented — `citation_shapes` was the first.

    **The rule that forced it**: *"Sollemnitas S. Ioseph, ubi est de
    praecepto servanda, si cum Dominica in palmis de Passione Domini
    occurrit, anticipatur sabbato praecedenti, die 18 martii"* — St Joseph
    impeded by Palm Sunday is ANTICIPATED BACKWARD to the preceding
    Saturday, the one clause in the whole Tabula/Normae transfer machinery
    that names an earlier date, not a later one. Live in 16 of 1583–2100
    (1595, 1606, 1617, 1690, 1758, 1769, 1780, 1815, 1826, 1837, 1967,
    1978, 1989, 2062, 2073, 2084) — colitur previously sent Joseph forward
    to Easter+9 instead (past the Annunciation's own Easter+8, since both
    solemnities were impeded that year and collided on the same forward
    target).

    **The alternative rejected**: leaving it documented-but-unimplemented
    indefinitely, precedence_of.mli's own status quo before this task. No
    rite-local workaround exists instead — `Calendar.place_transfers` is
    the only channel that ever places a transferred candidate, so a rule
    the kernel contract forbids from returning cannot be smuggled in
    beside it.

    **What it actually costs**: traced against `Calendar`'s real placement
    pass (calendar.ml's `place_transfers`/`year`), not assumed unsafe by
    inheritance from the EF-only citation that wrote the original text.
    The round loop re-resolves the WHOLE year fresh every round from the
    currently-settled assignment, and its two safety nets — the
    `~start`/`~stop` domain bound and `max_transfer_rounds`'s round-count
    guard — both test the target's own value with no comparison against
    `origin` anywhere, so a bounded backward jump converges exactly the
    way a bounded forward one does, and "resurrecting a superseded
    occupant" cannot occur (a day's occupant is decided fresh each round
    by the same contest every other day is). The ONE requirement that
    really is load-bearing: `target <> origin`, not merely `target >
    origin` — `Precedence.resolve` never deduplicates by slug, so a
    candidate returning its own argument unchanged would be handed to the
    RG 91/Tabula contest twice in the same round, a genuine self-collision
    (disposed of as both `winner` and `loser` against itself), not a
    docstring worry. The relaxed obligation is `Rite.t.transfer_target`'s
    new text (rite.mli); it does NOT license an unbounded backward
    SEARCH as safe merely because a bounded one is proven so — the
    termination obligation still falls entirely on the rite, exactly as it
    always did for a forward search, and the only shape actually verified
    is a CONSTANT one-shot displacement for a specifically-named collision
    (`Precedence_of.transfer_target`'s own Rule 0, `Date.add_days origin
    (-1)`, no internal search at all). `calendar.ml` itself needed NO
    logic change — only two comments correcting a blanket "every genuine
    transfer moves forward" claim that the trace showed no longer holds
    literally.

    **Blast radius, measured**: EF untouched — `git diff --stat
    v1.0.0..HEAD -- data/ef/` and `lib/rites/rite_ef/` both empty (the
    field's TYPE is unchanged, only its doc comment; no new required
    field), and `colitur day`/`colitur readings` for 2026, 1583 and 9999
    are byte-`cmp`-identical between this change and a `git worktree`
    build of the commit immediately before it. OF's own 2005–2050
    differential-testing window (CLAUDE.md's own oracle range) carries
    zero of the 16 affected years, and a full sweep of it shows zero
    unexpected diffs either. Each affected year's own diff is exactly two
    lines (18 March gains Joseph; whichever memorial or feria previously
    occupied the old, wrong Easter+9 target resurfaces there instead) —
    confirmed on 2062 by direct `cmp` against the pre-fix binary. The
    whole domain's transfer placement still reaches a fixed point:
    `Validate.run`'s "unconverged" check stayed clean across the full
    1583–9999 exhaustive sweep (`COLITUR_EXHAUSTIVE_SWEEP=1 dune test
    --force`, part of `make check`).

## 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.