aboutsummaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md162
1 files changed, 128 insertions, 34 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index cb8b0f4..25d3339 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -102,52 +102,146 @@ overlays**.
4. **Oracle cross-check** — vs missalemeum (EF) / litcal (OF) in lectio's `sources/`.
5. **Golden regression** — landmark + known-tricky years pinned.
-## Current state (Plan 1 DONE — verify with `git log`)
+**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.
-Branch **`ef-pilot-plan1`**, ~6 commits. The Computus **walking skeleton** is built
-and green:
-- `lib/kernel/date.ml[i]` — `Date`: opaque rata-die (Howard Hinnant civil↔days, as
- in lectio); `make ~year ~month ~day : (t,string) result` (validates, 1583..9999);
- `year/month/day/weekday/to_rata/of_rata/add_days/compare`.
-- `lib/kernel/computus.ml[i]` — `gregorian_easter`, `julian_easter`, and anchors
- `ash_wednesday/palm_sunday/ascension/pentecost/corpus_christi`.
-- `bin/main.ml` — CLI `colitur easter <year>` (prints Easter + anchors; bad year → exit 2).
-- `test/` — alcotest unit + qcheck property + a `cli.t` cram test. All green.
-- Build: dune 3.0, **OCaml 5.2.0 project-local opam switch** (`./_opam` already
- present), deps `dune alcotest qcheck qcheck-alcotest`, `cram enable`,
- `generate_opam_files`. License **AGPL-3.0-or-later**.
+**Know what each layer cannot see** — this is load-bearing, not a caveat:
+- Layer 3 **never compares commemorations** (lectio has no RG 111 admission logic,
+ so its "others" are losing candidates, not the admitted set) and never compares
+ the week column. It also **shares colitur's own lineage**: colitur's data was
+ bootstrapped from lectio, so an error both inherited is invisible here. Proven:
+ Holy Thursday was violet in both because both were wrong.
+- Layer 4 is **two years wide** and compares rank, colour-membership and
+ commemoration presence/count — **never celebration identity**.
+- Together they sleep through **commemoration identity**: reversing `admit`'s
+ dignity sort, so the engine admits the *worst* commemorations, leaves all eight
+ differential and oracle assertions green across 17 531 days.
-`sexplib`/`ppx_sexp_conv` and a Mustache lib are **not yet added** — they arrive
-with the data model in Plan 2/3.
+## Current state (Plans 1–3 DONE — verify with `git log`)
+
+**Plans 1 + 2 are on `main` (35 commits). Plan 3 is branch `ef-plan3`, 42 commits,
+259 tests green** (260 with the exhaustive sweep). The kernel, the **complete EF
+temporal cycle**, the **resolution engine**, the **sanctoral data**, and **all five
+validation layers** are built. `colitur day <year>` emits a full resolved year.
+
+**Kernel** (`lib/kernel`, pure, total, 1583–9999):
+- `date.ml[i]` — opaque rata-die (Hinnant civil↔days); validated `make`;
+ `to_iso8601`/`of_iso8601`; sexp form is an ISO-8601 atom that revalidates.
+- `computus.ml[i]` — `gregorian_easter`, `julian_easter`, Easter anchors.
+- Shared vocabulary: `colour` · `subject` (Lord/BVM/saint/temporal; named
+ `Subject` because `class` is an OCaml keyword) · `slug` · `lang` · `names`
+ (open lang-keyed assoc, canonically sorted) · `citation` · `date_spec`.
+- Rite-parametric: `vocab` (operations record) · `celebration` · `temporal`
+ (+ the `RITE` module type). **`Celebration.t` takes one parameter (`'r`)**,
+ not two — season is contextual to the day, not intrinsic to a celebration.
+- `layer` (slug-canonical, date-indexed once) · `overlay` (add/suppress/replace/
+ field-edit, ordered, last-writer-wins, **diagnostics not silence or failure**)
+ · `record` (flat all-string output view) · `validate` (the invariant harness).
+- **Plan 3 additions**: `precedence` (the rite-parameterised resolver — a rite
+ supplies `band` / `disposition` / `admit`) · `liturgical_day` (the result
+ schema) · `rite` (everything a rite supplies, bundled, so mismatched assembly
+ is unrepresentable) · `calendar` (**year is the primitive**, day derived —
+ transfers need whole-year knowledge, so per-date resolution cannot be correct).
+
+**EF rite module** (`lib/rites/rite_ef`): `vocab_ef` (8 RG-cited seasons, 4
+classes) · `temporal_ef` (season boundaries RG 71–77, named feasts, Sunday slugs,
+week numbering, the resumed-Sunday tail, ferias, four Ember sets, Rogations,
+`anchors`) · `precedence_ef` (the full RG 91 28-entry table, occurrence RG 92–95,
+commemorations RG 108–111, transfers RG 96–98) · `rite_ef` (the bundle).
+
+**Data**: `data/ef/sanctoral.sexp` (322 entries, bootstrapped from lectio, SHA-256
+in its provenance header) · `data/ef/adjustments.sexp` (overlay) · two cited
+allow-lists, `expected-divergences.sexp` (11 entries, vs lectio) and
+`expected-divergences-missalemeum.sexp` (14, vs the oracle). Fixtures live in
+`test/fixtures/` with asserted SHA-256s.
+
+**CLI**: `colitur easter <year>`, `temporal <year>`, `day <year>`.
+
+`band` is **provably total** over everything the engine constructs: zero
+`unclassified` across all 8 417 years, for a 28-branch hand-transcribed table.
+Transfers reach a fixed point everywhere — 6 739 out, 6 739 in, zero unconverged.
+
+Deps are `dune alcotest qcheck qcheck-alcotest sexplib ppx_sexp_conv` and are
+**frozen**. A Mustache lib is still **not** added — it arrives with rendering.
+
+**Gotcha that costs an hour if unknown:** `[@@deriving sexp]` on a type with
+primitive fields fails with `Unbound value string_of_sexp` unless the `.ml`
+opens `Sexplib0.Sexp_conv`. Every kernel module with primitive fields does.
+Argument-less variants (`Colour`, `Subject`) don't need it. Do **not** hand-write
+converters instead — that is reserved for `Slug`/`Lang`, whose `private string`
+smart constructors deriving would bypass.
### Build & test
```sh
eval $(opam env) # activate the project-local switch (run from this dir)
dune build
-dune test
-dune exec colitur -- easter 2026
+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 2 — kernel data model & resolution.** `LiturgicalDay` schema; `Overlay`
- (the layer-merge algebra); the `Precedence` framework (rite-parameterized
- resolver); the **EF `temporal`** (seasons per RG 71–77 incl. Septuagesima + the
- cycle); the `Validate` harness. Add `sexplib`/`ppx_sexp_conv`. Each piece
- property-tested. **Brainstorm → writing-plans → execute.**
-- **Plan 3 — EF end-to-end (the pilot).** The EF rite module: `temporal` +
- `precedence` **code** from the register's RG citations (RG 91 table, occurrence
- RG 92–95, commemorations RG 108–111, transfers RG 95–102); sanctoral + lectionary
- **data** bootstrapped from lectio; **plus a minimal output path** (CSV + one
- template) so it produces a real table. Differential vs lectio + oracle vs
- missalemeum green 2005–2050; deep-tail edges checked vs the Missal PDFs.
- **Capture the lessons.**
-- **Then**: OF rite module (proves `RITE` generalizes) → full output/rendering →
- hardening (property sweep to 9999, golden pins, first tag).
+- **Plan 4 — OF rite module** (proves `RITE` generalizes) → the lectionary
+ bootstrap and citations → full output/rendering → hardening and a first tag.
+ **Open Plan 4 with the two behaviour items below**, before OF: they are the
+ places the five layers, taken together, currently sleep through a real error.
+
+### Carried into Plan 4 (read before starting)
+
+The full record — every task's outcome, every ruling, the 21-item deferred-minor
+triage, and the whole-branch review — is in
+`.superpowers/sdd/2026-08-11-colitur-plan3-resolution-engine/progress.md`. That
+workspace is deliberately kept, because it and the register corrections exist
+nowhere in git (`docs/` is gitignored).
+
+**The two behaviour items, in order:**
+
+1. **RG 16(a) — 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**"*.
+ colitur commemorates the Sunday anyway: **5 996 wrong days over 1583–9999**,
+ 369 of them in 1583–2100. **Zero** instances fall in the oracle window and none
+ is pinned, so all five layers miss it. Needs a signature change — `privilege_of`
+ cannot currently see the winner's `subject`. Register §6.0.
+2. **Commemoration identity is unasserted** outside ~3 test rows (see the layer
+ caveats above). Related: `compare_dignity` breaks same-rank ties
+ **alphabetically**, with no RG warrant, and over 2005–2050 the admission cut
+ falls between two same-rank candidates on **66 days**.
+
+**Structural items, cheapest to fix before OF exists:**
+
+3. **`Record` and `Liturgical_day` both claim to be "the single stable output
+ schema".** `Record` cannot express what the engine now computes (no observed
+ celebration, no commemorations, no transfers), has **no test file**, and is
+ used only by the legacy `colitur temporal` path — `day` hand-formats instead.
+ Plan 2's carried item (add a `cycle` field for OF's Sunday A/B/C and weekday
+ I/II) is still open and now costlier: it must pass through `Temporal.t`, which
+ is embedded in the sexp-derived `Liturgical_day.t`.
+4. **`Temporal.RITE` and `Rite.t` are two competing abstractions.** The module
+ type still exists and `temporal_ef` still satisfies it, but it carries none of
+ `rules`, `anchors`, `season_runs`, `transfer_target` — satisfying it now proves
+ almost nothing. `Rite.t` is the load-bearing one.
+5. **EF-shaped things still in "rite-agnostic" kernel code**: `validate.ml`
+ hardcodes Sunday as the week start; `Liturgical_day.transferred_in` is an
+ `option` justified by RG 96; `Precedence.privilege` is defined by RG 111;
+ `Repose` is EF vocabulary emitted by nothing. Each is one field short of the
+ remedy already applied to `season_runs`.
+
+**Data defects traced upstream into lectio's generator** (register §6): 15 entries
+wrongly marked `Commemoration_only` that are really III-class feasts, clustered
+6 March – 5 April, **six of which produce a wrong observed office** in real years
+(2008-04-02/04/05, 2038-03-06/08/09); four missing entries (Agnes *secundo*,
+Boniface 14 May, Evaristus, Theodore); and `romanus`, which should not exist on
+9 August. lectio's ini is **generated from missalemeum**, so the two are one
+lineage, not two independent sources.
-Still-open research residuals are listed in the register §6 (EF reading-selection
-rules; Christ-the-King/Holy-Family placement; data audit) — verify-while-coding,
-not blockers.
+**Unbuilt, recorded**: the Sacred Triduum has no proper office (it uses ordinary
+Passiontide ferial slugs); RG 91 entry 27's BVM Saturday Office; RG 110's
+inseparable commemorations; Major Litanies (25 April); the Rogation-Wednesday
+commemoration. Allow-list entries M11 and M13 are `verdict open` by design.
## How to work here