aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* feat(ef): the Gloria in excelsis, RG 431-432, deferring to Breviary 237-238Lukasz Kasprzak2026-08-2221-92/+1378
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Phase 2 of celebrant-rubrics-phase1: colitur rubrics gains a fifth column, whether the Gloria is said. Follows the Creed's own seam exactly -- Rite.t.gloria, Liturgical_day.t.gloria, wired through calendar.ml the same way. RG 431(a)/432(a) defer the Gloria to the Breviary's own Te Deum rule (nn. 237-238), so te_deum is implemented as its own named predicate, cited clause by clause, not collapsed into a colour heuristic. 431(c) (Holy Thursday, the Easter Vigil Mass) and 432(b)/(d) (violet; a Requiem) are independent overrides checked ahead of the Te Deum-derived answer. Every clause this engine has no dimension to model (votive Mass classes, the wider n.302 "Missa festiva" categories) is stated as N/A with its own reasoning, not silently dropped. Validated against the FIUV universal Ordo (Gloria and Te Deum) and all three LMS editions (Gloria). A first pass over-trusted a clean-looking 15-for-15 FIUV contradiction of 237(b)'s own Septuagesima exception and replaced it with a blanket "every Sunday" rule; the evidence was itself corrupted -- the FIUV extractor recognised only one of the source's two Te Deum negations ("non dicitur", not "sine"), so every "sine Te Deum" Sunday read wrongly true. Fixed in tools/extract_fiuv_ordo.ml, fixture re-extracted, and the literal 237(b) reading restored once the corrected data confirmed it. A second bug surfaced alongside it (Palm/ Passion Sunday wrongly reading true via Temporal_ef.named's own table membership, then Christ the King wrongly reading false from an over-broad fix) is closed with an explicit two-slug exclusion. Domain-wide 1583-9999: every violet or Rose day is gloria=false except the Easter Vigil (RG 431(c) lex specialis), every Requiem is gloria=false, both measured exhaustively, zero exceptions. Mutation- proved: disabling 431(c) reddens 8 tests including all four oracle comparisons; disabling 238(c)'s feria-I-classis exclusion reddens exactly the dedicated Ash Wednesday unit test, a genuine blind spot in both oracle layers, reported rather than hidden. Two open, cited findings, neither fixed here (out of this task's "follow creed's exact seam" scope): a privileged Lenten/Passiontide feria carrying one commemoration reads Gloria=true in the LMS Ordo but Te-Deum=true/Gloria=false in FIUV -- the two oracles disagree with each other, not merely with colitur (data/ef/expected-divergences-lms.sexp L5, expected-divergences-fiuv.sexp F3); and a pre-existing, uncited Colour.Violet bug on Rogation Monday/Tuesday in Temporal_ef.temporal, surfaced by this comparison but root-caused as a separate defect (L6). day/readings verified byte-identical against a build from the branch tip before this task (v0.10.1's own tag predates an already-landed bissextile fix that legitimately changed both, so it is not the right baseline). 671 tests green (dune test); 678 with the exhaustive sweep (COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force, ~104s).
* feat(ef): implement the calendarium's bissextile February shiftLukasz Kasprzak2026-08-2217-27/+375
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The Missale Romanum's calendarium footnote for February (LT.txt:5011-5014, scan-corroborated scan2.txt:3050-3058): in a leap year the sixth kalends of March (24 February) is doubled rather than a 29th day appended, so St Matthias moves 24->25 February and St Gabriel of Our Lady of Sorrows 27->28, with 24 February itself carrying no fixed office that year. data/ef/expected-divergences-lms.sexp entry L3 tracked this as an open gap. Implemented as the rubric's own general mechanism (every fixed entry from 24 through 28 February shifts one civil day later in a leap year), not as "move these two named saints": the two readings are indistinguishable on shipped data (nothing else is fixed in that window) and diverge only for a future --overlay entry in the same range, which the mechanism reading covers correctly and a two-saints special case would not. This project has already been bitten once (RG 16(a)) by a rule implemented against shipped data's coincidental shape rather than the rubric itself. Kernel stays rite-agnostic: Rite.t gains a fixed_key field (Date.t -> (int * int) option), the same seam easter already establishes, read only by Layer.on_date's FIXED half -- the movable half (Easter_offset/Nth_weekday) is untouched. Default is the identity mapping, an optional argument on on_date, so every existing caller and every rite that supplies nothing is byte-identical to before this field existed. Date.is_leap is exposed in date.mli (already existed in date.ml) so the rite reads the kernel's own single-sourced leap-year definition. The EF implementation lives in Rite_ef.Temporal_ef.bissextile_fixed_key, fully cited, wired into Rite_ef.context. Blast radius, measured over the full 1583-9999 domain (Calendar.year run twice per civil year, real fixed_key vs identity, every day diffed): all 2,041 leap years in the domain show a visible change for each saint; 6,983 individual liturgical days change total, zero unclassified, across four shapes (Matthias vacates 24 Feb in 1,803 years / occupies 25 Feb in 1,811; Gabriel vacates 27 Feb in 1,699 / occupies 28 Feb in 1,670). L3's own prior estimate (1,650 years, observed-outright only) is confirmed close on the same definition (1,677 measured); the broader observed-or- commemorated count is higher, not lower, showing the narrow estimate undercounted visible impact rather than overcounting it. The lectio differential (2005-2050 fixture) gains a new cited entry, C40 in data/ef/expected-divergences.sexp: lectio implements no such shift, so every leap year in its window now diverges on the two shifted days (21 rows, not the naive 44 -- the comparator never compares commemorations, so a side where the shifted saint has zero comparator-visible footprint on both engines produces no row). The LMS Ordo's 2023-2024 fixture -- the window L3 was originally found in -- now matches on the Creed comparison; its formulary-override bucket count is corrected 182->181 (24 February leaves the counted Proper population, becoming Own_slug-sourced). The missalemeum oracle fixtures (2026-2027, 2038, 2035) cover no leap year and are unaffected. L3 is closed: converted from an active allow-list record to a prose closure paragraph (L1's own precedent), citation preserved verbatim, fix and measured blast radius recorded. The id-list assertion narrows to [L4] alone, and the now-dead "2024-02-24" -> "L3" date mapping is removed. Six golden values pinned across four new test cases: Matthias in a leap year (both the vacated and occupied sides) and a common-year control; Gabriel likewise, deliberately choosing a leap year where he is admitted only as an ordinary commemoration rather than winning outright, a different shape from Matthias's. Mutation-tested: forcing fixed_key back to identity reddens exactly five test cases -- the differential's Layer C count-pin (C40 drops to 0 actual vs 21 expected), the LMS Ordo's Creed and formulary-bucket checks, and both new golden pins -- confirming the tests actually exercise the fix. Reverted; dune test, the exhaustive sweep, and make check are all green.
* docs(L3): the bissextile rubric is scan-verified after allLukasz Kasprzak2026-08-221-1/+1
| | | | | | | | | | | | | | | L3 recorded that the leap-year shift rested on the LT.txt transcription alone, the scans having been searched without result. It is in scan2.txt at 3050-3058, OCR-damaged but unmistakable. The earlier search missed it for a findable reason, now written into the entry: the OCR mangles the saint and the keyword alike -- "Matthue", "Cabrielis", "bi&sextili" -- so neither "Matth" nor "bissext" matches. It turns up on the undamaged phrase "februarius est dierum". Transcription and scan now agree, which makes this two witnesses of one document rather than one. Still one document; the citation continues to say calendarium rather than RG, because a calendarium footnote is what it is.
* test(fiuv-ordo): add the FIUV universal Ordo as a second, independent witnessLukasz Kasprzak2026-08-227-1/+2506
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Wires in the FIUV (Foederatio Internationalis Una Voce) Ordo for 2025-2026 as validation layer seven -- the first UNIVERSAL (not diocesan) source in this project, and the first compiled by someone other than the LMS editions' own Peter Day-Milne (Joseph Shaw, FIUV President). tools/extract_fiuv_ordo.ml hand-parses pdftotext's -layout dump of the Latin-language ordo (no Str/regex, frozen deps). Three real parsing traps were found and fixed by tracing actual false results against the raw source, not assumed in advance: - "Gloria" is also the first word of "Gloria Patri" (the psalm doxology), which can appear INSIDE the very Mass clause being scanned (Good Friday: "...omittuntur ps. Iudica me et Gloria Patri), Gloria, sine Credo..."). Guarded by rejecting any "Gloria" hit whose next token is "Patri". - Christmas Day's own rubric uses the PLURAL "Missae"/"Missae" ("tres Missae celebrantur..."), never the singular "Missa" every other real day anchors on -- silently produced gloria=None/credo=None on the one most doctrinally unambiguous day in the calendar until traced and fixed, guarded in turn against "Missae defunctorum" (an unrelated Requiem-prohibition notice appearing at the end of many day-blocks). - The Vespers boundary (bounding the primary Mass option before a second "Vel Missa" alternative or before Vespers information) missed the ligature spelling "VESPERÆ", the dominant form in this source, letting a whole day's trailing prose leak into the captured [praef] field on the task brief's own worked example day (19 September). A distinct second stop-marker bug: the malformed trailing entry this source shares structurally with the LMS PDFs (a stray, mislabelled duplicate of 1 January) was initially over-matched by a generic "first token is 4 digits" heuristic, which false-positived on an indulgence-decree date citation wrapped mid-sentence in October ("...20 augusti / 1885, 26 augusti 1886..."), truncating the extraction to 308 of 400 real days. Fixed by anchoring on the tail entry's own distinctive "1st Jan" opening instead. Five opposite-prediction Creed probes are re-verified by the tool itself against its own extracted rows (a failed probe is a hard exit, not a printed claim) before the fixture is written. test/test_fiuv_ordo.ml compares the Creed against colitur across all 399 comparable days (Holy Saturday excluded, no Mass that day) -- CLEAN, zero unexplained divergences, a genuine second confirmation of {!Rite_ef.Rubrics_ef.creed} independent of the LMS lineage. [praef], [te_deum] and [class_] (including the FIUV ordo's own "III cl." vs "III cl. (Priv.)" Cum Sanctissima convention, stated on its title page) are captured into the fixture but deliberately not compared -- colitur has no Gloria/preface predicate yet, and its default configuration models no Cum Sanctissima overlay to compare the class convention against. All 634 tests green (dune test), 635 with the exhaustive sweep and make check. No lib/ file touched. Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* test(lms-ordo): extend the LMS witness layer from one year to threeLukasz Kasprzak2026-08-227-480/+3670
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Generalises tools/extract_lms_ordo.ml's start-of-body detection: the hardcoded "End of November 2024" sentinel assumed every edition opens with an "End of November" stub section. The 2023-2024 edition has none (Advent Sunday 2023 fell on 3 December, so the compiler folded the two tail days into the "December 2023" header directly) -- the generalised detector anchors on the body's own "ORDO" title line instead, verified unique in all three known editions and distinct from the Table of Contents' lowercase "Ordo" entries. Extracts test/fixtures/lms-ordo-2023-2024.sexp (397 rows) and lms-ordo-2025-2026.sexp (399 rows), each with a provenance header carrying its own SHA-256, source citation, and a per-edition characterisation record: five opposite-prediction Creed probes, independently re-verified against this run's own extracted rows (a failed probe is now a hard die, not a printed claim). The 2025-2026 edition needed a different sharp II-class pairing than 2024-2025's Joachim/Stephen one, because Joachim (16 August) is impeded by a Sunday that year -- recorded in the fixture header, not silently swapped. test/test_lms_ordo.ml is refactored into a parameterised make_suite, instantiated once per edition; every count Task 6 hardcoded against the single 2024-2025 window is now a per-edition parameter, several of which turned out to differ once measured (BVM-Saturday population 12/14/13, Ascension-week exclusion 1/3/2). Two mapping gaps surfaced only by the new windows and fixed here (test-side, not colitur): Low Sunday reached via Preceding_sunday for the first time (absent from 2024-2025's own window), and the Ascension-week exclusion was wrongly keyed off week number alone -- Rogation Monday also resumes week 6's Sunday but falls BEFORE Ascension, not after, and the Ordo prints the ordinary Sunday text there. Re-bounded by date, against Ascension and Pentecost. Two genuine divergences surfaced in the 2023-2024 window and are allow-listed (L3, L4) rather than fixed, per this task's no-lib-changes scope: - L3 (verdict ordo): colitur has no bissextile calendar rule. The 1962 Missal's own calendarium moves St Matthias from 24 to 25 February in leap years (docs/research/LT.txt:5011); colitur keeps him on the 24th every year. Domain-wide upper bound measured with a throwaway sweep: 1,650 of 2,041 leap years in 1583-9999 currently observe him there. - L4 (verdict open): Christmas Eve falling on the 4th Sunday of Advent (2023-12-24, roughly 1 year in 7). colitur says the Creed is said, reading RG 475(a)'s Sunday clause; the Ordo says it is not. RG 30(a)'s own "locum tenet ... nulla fit commemoratio" for this exact vigil reads like RG 16(a)'s Feast-of-the-Lord-on-Sunday shape, which colitur already treats as Creed-preserving -- but RG 475(a) names "festo" specifically, and a vigil is its own liturgical-day category (RG 35), so the counter-reading is real too. Left open rather than adjudicated. All 630 tests green (dune test), 631 with the exhaustive sweep. Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
* docs: six validation layers, not five -- the LMS Ordo is wired inLukasz Kasprzak2026-08-223-12/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Task 6 (celebrant-rubrics-phase1) added test_lms_ordo.ml as a sixth validation layer, comparing 400 days against a printed Latin Mass Society Ordo -- a genuinely independent fourth lineage, never passed through Divinum Officium/missalemeum/lectio. README.md's own table still said "Five validation layers"; man/colitur.1 still said "two published-calendar oracles"; CLAUDE.md still said "5 layers" in two places and, worse, stated as fact that "the published Ordo... Not wired in as a layer" in the file every future session is instructed to read first. Updated all three, describing what the new layer covers (the Creed, the BVM Saturday votive Mass's own seasonal selection, the reading formulary override) and what it cannot see (one civil year, only three of the formulary axis's five sources, England & Wales diocesan propers excluded from every comparison) -- consistent with docs/research/ordo/PROVENANCE-lms.md, not overstating it. CLAUDE.md's own stale claim is corrected in place, not silently rewritten, matching the file's own established convention: the paragraph it lived in was about a DIFFERENT, earlier Ordo (unreliable PDF column extraction, still genuinely unwired for that reason), and its own closing prediction -- "a structured Ordo would be the only way to add a fourth lineage" -- is exactly what the LMS Ordo's structured extractor (tools/extract_lms_ordo.ml) has since done. Historical "five layers" mentions elsewhere in CLAUDE.md, describing completed state as of earlier branches, are left alone -- they were true then, and rewriting them would misrepresent the record the same way overstating layer 6's own coverage would. Left README's pre-existing v0.9.0/569-tests staleness untouched, as instructed -- it predates this branch and is a separate matter.
* docs(cli): --help's rubrics section drifted to three fields, it has fourLukasz Kasprzak2026-08-222-7/+26
| | | | | | | | | | | | | | | | | | | | Task 5 (celebrant-rubrics-phase1) added a fourth rubrics column, creed, and updated the man page for it. bin/main.ml's own built-in --help was not: its "output formats" section and worked example still showed three fields (date, formulary slug, source), and nothing caught it because test/cli.t pins command dispatch but never the help body. Fixed the format line and its example, and added a short mention of the creed column to the surrounding prose paragraph (source, format, what --overlay/--lang/--raw do to it), matching the man page's own account without copying it verbatim. test/cli.t now cross-checks --help's own rubrics example against what `colitur rubrics 2026` actually prints for the identical day, so the two cannot silently drift apart again in either direction. Mutation-proved: reverting the field count in --help alone (output unchanged) reddens exactly this new check; reverted clean.
* fix(kernel): Mass_formulary.t.said is honestly optional -- was false for VotiveLukasz Kasprzak2026-08-2210-51/+153
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The .mli promised said is "the slug whose Mass is said". For Votive (RG 78/309(a), the Saturday votive Mass of Our Lady) it was set to the day's own ferial slug -- whose Mass is exactly the one NOT said. A consumer joining rubrics to readings on that slug would silently get the wrong Mass: 2026-01-03 reports ef-christmas-1-saturday, which has zero entries in data/ef/lectionary.sexp, because the citations actually come from bvm_saturday_citations, a season-keyed function with no slug of its own anywhere in the shipped data. Chose the type-honest fix over the interim documentation one: said is now Slug.t option, None exactly for Votive, because there is genuinely no slug in the shipped data this field could report for that one source. Adding real ids for the five seasonal BVM Masses (the reviewer's first option) is out of scope -- a data restructuring this round explicitly does not carry. Threading the office slug through a second field was considered and rejected as redundant: the day's own office is already available on the same Liturgical_day.t via observed.slug, which every caller already has in scope regardless of via, so said does not need to duplicate it. colitur rubrics stays byte-identical: rubrics_line already has d.observed in scope and falls back to its slug when said is None, printing the exact value it always printed for a Votive row (verified directly, diffed against pre-fix output across four years). colitur day/readings are unaffected (neither reads Mass_formulary at all). colitur emit --format sexp's pretty-printed line count for 2027 moved 9011 -> 9025: every day's formulary record widened by said's own extra option wrapping, and to_string_hum wraps by column width. Cosmetic only, diffed line by line to confirm every change is this shape or a consequent wrap shift; recorded in test/cli.t alongside the 476(f) note it now sits next to.
* test(ef): compare the LMS Ordo's formulary override STRING, not just presenceLukasz Kasprzak2026-08-221-13/+207
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | test_lms_ordo.ml's formulary-override check only asked WHETHER a Preceding_sunday day showed a "Mass of ..." override line, never WHICH Sunday it named. colitur could have resumed the wrong Sunday on all 66 such days and this layer would have stayed green -- resuming the right Sunday is precisely what step 3 of the reading chain exists to do. test_formulary_override_matches now derives the Ordo's own expected prose from Mass_formulary.t.said's own resolved slug (parsed directly, no Str/regex) and compares it against the Ordo's real text. Two narrow, evidenced exceptions, both counted rather than silently absorbed: - Christ the King's own Sunday reaches this population under a NAMED slug (ef-christ-the-king) that carries no embedded ordinal; its position among the Sundays after Pentecost varies by year, so Temporal_ef.week is read for that one slug specifically, not called generically (an earlier version of this fix read [week] for every day and was wrong -- caught by its own assertion on the very first run, because Time_after_pentecost's own last-Sunday and surplus-Sunday renumbering diverge from the raw week count. Reading the number back out of the already-resolved slug sidesteps both special cases and is not tautological with the wrong-Sunday failure mode this fix exists to catch). - Three real days between Ascension and the following Sunday (2025-05-30, 2025-06-02, 2025-06-03) are a genuine, newly-found defect: colitur resumes the nearest calendar Sunday, but the Missal, and the Ordo, keep saying Ascension's own Mass through this stretch instead. Structural (Ascension is a fixed Easter+39), not a fixture artefact, and not fixed here -- it needs a lectionary-data change out of this fix round's own scope. Documented in expected-divergences-lms.sexp as a prose-only note (no active sexp record: this file's id-lookup machinery is scoped to the Creed axis, and a second axis reusing it would let one test's "declared but never fired" check spuriously trip on the other's entry) and enforced directly by this test's own count assertion instead. Mutation-proved: offsetting step 3's Sunday lookup by an extra 7 days reddens this test (23 failures total across the suite: layers 3-6, golden pins, and lectionary_ef's own unit tests), confirmed via the compiled test binary directly; reverted clean, full suite green again.
* fix(ef): RG 476(f) -- the Creed is not said at a Requiem MassLukasz Kasprzak2026-08-225-45/+247
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | colitur said the Creed IS said on All Souls' Day and every other Requiem-shaped day: RG 475(b)'s "in festis I classis" branch fired on rank alone, with nothing checking RG 476(f), "Non dicitur symbolum ... f) in Missis defunctorum". Allow-listed as L1 in expected-divergences-lms.sexp on the argument that [creed] has no notion of a Requiem Mass in its inputs and could not express the rule without a signature change. That argument does not hold. Colour.Black is used by exactly two celebrations in the whole engine, verified by grepping every occurrence in lib/ and data/: commemoration-of-all-souls (data/ef/sanctoral.sexp) and Good Friday (temporal_ef.ml, already false via the earlier RG 23(b) feria exclusion and has no Mass at all in the 1955-restored Holy Week). So on every day this engine can construct, colour = Black if and only if the Mass is a Requiem -- a citable implication (RG 117 assigns black to Masses of the dead) on this data, not a heuristic. Rite_ef.Rubrics_ef.creed now reads observed.colour as RG 476(f)'s own guard, positioned ahead of 475(b)'s rank branch. A new test, test_colour_black_population_is_exactly_two, sweeps both the shipped data and Temporal_ef's own code across a century and fails loudly the day a third Colour.Black celebration is ever introduced, so the proxy cannot silently rot. L1 is closed in expected-divergences-lms.sexp, citation preserved. Blast radius, measured (full 1583-9999 sweep, grouped by observed slug): 8 416 days change from true to false, every one commemoration-of-all-souls (missing from exactly 1 of the 8 417 domain years, a boundary-year transfer effect at 1583/9999). A further 8 416 Colour.Black days are Good Friday, unaffected (already false before this fix). colitur day/readings stay byte-identical to v0.10.1 (checked directly against a v0.10.1 worktree build, 9 domain-spanning years); colitur rubrics changes on exactly the 8 416 days above. colitur emit --format sexp's pretty-printed line count for 2027 moved 9010 -> 9011 as a side effect: Sexplib.Sexp.to_string_hum wraps by column width, and All Souls' Day 2027's record grew one wrapped line because "false" is one character longer than "true". Cosmetic only -- no field's data changed -- and pinned in test/cli.t with the measurement recorded.
* test(ef): the LMS Ordo as a sixth validation layerLukasz Kasprzak2026-08-227-1/+2474
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | An independent lineage -- neither Divinum Officium, missalemeum nor lectio -- covering the Mass formulary and the Creed. Characterised before use, per the discipline the extraordinaryform.org Ordo's blanket St Lawrence vigil gap taught this session: five opposite- prediction Creed probes (Sunday/feria, octave-override vs plain II-class, I-class) all confirmed against the raw extracted text before a single divergence was adjudicated, recorded in the fixture's own provenance header. tools/extract_lms_ordo.ml hand-parses pdftotext -layout output (no Str/regex, frozen deps) into test/fixtures/lms-ordo-2024-2025.sexp, 400 day-rows (2024-11-27..2025-12-31). Gl/Cr print per Mass-option, not per day; the extractor takes the first pair in reading order, which the source's own layout guarantees is the universal entry's, since a diocesan variant always follows it, never precedes it. The Ordo's I-V roman numeral ("Missae de sancta Maria in sabbato", RG 309(a)) is matched whole-line only, never by substring -- "V Mass of BVM" is a substring of "IV Mass of BVM". test/test_lms_ordo.ml compares two axes 1:1 by date: - the Creed (RG 475-476): 399 of 400 days comparable (Good Friday has no Mass at all, asserted as the only such day); one genuine divergence, adjudicated to the ORDO, not colitur -- All Souls' Day 2025-11-03 is a Requiem Mass, and colitur's own creed predicate has no notion of one, a pre-existing scope gap rubrics_ef.ml already documented in its own header before this task existed. - the BVM-Saturday seasonal Mass selection, the addendum's own "sharper check": all 14 Votive days in the window match their season's own numeral exactly, both directions, independently re-derived from RG 309(a) rather than calling colitur's private function. - the formulary override, for the three sources it can honestly discriminate (Proper 179, Preceding_sunday 66, Common 2, all clean); Own_slug (139) is excluded with evidence, not silently -- several ferial slugs carry citations byte-identical to their Sunday's own, so the Ordo's override line cannot distinguish the two representations. data/ef/expected-divergences-lms.sexp carries the one adjudicated entry (L1), cited to RG 476(f). Mutation-tested: inverting RG 475(a)'s Sunday clause reddens 10 existing Rubrics_ef unit tests AND this new layer's own comparator, independently, on a data source none of the other five layers touch. Reverted after confirming. No lib/ changes. day/readings/rubrics output re-confirmed byte- identical (1583, 2026, 2038, 9999) after this task; all three gates green (dune test, exhaustive sweep, make check).
* fix(ef): check RG 24/25's ferial invariant instead of asserting itLukasz Kasprzak2026-08-222-11/+215
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | rubrics_ef.ml's Creed check excluded feriae from 475(b)/(c)/(e) via an explicit test for feria I classis (Ash Wednesday, Holy Week) and vigils only. RG 24/25's feriae II and III classis (Advent 17-23 December, the Advent/Lent/September Ember sets, the numbered Lenten/Passiontide ferias) were excluded by no check at all, correct today only because of an unstated Temporal_ef property (no ferial-origin office it builds carries subject Lord/Bvm or an apostle-list slug). A comment read as if this were established "by construction of the two exclusions immediately above", which overclaimed: those two exclusions cover feria I classis only. Corrected the comments to name RG 24/25 explicitly, state what is and is not checked, and cite where the real guarantee comes from. Added a test that sweeps Temporal_ef.temporal across a century (1583-1682), asserting the invariant directly against every ferial-origin day found, with its own coverage counters confirming the span actually reaches Advent 17-23, all three non-Whitsun Ember sets, and Lenten/Passiontide ferias. The first run of the test found a real gap in its own population filter (RG 17(a)'s 2 January Holy Name fallback, a genuine feast sitting outside Temporal_ef.named for an architectural reason), fixed by naming that date explicitly rather than narrowing the span. Proved the test has teeth by mutation: forcing the ferial builder's default subject to Lord reddened it (and 24 other tests, expected collateral from an intentionally broad mutation) before being reverted. No production logic changed -- only comments in rubrics_ef.ml. day, readings and rubrics output confirmed byte-identical against the pre-fix binary for four spot years across the domain. Also corrects a stale test-case count in a prior task report (18 in the array, not the 19 recorded there); the array now genuinely holds 19.
* feat(ef): the Creed, RG 475-476Lukasz Kasprzak2026-08-2217-29/+612
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Whether the Creed is said at Mass. New Rubrics_ef.creed, reached through a new Rite.t.creed field and a new Liturgical_day.t.creed bool (false, not an option, for a rite that has not implemented the rule); colitur rubrics gains a fourth TAB-separated column. 475(a) reads the TEMPORAL cycle's own weekday, not the observed day: a Sunday impeded by a Feast of the Lord (RG 16a) still says the Creed. 475(d)'s three octaves (Nativity, Easter, Pentecost) are pure date/ Easter-offset windows, checked first, since a saint's feast winning the day inside one of them still says the Creed ("etiam in festis occurrentibus") -- St Stephen, 26 December, is the live witness. RG 23 classifies Ash Wednesday and every feria of Holy Week (including the Sacred Triduum) as feriae, not festa, however high their rank, so 475(b)/(c)'s "in festis" never reaches them; this single check subsumes 476(a)'s own naming of the Chrism/Lord's-Supper Mass and the Easter Vigil. RG 28-34 vigils are a third liturgical-day category, also excluded from "in festis" regardless of rank -- reuses the already- exported Precedence_ef.is_vigil rather than a new list. 475(e)'s Apostle/Evangelist natalicia list (creed_apostle_slugs) was derived by grepping data/ef/sanctoral.sexp directly and checking each candidate's own date against whether it is that saint's dies natalis -- not copied from any list supplied with the task. The Conversion of St Paul and the 30 June Commemoration of St Paul are excluded (neither is a natalicium); the Chair of St Peter and St Barnabas are included only because the clause names them explicitly, which is exactly why it has to: neither is a natalicium either. 475(c)'s BVM half reuses Precedence_ef.marian_slugs (newly exported) rather than the subject field alone: checked against the shipped data, almost every Marian sanctoral entry ships subject=Saint, not Bvm. man/colitur.1's rubrics section is updated to match the new column; test/cli.t repinned via dune promote for the same reason. Verified day/readings output byte-identical to v0.10.1 across the whole 1583-9999 domain (both binaries' concatenated day+readings output, 6,148,492 lines each, zero diff). Domain-wide: 882,996 days say the Creed, 2,191,250 do not; every one of the domain's 439,178 Sundays says it, zero exceptions.
* feat(cli): colitur rubrics prints the day's Mass formularyLukasz Kasprzak2026-08-223-18/+248
| | | | | | | | | | | | | | | | | | | | | | Separate from day for the reason readings is: a formulary name contains spaces and day's row is space-separated with a variable-length tail. Modelled on readings' own structure (year parsing, --overlay plumbing, error handling): --overlay is accepted, since an overlay can change which celebration is observed and hence which Mass is said. --lang, --raw and --sigla-* are refused rather than silently ignored -- this row resolves no display name and no citation for any of them to affect. Output is TAB-separated (date, formulary slug, source), not space or " | " like the two existing reports: a resolved formulary NAME is a column a later task may add, and it can carry both spaces and punctuation a citation never does, ruling out either separator already in use. day and readings verified byte-identical (sha256) against a v0.10.1 build across 1583/1900/2026/2038/9999. Man page documents rubrics beside readings and renders through groff with no errors.
* docs(ef): retract the false LMS Ordo witness claim for Mass_formulary.VotiveLukasz Kasprzak2026-08-223-23/+61
| | | | | | | | | | | | | | | | | | | | | | | | | Fix round 1's own comment claimed the Latin Mass Society Ordo witnesses the Votive tag directly, reading its "V Mass of BVM" as an abbreviation for "votive". That was wrong: the Ordo's I-V are roman numerals naming WHICH of the Missal's five seasonal "Missae de sancta Maria in sabbato" (RG 309(a)'s own "iuxta temporum diversitatem") is said on a given Saturday, not a marker of the Mass's kind. All five numerals occur through the Ordo (I x1, II x4, III x3, IV x3, V x8); the false reading came from "V Mass of BVM" also being a substring of "IV Mass of BVM". Votive itself is unaffected and stays correct on RG 431(e) alone, quoted verbatim in its place: "e) in Missis votivis IV classis de Angelis, quocumque die, et de B. Maria Virg. quae in sabbato celebrantur" -- votive Masses of the IV class, of the BVM, celebrated on Saturday. The retraction is recorded, not softened, in lectionary_ef.ml/.mli and the test pin's own comment, together with what the Ordo's numeral actually means so the mistake is not repeated, and the consequence it leaves behind: because the numeral names WHICH seasonal Mass is said, a future comparison against the Ordo (Task 6) can validate bvm_saturday_citations' own season-keyed selection, not merely that some BVM Mass was chosen. Comments and docs only -- no behaviour change. Both suites re-run green and colitur day re-confirmed byte-identical.
* fix(kernel,ef): a votive Mass is not the day's own -- add Mass_formulary.VotiveLukasz Kasprzak2026-08-216-26/+69
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Mass_formulary.source was missing a case for a Mass said IN PLACE of the day's own office's Mass while that office is itself kept unchanged -- RG 309(a) and RG 431(e) both classify the RG 78 Saturday Mass of Our Lady this way, in the Missal's own words, as a "Missa votiva IV classis... de B. Maria Virg.", not as the day's own office's Mass. The Latin Mass Society Ordo (docs/research/ordo/lms-ordo-2024-2025.pdf) witnesses it directly, printing that day as "V Mass of BVM". The BVM-Saturday branch in Lectionary_ef.readings was tagged Own_slug for lack of a better constructor when Task 2 landed, flagged there as a judgement call rather than a specified answer. That call was wrong: left as Own_slug, a future comparison against the LMS Ordo (a later task in this plan) would read every BVM Saturday as a manufactured divergence between colitur's "own" Mass and the Ordo's votive one. Retagged to Votive; said is unchanged (still the day's own, reused ferial, temporal slug) since the office itself is unaffected, only the Mass said for it. Added the new constructor's source_to_string case ("votive") and its own test row, and pinned the branch itself in test_lectionary_ef.ml's formulary cases at 1 August 2026, verified directly against the resolver rather than assumed. Both this session's own drifted pins that the earlier commit inherited from the task brief are unaffected by this change, and both are re-confirmed independently correct in this round: dune test and the exhaustive sweep are green, and colitur day stays byte-identical against the pre-fix-round binary.
* feat(kernel,ef): the lectionary reports which Mass it saidLukasz Kasprzak2026-08-2112-46/+281
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Rite.readings now returns (Mass_formulary.t option * Citation.t list) instead of a bare citation list, and Liturgical_day.t carries the result as a new formulary field. Validate holds a rite that resolves a formulary at all to resolving one on every day, the same discipline it already applies to citations; the EF lectionary chain resolves Some on every day of every year 1583-9999, confirmed by a direct sweep over 2005-2050 as well as through Validate itself. Plan Tasks 2 and 3 are merged into this one commit on the coordinator's own instruction: Rite.readings' signature and the field that consumes it are one atomic edit, and the intermediate state does not compile on its own. Each of the four lectionary steps now builds its own Mass_formulary.t at the point it decides, not by re-deriving it afterwards from the citations it returns: step 1 tags Proper with the observed slug, step 2 tags Own_slug with the day's own temporal slug, step 3 tags Preceding_sunday with the resumed Sunday's temporal slug, and step 4 tags Common with the Common's own id -- Commons.find now returns that id alongside its citations rather than discarding it, since it is only ever in scope at the point the assignment is looked up. The RG 309(a) Saturday votive Mass of Our Lady, which answers between steps 4 and 2 rather than as one of the four numbered steps, is tagged Own_slug too: Mass_formulary.source has no dedicated constructor for it, and its own guard only ever fires when the observed celebration already is the day's own (reused ferial) temporal slug, which is exactly what Own_slug documents. Recorded as a judgement call in the task report, not a specified answer. test/cli.t's `emit --format sexp` line count is repinned (8472 to 8881): that command serializes Liturgical_day.t whole, so the new field grows its output. `colitur day` itself is untouched -- verified byte-identical against the pre-change binary across 1583, 1900, 2026, 2038 and 9999.
* feat(kernel): a type for which Mass a day saysLukasz Kasprzak2026-08-214-1/+65
| | | | | | | The lectionary's four-step chain already decides whether a day says its own proper, its own slug's entry, the preceding Sunday's Mass or a Common, and then discards that decision once the citations are out. An ordo needs to print it.
* release: v0.10.1v0.10.1Lukasz Kasprzak2026-08-213-3/+3
|
* docs: CHANGELOG entry for 0.10.1Lukasz Kasprzak2026-08-211-0/+3
|
* docs(C39): day-level confirmation from the LMS OrdoLukasz Kasprzak2026-08-212-8/+20
| | | | | | | | | | | | | | | | | | | | | | | | The Latin Mass Society Ordo 2024-2025 for England and Wales (v2.21, compiled by Peter Day-Milne) gives 9 August 2025 as "OUR LADY on SATURDAY IV Cl W / V Mass of BVM / Commem of St Romanus M at Low Mass" -- no vigil -- and 10 August as the 9th Sunday after Pentecost with St Laurence commemorated. That is colitur's output on both days, field for field. It is discriminating where the Ordo already in docs/research was not. This edition lists six vigils as the day's office across its year, including the Assumption's on 14 August 2025, and its footnotes discuss arranging a diocesan feast around the Vigil of St Laurence in other years. Its absence on 9 August 2025 is an application of the rule, not a data gap. The extraordinaryform.org Ordo omits that vigil on every date it covers, including 2027-08-09 where the clause says to keep it, which is why it witnesses nothing here. Also confirms the Vigil of the Assumption as II Cl violet, the RG 128 colour this project corrected from the bootstrap's White. The clause's other shape -- feast transferred away, 2022/2033/2038/2044 -- remains unwitnessed; no accessible Ordo covers those years. Stated in the note rather than left implied.
* merge: RG 33's third omission triggerLukasz Kasprzak2026-08-2116-26/+494
|\ | | | | | | | | | | | | | | | | | | | | The last unbuilt clause in the EF module. A II/III-class vigil is omitted entirely when the feast it precedes fails to keep its own day -- 1 744 days across 1583-9999, three verified shapes, zero unexplained. Rule and vigil inventory independently confirmed by O'Connell 1964, outside the Divinum Officium -> missalemeum -> lectio lineage. The 10 affected days in 2005-2050 are allow-listed as C39; no external day-level witness exists, which the note states rather than glosses.
| * docs(C39): O'Connell 1964 confirms the rule and the vigil inventoryLukasz Kasprzak2026-08-211-1/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Replaces the withdrawn Ordo claim with a real one of a different kind. O'Connell, The Celebration of Mass (4th ed., 1964) -- a rubrics commentary contemporary with the 1960 Code, outside the Divinum Officium -> missalemeum -> lectio lineage, already cited here for RG 128 -- states all three of RG 33's triggers in English, the third included, verbatim in substance with the Latin. The same passage confirms vigil_feast_table's membership independently: Christmas Eve and Pentecost I class and so outside the rule; Ascension, Assumption, John the Baptist and Peter & Paul II class; St Lawrence the sole III-class vigil. Five entries, the same five, same class split. It confirms the RULE and the ENTITY LIST, not the 10 days -- O'Connell publishes no day-by-day calendar. The distinction is stated in the note rather than blurred, since blurring it is what the withdrawn Ordo paragraph did.
| * docs(C39): withdraw the Ordo corroboration claimLukasz Kasprzak2026-08-212-5/+11
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | C39 and the 2025 golden pin both claimed the published Ordo independently confirms RG 33's third trigger, because it omits St Lawrence's vigil on 2025-08-09 where lectio and missalemeum keep it. It does not. The Ordo omits that vigil on 2027-08-09 as well, where the clause says to keep it (10 August is a Tuesday, the feast holds its day), and 2026-08-09 is a Sunday where the rule's first trigger omits it anyway. The 2024 date has no row. So the Ordo never shows St Lawrence's vigil on any date it covers, and the 2025 agreement is a blanket gap coinciding with the right answer, not evidence. The rule itself is unaffected: it rests on RG 33's text, which admits no latitude, and on lectio not evaluating the clause at all -- "RG 33" appears nowhere in its production code, and the third trigger cannot emerge from per-day rank comparison because it turns on what is observed the following day. The divergence is architectural, not a disagreement about the rubric. Recorded rather than deleted: the withdrawn claim is why this entry was first written up as unusually well corroborated.
| * docs(C39): correct the shape split, 7/3 -> 6/4Lukasz Kasprzak2026-08-211-1/+1
| | | | | | | | | | | | | | The note apportioned the entry's ten rows as seven commemoration-reduced and three transferred, counting 2038-06-23 in both halves to make the arithmetic work. It is six and four; the row count itself was always right, and is pinned by the comparator.
| * chore: gitignore .superpowers working artifactsLukasz Kasprzak2026-08-211-0/+4
| | | | | | | | | | | | Agent research reports, SDD ledgers and review packages, same local-only reasoning as docs/. Untracked but not ignored until now, so they showed up in every git status.
| * feat(ef): implement RG 33's third omission triggerLukasz Kasprzak2026-08-2115-26/+478
|/ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | RG 33 omits a II/III-class vigil in three cases: it falls on a Sunday, it falls on a I-class feast, "vel si festum cui praemittitur in alium diem transferri aut ad commemorationem reduci contingat". Only the first two were built; the third was recorded in precedence_ef.ml as unimplemented on the grounds that no witness existed in the shipped data. That reasoning was wrong, and the rule fires on 1 744 days across 1583-9999. Both halves of the clause reduce to one observable question -- is the feast the OBSERVED office on the following day (RG 34 puts it there) -- so the kernel asks it once per candidate, after place_transfers has settled the year. No fixed point is needed: a vigil is a candidate only on its own day, never on its feast's, so suppressing it cannot change what the next day observes. Precedence.rules gains vigil_feast, which returns the slug of the feast a vigil precedes; the kernel cannot infer that itself, because only two of the five vigil/feast pairs share a slug stem. Blast radius, measured pre-change binary vs HEAD over the whole domain and classified: 1 744 days, three shapes, zero unexplained. 1 199 are the feast reduced to a commemoration (10 August on a Sunday, St Lawrence); 478 and 67 are the feast transferred under RG 96 after the Sacred Heart or Corpus Christi takes its day. The Assumption's and the Ascension's vigils never qualify -- their I-class feasts always keep their own day. Independently witnessed, which is unusual here. The published Ordo -- the only witness outside the Divinum Officium -> missalemeum -> lectio lineage -- omits St Lawrence's vigil on 2025-08-09, agreeing with colitur against both engines. That date had been read earlier as an Ordo gap; the Ordo was right, and correcting the misreading is what surfaced this clause. On 2027-08-09 the feast does keep its day and the Ordo omits a vigil colitur correctly keeps, which is a genuine Ordo gap. Allow-lists: C39 (lectio, 10 rows) and a 2038 oracle class citing the register, the 2026-2027 window having no instance. The golden pin asserting St Lawrence's vigil is violet moved 2025 -> 2027; its own comment had reasoned about the vigil's weekday and missed that RG 33 also looks at the feast's. Two new pins cover both shapes of the clause. The vigil/feast table is built with Slug.of_string_exn: mutation testing showed that of_string plus Result.to_option turns a typo into None, which this hook's contract reads as "not a vigil", switching the rule off in silence. Two tests assert the table against the shipped data in both directions.
* docs(config): document the flavour keyLukasz Kasprzak2026-08-211-0/+40
| | | | | | | | | | | | colitur.1 documented the flag and colitur-config(5) documented every [defaults] key except this one, which the previous commit added. Says what a flavour IS rather than only listing the values: the set of characters that must be neutered so a value cannot corrupt its target. A semicolon is ordinary text in LaTeX and a field separator in iCalendar; an unescaped & or _ does not look wrong in LaTeX, it aborts the build. Also why unset means INFER rather than 'no escaping', and why there is no silent fallback.
* release: v0.10.0v0.10.0Lukasz Kasprzak2026-08-213-3/+3
|
* feat(config): flavour is settable, and an INI overlay is diagnosedLukasz Kasprzak2026-08-216-5/+77
| | | | | | | | | | | The escaping flavour was the one rendering setting with no config key, so a template whose extension says nothing needed the flag every run. Unset still means infer, which config --show reports as (infer) rather than (none). Backfills the 0.8.0 and 0.9.0 changelog entries. Both were tagged by hand rather than through 'make release', which is precisely the guard that would have refused a release with no changelog line.
* fix(cli): diagnose an INI overlay passed to --overlayLukasz Kasprzak2026-08-212-0/+45
| | | | | | | | | | | | | | The INI overlay form is a SOURCE format that 'colitur convert' turns into the S-expression one. Handing the INI to --overlay instead failed deep in the sexp reader with 'more than one S-expression in file', which names neither the cause nor the cure -- and the shipped examples under data/ef/examples/ are exactly the files someone would try that with. The detector reads past the comment header to the first real line and looks for a [section]. Its first version capped that scan at 40 lines and poland.ini's own header is 43, so it silently never fired on the very file it was written for; the cap now bounds only how much of a binary gets read, and is not a guess at header length.
* docs: correct a stale READMELukasz Kasprzak2026-08-211-10/+44
| | | | | | | | | | | | | | | | | | | | | Three claims were false: The download paragraph said older tags are not offered for download. They are again -- per-tag snapshots were re-enabled, and only the summary page's tag list is trimmed. A stable colitur-latest.tar.gz still exists for scripts that should not know a version. 'Starting with the Roman EF (1962) and OF forms' read as though both existed. lib/rites/ holds rite_ef and nothing else; the OF is the next module. The tagline claimed multiple rites for the same reason. Suite timings were from a much smaller suite: the fast run is ~8s over 568 tests, not ~3s, and the exhaustive sweep ~80s. Adds a Status section with the real numbers, the five validation layers and -- more usefully -- what they cannot see, since the differential and the oracle share one data lineage. Notes that chants are deliberately absent, and points at the colitur-x viewer.
* build: verify the upload instead of assuming itLukasz Kasprzak2026-08-211-5/+21
| | | | | | | | | | | | release-tarball chained its steps with ';', so a failed scp or ssh still reached the final echo and it announced a publication that had not happened -- the file was never in the docroot. Every step is now chained with && and the result is checked over HTTP before anything is claimed: the URL must respond and the bytes must untar. Also passes -o ClearAllForwardings=yes, since the i2p port forwards in the ssh config fail when another session holds them and their noise buries real errors.
* build: publish the latest tag, not strictly HEADLukasz Kasprzak2026-08-211-2/+4
| | | | | | | Requiring HEAD to be exactly a tag made the target unusable: the commit that adds the download link to the README necessarily comes after the tag it links to. It now archives the most recent tag and says so when HEAD is ahead of it.
* build: publish one current tarball instead of per-tag snapshotsLukasz Kasprzak2026-08-212-2/+28
| | | | | | | | | | cgit offers snapshots for every tag or for none, so colitur.git now has cgit.snapshots=0 and 'make release-tarball' uploads the tagged archive to a stable URL. Ten tags had meant twenty download links, nineteen of them stale. The target refuses to run from an untagged or dirty tree, so what is published always matches a tag.
* chore: v0.9.0v0.9.0Lukasz Kasprzak2026-08-203-3/+3
| | | | | | | | | | | | Robustness and hackability pass over the shipped program. The citation parser is strict about numbers, four pairs of books no longer share a full title, shipped styles re-parse their own output, and overlay errors are written for the person editing the file rather than for a compiler author. colitur-templates(5) already refers to "before colitur 0.9" for the month-key change, so the version moves with it.
* Merge branch 'robustness-and-hackability'Lukasz Kasprzak2026-08-2016-95/+598
|\ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | An audit of the shipped program, plus the fixes it found. The citation parser accepted OCaml integer-literal syntax, so a typo like 'Luke 1_1:5' silently became a different chapter. Four pairs of different books shared a full title -- 1 and 2 Corinthians both rendered 'Epistola ad Corinthios' -- leaving 108 citations in 2027 alone that a reader could not resolve to a book. Spec section 8.5 is now delivered rather than recorded: shipped styles re-parse their own output. Overlay errors no longer name OCaml source files at the reader. Also: the new-overlay scaffold shows citations at the right nesting level, config --show validates before printing, error messages no longer echo whole file lines, and a month answers to both spellings of its own name.
| * fix(render): a month answers to both spellings of its own nameLukasz Kasprzak2026-08-203-5/+62
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A week object carries month_name/month_num/month_abbr, because at that level a bare `name` would be ambiguous. An author who learned those names inside {{#weeks}} reaches for them one level up inside {{#months}} too -- where they resolved to nothing, and an unknown key renders as the empty string by design, so the result was a silently blank month heading rather than any error. Found by writing a template from scratch rather than copying a shipped one; every shipped template sidesteps it, which is why nothing caught it. The aliases are additive, so no existing template or golden changes. colitur-templates(5) now documents both spellings and says why they exist.
| * fix: audit findings — parser strictness, name ambiguity, and errorsLukasz Kasprzak2026-08-2013-90/+536
|/ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Found by auditing the shipped program rather than the diff. The parser accepted OCaml integer-literal syntax, so "Luke 1_1:5" read as chapter ELEVEN and "+5" as 5 -- a typo silently becoming a different chapter, reachable through any user overlay. Numbers are now plain digits and positive, and a descending range is rejected: 1:20-10 is always a transcription error. No shipped citation changed. FOUR PAIRS OF DIFFERENT BOOKS SHARED A FULL TITLE. 1 and 2 Corinthians both rendered "Epistola ad Corinthios", as did Thessalonians, Timothy and Peter -- 108 citations in 2027 alone that a reader cannot resolve to a book. This is the Kings defect fixed earlier and not generalised. The titles now carry their volume numeral, marked CONSTRUCTED, and a test asserts no two books share a name -- while allowing the case where two ids ARE the same book under different numbering, which a tradition relates. Spec section 8.5 is now delivered rather than merely recorded. Shipped styles did not re-parse their own output: 32 of 52 Latin abbreviations and 49 of 52 full titles failed, so a citation copied from colitur's own output into an overlay was passed through untouched and printed in the wrong language, silently. Every shipped name is registered as a spelling and split_book learned multi-word titles by longest-token match. Now 0 of 52 fail beyond the same-book aliases. Overlay errors were written for a compiler author: they named an OCaml source file the reader does not have and buried the useful token. The existing five-path rewriter is replaced by a generic one, applied to every load path rather than one, so "rank: is not one of the allowed values (at Class9)" replaces the raw Of_sexp_error dump. Also: the new-overlay scaffold documented citations and layer without showing them, and its comment implied the wrong nesting -- the single easiest thing to get wrong; error messages echoed whole file lines, copying an unrelated file's contents into stderr when a flag pointed at one; and config --show validated partway down its table, exiting 2 after writing five rows to stdout.
* chore: v0.8.0v0.8.0Lukasz Kasprzak2026-08-202-2/+2
| | | | | | | | | | Citations become configurable: parsed into structure and re-rendered, so book names, abbreviations, punctuation style and numbering tradition are files a user edits rather than strings frozen in the data. New lib/citation (Book, Parse, Render, Sigla); [bible] and [sigla] sections in language files; lang/traditions.ini; three config keys and CLI flags. --raw emits every citation byte-for-byte as stored.
* Merge branch 'citations-and-sigla'Lukasz Kasprzak2026-08-2046-1679/+4600
|\ | | | | | | | | | | | | | | | | | | | | | | Citations are parsed into structure and re-rendered, so book names, abbreviations, punctuation style and numbering tradition become files a user edits rather than strings frozen in the data. New lib/citation (Book, Parse, Render, Sigla); [bible] and [sigla] sections in language files; lang/traditions.ini for numbering; three config keys and CLI flags. --raw emits every citation byte-for-byte as stored, bypassing the whole pipeline, so output stays diffable against lectio and the raw view does not depend on the parser being correct.
| * fix(citation): close the final review's blocking findingsLukasz Kasprzak2026-08-207-18/+94
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The branch was RED and reported green. `dune test` exited 1: test/cli.t pinned the pre-fix output `kings_1 19:3-8`, which the previous commit had already fixed to `1 Reg 19:3-8`. The gate command piped dune through `tail`, so it reported tail's exit status, and cram prints its diff BEFORE the alcotest summary, so the two lines shown were the passing ones. Verify with `dune test; echo $?`, never through a pipe. A style file's own `book` key was unreachable. sigla_book resolved against a hardcoded "abbr" and the result was applied unconditionally, so the documented `[sigla] book = full` could never win. Render gains book_string, and the style's own value is now the default that a flag or config overrides. The unit test pinned style_of_fields correctly while the wiring defeated it. `lang --check` filtered the reference set to the celebration prefix, so a file with no [bible] section at all reported a clean bill of health -- contradicting both the reason the keys change was made and lang.ml's own comment. It now reports missing book names too. The token test missed a FOURTH citation-bearing file: adjustments.sexp writes citations as `Set_citation`, not `(reference ...)`. Its 16 citations all parse, so nothing was broken, but nothing was checking. The first attempt at this fix read the file and extracted NOTHING -- the marker stopped before the opening quote, so every payload was the part label -- which is recorded in the code rather than left as a trap. Also: colitur-config(5) claimed a trailing period the data does not carry, and two la.ini scan quotes silently corrected OCR damage ("Ionae 3, I - I O", "Epistolse") while presenting themselves as verbatim. Both are now marked as corrections.
| * fix(lang): stop internal book ids reaching output, and finish the docsLukasz Kasprzak2026-08-204-17/+132
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three defects, all found by running the documented examples rather than by reading code. All seven modern-numbering targets carried their own internal id as their Latin name (kings_1.full = kings_1), so --sigla-tradition modern printed "kings_1 19:3-8" -- leaking a key book.mli states is never shown to a reader. They now carry Vulgate-derived Latin names marked CONSTRUCTED: the 1962 Missal uses Vulgate numbering throughout, so it can contain no incipit for a book that exists only under a later convention. In Latin the modern tradition therefore only moves Kings and Esdras; Osee, Ionas, Ecclesiasticus and the Apocalypse keep their names either way, since modern numbering is a vernacular convention. The coverage test could not catch this. It compares a name against its KEY ("kings_1.full"), and "kings_1" differs from that, so the row passed. A second check now asserts no name IS its own id. The two are complementary: one catches a missing row, the other a row present but filled with the wrong thing. Mutation-proved. --help still claimed sigla settings were resolved "but not yet rendered", stale since the render wiring landed. colitur-config.5 documented none of the three settings even though config --show prints them.
| * docs(citation): sigla flags, book naming and traditionsLukasz Kasprzak2026-08-202-5/+437
| | | | | | | | | | Includes that --raw emits citations verbatim, which is the behaviour someone diffing against lectio depends on.
| * fix(lang): disambiguate the two Books of Kings in full modeLukasz Kasprzak2026-08-201-6/+12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Both kings_3 and kings_4 carried the full title "Liber Regum", so --sigla-book full rendered 3 Kings and 4 Kings identically and a reader could not tell which book a citation named. Seven citations in 2027 alone. The cause is real and now recorded rather than papered over: this Missal's incipit "Lectio libri Regum." is UNDIFFERENTIATED -- that exact unqualified phrase precedes both a "3 Reg." and a "4 Reg." locator in the same book -- so it is not a valid full title for either volume on its own. Only the numbered locator distinguishes them, and both "3 Reg" and "4 Reg" are independently attested in the scans. The full titles are therefore marked CONSTRUCTED, a third category beside sourced and UNSOURCED: both halves are transcribed, their combination is not. The numeral is written in Roman to match this project's own output convention ("Feria IV", "Hebdomada I"). Abbreviated and --raw output are unchanged.
| * feat(lang): Latin and English book names, and the shipped sigla stylesLukasz Kasprzak2026-08-2015-175/+730
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | la.ini and en.ini both gain [sigla] (the current Vulgate/Latin punctuation convention, byte-identical to Render.default_style) and [bible] (a .full and .abbr row for every id in Book.all -- 45 cited ids plus the 7 tradition targets, 52 total). Shipping [sigla] changes no output, asserted by the full test run. Shipping [bible] does change rendered book names, which is the point. la.ini's titles are sourced from docs/research/scan1.txt/scan2.txt (the 1962 Missal scans), each row citing the line its incipit pattern was read from. Six pairs (kings_3/4, corinthians_1/2, thessalonians_1/2, timothy_1/2, peter_1/2) share one Missal incipit and differ only in the sourced .abbr, matching what the primary text itself does. Three ids (proverbs, song_of_songs, ecclesiasticus) and the seven tradition targets are marked UNSOURCED and fall back to the data's own spelling, per this project's central rule against inventing a Latin title. Re-running the task's own sourcing note against the scans, counting every hit rather than eyeballing a frequency-sorted list, found nine of its NOT-sourced verdicts were undercounted (a single clean hit, buried under higher-frequency matches): Galatians, Colossians, both Thessalonians, both Peter, Malachi, Numbers, Jonas, Osee and Esdras all have a clean incipit in the scans and are sourced here. The note's other three verdicts stand, confirmed independently. Ecclesiasticus is not simply unfound: this Missal reuses Wisdom's own Lectio libri Sapientiae incipit for Ecclesiasticus readings too (both were anciently classed as one Sapiential group), so using it for Ecclesiasticus would misidentify the book, not merely abbreviate it -- recorded in that row's own comment. en.ini's [bible] is filled in full, not left partial the way [celebration] is: without it, a Vulgate-numbered id would fall through the [meta] fallback chain to la.ini's Latin name, not merely a less complete English one. Traditional Douay-Rheims names for the Vulgate ids (3 Kings, Osee, Ecclesiasticus, Isaias, Apocalypse), modern names for the seven tradition targets, since --sigla-tradition modern is the reader asking for modern numbering. test_lang_coverage.ml gains test_every_book_named, asserting every Book.all id has both forms in la.ini -- the check that catches a forgotten tradition target, since nothing else in the suite ever names them. Shipping real book names changes 11 golden template renders (Render/golden) and several test/cli.t examples that used to demonstrate the pre-Task-10 default-spelling fallback; both are updated to the new, correct output, each line checked against a fresh render before promoting.
| * feat(render): citations render through Sigla at both call sitesLukasz Kasprzak2026-08-2018-1558/+1804
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | View.citation_ref and readings' part_ref are the only two places a citation reaches output; every emitter goes through View. --raw passes Sigla.verbatim rather than a style built over Lang.raw: an identity name table would still reformat punctuation and renumber. Loosen reject_sigla_for: --sigla-style/--sigla-book/--sigla-tradition now actually render on readings/table/render/emit/publish (each builds its own Sigla.t via the new load_sigla), and stay refused only on commands that render no citation (day, easter, temporal, ...). names_of degrades a Lang.bible miss to Book.default_spelling rather than printing the lookup key itself ("luke.abbr"); pinned in cli.t against a language file with no [bible] section at all, independent of la.ini/en.ini's own eventual [bible] section. Regenerate the 11 golden templates for 2027: every changed line is one canonical citation replacing a stored variant, verified against the full readings 2027 diff (raw vs default) cell by cell -- 86 distinct (raw, rendered) pairs account for the entire diff across all 11 files, with zero unexplained residue. Two families: the seven duplicate book spellings collapsing onto one canonical form (Isa./Isa, 3 Kgs./3 Kings, ...), and citations reconstructed from parsed structure dropping stray punctuation the parser already treats as noise (a trailing period or semicolon, a comma chapter/verse separator, an elided inherited chapter) -- each of the latter already named and tested in test_citation.ml's parse_suite before this task.
| * feat(config): sigla_style, sigla_book and sigla_traditionLukasz Kasprzak2026-08-205-34/+284
| | | | | | | | | | | | | | | | | | Same flag > config > default precedence as --lang, and each reported by config --show with its source, so an override is visible rather than mysterious. A language file's [sigla] section IS a style; a config key SELECTS one and may override settings within it -- the two are not a duplicate setting.
| * fix(citation): complete the modern tradition, and test the shipped fileLukasz Kasprzak2026-08-202-4/+41
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Apocalypse -> Revelation was missing from [modern] while `revelation` was already a declared tradition target, so the target existed with nothing mapping onto it. Same Vulgate-to-modern shape as Ecclesiasticus -> Sirach. The injectivity test used to hand-copy the mapping table, which asserted things about the copy and nothing about the artifact: a mapping added to traditions.ini and forgotten in the test would have passed. It now reads lang/traditions.ini and additionally asserts that [vulgate] is empty -- an entry there would silently renumber the DEFAULT, the one thing this design promises never happens -- and that no declared target is left unreachable. Mutation-proved three ways: declaring an unused target, deleting a mapping, and adding an entry to [vulgate] each redden it, the first two naming the offending book.
| * feat(lang): traditions.ini, and its install ruleLukasz Kasprzak2026-08-206-6/+126
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Which book a reference denotes does not vary by language, so it lives in its own file rather than in la.ini beside [weekday]. Six mappings, not four: Osee/Jonas are the same question as 3 Kings in transliteration form. The install rule is added in the same commit deliberately -- lang/ once shipped without one, so the feature worked from the source tree and was broken once installed. bin/dune gains colitur_citation as a linked library, needed for the new loader; lang_list's directory scan now excludes traditions.ini, which is not a language file and does not parse as one.