| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
--pretty made a report readable and, in the same stroke, ungreppable: a box
spans seven lines, so `grep 2026-03-` returns fragments of March rather than
March. The nearest working equivalent is a paragraph-mode awk incantation that
depends on the boxes happening to be blank-line separated -- knowledge no
reader should need to select a month.
The three flags are ALTERNATIVES, not a stack. Naming two exits 2 rather than
letting one quietly win, the same discipline the positional year and --year
already follow, and the same reason this program refuses a flag it cannot
honour instead of accepting and ignoring it.
--date and --today CARRY a year, so the year becomes optional on those two and
required only after the window has had its say: `colitur day --today` is a
complete command while `colitur day` still is not. A year named alongside them
must agree. The disagreement message names the flag rather than printing two
bare numbers, because with --today the second year appears nowhere on the
command line for the reader to trace.
temporal refuses --year yet accepts --date/--today including as its year
source. That is not a back door to the refused spelling: --year is a second
spelling of the positional year, which temporal deliberately does not offer,
whereas --date selects a DAY and merely happens to determine which year
contains it.
The window is a predicate over an ISO date STRING rather than over a Date.t,
because the two report shapes reach it differently -- resolved days carry a
Date.t, temporal_report carries a flat Record whose date is already text --
and one shared test is one implementation rather than two that can drift.
Accepted by day, readings, rubrics and temporal; refused by easter, emit,
table, render, publish, check, convert, lang, config and new-overlay.
--pretty had shipped undocumented in both --help and colitur(1). Both now
carry it, alongside the new narrowing section; the man page's awk example is
shown precisely so the flag that replaces it is justified.
Default output is byte-identical to 1.1.0 across seven years, five commands
and both rites, and across all five emit formats and the shipped templates.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Aligned columns made the fields legible but the days ran together -- with 365
of them the eye had nothing to catch on. Each day now gets its own box: a
heading carrying the date and the liturgical colour, then the celebration, its
rank and season, and any commemorations, each on its own line.
The box art is pure ASCII, only + - and |, never Unicode box-drawing. That is
the point rather than a limitation: this format exists to be pasted or piped
into a document, a mail or a plain-text ordo, and U+2500 and its relatives
survive that only when every stage agrees about encoding and font. +---+ has
never failed to render anywhere.
readings gets a labelled block, so a citation says what it is instead of being
the second of three bar-separated fields; the OF second reading simply omits
its row on the days without one. rubrics becomes a label/value list. temporal
is the day box minus the sanctoral it does not have.
A blank line separates consecutive boxes -- without it the bottom rule of one
day and the top rule of the next sit adjacent and read as a single doubled
line, which is the same "not distinct enough" this change set out to fix.
Alignment counts UTF-8 code points, not bytes, so "Pen~afort" and "Fremiot"
still line the right edge up at 76 columns; a byte-counting pad shears the box
by one per multi-byte character. Over-long values are truncated with a ~
rather than allowed to overflow, since a box whose right edge does not line up
is worse than a clipped name that the default output still carries in full.
Verified: no ANSI escape reaches a pipe on any of the four commands, and the
default output is byte-identical to installed 1.1.0 across all five.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The row commands print for awk: single-space fields, slugs, a variable tail.
That is right for the default and wrong for a person, who mostly wants to know
what today is.
--pretty gives the same four commands -- day, readings, rubrics, temporal --
aligned columns, the day's liturgical colour as a swatch, and commemorations
on their own indented line rather than lengthening the row. The colour was
already computed and simply thrown away on a terminal.
Colour is written only when stdout is a terminal, so piping or redirecting
yields plain aligned text: the alignment survives, the escapes do not, and the
swatch degrades to the colour's initial so the information is not lost with
them. NO_COLOR is honoured on PRESENCE whatever its value, which is the
convention's own rule -- treating it as a boolean is the usual way to get it
wrong.
Every other command refuses the flag rather than accepting it and doing
nothing: emit, table, render and publish already choose their shape through
--format and --template, and easter prints six key/value lines, not a grid.
An intermediate version accepted it everywhere and silently ignored it on
five commands, which is the failure mode this program refuses everywhere else.
Two things the layout had to learn. Column widths are a minimum, not a
maximum: the Latin season names run past them ("Tempus per annum ante
Septuagesimam" is 35 against 34), so pad always leaves a separator or the next
field fuses onto it -- that is how "Septuagesimam 1S. Hilarii" happened. And
the commemoration indent is measured from the row actually printed rather than
computed from the column constants, or it sits under the wrong column on
exactly the rows that have something to indent.
Presentation only: bin/pretty.ml decides nothing about what a day is, and
nothing reads it. Default output is byte-identical to v1.1.0 -- verified
across day, readings, rubrics, temporal and easter for 2026, 1583 and 9999.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
An audit probed every flag against every command rather than reading --help,
and found three inconsistencies.
--rite reached only `day` and `readings`. main.ml's own comment gave the
reason -- "has not been widened to a second rite in this task" -- a scope note
that had outlived its task and hardened into apparent design. View.of_days was
already polymorphic over the rite's type parameters, so widening was plumbing,
not library work: `temporal`, `rubrics`, `emit`, `table`, `render` and
`publish` now all take it.
`temporal` was the sharpest case. Its refusal said "--rite has no effect on
`temporal`", which was false: the EF has Septuagesima and Passiontide, the OF
neither, and every EF slug is ef-prefixed, so the flag would change nearly
every line. A message claiming no effect where the effect is total is exactly
what the audit set out to find.
Three ways of naming a year (positional, --year, --from/--to) now cross-accept
additively; naming two that disagree is a usage error rather than one silently
winning.
emit --format csv gained a rite-dependent header: EF's 16 columns are
unchanged, OF gets a 17th "second" between "first" and "gospel". An earlier
task had recorded RFC 4180 as permanently blocking this; the rule constrains
one file, not a family of them.
`rubrics` keeps refusing --lang/--raw, now with its reason. An intermediate
version accepted them by adding a name column, which changed the default from
six tab-separated fields to seven and broke both existing consumers and the
byte-identical-EF rule. The asymmetry is real but principled: the row is a
date, a slug, a source keyword, two booleans and a preface key, so there is
nothing to translate and nothing to strip. `easter` refuses --rite for the
same kind of reason -- its six anchors sit at identical Easter offsets in both
rites.
EF output verified byte-identical to 8590338 across day, readings, rubrics,
temporal and easter for 2026, 1583 and 9999, and across all five emit formats.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The OF lectionary (data/of/lectionary.sexp) carries 183 Second readings,
146 of them on Sunday keys, and the kernel resolves them correctly -- but
readings_line (EF) and readings_line_of (OF) in bin/main.ml only ever
printed Citation.First and Citation.Gospel, so `colitur readings --rite of`
silently dropped every Second reading it computed.
Both functions gain a second_suffix lookup, spliced between the existing
First and Gospel fields, present only when the day carries a Second
citation. No rite check: EF's citation_shapes is [[First; Gospel]] alone,
so EF rows are unaffected by construction, not by special-casing.
EF byte-identity verified two ways: `git diff --stat v1.0.0..HEAD --
data/ef/` is empty, and `colitur day`/`colitur readings --rite ef` for
2026, 1583 and 9999 cmp identical against a build of the prior commit.
test/cli.t: fixed the one now-stale assertion and added coverage for the
three-citation OF Sunday shape alongside the unchanged two-citation OF
weekday and EF shapes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
day_line_of/readings_line_of resolved every displayed name through
lang/la.ini -- data documented as "transcribed from the 1962 Missale
Romanum", i.e. EF vocabulary, keyed on slug. 18 of 222 shipped OF
sanctoral slugs collide with an EF slug there and all 18 printed the
WRONG (1962) title (e.g. "S. Marthae Virg.", a rank word the 1969
reform abolished, silently overriding amendment 011's own "Ss.
Marthae, Mariae et Lazari"); the other 204 had no entry and degraded
to a bare slug, so none of the 208 titles calendar-2002.sexp itself
transcribes (cited against the 2002 Missal) was ever reachable.
Add observed_name_of: prefer the observed celebration's own
Celebration.names (looked up by the requested language code), fall
back to the lang/*.ini table only on a miss. Under --raw this is a
no-op (Lang.raw's own code, "raw", is not a valid 2-letter ISO code,
so the names lookup always misses and falls through to the identity
table exactly as before). EF's day_line/readings_line are untouched --
EF's Celebration.names is almost always empty by design, lang/la.ini
is its real, independently-cited name table.
Regression net, two layers: test_lang_coverage.ml gains a test walking
the real assembled OF layer's entries, asserting that every genuine
lang/la.ini collision resolves to the OF's own name, never the EF
table's; test/cli.t pins one real collision (Martha, 29 July) through
the actual CLI in both la and en.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Task 5 of Phases 3-5 (the payoff task): bundles Tasks 1-4's separately
built pieces (Temporal_of, Precedence_of, Rubrics_of, Lectionary_of)
into a (Vocab_of.season, Vocab_of.rank) Colitur_kernel.Rite.t, and
wires colitur day/readings --rite of <year> into the CLI. Default
stays ef; every pre-existing invocation is byte-identical (asserted
directly in test/cli.t and against a real v1.0.0 build for three
years, plus git diff --stat v1.0.0..HEAD over lib/rites/rite_ef/,
lib/kernel/, data/ef/ is empty).
No kernel change was needed, confirming the spec's central claim.
season_runs needed real thought rather than the brief's own suggested
Vocab_of.seasons: Temporal_of.season's own 'first run'/'second run'
comments confirm Ordinary Time is genuinely interrupted by Lent and
Easter over one liturgical year, so the expected run-length-compressed
sequence names it twice, six runs over five seasons -- exactly the
case Rite.t.season_runs's own doc comment already names as its
illustration for why this field is rite-supplied rather than derived.
easter/fixed_key are as specified, each carrying its own citation
(Gregorian computus; the 2002 Missal's one bissextilis occurrence is a
leap-year table entry, not a kalends-doubling rule, so fixed_key is
identity).
Follows rite_ef.ml's template and its lectionary-parameter lesson
exactly: lectionary is caller-supplied, not closed over.
test/test_rite_of.ml resolves the real assembled rite (calendar-2002
.sexp + all 13 amendment overlays + lectionary.sexp) through Calendar
and Validate.run, the same discipline test_validate.ml/test_rite_ef.ml
already apply to EF. This is the first time rite_of has been checked
this way, and it surfaced two new, real, cited gaps outside this
task's own scope to fix: Temporal_of never reaches Normae n.35(a)'s
30-December Holy Family fallback in a year with no Sunday in 26-31
December (first hit 1583, the domain floor), and Temporal_of.anchors
disagrees with temporal on 24 December when that date is itself a
Sunday (Advent IV outranks the Vigil, first hit 2006). Both pinned as
known-wrong, not fixed, the same treatment the brief's own pre-known
St Joseph/Palm Sunday gap (Normae n.56(f)) gets, pinned on 2062. A
third gap was found in data/of/lectionary.sexp's own coverage (audited
against one civil year only, so the other weekday-cycle letter is
unaudited and at least one pairing is asymmetric on shipped data) --
that file is excluded from this task, so the wide 2005-2050 sweep
checks structural invariants only and leaves the citation-chain checks
to the small, individually-audited landmark-year set.
make check (COLITUR_EXHAUSTIVE_SWEEP=1): exit 0, 867 tests, ~316s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Item 2 of Phase 3 (celebrant-rubrics-phase1), the bulk of this phase
and the last EF Mass rubric this project scoped: which preface is
said, deferring to seasonal/proper-title rules for the fourteen named
prefaces, then RG 498's Common residual.
RG 482 gives the resolution chain: the Mass's own proper preface,
failing that the seasonal one, failing that Common. Read literally,
RG 484-497 look like fourteen separate rules, but each numbered
rubric's own propria/de-Tempore pair produces the SAME preface
identity either way, so the whole chain collapses into one priority-
ordered decision: title/mystery triggers (Holy Cross, Sacred Heart,
Christ the King, Trinity, St Joseph, BVM, the Nativity octave, the
Apostles, Epiphany), each independent of season, then six seasonal
windows (Nativity, Epiphany, Lent, Holy Cross/Passiontide, Easter,
Ascension, Holy Spirit, Trinity-for-ordinary-Sundays), then Common.
The "one genuinely unproven piece" the design spec worried about --
per-feast proper prefaces extracted from the Missal's propers at scale
-- turned out not to be a large-scale extraction problem at all: every
one of the fourteen propers is a closed, small, subject/slug-keyed
trigger (mirroring Precedence_ef.band's own RG 91 table), not
thousands of individual saints' pages. The one genuine open question
(RG 488, the Chrism Mass) is N/A: this engine resolves one Mass per
civil day and has no separate Chrism-Mass dimension.
Preface is a new kernel type (lib/kernel/preface.ml[i]), not an
EF-specific one, the same placement as Colour/Subject/Mass_formulary:
Liturgical_day.t is parameterised only over season/rank, so any field
it carries generically must live in the kernel even though only EF
constructs a value of it today. Wired exactly as creed/gloria were
(Rite.t.preface, Calendar.ml, Rite_ef.context), but Preface.t option,
not a bare bool: unlike creed/gloria, a preface is said only at a
Mass, and Good Friday (1955-restored Holy Week) resolves an observed
celebration but has no Mass at all -- None is the honest answer there,
and also the neutral value an unimplemented rite returns.
Two priority-order findings only the oracle settled, not derivable
from the Latin text in isolation, both cross-checked against 358
individually classified entries in the FIUV Ordo's own praef column
(test/fixtures/fiuv-ordo-2025-2026.sexp, already captured, wired up
here as a new comparison axis): RG 484(b)'s own "except Masses with a
proper of the divine mysteries or Persons" is narrower than every
other window's implicit exception (an Apostle inside the Nativity
octave is overridden to Nativity; outside it, keeps his own preface
even inside another window); and RG 495's "et votivis" half is live
for the one office this engine models without a votive-Mass dimension
(the Saturday Office of the BVM), which also proved a vigil is not a
"festum" for this purpose (the Assumption's own vigil takes Common,
not BVM, correcting an initial reuse of Precedence_ef.marian_slugs
that had no reason to make that distinction for its own, different
rubric). A third, RG16(a)-shaped fix landed the same way: RG 494(b)'s
own Trinity grant must read the day's TEMPORAL season, not the
celebration that actually won it, or a Class1 feast with no preference
of its own (All Saints) wrongly falls to Common on a Sunday it merely
commemorates.
colitur rubrics gains a sixth TAB-separated column. Domain-wide
1583-9999 exhaustive sweep confirms every Christmastide day resolves
Nativity/Epiphany/Bvm, every Paschaltide day one of its own three
windows or a season-independent title, every Lent day Lent or a
title, and Passiontide legitimately produces Easter exactly once a
year (the Vigil Mass) -- 8416 of the domain's 8417 years, the one
short year being the domain's own upper boundary (the liturgical year
opening in 9999 cannot construct dates in year 10000, a pre-existing
edge this sweep re-confirms rather than a new one).
day/readings verified byte-identical to the branch's own state before
this phase (commit 9c96e0a) across a 455-year sample spanning the
whole domain -- not literally to the v0.10.1 tag, which 22 earlier
commits on this same branch (Phase 1/2, the bissextile shift, the
Rogation colour fix) had already moved past before this phase began.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The booklet's title page and running header read "Ordo 2027 . ef" --
an internal identifier reaching a reader, the same defect class the
sanctoral/temporal slugs were already fixed for.
lang/la.ini and lang/en.ini each gain a [rite] section mapping the
rite id to a reader-facing name, sourced from the Missal's own title
page (docs/research/LT.txt:6 "MISSALE ROMANUM", :15 "EDITIO TYPICA")
plus the 1962 dating this project uses throughout (rules-register.md,
CLAUDE.md) -- both cited in the ini comments, marked PATTERN since the
phrase combines two title-page elements rather than quoting one
verbatim heading.
Lang.t gains a rite table/lookup (same total, miss-echoes-the-key
contract as every other lookup here); View.of_days exposes rite_name
alongside the existing rite field, which stays the stable key exactly
as slug is kept beside name. Every shipped template that printed
{{rite}} now prints {{rite_name}} instead.
test_lang_coverage.ml gains a coverage assertion over every rite id
the engine can emit -- one today -- so a second rite module (OF)
landing without a matching [rite] entry fails loudly instead of
printing its own bare id.
Goldens regenerated through the Test_view.view_of path
test_render_golden.ml itself uses (English with a Latin fallback),
not the CLI (whose --lang default is plain Latin and would have
pinned output the tests never produce). test/cli.t's own JSON prefix
assertion updated to match: rite_name is a real new key in that
generic view dump, sitting right after rite.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The task this branch of work set out to test was whether the template
engine is genuinely flavour-agnostic outside lib/render/escape.ml. It
is not, quite: bin/main.ml's own --flavour error/help text and both
man pages hand-typed the flavour list and its count ("the six
flavours") as literal prose, so adding Typst as a seventh left three
places silently wrong (an incomplete flavour list in two error
messages, and "six" where the true count is now seven) until this
commit.
bin/main.ml's two error messages (unknown --flavour value, and cannot
infer a flavour from an extension) now build their flavour list by
walking Escape.all/to_string instead of a literal string, so the next
flavour added will not reintroduce the same drift; the static --help
body text and colitur(1)/colitur-templates(5) still name flavours and
their count by hand; there was no equivalent gain available for
prose formatted this specifically, so those were updated by hand
instead, along with colitur-templates(5)'s FLAVOURS section, which
gained typst's own subsection alongside latex/groff/html/xml/ics/none.
test/cli.t's two pinned error-text assertions are updated to match,
and gained a new case: .typ inferred from extension, and a real
shipped citation's hyphen surviving --flavour typst's own escaping
live, the same shape the 2035 Ss. Petri et Pauli case already proves
for latex's ampersand just above it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Names are Latin by default now; --raw restores the old byte-exact output,
verified against a pre-branch binary built from a clean HEAD checkout, not
merely asserted. day and readings gain a trailing display-name field
(day's own name suffix, readings' own " | name" suffix) rather than a
substitution in the slug's existing position: a name contains spaces and
would otherwise break the space-separated columns, the same reasoning that
already keeps readings a separate command. The field is present only when
it differs from the slug already shown, which is a direct consequence of
Lang.raw being the identity table (no lookup ever misses), not a special
case threaded through day_line/readings_line -- under --raw, or any
language with no entry for a given day, the trailing field is simply
absent, byte for byte what this project printed before naming existed.
An unknown language is an error naming what is available, never a silent
fallback to Latin: a booklet quietly printed in the wrong language is worse
than one that refuses to print. A declared fallback still chains, so a
partial translation shows its fallback language rather than slugs.
lang_dir mirrors data_dir's probe order exactly -- installed prefix, then
build tree -- because an installed binary that finds calendar data but no
language files is the failure this would otherwise ship.
Config (lang/overlay/template/format) is loaded once, unconditionally,
before dispatch: a config file that exists but fails to parse is fatal on
every command, not only the ones that consult it, because a config the
user wrote and colitur cannot honour is not something to silently carry on
past. An unknown key or unknown section is reported and ignored, worded
differently so a misspelled section reads differently from a misspelled
key inside a recognised one. overlay's config default applies only when
zero --overlay flags were given, never merged with a partial CLI list, so
precedence stays flag > config > default in one direction throughout.
colitur lang --list/--dump/--check and colitur config --show are what make
"anyone can write a language file" true rather than merely permitted, the
same pattern the overlay system already established with new-overlay and
check. --check reports what is missing AND rejects an unknown slug (exit
1), because a key matching nothing is silently dead and its author would
otherwise never learn why their own name does not appear. --dump emits
sections in a fixed order (Lang.keys' own global sort keeps every
section's keys contiguous), so two dumps of the same table are
byte-identical. config --show resolves through the same Config.resolve
every other command uses -- deliberately no separate provenance function,
so the two cannot disagree -- and previews exactly what flags given on the
same command line would resolve to anywhere else.
One authorized change outside this task's own file list: emit_ics.ml's
SUMMARY line was reading the unlocalised rank/colour pair ("class-1,
white") beside an already-localised name, where the JSON schema right next
to it correctly carries rank_name/colour_name ("II classis"/"albus").
Found while reviewing the published site; fixed at the source.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The view's name is now the RESOLVED display string and slug is untouched,
so machine formats carry both -- a script keeps the stable key, a human
reads the name.
name is a plain string, not a lang-keyed object. That removes the
shadowing hazard outright: a dotted {{name.la}} used to fall back
WHOLESALE to the enclosing month's name.la and print Ianuarius on every
unnamed day, which is how a printed booklet came to show the month where
the feast belonged.
weekday, season, rank and colour all gain localised companions, because a
calendar in a language needs more than feast names, and templates gain a
term vocabulary so fixed strings need no template edit to translate.
Asserted over a whole year: no day renders its slug as its name.
Beyond the brief's own code sample:
- comm_value's own name is now resolved through the same lang.celebration
table too (not only the observed day's), because Task 8's own ordo
template interpolates a plain {{name}} inside {{#comms}} -- an Obj there
would render silently blank. A commemoration slug without Latin
coverage still degrades to the slug, same as everywhere else in this
system; that is a lang/la.ini DATA gap (113 of 327 sanctoral slugs,
measured), not a regression this task introduced.
- bin/main.ml's emit/table/publish call sites needed ~lang to compile at
all, which is collateral from the of_days signature change, not this
task's own file list. Rather than pass Lang.raw and ship the very
slug-as-name defect this branch exists to fix, they load the shipped
Latin table by the same probe order data_dir() already uses -- a
deliberate, commented BRIDGE that Task 6 replaces wholesale with real
--lang/--raw/config resolution. bin/dune gained colitur_naming
accordingly.
- test/cli.t needed two related fixes to stay green: the CSV header/row
example, and a table/LaTeX escaping demonstration that relied on the
kernel's own English name for Sts Peter & Paul -- gone from the view
now that name resolves through lang tables only, and the Missal's own
Latin spells the feast with et, never an ampersand. Escaping itself is
still proved live on 2035 data in test_emit.ml.
- Both schemas gained the new day/week/top-level keys (season_name,
weekday, rank_name, colour_name, term, weekday_headings, month_num,
month_name), not only the name shape change; schema/colitur-v1.xsd
verified against real emitted XML via xmllint (make check-schema).
Render/golden's 9 cases (the shipped ordo/grid templates, all six
flavours) now fail as expected: their old {{name.la}} / {{#name}}...
idiom finds nothing on a plain string. That is Tasks 8/9's own scope to
rewrite, per the plan's own pre-flight conflict scan -- not fixed here,
and not silently pinned by regenerating goldens off broken output.
495 tests run (490 + 5 new), 486 pass; the 9 failures are exactly
Render/golden's ordo/grid cases.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
publish's own mkdir_p/write_file (unlike every other IO path on this
branch) were unguarded: an unwritable --out parent raised a bare
Unix.Unix_error(EACCES,...) and an --out naming an existing file raised
ENOTDIR, both as uncaught exceptions with a stack trace rather than the
project's one-line "colitur: ..." form. The same defect class commit
6bd741b already fixed once for template reads -- --out is user input
too. Fixed by wrapping the whole publish_report call (not each
write_file site) in one handler for Unix.Unix_error and Sys_error,
mirroring why that earlier fix guarded the whole read and not only the
open. Added a cram case using a read-only directory inside the test's
own cram sandbox, not /tmp, so a failed cleanup cannot leave an
unwritable directory behind in a shared location.
--dtstamp was the only user string reaching output unescaped and
unvalidated: "--dtstamp hello" silently emitted an invalid
"DTSTAMP:hello", and a value carrying its own CRLF injected extra lines
into every VEVENT. Fixed by rejecting anything not matching RFC 5545's
UTC form (8 digits, 'T', 6 digits, 'Z') before either emit or publish
does anything else, one line to stderr, exit 2.
usage() was byte-unchanged from before the branch and listed only the
six pre-existing commands, omitting all four commands this branch added
(emit, table, render, publish). Added them; the three cram pins of the
exact usage string are updated to match.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
CRITICAL: .colitur-manifest lives INSIDE the tree publish writes into --
the very tree this feature exists to have committed into a git repo. A
manifest entry with a ".." path component, or an absolute path, let
--prune Sys.remove/Unix.rmdir a file OUTSIDE --out. No attacker is
required: an ordinary bad merge, a conflict resolved the wrong way, or
a hand-edit of that file is enough to plant such an entry, and publish's
own stated contract -- it never deletes a file it does not own -- broke
outright the moment one was present.
Two independent checks, both required, applied before every deletion:
- structural (manifest_entry_is_safe): reject an entry that is
absolute or has a ".." path COMPONENT, by splitting on '/' and
comparing components, not by substring-matching ".." (which would
wrongly reject a legitimate name like foo..bar).
- containment (resolves_under): resolve both --out and the candidate
with Unix.realpath (closing a symlink-inside-out gap the structural
check alone would miss) and verify the candidate is a genuine path
descendant of --out, not merely a string with the same prefix.
Applied at both the file-deletion loop and prune_empty_dirs' own
directory removals. A rejected entry is skipped with a one-line stderr
warning; publish completes rather than aborting -- a corrupted manifest
must not make the tool itself unusable.
test/cli.t reproduces the exact canary scenario (a ".." entry
surviving deletion of a file outside --out), an absolute-path entry,
and a legitimate dotted filename (no .. component) still pruning
normally, alongside the existing --prune coverage.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Writes ef/<year>.{json,csv,xml,ics}, one JSON per day, the schema and a
generated index. That tree is the API: any web server or git repo serves
it, and nothing runs at request time.
Deterministic: publishing twice is byte-identical, asserted in cli.t.
That is what makes publishing into a git repo safe -- the diff shows
only real change, and you review it before pushing.
Non-destructive: a manifest records exactly the files this tool wrote,
so --prune can only remove files a previous run created. A file you put
in the output directory yourself is never touched, with or without
--prune. Asserted in both directions.
Pruning a stale file also removes any directory it leaves empty behind
it (e.g. an old year's own ef/<year>/ tree), stopping at --out itself --
without this, a pruned year's own directory would survive empty and
test -d would still see it.
schema/day-v1.json is resolved the same prefix-relative way data/ef's
own sexp files are (installed vs build-tree, probed rather than
assumed), never from cwd, and a missing schema fails with one line on
stderr before anything is written rather than emitting an empty file.
Needed schema/day-v1.json wired into the root dune file's default alias
and into test/dune's cram deps -- unlike data/ and templates/, nothing
made dune mirror schema/ into the build tree before this.
unix is added to bin/dune's libraries for mkdir_p; it ships with the
compiler, so colitur.opam and dune-project are unchanged.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
read_file guarded open_in_bin but left in_channel_length and
really_input_string unguarded, so a path that opens but cannot be read
as bytes -- a directory -- escaped as an uncaught Sys_error and crashed
the program, leaking the open channel on every failure path. A template
is user input; it must never crash the program.
Wrap the whole read in Fun.protect so the channel closes on every path
(success, exception, early return), matching the close-on-every-path
pattern already used in the test suite. The missing-file message stays
exactly as before; a read failure after a successful open now carries
the exception text, the same path: exception shape Layer.load and
Overlay.load already use.
New cram case points --template at a directory (the sandbox's own cwd,
not /tmp) and asserts one stderr line and exit 2, not a crash.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Computes and renders in one process. There is deliberately no stdin-fed
render: honouring the pipe would need a JSON parser we would have to
write, purely to serialise and immediately re-parse our own view -- a
second hand-rolled component and a second place for the contract to
drift, for no benefit. colitur emit --format json | jq still composes.
An unknown extension with no --flavour is an error naming the six valid
flavours, never a silent fallback to none: guessing wrong produces
malformed output that looks fine until it does not.
A malformed template reports the parser's own reason and exits 2. A
template is user input; it must never crash the program.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Reuses resolved_year_report's existing two-liturgical-year indexing
rather than copying it: that walk owns the civil-vs-liturgical span
reasoning, and a second copy would drift. It is refactored to return the
days, with the printer layered on top, so day and readings behave
identically -- which cli.t proves byte-for-byte.
CSV emits one header for a whole multi-year run, not one per year.
A reversed range is a usage error rather than silently empty output.
Asserted in cli.t: two ics runs are byte-identical, because nothing in
the path reads a clock.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Poland and the Benedictines, in the flat INI form, installed beside the
invented diocesan example.
poland.ini -- 17 entries from the Calendarium Perpetuum pro Dioecesium
Poloniae (1964), promulgated under Rubricarum instructum and applying to the
1962 Missal. Transcribed from a published Polish EF ordo that names that same
calendar, cross-checked against missalemeum's supplement page for the two
formularies 1964 added (13 and 15 July). Date, Latin name and class were read
from the source for every entry; three classes could not be recovered from
the PDF's column layout and are marked INFERRED where they appear, with the
reasoning. The header carries an edition warning that is a real trap here:
the Proprium Poloniae of 1921 and 1934 is still bound into many missals and
carries an outdated arrangement of dioceses and ranks. It is not this
calendar, and it is exactly the kind of plausible wrong-edition source that
has cost this project time before.
benedictine.ini -- two entries, and the comment explaining why is the point of
shipping it. Counted across the Norcia ordo: 86 "I cl.", 43 "II cl.", ZERO
"III cl." and ZERO "IV cl.", against 103 "Semidup." and ~96 "Dup.". The
monastic rite uses the Roman classes at the top and the older Duplex /
Semiduplex grades below, exactly where the Roman calendar has III and IV
class. colitur's rank vocabulary cannot express those, so most Benedictine
propers cannot be written here at all -- a mismatch of vocabularies between
related rites, not a gap in the data. Rather than invent a Duplex -> Class3
mapping the source never states, that file ships only what its ordo gives in
Roman terms and lists roughly two dozen excluded feasts BY NAME, so the
omission is visible instead of silent.
Both headers say plainly that they are examples and not authorities: they are
transcriptions from published ordines, none of the five test layers can vouch
for either, and both should be checked against the reader's own ordo. The
cram test asserts only what we control -- that they parse, convert and apply
-- and says so.
They also demonstrate the precedence engine on real data: the Benedictine
Transitus is I class and takes 21 March with the Lenten feria commemorated,
and Maurus is II class and takes 15 January with Paul the First Hermit
commemorated.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A convenience format for calendars that add a few local feasts and drop one
or two universal entries. Section names are slugs, a [overlay] section carries
the id, and status/subject/layer default so the common case -- an ordinary
local saint's feast -- says only what distinguishes it.
It is a FRONT DOOR, not a second data model. It parses to exactly the
Overlay.t the S-expression form parses to, and everything downstream is the
same code on the same values; a test asserts an INI overlay and its
hand-written sexp equivalent produce identical Overlay.t values. It is also
deliberately less expressive -- Add, Suppress and single-field Edit only --
and refuses Replace, multi-field edits and citation edits BY NAME rather than
dropping them silently. Anything it cannot say is a reason to write sexp.
Little of this is new machinery: tools/bootstrap_sanctoral.ml has parsed INI
and mapped it to celebrations since the sanctoral was bootstrapped from
lectio. The dates needed extending, since that mapping handled only MM-DD;
the flat forms are easter+N/easter-N and mon/day/nth, with nth negative to
count from the end.
`colitur convert` is a separate step rather than --overlay sniffing the
extension, so the author can read what their INI became. When a date form was
mistyped, "what did the engine actually get" is the question, and an invisible
transpile cannot answer it.
The conversion verifies its own output: the emitted text is parsed back with
the same function that loads an overlay and must equal what the INI denoted,
or nothing is written. That is the point of the module. A transpiler emitting
valid-but-wrong sexp is the failure a convenience format invites, and `colitur
check` could never catch it -- the output would parse cleanly and mean
something else.
That check was WRONG on the first attempt, in exactly the way it exists to
prevent. It re-serialised the parsed value instead of parsing the text being
returned, so it verified t -> sexp -> t, which is true by construction and
proves nothing. Found by mutation: corrupting the renderer to emit a different
overlay id sailed through and exited 0. It now parses the returned text, the
mutation is caught with exit 2, and two tests fail under it where none did
before.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Writing a local calendar had no feedback loop. An overlay is applied, not
validated -- that stays true, and the five test layers still cannot vouch for
a user's file -- but before this the only way to learn whether yours did what
you meant was to generate a year of output and grep for your own slug, and the
only way to see that a directive matched nothing was to notice a warning
scroll past among 365 lines.
`check` loads each overlay, applies it to the real shipped calendar, and
reports the directive counts, the slug each one targets, and any directive
that found no target. It exits 2 when a file fails to load or a directive
matched nothing, so it composes into a Makefile or a pre-commit hook rather
than merely being readable. It is applied to the SHIPPED calendar and not to
an empty layer on purpose: against an empty one every Suppress would fail
trivially and the check would be worthless.
It answers three narrow questions -- does the file parse, does every directive
find its target, what does the merged result contain. It does not validate a
calendar against the rubrics and cannot, and both the help text and the man
page say so rather than letting the name imply more than it does.
`new-overlay` prints a starter to stdout for redirection, rather than writing
a file where it likes. Every value in it is a placeholder that will appear in
`day` output if left unedited, so a half-finished overlay is visible rather
than silently inert, and it documents the three date shapes and the legal
values for each closed field inline.
load_ef_layer now returns its diagnostics instead of printing them: day and
readings still want them on stderr beside a year of output, while check wants
them on stdout, attributed to the overlay that produced them, and counted.
Printing at the source made the second impossible.
The cram test round-trips new-overlay through check rather than pinning the
template line by line -- editing its prose should not fail a test, but a
syntax error in it still must.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`--overlay` shipped last week with nothing to point at. The only example
was a test fixture, explicitly labelled invented and living where no user
would find it, so "colitur supports local calendars" was true but
undemonstrated.
data/ef/examples/diocesan-example.sexp is installed to
<prefix>/share/colitur/examples/ and is runnable documentation: every
celebration in it is invented, with example- slugs that cannot collide,
and its header explains the four directives, the three date shapes, and
the field vocabularies before showing any of them.
It demonstrates the range deliberately rather than the minimum: a
fixed-date local patron with its own propers; a movable dedication on the
first Sunday of October, I class because a church's own dedication
anniversary is I class in that church and at III it would lose to the
Sunday every year; an Easter-relative commemoration; an Edit raising a
universal feast for local use; and a Suppress shown commented out,
because suppressing a universal feast is a real liturgical act and an
example should not invite it casually.
Two things the header says plainly, since this is the one file that lets
someone change what colitur computes: an overlay is applied ON TOP of the
shipped calendar and never instead of it, and an overlay is applied, NOT
validated -- the five test layers assert things about the shipped data
and cannot vouch for a user's file.
A test asserts it loads against the real EF rank vocabulary, still
demonstrates at least four directives, and applies with NO diagnostics.
The last matters most: a directive naming a slug the universal calendar
lacks would warn, and an example that warns teaches the wrong lesson. It
deliberately does not pin which days it produces -- those celebrations
are invented, and pinning their dates would make an illustrative file
behave like calendar data.
|
| |
|
|
|
|
|
|
|
| |
3 January 2026 is an unoccupied IV-class Saturday, so its office is Our
Lady's and now its Mass is too -- the second of the Missal's five, assigned
to Christmas through the Purification. The pin held the Christmastide
feria's readings, which is what colitur said before RG 309(a) was
implemented. Caught by make check on the 0.3.0 release, which is what that
gate is for.
|
| |
|
|
|
|
|
|
|
|
|
| |
The release target bumps bin/main.ml and dune-project, but cli.t pinned
the literal too, so cutting 0.2.0 failed `make check` on a cram diff that
was purely the bump working correctly. Every release would have had to
edit a test for no gain.
The shape is asserted here instead. The value is already checked where it
can actually drift: the release target requires the freshly built binary
to report the version it just wrote.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Overlay algebra has been built and tested since Plan 2, and
Date_spec grew movable variants last commit, but there was still no way
to hand colitur a file: bin/main.ml applied exactly one overlay, the
shipped data/ef/adjustments.sexp, with the path hardcoded. This is the
plumbing.
--overlay is repeatable and ordered, and applies ON TOP of the shipped
adjustments rather than instead of them. That ordering is the whole
point: adjustments.sexp carries RG 110's own 30 June companion, the Major
Litanies, St Barbara and Rogation Wednesday, and a user file that
replaced it would silently drop all four while appearing merely to add a
local feast. Overlay.merge is last-writer-wins, so a local calendar can
still override a universal entry deliberately, by naming its slug.
The dispatch needed real argument parsing. It matched Sys.argv as an
exact array, which does not survive a repeatable flag -- two --overlay
arguments are a different array shape from one, and each further flag
would multiply the patterns again. Flags are now stripped first and the
remaining words matched as command plus year. Hand-rolled, because the
dependency list is frozen and this is fifteen lines. One defect found by
the cram suite on the first run: the unknown-option guard rejected
--help and --version, having excluded only their short forms.
Refused, not ignored, on easter and temporal. Neither reads sanctoral
data -- temporal deliberately runs the cycle before any layer exists --
so accepting the flag there and silently doing nothing is the failure
mode this project refuses everywhere else.
A file that fails to load is fatal, exactly as the shipped overlay is; a
directive naming a slug that does not exist warns on stderr and the run
continues. The second matters more for a user file than for ours: a typo
in a diocesan calendar should say so rather than quietly do nothing.
test/fixtures/overlay-example-diocesan.sexp is a worked example and is
labelled as invented, not calendar data. It carries both shapes a real
local calendar needs: a fixed-date patron, and a dedication on "the first
Sunday of October" -- which is the case the Date_spec work existed to
unlock and which lands on 4 October in 2026, 3 October in 2027, 1 October
in 2028. The dedication is I class because a church's own dedication
anniversary is I class in that church; at III class it lost to the
II-class Sunday every year, which a first draft demonstrated correctly
and uselessly.
The man page gains an OVERLAYS section saying plainly that an overlay is
applied, NOT validated: the five test layers assert things about the
shipped calendar and none of them can vouch for a file the user supplies.
This is the first feature that lets someone change what colitur computes,
and that distinction should not have to be inferred.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Groundwork for tagging. The project had no version anywhere: not in
dune-project, not in the binary, no CHANGELOG, no tags.
The version lives in two places -- dune-project, which generates
colitur.opam, and a constant in bin/main.ml, which is what --version
prints. Two rather than one because dune's watermarking only substitutes
in a release tarball, so a binary built the ordinary way from a checkout
would report a placeholder. The release target rewrites both and then
re-checks both, and finally requires the freshly built binary to report
the version it just wrote: a release that bumped one and not the other
would ship a binary disagreeing with its own package metadata.
--version is deliberately not embedded in the help text. cli.t pins
help's first line, and a version there would mean editing a test
expectation every release for no gain.
The release target mirrors lectio's, refusals included: no release from a
dirty tree, none without a CHANGELOG entry for that version, none whose
version bump silently failed to apply, and `make check` -- the full
1583-9999 sweep, not the sampling suite -- must pass before the tag is
created.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three things the project had no answer for: how to install it without
knowing dune, where to read about it, and what it does when asked.
Makefile, same shape as lectio's -- PREFIX ?= $(HOME)/.local, BINDIR,
MANDIR, and the '## '-comment help target -- so the two siblings are
driven the same way. Every recipe wraps dune in `opam exec --`, which is
the actual point of having one here: `make build` works from a plain
shell with no `eval $(opam env)` first. install goes through `dune
install` rather than a hand-rolled copy, because the binary finds its
calendar data relative to its own path; the man page is installed
separately to share/man/man1, matching lectio. install and uninstall
were both run against a scratch prefix and checked: uninstall leaves
zero files behind.
PREFIX defaults to ~/.local because that is where lectio installs and
where it actually lives on this machine, so colitur lands on an existing
PATH with no shell change. An earlier install this session went to
~/opt/colitur, which was me over-applying a rule meant for third-party
tools to one of the author's own projects; it has been removed rather
than left as a second, staler binary competing on PATH.
man/colitur.1 documents the four commands, both output formats and why
they differ, COLITUR_DATA_DIR and its refusal to fall back, the data
resolution order, exit statuses, and -- deliberately -- the limitations:
EF only, Epistle and Gospel only with the chants unbuilt and rejected
rather than guessed, and the BVM Saturday Mass-selection gap. A man page
that only lists what works is half a man page. Renders clean under
`groff -ww -z`, no warnings.
--help prints to stdout and exits 0; a usage error prints one line to
stderr and exits 2. That is the Unix convention rather than a
preference: asking for help succeeded and should be pipeable, being
invoked wrongly did not and must not pollute stdout. Both directions are
asserted in cli.t, along with a loop confirming every command the help
text advertises is one the dispatch actually accepts -- the check that
catches help drifting away from the code.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`dune install` produced a colitur that could not run: the binary locates
its data relative to its own path, nothing installed the four .sexp files
anywhere, and an installed colitur exited 2 unable to read sanctoral.sexp.
`dune build @install` produced an empty tree.
data/dune installs the four RUNTIME files into <prefix>/share/colitur/ef.
Deliberately not the two allow-lists: those describe where colitur and a
comparison oracle disagree, are read only by the differential and oracle
tests, and are meaningless to a running colitur -- installing them would
ship an assertion about lectio's and missalemeum's behaviour as though it
were calendar data. No glob, on purpose: a glob would silently start
shipping them again the moment one was renamed.
Resolution now probes rather than computing one path and hoping. An
installed prefix is tried first, the build tree second, and a candidate
counts only if sanctoral.sexp is actually readable inside it -- so a
failed or half-removed install falls through to a working tree instead
of shadowing it and failing later with a per-file error.
COLITUR_DATA_DIR overrides both and NEVER falls through. This was the one
real design question and the first version got it wrong: it treated the
override as just another candidate, and a deliberately bogus value
produced a full, plausible, entirely un-flagged year computed off the
build tree. Someone who names a directory has stated an intent, and
silently calendaring off a different one is the silent substitution this
project refuses everywhere else. It is now exit 2 naming the directory.
Verified end to end, not inferred: installed to a scratch prefix, then
ran the binary from an unrelated cwd with no build tree near it, and
confirmed 2038-03-06 still resolves Perpetua and Felicitas through the
Common route. All four paths exercised -- build tree, valid override,
bogus override, and an empty share/ falling through.
Environment reads stay confined to bin/. The kernel's contract forbids
them and nothing below the CLI learns where the data came from; the
loaders take a path.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The lectionary has been resolvable since Task 4 but invisible from the
command line: `colitur day` prints no citations, so the branch's whole
deliverable could only be seen through the test suite.
A separate command rather than extra columns on `day`, which is where the
plan pointed. Its instruction was to append the Epistle and Gospel to
each row "matching the existing column style", and that turns out not to
be possible: a citation contains spaces and commas ("Ezech 34:11-16",
"Ecclus 51:1-8, 12") while a day row is space-separated with a
variable-length +slug commemoration tail, so appending them leaves the
row unsplittable -- no awk or cut field number recovers where the Epistle
ends. That is the opposite of what the row is shaped for.
So `day` keeps its format byte-identical, asserted directly in cli.t
rather than left implicit, and the citations get their own row with
" | "-delimited fields, safe for values containing spaces. Both formats
are a stopgap and say so in the source: the design calls for one schema
rendered through a logic-less template engine, and two ad-hoc formats are
easier to retire later than one overloaded format whose parsing rules
nobody wrote down.
The year walk is now shared. day_report and readings_report differ only
in how a day is printed, and the two-liturgical-year indexing -- with its
own reasoning about civil-versus-liturgical spans -- is exactly the part
that must not be duplicated and drift.
"-" for an absent part, though no EF day can currently print one: layer 2
asserts exactly one First and one Gospel on every day of every year
1583-9999. The CLI still does not assume a guarantee the kernel makes
about data rather than about types.
cli.t gains the four chain-step cases test_golden.ml pins against the
scans, so the CLI path is checked to agree with the library path, plus
the 2 January line, which reads the Circumcision's Mass under the
Missal's own ferial rubric (scan1:6523-6526) and not RG 17(a). Verified
the cram tests actually run and have teeth: corrupting one expectation
produces a diff and exits 1.
CLAUDE.md: state, test count (369, 370 with the sweep), the readings
command and why it is separate, and What's next -- the lectionary landed
early rather than inside Plan 4, so what remains there is OF's own
lectionary, not the mechanism. Chants stay deliberately unbuilt, and
Validate now rejects any part outside First/Gospel.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The register was transcribed faithfully but was itself wrong: RG 96's
Attamen (a), primary-source-verified from the scans and now corrected
in the register, reads 'festum Annuntiationis B. Mariae Virg., quando
est transferendum post Pascha, transfertur ... in feriam II post
dominicam in albis' -- the Monday-after-Low-Sunday seat applies ONLY
'quando est transferendum post Pascha', when the feast is to be
transferred PAST EASTER. The unconditional transcription made the
exception fire on every impeded Annunciation regardless of cause.
transfer_target now computes the general RG 96 target first, for every
candidate, and overrides to the Monday after Low Sunday only when that
general target itself falls after Easter Sunday -- testing the rubric's
own condition directly rather than re-deriving a date-proximity rule
from first principles.
Confirmed against three real years the review named: 2007, 2012 and
2057 all previously sent the Annunciation to Easter + 8 (16 April,
16 April, 30 April respectively) when the correct, now-produced target
is the next free day before Easter (26 March in each case -- Passion
Sunday in 2007/2012, Lent III Sunday in 2057). Verified with actual CLI
output for all three, before and after.
Also cites RG 96 Attamen (b), the same primary-source passage, as the
direct authority for All Souls' own move to the following Monday when
impeded by a Sunday -- previously inferred from RG 91 entry 8's
parenthetical plus the general walk, which happened to produce the
right date; now stated directly.
Rewrote the two existing Annunciation unit tests, whose synthetic
occupants no longer trigger the (now correctly conditional) exception,
and added a 2057 regression test using the real Temporal_ef.temporal as
occupant plus a real-data cram pin -- both mutation-verified against
the unconditional reading.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Assembles Rite_ef.context (lib/rites/rite_ef/rite_ef.ml[i]): temporal,
anchors and vocab from Temporal_ef; rules from Precedence_ef's band,
disposition and admit; season_runs = Vocab_ef.seasons; transfer_target
newly implemented here.
transfer_target (RG 96): the next following day that is not I or II
class, with the Annunciation's own exception (Monday after Low Sunday).
Terminates by a structural step bound on its internal search, independent
of Calendar's own round guard, which bounds rounds across a year, not one
call's walk; documented as an obligation on rite.mli's transfer_target
field, which did not previously state it.
Fixes the vigil-naming mismatch Task 7's review predicted: the sanctoral
bootstrap names its vigils with a vigil-of-X prefix (lectio's own
convention), while Precedence_ef's is_vigil only recognised the temporal
cycle's own X-vigil suffix. Both are now recognised, fixing RG 91 entries
21/26 and RG 33's vigil omission for the four affected celebrations.
Verified by unit test and by mutation-testing the fix (reverting it fails
exactly the new rows) and against real output across several years.
Suppresses data/ef/sanctoral.sexp's vigil-of-christmas via a new overlay,
data/ef/adjustments.sexp: it is the same celebration as the temporal
cycle's own ef-nativity-vigil, both dated 24 December.
colitur day <year>: one line per civil-year day, temporal and sanctoral
fully resolved through Layer, Overlay, Precedence_ef and Calendar -- the
first CLI path exercising the whole Plan 3 pipeline against real data.
Verified the All Souls transfer chain (Tasks 7-8-11) end to end against
real output for both a Sunday year (2025, lands on 3 Nov) and a
non-Sunday year (2026, observed directly on 2 Nov).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
week is "" for roughly 30 days a year (any day outside a numbered
week). Printed as-is among seven space-separated fields, that collapses
two fields into a double space, so naive field-position parsing (e.g.
awk '{print $4}') silently reads the wrong column on those days -- a
real defect for a project whose stated design is Unix-composable CLIs.
Emit "-" instead, so every line always has exactly seven
single-space-separated fields.
Record.headers/to_row exist for exactly this kind of column-safe
encoding but use a different column set and order than this CLI's
documented date/weekday/season/week/slug/rank/colour format (design
spec §6); adopting them here would be a larger, unrequested format
change, so this keeps the CLI's own field list and only substitutes
the empty value.
The cram test's pinned lines are re-verified against liturgical
anchors before promoting, not blind-promoted: 1 Jan 2026 (Circumcision,
class-1, white) and 2-3 Jan (ordinary class-4 Christmastide ferias,
the finding-1 slug fix already re-pinned separately) are correct;
2026-04-05 remains the sole Easter Sunday line, Paschaltide week 1,
class-1, white, matching the independently-computed Easter anchors
printed by `colitur easter 2026` immediately above in the same file.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two correctness bugs in Temporal_ef, both only visible across many
years, not a single point assertion:
1. Duplicate slugs within one liturgical year. Christmastide has no
numbered weeks, so the ferial fallback's <season>-<week>-<weekday>
scheme collapsed every feria in it to literal week "0". Since
colitur's Christmastide runs 25 Dec - 13 Jan (RG 72-73, a deliberate
divergence from lectio), the same weekday recurs across that span
and the keys collided (e.g. ef-christmas-0-saturday on three
different dates). Fixed with a dedicated christmastide_feria_slug
that splits the span into four sub-stretches, prioritising
compatibility with lectio's own keys wherever lectio has one:
- 26-28 Dec keeps lectio's existing ef-christmas-0-<weekday>.
- 2-5 Jan becomes ef-christmas-1-<weekday> -- lectio collapses
this indistinguishably into the same key as the stretch above,
so there is nothing to preserve; a colitur-only lectionary gap.
- 7-13 Jan splits at the actual first-Sunday-after-Epiphany
origin: on/after it, this is genuinely week 1 of Time after
Epiphany and takes lectio's own ef-time-after-epiphany-1-<weekday>
(which also can't collide with that season's own later week-1
ferias, since it's the same computation). Before it (0-6 days,
whenever Epiphany doesn't fall on a Saturday), a first attempt
at labelling this "week 1" too, matching a literal reading of
lectio's behaviour, was verified empirically (a throwaway sweep
of the full 1583..9998 domain) to reproduce duplicates in most
years -- so this remainder is its own ef-christmas-2-<weekday>,
a further colitur-only gap.
Verified with the same throwaway sweep: zero duplicate slugs across
1583..9998 after the fix.
2. named's week field was set by hand on some branches (Passion/Palm
Sunday, Easter, Low Sunday, Pentecost and its Vigil, Christ the
King) and left at None on others (Ascension and its Vigil, Corpus
Christi, Sacred Heart) even though all of them sit inside a
numbered season run. named no longer carries a week at all --
temporal now calls week itself for every day, named or not, so "a
named day inside a run carries that run's week" holds by
construction rather than by remembering to set it on each branch.
temporal.mli's week field doc is reworded to state the actual rule.
Covering tests: point assertions for all four Christmastide
sub-stretches; a QCheck property scanning random years for any
duplicate slug within a liturgical year (excluding the deliberate
resumed-Sunday reuse); point assertions for the four previously-None
days now carrying their week (first to fail without the fix: Ascension
Vigil, expected Some 6, got None); a QCheck property asserting
temporal's week equals week for every day of the year, replacing a
prior property whose fallback made it structurally incapable of
detecting an omitted week.
Also: cite RG 91 e18 for the September/Advent Ember days matching
lectio and flag the Lent/Whitsun Ember and Rogation slugs as
colitur-only lectionary gaps inline, matching the existing
Nativity-vigil/octave-day convention; cite RG 117/123/127/128/131 for
season colours and Gaudete/Laetare rose; drop the unreachable
Passiontide arm from the Sunday-rank match (Passiontide has only two
Sundays and both are already named above, so no Passiontide Sunday
ever reaches that fallback).
|
| |
|
|
|
|
|
| |
One line per day -- date, weekday, season, week, slug, rank, colour -- built
through Record, the canonical output view. Until Plan 3 brings the differential
and oracle layers, this dump is how a human checks EF temporal against
missalemeum by eye.
|
|
|
Argv-parsed subcommand; validates the 1583..9999 domain (exit 2 otherwise);
prints 'name YYYY-MM-DD' lines. Cram test covers the output and the exit codes.
|