aboutsummaryrefslogtreecommitdiff
path: root/test/test_colitur.ml
Commit message (Collapse)AuthorAgeFilesLines
* rite(ef): clamp the RG96 search at the domain ceilingLukasz Kasprzak2026-08-121-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | search_from could walk up to 400 days past origin before Calendar's own ~start ~stop clamp is ever consulted, and nothing stopped it probing occupant on a date past 31 December 9999 -- occupant chains through the real EF rite's temporal, which calls Computus.gregorian_easter, not total outside 1583..9999 (it Date.makes and failwiths on Error). Not reachable with the shipped sanctoral data alone, but reachable through the project's own primary extension path: an overlay adding an I-class feast on 25 December leaves nothing but Class2 Nativity-octave days for the rest of civil year 9999, so the unguarded search reached 1 January of year 10000 and crashed there with 'computus: year 10000 out of range 1583..9999'. 9999 is an in-range year and the kernel's contract is 'never raises on in-range input'. search_from now also stops, without probing occupant again, once it passes Date's own domain ceiling -- the same 'return a finite date, let Calendar's own out-of-range handling record it, never pretend to have found something admissible' contract the existing step-count guard already follows. Two new tests, both mutation-verified to actually reproduce the crash when the guard is removed (see the task report): a precedence_ef.ml unit test using the real Temporal_ef.temporal as occupant (a synthetic occupant can never discriminate this, since it never calls Computus itself), and a Calendar-level integration test reproducing the exact overlay-based scenario the review found.
* data(ef): bootstrap the 1962 sanctoral from lectioLukasz Kasprzak2026-08-121-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Convert lectio's tridentine-calendar.ini (322 entries) into data/ef/sanctoral.sexp via a validating OCaml converter, tools/ bootstrap_sanctoral.ml, rather than a hand-written script: every field is built through Slug.of_string, Colour.of_string and Vocab_ef.rank_of_string, so the emitted sexp is valid by construction. Two conversion decisions, both documented rather than buried: - subject defaults to Subject.Saint, overriding Celebration.make's kernel default of Subject.Temporal, for the 316 entries with no explicit class; - rank = commemoration maps to status = Commemoration_only with an inferred Class3 (not a citation -- it is what the 1960 reform reduced most simple feasts from), recorded as an open item in the rules register for the oracle to adjudicate. Every celebration is tagged layer = Precedence_ef.universal_layer, the provenance id RG 91's band classifier reads to tell the universal calendar from proper/indult data. The generated file carries a provenance header: source path, its SHA-256, and the UTC conversion date, so re-bootstrapping against a newer lectio is reproducible and diffable. Output is byte-identical across runs. test/test_sanctoral_ef.ml loads the file through Layer.load and checks the counts independently derived from the source INI (322 entries, 114 Commemoration_only, 12 Class1, no Subject.Temporal, every date resolves in a leap year), plus two named spot-checks against the INI's own text -- one entry with an explicit class field, one commemoration -- so a passing count cannot hide the wrong 322 entries having been converted.
* rite(ef): RG 91 Table of PrecedenceLukasz Kasprzak2026-08-111-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Precedence_ef.band transcribes RG 91's 28-entry Table of Precedence (rules-register.md ยง4) for the EF rite: given a day's context and a candidate celebration, returns the table's own entry number, 1-28 (I class 1-13, II class 14-21, III class 22-26, IV class 27-28); lower wins. Every branch carries its entry number and register citation in a comment, checked in the table's own numeric order. Two entries are transcribed as the register states them even though they invert the pattern the rest of the table follows: at III class, 23 (particular calendars) outranks 24 (universal), the reverse of how 11/12 and 14/16/19/20 rank a universal feast ahead of a proper one at I and II class. Sanctoral-origin, layer-decided entries (11-13, 14/16/19/20, 23/24) follow the brief's structural insight: a celebration whose layer is not the universal base is an overlay -- proper, or indult if its layer id also carries the indult prefix. Neither the universal-layer id nor the indult prefix is an RG citation; both are colitur's own data-modelling convention, exposed from the module so whichever task loads the real EF sanctoral overlays can align to them. Vigils (21, 26) are read off the temporal cycle's own -vigil slug suffix rather than gated on origin, since a II/III-class vigil can be either temporal-origin (Ascension, already produced by temporal_ef) or sanctoral-origin (a saint's vigil, no task has loaded yet); Ember days (part of entry 18) are read off temporal_ef's own ember slug prefixes rather than re-derived, since the September anchor is independently flagged there as one of the more contested dates in the calendar. A candidate shape the table has no row for (e.g. a Class1 vigil that is not Nativity or Pentecost, or a Class4 candidate marked as a vigil -- RG 91 has no IV-class vigil either) returns a dedicated unclassified sentinel (max_int) rather than being folded into a same-rank entry it does not belong to. test_precedence_ef.ml is table-driven: one Alcotest.test_case per RG 91 entry (55 rows total, several entries covered by more than one named day so a single missed offset cannot hide behind a passing sibling), each date computed from Computus.gregorian_easter rather than hand-typed, so an arithmetic slip cannot pass by accident.
* kernel(calendar): the year is the primitive, the day is derivedLukasz Kasprzak2026-08-111-1/+2
| | | | | | | | | | | Transfers make per-date resolution impossible to do correctly: resolving 25 March can push a feast onto 26 March, and RG 97-98 has coinciding I-class feasts transfer in table order, which needs global knowledge. So year computes a whole liturgical year in one pass and day indexes into it. Pure, no cache, no mutable state. This commit resolves each day but does not yet place deferred transfers; they are recorded with a reason. Task 6 adds the placement pass.
* kernel(precedence): rite-parameterised resolverLukasz Kasprzak2026-08-111-1/+1
| | | | | | | | | | | | Three rite-supplied functions, not one: band (who wins, RG 91), disposition (what happens to the loser, RG 92-95) and admit (how many commemorations are admitted, RG 111). The loser's fate depends on the loser's own rank, so conflating them would resist extension. resolve takes the temporal candidate separately from the sanctoral list, which makes it total by construction. Every candidate lands in exactly one of observed, commemorations, deferred or omitted -- nothing is dropped silently, which is what makes the no-celebration-lost invariant checkable.
* kernel(validate): invariant harness over liturgical yearsLukasz Kasprzak2026-08-111-1/+1
| | | | | | | | | | | Coverage, season contiguity and completeness, Sunday-aligned week numbering, slug well-formedness, weekday agreement and vocabulary closure. Checks run over a liturgical year rather than a civil one, since Christmastide straddles January and would otherwise appear to recur. Run against EF temporal for landmark years, both Easter extremes, and 200 random years across 1583..9998 -- the property layer is how confidence reaches past the oracle horizon.
* rite(ef): season and rank vocabulary per RG 71-77 and RG 8Lukasz Kasprzak2026-08-111-1/+1
| | | | | | | Eight seasons in canonical liturgical-year order, each carrying its RG citation, and the four classes. season_slug_word is deliberately distinct from season_to_string: slugs are lectionary keys adopted verbatim from lectio, which calls Paschaltide 'easter' and Christmastide 'christmas'.
* kernel(layer): sanctoral layer with canonical order and date indexLukasz Kasprzak2026-08-111-1/+2
| | | | | | | Entries sort by slug so equal layers serialise identically. The by-date index is built once per layer rather than per year, since fixed dates are year-independent; a full-domain sweep would otherwise rescan every entry for every day. load turns parse and validation failures into result.
* kernel: Names, Citation and Date_specLukasz Kasprzak2026-08-111-1/+1
| | | | | | | Names is an open language-keyed assoc kept in canonical order so equal name sets serialise identically. Citation carries references only, never text. Date_spec ships the one form the EF sanctoral needs; 29 February is constructible and resolves to None in common years.
* kernel: Slug and Lang validated private stringsLukasz Kasprzak2026-08-111-1/+3
| | | | | | Both parse through a smart constructor returning result, and both hand-write t_of_sexp so a malformed value in a data file is rejected at load rather than silently accepted -- deriving the converter would have bypassed validation.
* kernel: Colour and Subject shared vocabularyLukasz Kasprzak2026-08-111-1/+1
| | | | | | The six liturgical colours and the Lord/BVM/saint/temporal distinction are common to both Roman forms, so they are shared closed variants rather than rite-parametric. Subject is so named because class is an OCaml keyword.
* kernel(computus): Gregorian + Julian EasterLukasz Kasprzak2026-07-311-1/+1
| | | | | | | Anonymous Gregorian (Meeus/Jones/Butcher) for OF+EF; Meeus Julian mapped to the proleptic-Gregorian date for future eastern rites. Verified vs known dates (2000/2024-27, 1583; Orthodox 2023/24) and EXHAUSTIVELY over 1583..9999: every Easter is a Sunday in [Mar22,Apr25].
* kernel(date): proleptic Gregorian date, validated make + arithmeticLukasz Kasprzak2026-07-311-5/+2
| | | | | | | Hinnant civil<->days rep (1970-epoch rata die); make validates month/day and the 1583..9999 domain; of_rata/add_days are total arithmetic. Weekday, compare. Tested: known weekdays, leap boundaries, rejects; qcheck round-trip / add-inverse / weekday-cycle properties over random in-range dates.
* chore: scaffold dune project (kernel lib + cli + tests)Lukasz Kasprzak2026-07-311-0/+5
OCaml 5.2.0 local switch; colitur_kernel library, a colitur executable stub, and an alcotest+qcheck test runner. AGPL LICENSE, README, generated colitur.opam.