diff options
| -rw-r--r-- | CLAUDE.md | 162 |
1 files changed, 128 insertions, 34 deletions
@@ -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 |
