summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 11:21:30 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 11:21:30 +0200
commit36df2bd0af9a555a09334e14b0d89118be1dbbc0 (patch)
tree57824e121606286ebdf8fff68439986dd3300eae
parentd0b78ca2ef533080d4621b22071718bb8d3a6158 (diff)
downloadcolitur-36df2bd0af9a555a09334e14b0d89118be1dbbc0.tar.gz
colitur-36df2bd0af9a555a09334e14b0d89118be1dbbc0.zip
docs: refresh CLAUDE.md for Plan 3
Plans 1-3 are done: the resolution engine, the sanctoral data, and all five validation layers. Records what each layer CANNOT see, which is the part a fresh session most needs and the part a green suite hides -- the lectio differential never compares commemorations and shares colitur's own data lineage, the oracle is two years wide and never compares celebration identity, and between them they sleep through commemoration identity entirely. Carries into Plan 4 the two behaviour items that all five layers currently miss (RG 16(a), 5996 wrong days; commemoration identity), the structural items cheapest to fix before a second rite exists, and the data defects traced upstream into lectio's generator. Adds the exhaustive-sweep invocation to the build block: the default suite samples, so the "clean across every year" claim is reproducible only on demand.
-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