| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
LaTeX and groff are the print paths; HTML carries a print stylesheet;
AsciiDoc, Markdown and plain text are the plain-consumer paths.
AsciiDoc and Markdown use flavour none, and say so in a comment: their
metacharacters are context-dependent and escaping them aggressively
produces worse output than not escaping. The consequence is real and
documented -- a feast name containing * renders as emphasis.
Golden tests pin all six byte-for-byte for 2027. They prove the
templates RENDER, not that they TYPESET; compiling needs TeX and groff,
which is Task 13's opt-in make check-templates.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
F1: Date.add_days is UNBOUNDED (date.mli) -- only Date.make enforces
1583..9999 -- and Date.to_iso8601 pads but never truncates, so
9999-12-31's naive successor formatted as "10000-01-01", and compact
turned that into a 9-digit, non-conformant DATE on the last VEVENT of
year 9999. Confirmed at the source before fixing, and reproduced
against real `colitur emit --format ics --from 9999 --to 9999` output
(DTEND;VALUE=DATE:100000101) before touching any code.
RFC 5545 section 3.6.1: a VEVENT with a DATE-valued DTSTART and
neither DTEND nor DURATION has an implicit one-day duration, so
omitting DTEND for that one event is the standard's own correct
answer, not a workaround. dtend_of re-derives the successor's
year/month/day and re-validates them through Date.make -- the one
function that actually enforces the domain -- before trusting the
string; None means the caller omits the DTEND line entirely.
F2 (minor, same function): documented next_day's own Error branch as
dead-but-silent on shipped data (event's iso <> "" guard is the only
caller and always parses) -- behaviour unchanged, comment only.
Two new tests: the domain's last VEVENT (DTSTART 99991231) has no
DTEND line at all; every DTEND anywhere in a 9999 feed is exactly 8
digits (the general form of the bug, catches a regression anywhere
else in the domain too). Existing 2027/2028 DTEND-arithmetic
assertions untouched and still pass.
Mutation-proved: both new tests fail against the pre-fix code
(9-digit DTEND value caught verbatim), pass after.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Not a template job: folding, escaping, exclusive DTEND and stable UIDs
are rules a logic-less template cannot enforce, and each fails silently
in a subscriber's client rather than loudly at generation.
DTEND is EXCLUSIVE for an all-day event (section 3.6.1). Wrong here
shows every event a day short, everywhere.
UIDs are YYYYMMDD-<rite>@colitur and stable across regenerations
(section 3.8.4.7). Wrong here duplicates the whole year in every
subscriber's phone, months later.
Every line is CRLF-terminated and folded at 75 octets (section 3.1).
No RRULE: a liturgical calendar is not a recurrence rule. Asserted, so
nobody optimises it later.
DTSTAMP is a parameter, not a clock read. RFC 5545 requires it and the
obvious implementation reads the wall clock -- which violates the
kernel's determinism rule and would make two feeds from identical data
differ byte-for-byte, defeating reproducible builds and any reviewable
diff on a published tree.
Corrected one test literal against real engine output: DTSTAMP is a
per-VEVENT property (section 3.8.7.2), not calendar-level, so the
default-value line count is 365 (every event), not 1.
Mutation-tested: a non-exclusive DTEND reddens the suite.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Element-per-field; attributes carry identity only and there is no mixed
content, so a consumer's XPath never has to distinguish the two.
Schema validation is an opt-in make check-schema via xmllint, not an
in-suite assertion: validating XSD needs an XML library and the
dependency list is frozen. It prints SKIPPED loudly when xmllint is
absent, because a silent skip reads as a pass. The suite asserts
well-formedness properties directly instead.
This corrects the design spec, which claimed in-test validation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Both consume the VIEW, not the kernel, so every emitter and every
template describe exactly the same fields -- there is one vocabulary,
not five.
CSV is RFC 4180: a field with a comma is quoted. That is live on real
data, not hypothetical -- 'St. Joseph, Spouse of the Bl. Virgin Mary'
would otherwise split into two columns.
JSON is hand-rolled because the dependency list is frozen and escaping
is the only subtlety. Control characters below 0x20 are \u-escaped per
RFC 8259 section 7.
There are no numbers in the view, deliberately: a consumer never has to
guess whether week is 2 or "2".
schema/day-v1.json pins the shape. Once a phone subscribes or a site
fetches this, it is a promise to strangers -- adding a field is minor,
renaming one means /v2/.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Shapes a civil year of resolved days into the value a template renders
against. This layer is why the engine can stay logic-less: a month grid
needs leading blank cells, week bucketing and an in-month test, and a
logic-less template can compute none of it.
Both weeks and days are offered at every level -- the booklet walks
days, the grid walks weeks -- so the two artefacts cannot drift.
Colours are six booleans, not hex: hex bakes a presentation policy into
the engine, and LaTeX, groff and HTML each want a different colour
expression. Asserted: exactly one of the six is true on every day of a
whole year, so a template keying off them can never get none or two.
Padding cells carry every field a real day carries, empty, so a template
never hits a missing key mid-grid.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every interpolated value is escaped for the template's flavour; the
template's own literal text never is, because that is the author's
markup. There is no raw form, so a template cannot opt out.
Scope is a stack with outward fallback, so a grid template can reach the
year number from inside a week without the view duplicating it into
every cell.
A missing key renders empty -- the one deliberate silence, so a template
survives a rite that does not set every optional field.
Mutation-tested: dropping the Escape.apply call reddens the
data-cannot-escape-flavour case.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
F1: test_no_raw_or_partial_form's first assertion only excluded one
literal shape (Ok [Var ["{name"]]), so it could not actually catch a
future raw/unescaped constructor under a different name. Replace it
with an assertion of the real parse result for {{{name}}}
(Ok [Var ["{name"]; Text "}"]), documented behaviour rather than a
guarantee this test cannot check -- the real guarantee is structural:
node has exactly four constructors and none of them is raw.
F2: {{.}}, {{#}}, {{^}} and {{/}} used to parse to a Var/Section/
Inverted with an empty path, reachable but never designed. This engine
has no "current context" for a bare dot to mean, so a bare-dot or
empty-sigil path is now a parse error at lex time, covering all four
sigil forms via one path helper. The existing "empty tag {{}}" branch
is unchanged and still reachable (a fully empty body is a distinct
case from a sigil with an empty path).
|
| |
|
|
|
|
|
|
|
|
|
| |
Placeholders, sections, inverted sections, comments. Nothing else: no
partials, no lambdas, no expression evaluation, no raw form. A template
is data, never a program, which is what keeps an untrusted template safe.
Errors rather than silence on a malformed template: an unterminated tag,
an unclosed section, a mismatched close and a partial all return Error.
Swallowing '{{name' as text is how a typo becomes invisible missing
output in a printed booklet.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
fold_ics's UTF-8 backoff loop could back `cut` all the way down to
`pos` on 74+ consecutive continuation bytes (0x80-0xBF), producing a
zero-length chunk and recursing on the identical position forever --
not producible by valid UTF-8, whose longest continuation run is 3,
but the kernel's own totality requirement covers arbitrary octet
strings, not only valid ones. When backoff finds no boundary inside
the window, cut hard at the limit instead, so forward progress is
unconditional.
test_fold_never_splits_utf8 previously asserted only that unfolding
reproduced the original bytes, a property folding preserves at any
cut position and therefore blind to a boundary violation. It now also
asserts the named property directly: no continuation chunk may start
with a UTF-8 continuation byte. A new regression test feeds fold_ics
100 consecutive continuation bytes and asserts it terminates with
every line at or under 75 octets.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Six flavours: latex, groff, html, xml, ics, none. Markdown, AsciiDoc and
plain text map to none deliberately -- their metacharacters are
context-dependent and escaping them aggressively produces worse output
than not escaping.
An unrecognised extension returns None rather than falling back to none:
guessing the flavour wrong produces malformed output that looks fine
until it does not.
Folding backs off to a non-continuation byte, so a fold never splits a
UTF-8 sequence -- the failure mode that would corrupt Polish and Latin
names in a published feed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio adopted the last two Missal corrections colitur argued:
C14 3 -> 0 RG 96(a)/97/98. lectio resolved each impeded I-class feast's
transfer independently, so in 2008, 2035 and 2046 St Joseph and
the Annunciation both walked onto the Monday after Low Sunday
and Joseph, losing there, was observed on no day of those years
at all. It now resolves the year's transfers as a set: the
Annunciation takes the Monday as its sedes propria, Joseph the
Tuesday.
C15 7 -> 0 The Holy Family propers' own 13-January rubric, "sine
commemoratione Baptismatis D.N.I.C.". lectio kept the Baptism
as the observed office on all seven such years in 2005-2050.
Those were the last two BEHAVIOUR classes. What is left is C1 (138) and C6
(119), both pure slug vocabulary.
Measured across the whole fixture, applying only the season normalisation the
test already applies: 579 of 16801 days differ, and every one of them differs
on the SLUG ALONE. Zero days differ on season, rank, colour, Epistle or Gospel.
(The test's own norm_slug table already maps 322 of the 579; the 257 left are
C1 and C6. The field profile is the same either way.)
So the two engines now agree on every liturgically meaningful field on every
day of 2005-2050. The residue is what the two projects call things --
ef-christmas-2-friday against ef-time-after-epiphany-1-friday for the same day,
same Mass. Aligning it means renaming lectio's slugs, and those are keys: its
lectionary, clectio's generated tables and any user overlay are built on them.
Left as vocabulary rather than forced.
Fixture refreshed against lectio e713da2; 13 rows changed, colitur agrees with
all of them. 24 divergence classes at the start of this work, 2 now.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The ferias between Epiphany and the first Sunday after it repeat Epiphany's
own Mass in lectio now; they had been taking the Mass of the Sunday that
follows them.
C31 98 -> 0
C1 40 -> 138 grew, and changed character
C1 is now SLUG VOCABULARY ONLY, and its note says so. Season, rank, colour and
both citations agree with colitur on every day of the January window; what
differs is the identifier -- ef-christmas-2-thursday against
ef-time-after-epiphany-1-thursday for the same day with the same Mass. C6 is
the same shape.
That is 257 of the 267 remaining rows. Aligning the vocabularies would mean
renaming lectio's slugs, and those are keys: its own lectionary, clectio's
generated tables and any user overlay are built on them. Recorded as
vocabulary rather than closed, because the rows do differ -- just not in
anything a reader of either engine's output sees.
Measured for 2026: season, rank and colour agree on all 365 days; 14 days
differ and every one differs on the slug alone.
5 classes -> 4, agreement 98.4%.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio carries the readings of the nine Common-routed saints and of three more
that had proper Masses but no reading at all. Every day of 2005-2050 now
resolves a reading there; four days used to come back empty.
C38's own note said it could not close without lectio gaining a Commons
concept. The readings arrived instead of the abstraction, which was enough:
each Common resolves to one first/gospel pair for the saint that names it, so
a table plus assignment layer would have bought indirection and nothing else.
C20 14 -> 0
C38 70 -> 0
7 classes -> 5, 351 rows -> 267, agreement 97.9% -> 98.4%.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio gained the Passiontide week split and thirteen lectionary entries.
C23 230 -> 0 Holy Week's own six Masses
C32 99 -> 0
C35 60 -> 0 the first weeks after Epiphany and Pentecost (RG 299)
C33 46 -> 0 Corpus Christi
C34 46 -> 0 the Sacred Heart
C26 43 -> 0 Passion Tuesday
C19 7 -> 0 the Holy Family week-index residue
C1 75 -> 40
C31 63 -> 98 GREW
The Passiontide split is the one that carried most of this. efWeek had no
Passiontide case, so both weeks numbered 0 and every day of Holy Week took the
slug of its Passion-week namesake -- one set of readings for two weeks with
entirely different Masses. Good Friday was reading Passion Friday's.
C31 growing is the same bookkeeping as C1's earlier growth and is recorded in
its own note: C32 and C35 closed, and rows that carried several causes now
carry only this one. Nothing new disagrees.
17 classes -> 10, 1019 rows -> 488, agreement 93.9% -> 97.1%.
What remains is almost entirely NAMING rather than disagreement: C6 (119) is
colitur's ef-nativity-octave-day-N against lectio's ef-christmas-N-weekday on
days where both now read the same Mass, and C1 (40) is the same shape in the
January window.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio gained Rogation Monday and Tuesday (RG 87, violet under RG 128(d)) and
the Missal's own Mass for the weekdays within the Octave of the Nativity
("Diebus infra octavam Nativitatis Domini", Titus 3:4-7 / Luke 2:15-20).
C8 26 -> 0 CLOSED
C6 119 -> 119 narrowed: the cause halved, the count did not
C6 is worth reading carefully rather than skimming the number. Its rows no
longer differ on First_f or Gospel_f at all -- the two engines now read the
SAME Mass on 29-31 December, where lectio previously served the Sunday's. What
is left is Slug_f alone, colitur's ef-nativity-octave-day-N against lectio's
ef-christmas-N-weekday. That is vocabulary, not disagreement. The count did not
move because the entry's gate already admitted a Slug_f-only subset, which is
exactly the case where a row count is a misleading summary.
17 classes, 1019 rows, agreement 93.9%.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio built the votive Office of Our Lady on Saturday (RG 78) and then said
RG 309(a)'s own Mass for it. Both halves were needed: RG 78 keeps the office
and gives it white, RG 309(a) picks among the Missal's five seasonal
formularies. Fixing only the first leaves the day announcing Our Lady and
reading the feria's Mass -- the bug colitur itself shipped briefly and fixed
in its v0.3.0.
C17 439 -> 0 CLOSED -- the largest class between the two engines
C31 103 -> 63 Saturdays that carried both causes; only this one left
C35 69 -> 60 same
C1 35 -> 75 GREW
C1 growing is not a regression and is recorded as such in its own note: the
January Saturdays C17 used to absorb now differ only on the slug vocabulary
C1 already covers, so they land there instead. Rows moved between entries as
the larger cause was removed.
Also in this refresh, from the same session: weeks after Epiphany now count
from the first Sunday after it rather than from 6 January, which took C19
from 35 to 7 -- what remains there is the Holy Family interaction, a different
cause needing RG 17(b) in lectio.
19 classes -> 18, 1493 rows -> 1045, agreement 91.1% -> 93.8%.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio has taken three further corrections colitur argued from the Missal: the
twelve RG 124 colours (red is for Apostles, Evangelists and Martyrs, and
missalemeum had the rule inverted in both directions), Good Friday's black
under RG 128(b) with RG 132, and RG 72's Christmas Time boundary at 13 January
inclusive.
864 rows changed. Two classes closed outright and two moved:
C18 450 -> 0 the RG 124 colours -- the LARGEST single class in this file
C36 46 -> 0 Good Friday's colour
C1 159 -> 35 the season boundary stopped differing
C23 184 -> 230 Good Friday's rows fold back, the split no longer needed
C36 is closed rather than kept at zero because the split it represented has
dissolved: it existed only to hold Good Friday apart while its diff set had a
third member, and with the colour agreeing the rows are the exact pair C23 has
always gated on. Restoring 230 is not a regression, it is the number returning
to what it was before the split.
Cited classes 24 -> 19, divergent rows 2252 -> 1521, agreement 86.6% -> 90.9%.
The percentage is again the least interesting part. C18 alone was 450 days on
which the two engines agreed about nothing and now agree because both apply
RG 124 the same way round.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every previous refresh of this fixture carried lectio's answers TO colitur,
because colitur's data was bootstrapped from lectio. This one carries
colitur's answers BACK. lectio has merged eight field corrections colitur
argued from the 1962 Missal: ubaldus and didacus promoted from commemoration
to III-class feast on the Missal's own calendarium, both August vigils
recoloured violet under RG 128, and both Ember Saturdays' readings, which
lectio had carrying St Thomas's and St Matthew's Masses respectively.
227 of the 16801 rows changed. Three cited classes went to zero and are
CLOSED and REMOVED, citations preserved in the register, the same discipline
C2-C5, C7, C9-C13, C21, C22 and C25 already record:
C27 40 -> 0 the Advent Ember Saturday's readings
C28 40 -> 0 the September Ember Saturday's readings
C37 77 -> 0 the two vigil colours
C37 is worth noting by name: its own note called the shared lineage "the
sharpest demonstration in the project", colitur and lectio and missalemeum all
carrying the same two wrong colours. Two of the three now carry the right one.
C38 NARROWED rather than closed, and the distinction matters. Its identity
half is gone -- lectio ranks both saints class-3 now -- but the same 70 rows
still differ on readings, because both saints take their Mass from a Common
and lectio has no Commons concept to resolve one. It is gated on
diffs = [First_f; Gospel_f] exactly, which is C18's shape. It cannot close
without lectio gaining a capability, which is a feature rather than a fix.
Net: 24 cited classes to 21, 2252 divergent rows to 2095, agreement 86.6% to
87.5%. The number that matters is not the percentage though -- it is that
those 227 days now agree BECAUSE BOTH ENGINES FOLLOW THE MISSAL, where before
they agreed because both inherited the same errors from Divinum Officium.
That is the failure mode CLAUDE.md's lineage note describes, resolved rather
than documented, for the first time in this project.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A user-supplied overlay is the only sexp this engine reads that a human writes
by hand, and two of Celebration.t's eight fields carry nothing such an author
can meaningfully supply: citations is always empty for a local feast, since
citations come from the rite's lectionary and never from calendar data, and
layer merely repeats the overlay file's own id.
Requiring both made the commonest first mistake -- omitting them -- fail with
'lib/kernel/celebration.ml.t_of_sexp: the following record elements were
undefined: citations layer', which names a source file the author will never
open and does not say what to write instead. A minimal local feast needed 12
lines, two of them noise.
Overlay.load now fills each field only where ABSENT, so an explicitly stated
value always wins: an overlay may legitimately name a layer different from its
own id, and defaulting must not silently overwrite that. A test pins both
directions.
Deliberately scoped to overlays. Layer.load, which reads the shipped
sanctoral, is untouched and stays strict -- that data is the project's own,
every field of it is asserted by tests, and a missing one there is a defect
rather than a convenience.
Parse failures also stop naming kernel source paths: the five prefixes that
actually reach a user are rewritten into the vocabulary of the file being
edited, and anything unrecognised passes through verbatim rather than being
reworded into something possibly wrong.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
"113. Commemoratio de Tempore fit primo loco. In admittendis et ordinandis
aliis commemorationibus, servetur ordo tabellae praecedentiae." Only the
second sentence was implemented, adopted on ef-rg16a as the admission and
ordering criterion. The first -- the commemoration OF THE SEASON is made in
the first place -- was not, so ordering ran through band alone for every
commemoration including the seasonal one.
Reconciled with RG 110(c), which adds the inseparable Peter/Paul pair "ante
omnes alias commemorationes". Both rubrics are primary-source certain: the
photographic scans' OCR is illegible at RG 110(c), but docs/research/LT.txt,
the electronic transcription, carries the Rubricae Generales complete, and
O'Connell's footnote 39 turns out to render that clause exactly rather than
gloss it. They share a technical term. RG 113's own sentence fixes the sense
of "aliae commemorationes" as the ones other than the de Tempore one it has
just placed primo loco, and RG 110(c) sits three paragraphs earlier in the
same code. Read consistently, the pair heads the OTHERS, not the whole list:
season, then the pair, then the table order. So this runs after
rg110_additions, and List.partition's stability keeps the pair adjacent while
the seasonal commemoration steps in front of it.
Blast radius, two full 1583-9999 sweeps diffed: 3 533 days, every one
ORDER-ONLY -- the commemoration set is identical on both sides of every
changed day, checked as sorted multisets, zero days where anything else
moved. All are 22 February with chair-of-st-peter observed. Exactly the
population measured before the rule was written.
The order-sensitive test row is in test_precedence_ef.ml's admit_cases, which
is still the only place in the suite that asserts commemoration order at all.
It needed a mixed-origin candidate: every other candidate in that table is
origin = Temporal by default, so the rule is an identity on them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Both carried rank Class3 with status Commemoration_only, bootstrapped from
lectio, which inherits it from missalemeum, which is generated from Divinum
Officium. The Missal's own universal calendarium ranks both "III classis"
outright, with no commemoration rubric: "S. Ubaldi Ep. et Conf., III classis"
(16 May) and "S. Didaci Conf., III classis" (13 November), corroborated in
the second scan and in a published 1962 Ordo.
Universal by construction, not by assumption: the calendarium carries no "pro
aliquibus locis" marker in its 573 lines while the wider Missal carries 79 of
them elsewhere. Each saint also has his own Mass entry in the Proprium, which
a bare commemoration never has -- a commemoration carries an oration only.
Found by auditing ALL 290 fixed-date entries against that calendarium, day by
day, anchored on the Roman calends column because the arabic day column is
OCR-wrecked and the dominical-letter column vanishes in some months. These
two were the only status defects in the whole file. The 15 days the automated
pass could not cover were read by hand and all agree.
Neither needed a proper authored: each takes its Mass from a Common with only
its own Oratio proper, per its own rubric at its own date. ubaldus shares
patrick's Common exactly. didacus needed one newly transcribed --
common-of-a-confessor-not-a-bishop-2, the "Altera Missa" Iustus, 1 Cor 4:9-14
and Luke 12:32-34, read independently in both scans, which agree, so the
page-image third reading this file's method requires on disagreement was not
triggered.
Blast radius, full 1583-9999 sweep, every day classified: 13 036. That is
5 564 ubaldus observed, 7 196 didacus observed, and 276 where the Ascension
Vigil still wins on 16 May but its single RG 111(c) slot passes from
rogation-wednesday to ubaldus -- correct and already pinned, since the
Rogation days are the MINOR Litanies and so an ordinary commemoration, which
RG 113's table order ranks below a III-class feast. Cited as C38 (70 rows)
and M30 (3); C8, C17 and M18 shrink accordingly, each with the reason
recorded rather than the count silently adjusted.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 128, transcribed in the rules register since 2026-08-11, gives violet to
"vigils of II and III class outside Paschaltide". The Vigil of the Assumption
(14 August, II class) carried White and the Vigil of St Lawrence (9 August,
III class) carried Red. Both are outside Paschaltide, so both are violet. The
Ascension's vigil is untouched: it is the one II-class vigil inside
Paschaltide and was already correctly white.
The rule had been sitting in the register, disagreeing with the shipped data,
since before the data was bootstrapped. It was invisible because colitur,
lectio and missalemeum all carried the same two wrong colours -- colitur was
bootstrapped from lectio, lectio's ini is generated from missalemeum, and
missalemeum uses Divinum Officium's data files. Three sources agreeing is one
source counted three times, and the differential and the oracle both went
green on the shared error. It surfaced only against witnesses outside that
tree: a published 1962 Ordo flagged the Assumption's, and O'Connell, The
Celebration of Mass 4th ed. (1964), section 4(c) and footnote 169, gave the
general rule and with it St Lawrence's, which the Ordo omits entirely.
Two golden pins added, in years where each vigil is actually observed rather
than impeded. The divergence this creates against every DO-lineage source is
cited in both allow-lists: C37 (lectio, 77 rows -- 92 possible minus 15 where
the vigil falls on a Sunday) and M29 (missalemeum, 3 rows in 2026-2027 and 2
in each of 2035 and 2038). Both gated on the exact slugs or dates and on
colour alone, so any other divergence on those days would still surface.
Also recorded, no code change: rose on Gaudete and Laetare stays. RG 131 is
permissive ("adhiberi possunt") and the Ordo prints violet, so the colour
field carries a permission rather than a requirement on those two Sundays a
year -- a decided position now, not an unexamined one.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 128(b)'s own exception list, transcribed in the rules register and
primary-source-verified since Task 16, excepts "Actione liturgica feria VI
in Passione et Morte Domini usque ad Communionem exclusive" from the violet
that otherwise runs from Septuagesima to the Easter Vigil. RG 132 assigns
black there. The register recorded this as an acknowledged gap rather than a
disputed reading -- the code comment beside the Holy Thursday exception says
so in as many words -- and it is closed here.
Found by two witnesses outside the Divinum Officium lineage that colitur,
lectio and missalemeum all share: O'Connell, The Celebration of Mass, 4th ed.
(1964), revised throughout to the Codex Rubricarum (1960) and the 1962
Missal, and a published 1962 Ordo. Both say black. So does missalemeum's own
colour set, which orders the day "bv".
The rubric is per-action ("usque ad Communionem exclusive") and the model
emits one colour per day; black is the day's principal one, the same
acknowledged limit RG 126's Palm Sunday blessing already carries.
Blast radius: one day per year, colour only, across the whole 1583-9999
domain. Two golden pins re-pinned. Good Friday's 46 differential rows leave
C23 for a new C36 gated on the exact triple [First_f; Gospel_f; Colour_f],
so C23's own exact-pair gate still means what its note says for its
remaining four days: 184 + 46 = 230, no residue, both counts from the
comparator rather than hand-counted.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
M11 was the one entry in the missalemeum allow-list adjudicated against
colitur, and it was right to be. The calendarium directs "Com. octavae
Nativitatis" under each of 26, 27 and 28 December (LT.txt:5454-5459) and
RG 109(c) makes a commemoration of the days within the Octave privileged.
colitur emitted none.
The cause was a reasoning error stated in Temporal_ef's own comment:
"26-28 Dec are Stephen, John and the Innocents, hence sanctoral". Their
office is sanctoral -- those II-class feasts win the day -- but they
remain days WITHIN the Octave (RG 67, "Dies infra octavam sunt II
classis"), and colitur built them as generic Class4 ferias. With no
octave-day candidate there was nothing for the feast to be commemorated
over. Building days 2-4 exactly as days 5-7 always were makes the
commemoration fall out of the existing machinery rather than needing a
special case: the feast wins on rank, the octave day loses, RG 109(c)
privileges it, RG 111(c)'s single slot goes to it.
A second, independent defect surfaced while fixing it. The octave-day
branch covered 29-31 December unconditionally, so a Sunday falling there
got a feria identity -- where RG 69 says a Sunday occurring 26-31
December SEMPER keeps its own office and commemorates the feast.
Confirmed against the oracle rather than argued: missalemeum shows
"Sunday in the Octave of Christmas" on 2035-12-30, a Sunday, where
colitur showed a feria. Both named and anchors now exclude Sundays;
anchors had asserted the octave slugs unconditionally and was wrong the
same way.
Blast radius, full sweep against the pre-change binary: 25251 days, every
one 26-31 December, exactly two shapes, zero unclassified -- 21653
carrying an octave-day slug and 3598 that are all Sundays, the RG 69
correction.
Allow-lists: M11 closed and removed. C6 narrowed 138 to 119, nineteen of
its days having changed temporal identity rather than verdict. M15
widened 18 to 22 -- both streams now commemorate the octave, but the
candidate is temporal-origin and carries no English name to match against
missalemeum's own, this entry's documented limit on four more dates.
Stated rather than glossed: this trades a substantive divergence, colitur
omitting a commemoration the Missal directs, for a comparator limit, the
commemoration existing but not being name-matchable. That is a real
improvement, but what the oracle now proves is that both streams
commemorate something on those days; the identity rests on the rubric and
the calendarium.
Register: section 6.17.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Commemorations were checked against an oracle in the 2026-2027 fixture
alone -- 227 days out of roughly 968 000 commemoration-bearing days in the
domain, about 0.02%, and by some distance the thinnest axis in the
project. Layer 3's 16 801 days compare none of them and never can:
lectio's trailing "+slug" tokens are its losing candidates, not an RG 111
admitted set. About a third of all days carry a commemoration and admit is
the most intricate code in the engine, so the gap was worth closing.
2035 and 2038 now compare presence, count and identity, reusing the
2026-2027 comparator's own identity_diff with the same limits: a
sanctoral-origin commemoration resolves by colitur's English name, a
temporal-origin one cannot and is reported as a counted, classified
outcome rather than a silent pass. Observed-identity stays out
deliberately -- that is M18's separate limit and folding it in would
flood these windows with a class that says nothing about commemorations.
36 differing rows across 730 days, every one classified. The great
majority of commemoration days agree, which is the headline. Four rows
are not calendar differences at all but name forms -- colitur's "The
Seven Sorrows of the Blessed Virgin Mary" against the oracle's "For Our
Lady of the Seven Sorrows", "The Major Litanies" against "Pro
rogationibus" -- which is this month's new entities appearing correctly
in windows that had never seen them.
One finding is genuinely new. On 2038-06-02 the Ascension Vigil admits
one commemoration under RG 111(c) and two candidates compete:
rogation-wednesday and sts-marcellinus-peter-erasmus. Both are
Commemoration_only, so neither has a row in RG 91's table, band returns
unclassified for both, RG 113 has nothing to order by, and admit falls
through to its alphabetical slug tie-break -- which decides the day.
missalemeum keeps the saints.
That tie-break was already documented as existing only between two
Commemoration_only candidates; this is its first live,
externally-witnessed instance, and the decision is made by slug alphabet,
which is arbitrary and not rubrical. There may be a real rule going
unused: RG 113's first sentence is "Commemoratio de Tempore fit primo
loco", and a Rogation is arguably de Tempore -- which turns on whether
the entry should carry subject Temporal rather than the Saint it
inherited from the Major Litanies precedent, a question the
movable-date-specs spec deferred as behaviourally inert and which
demonstrably is not.
Not decided here. Adopting whichever answer matches the oracle is exactly
the reasoning this project refuses, so it is recorded and cited for a
task that can weigh it on its own terms.
Register: section 6.16.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`--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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Step 4's blind spot is now closed as far as the method allows. 2038
witnessed two of the five Common-routed saints; isidore-of-seville is the
third and the last that any year in 2005-2050 can reach, being observed
only in 2008, 2035 and 2046. He is also the only saint routing through
common-of-doctors, so that Common had never been compared against any
external source in any window.
Three independent agreements on it: the scan (Commune Doctorum,
scan1:41878ff, 2 Tim. 4, 1-8 / Mt. 5, 13-19), colitur, and missalemeum's
own 2035-04-04 row. Asserted by date in its own test rather than folded
into an aggregate count, so a regression there cannot hide.
The remaining two saints are unreachable by construction, not for want of
a fixture: gregory-the-great and patrick both sit in March and are
impeded by Lent's privileged ferias in every one of the 46 years the
differential covers. The blind spot is closed to its limit, not closed
absolutely, and the register says so.
The comparator is generalised rather than copied -- compare_live takes a
fixture and a year -- so a fourth window would cost a fixture and a count
list.
2035 differs on 3 days, both families pre-existing and already cited.
2035-04-02/03 are the Joseph/Annunciation transfer pair landing in the
opposite order from missalemeum's: neither stream loses a feast, the
sequence differs, which is C14's own RG 96 collision finally getting an
oracle window. 2035-12-30 is the Sunday within the Christmas Octave
against colitur's numbered octave-day slug, C6's family. Both are
recognised by slug rather than by date, so a year exhibiting only half
the shape would surface as unexplained rather than be quietly absorbed.
393 tests green with the exhaustive sweep.
Register: section 6.15.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 78's office has been built since ef-bvm-saturday: an otherwise
unoccupied IV-class Saturday is de sancta Maria in sabbato, white, IV
class. The Mass stayed the feria's, so the day observed Our Lady and then
read Colossians on the parable of the weeds. That is M26 shape 1.
RG 309(a) is the rule -- the Masses the Missal assigns "iuxta temporum
diversitatem" -- and it prints five, each under its own season heading.
All ten citations are transcribed from the scans with line numbers in the
source. Masses II-V were confirmed twice, against the scan and against a
live missalemeum capture of 2038 which names them and agrees on both
readings. Mass I rests on the scan alone: no oracle year to hand has an
Advent Saturday carrying this office. One witness where the others have
two, and the code says so rather than blurring it.
The mapping looked like the hard part and dissolved. The Missal's ranges
are cut by civil dates and movable feasts, not by this rite's seasons --
II ends mid-season at the Purification, III spans four seasons. But
measured against what a IV-class Saturday can actually be, it reduces to
a seasonal mapping plus one date test at 2/3 February, with no Easter
arithmetic at all: III's end to IV's start leaves the Triduum unassigned,
unreachable because Holy Saturday is I class; IV's end to V's start
leaves Pentecost week unassigned, also unreachable because that week's
Saturday is an Ember Saturday. Time after Pentecost then begins exactly
at Trinity, exactly where Mass V begins. Both gaps verified by sweep --
zero IV-class Saturdays in either across 2000-2059 -- not inferred.
The office is identified structurally, never off the slug, which it
deliberately shares with the ordinary feria: subject Bvm, IV class,
Saturday.
Blast radius: 87611 reading rows over the full domain, every one a
Saturday.
Allow-lists: M26 27 to 6, shape 1 gone, leaving only the Christ-the-King
residue argued in colitur's favour last commit. The 2038 suite's
BVM-SATURDAY-MASS class is removed rather than zeroed -- an id with no
rows must not be declared. M18 355 to 376. C17 widened 397 to 445,
taking its own Saturdays back from C1, C19 and C25, since one cause
should not be filed under two ids; C31 89 to 103; C25 closed at 0 rows,
its Holy-Name-week Saturdays now answered before step 3 is reached.
Three unit tests pinned the old behaviour and are corrected rather than
re-pinned: two asserted the day "keeps its ferial Mass", which was the
honest thing to assert while this was unimplemented. The third still
tests what it always did -- that step 4 refuses a temporal office -- and
only what answers instead has changed.
Register: section 6.13.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Not the II-class feast of 15 September, which colitur has had since the
bootstrap, but the other Seven Sorrows: the movable commemoration of
Passion Friday, of which colitur had no trace at all.
The 1962 calendarium lists it at the foot of March, below the fixed
dates, exactly where a movable entry belongs (LT.txt:5056): "Feria VI
post dominicam I Passionis: Commemoratio septem Dolorum B. Mariae Virg."
Note the word -- Commemoratio. Under the 1960 rubrics it is a
commemoration, not a feast, so it can never win the day.
Friday after Passion Sunday I is Easter-9, confirmed independently by
both dates the oracle allow-list already disputed: Easter 2026 is 5 April
and M16's date is 27 March; Easter 2027 is 28 March and M13's is 19
March. It could not be built before Date_spec grew Easter_offset -- the
same wall Rogation Wednesday hit, and the second entity that work has now
unblocked.
M16 is re-adjudicated and its verdict REVERSED, from missalemeum to
colitur. The entry existed because colitur admitted St John Damascene
where missalemeum showed the Seven Sorrows, colitur having no candidate
to admit. Both streams now name the Seven Sorrows and that mismatch is
gone. What remains runs the other way, and the rubric is explicit: RG
111(d) admits TWO commemorations on a III-class day, RG 113 puts the
seasonal one first, and RG 114 omits only what exceeds the number. 27
March 2026 is a III-class privileged Passiontide feria; colitur admits
exactly two, the Seven Sorrows and John Damascene, a real III-class
universal feast genuinely falling that day. missalemeum admits one and
reports John Damascene as displaced, dropping a commemoration the rubric
allows.
That makes four divergences adjudicated colitur where missalemeum drops
or mis-assigns something -- Monica's Epistle, Perpetua's Common, Christ
the King's week, and this. Recorded as a pattern rather than four
coincidences: the oracle stays a comparison instrument, but its error
rate on adjudicated cases is no longer negligible.
M13 is untouched and stays open: 19 March 2027 is a different dispute,
whether Joseph is displaced from Passion Friday at all, and on a I-class
day RG 111(a) admits one privileged commemoration which the feria takes.
Blast radius, full 1583-9999 sweep against the pre-change binary: 7874
days, every one carrying the new commemoration, zero unclassified, line
counts equal both sides.
Register: section 6.12.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 299 located (scan1:1096-1098), which does two things at once.
First it closes a standing gap: the ferial-Mass selection rule was
recorded in the register as unconfirmed against the primary source, and
step 3 said so in its own comment -- "lectio's own observed behaviour,
not a confirmed Missal citation". It is confirmed now. "In reliquis
feriis dicitur Missa dominicae praecedentis, nisi a rubricis aliter
provisum sit", restated in the propers as "...nisi propria Missa
assignetur".
Second, that escape clause splits M26 shape 2 in half, which is why the
shape was never resolvable as one thing.
Shape 2(a), the week after Trinity, is fixed. Trinity is the temporal
identity of its own Sunday, so a plain walkback lands on Trinity's Mass
-- but the Missal prints a named formula for the following week, "Missa
dominicae I post Pentecosten" (scan1:21758), precisely because the feast
has taken that Sunday's own. Those ferias now resolve at step 2 from it
and never reach the walkback.
Shape 2(b), Christ the King's week, turns out not to be a bug at all. No
proper Mass is assigned there -- checked directly, no rubric precedes
that Mass heading (scan1:39163ff) -- so RG 299's default governs and "the
Mass of the preceding Sunday" genuinely is Christ the King's own, which
is what colitur emits and missalemeum does not. Kept open only because no
rubric specific to that week was located either way; the verdict is now
argued from the primary text rather than resting on oracle disagreement.
No Thursday in the slug list: Corpus Christi is Easter+60, which IS that
week's Thursday every year, so the ferial slug never exists. Found by the
bootstrap's own reachability guard refusing to emit a key no Temporal_ef
slug can match, not reasoned out in advance.
Allow-lists: C35 opened (69 rows, verdict colitur -- lectio still walks
back); C17 narrowed 406 to 397, nine Saturdays moving to C35 because they
carry both a citation and a colour difference, with C35 gated to require
the citation so a colour-only Saturday still falls to C17; M26 narrowed
30 to 27; M18 widened 352 to 355, the same three days keeping only their
observed-identity difference.
Register: section 6.11.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Date_spec carried one variant, Fixed(month, day), and its own header said
Sunday- and Easter-relative forms would arrive with the OF sanctoral. They
arrive early because two things needed them at once: a user-supplied
overlay carrying a local movable feast had no way to express "the first
Sunday of October", and Rogation Wednesday's commemoration has been
recorded as architecturally blocked since 2026-08-13.
That blockage turns out to have been a premise, not a fact. The register
concluded the Rogation "is not Commemoration_only sanctoral data by
nature... it needs a genuinely THIRD kind of thing this architecture has
no name for" -- but the reasoning rested on there being "no civil (month,
day) pair to anchor a Fixed entry to". That is a statement about
Date_spec's expressiveness. With Easter_offset the entity is ordinary
sanctoral data, the identical shape Add major-litanies already ships,
differing only in how the date is written. No third channel was needed.
The register is corrected rather than left contradicting the code.
Two variants: Easter_offset of int, and Nth_weekday of {month; nth;
weekday} with negative nth counting from the end. Fixed's sexp
representation is untouched, so all 327 sanctoral entries and the whole
overlay parse unchanged. Both are re-validated in t_of_sexp through their
smart constructors, extending the idiom Fixed already used -- the failure
that guards against is invisible, a spec deserialising into something
that silently never resolves and a celebration vanishing with no
diagnostic.
Easter is supplied by the rite, not computed in the kernel. Computus
ships Gregorian and Julian both, and picking one here would hard-code a
Roman assumption into rite-agnostic code and be silently wrong for a
Julian-reckoning rite. Rite.t gains an easter field; Rite_ef supplies the
Gregorian one.
Layer keeps a split index. Fixed entries stay in the year-independent
(month, day) table, which preserves the fast path and the "30 November
counted twice in a 371-day span" behaviour validate.mli documents for St
Andrew. Movable entries have no year-independent key by construction --
the same spec lands on a different (month, day) each year -- so they
resolve per civil year into a rata-die table. index_by_date is removed
rather than kept as an alias: two ways to index a layer, one of which
silently ignores movable entries, is the trap this change exists to
avoid.
Two domain-edge defects, both found by the suite rather than reasoning. A
liturgical year is Advent-anchored, so resolving civil year y names y +/-
1, and at the edges those are 1582 and 10000, which Computus correctly
refuses by raising. The ceiling surfaced through the domain-ceiling test,
the floor through `colitur day 1583` failing outright. Fixed once, in
Layer.index, the single point that calls the rite's easter -- a clamp
repeated per caller would have been two places to get wrong a third time.
Rogation Wednesday: RG 87 assigns the Litaniae minores to the Monday,
Tuesday AND Wednesday before Ascension (scan1:691); colitur built the
first two from the start and never the third. RG 88 keeps it out of the
Office, so the Ascension Vigil remains observed. RG 89 routes it through
the Major Litanies' rules, but RG 109's closed list names only maiores,
so by RG 107 it is an ORDINARY commemoration -- confirmed in the output,
not assumed. Name from the Missal's own "feria IV" (scan1:20495-20497),
violet to match the Monday and Tuesday offices.
Blast radius, full 1583-9999 sweep against the pre-change binary: 2257
days, every one the same shape, zero unclassified, line counts equal both
sides. The complement is the informative half -- in the other 6160 years
the Rogation does not appear at all, because RG 111 admits one
commemoration on a II-class day and an impeded feast takes it under RG
113, a Commemoration_only candidate having no row in RG 91's table. Both
shapes are pinned.
Allow-lists unmoved: layer 3 compares no commemorations and is blind by
construction, and layer 4's 2026-2027 window has a competing feast on
both Wednesdays, so the Rogation is capped out in exactly the years the
oracle can see. C8 is unchanged, asserted by its own count pin.
Not built: Sunday-relative specs, which nothing needs yet, and the
--overlay plumbing that would let a user actually supply a local movable
feast. Nth_weekday therefore ships with synthetic and property coverage
only; inventing a shipped entry to exercise it would be fabricating
calendar data.
Register: section 6.10.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Register section 6.7 recorded that step 4 of the reading chain -- the
Common route -- had no external witness of any kind, and proved it by
mutation: corrupting a Common citation left both the 16801-day
differential and the 730-day oracle green. This closes that.
2038 is the only year in 2005-2050 in which two of the five
Common-routed saints are the observed office (Perpetua and Felicitas on
6 March, Frances of Rome on 9 March). 365 days were captured live, one
request per day, zero failures.
A separate fixture, not more rows on the existing one. sources.md
already records that the live endpoint has drifted from lectio's
archived 2026-2027 snapshot; merging two versions of one source behind a
single set of expectations would make any future disagreement
unattributable -- calendar or drift, no way to tell. Nothing in the 2038
comparison is checked against the older fixture, and its provenance file
says so, along with the fact that a live capture is not reproducible
byte-for-byte on demand the way a snapshot-derived one is.
338 of 365 days match. 27 differ, every one of them in a named,
already-adjudicated class, none unexplained: 13 the BVM Saturday votive
Mass (M26 shape 1), 7 colour (M21), 2 Judith (M28), 2 Christ the King's
week (M26 shape 2b), 1 Christmas multi-Mass (M27), 1 Perpetua's Common,
1 new. Each was decided in a different year, so 2038 re-confirms them
independently -- the point of a second window is not new rulings but
evidence the old ones are not artefacts of their own year.
Two findings came out of it. Perpetua and Felicitas: missalemeum serves
the Common of Virgins and calls them "Virgins and Martyrs" in its own
oration, where the Missal directs "Missa Me exspectaverunt, de Communi
non Virginum I loco" (scan1:27634-27635). Both Commons share the Introit
Ps 118:95-96, which is exactly why the calendarium qualifies its
direction, and both women were mothers. Verdict colitur; the mechanism
is located on missalemeum's side, not merely asserted. Passion Tuesday:
the Missal prints "Dan. 14, 27 et 28-42" (scan1:11106) and colitur
reproduces that two-part form where missalemeum collapses it to
27-42. Same verses; the convention is deliberate, appearing also in the
Seven Sorrows and the Common of Non-Virgins. Verdict colitur, cosmetic.
Citations are notation-normalised before comparing, as layer 3 already
does: Ecclus/Sir, Joann/John, Luc/Luke, Matth/Matt. Each pair was added
because a real row needed it, and no target contains its own source as a
substring, so the set is idempotent.
Not compared, stated rather than left to be found: commemorations and
observed-identity. That machinery is built around a date-literal
28-entry allow-list specific to 2026-2027, and re-deriving it for a
second year is its own task. 2038 compares rank, colour, Epistle, Gospel.
The extractor's day count was hardcoded to 730, which silently forbade
any other window. It is now a parameter defaulting to 730, so the
existing documented command keeps its guard and a partial fetch still
fails loudly instead of producing a short fixture that passes a
comparison it never ran.
Teeth, by re-running section 6.7's own experiment: corrupting the Common
of Non-Virgins II now reddens four tests, two of them external-oracle,
where the same mutation previously reddened neither oracle layer.
Residual: isidore-of-seville is still unwitnessed and needs 2035 or
2046; gregory-the-great and patrick are never the observed office in any
year 2005-2050, so no fixture in that range can reach them.
Register section 6.8 and 6.9.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Layers 2 and 5 for the reading citations, which until now had coverage
from layers 3 and 4 only -- both oracle-bounded, at 2050 and 2027.
test_validate.ml said so in its own words ("No fixture here exercises
citations") and test_golden.ml's describe pinned twelve fields, none of
them a citation. 358 tests -> 369, and 370 under the sweep.
Layer 2 as two Validate checks rather than standalone QCheck properties,
so they ride the existing prop_invariants and the exhaustive sweep
instead of duplicating that machinery. "citations" asserts a day's parts,
sorted, are exactly [First; Gospel] -- one condition carrying three
invariants: zero or two but never one, nothing outside First and Gospel
since the chants are deliberately unbuilt, and no part twice, which a
bare cardinality test would miss. "citations-unresolved" is the separate
coverage half: the chain fell through every step. Both are gated on the
rite producing some citation somewhere, so a rite whose lectionary is not
built is held to neither and the synthetic fixtures stay unaffected.
The result over the whole domain: every day of every liturgical year
1583-9999 resolves exactly one Epistle and one Gospel. Mutation-proved
live rather than merely silent -- treating a well-formed pair as
malformed reddens the suite with real days showing [first,gospel].
citations-unresolved has no live witness at all, which is why
test_validate.ml gains five fixtures driving both checks through a new
?readings override, including the gate itself.
Layer 5 as six pins, one per chain step, so a regression names the step
that broke. Every literal was read out of the scans before being
compared with colitur, per that file's standing rule; scan lines are in
each pin's comment. Two of the six cover step 4, and the plan's own dates
for steps 1 and 4 were wrong: 2030-01-13 is a temporal day (Holy Family
on the generic Sunday slug) and would not have exercised step 1, while
2038-03-08 is John of God, who has a full proper Mass and never routes
through a Common -- so he becomes the step-1 pin instead.
A separate formatter rather than widening describe: widening would
invalidate all thirty existing literals at once and the only practical
way back would be pasting in whatever colitur printed, which is what the
file's header forbids. Restoring them honestly means hand-verifying
thirty more Masses against the scans, a task of its own. Stated rather
than hidden, and defensible because layer 2 now checks the shape of
every day in every year, which is the breadth that widening would have
bought.
Two pins earned their keep immediately. Lent I Monday failed on "Ezek"
against colitur's "Ezech" -- notation only, and colitur matches the
Missal's own abbreviation; layer 3 normalises this away and cannot see
it. The displaced-Sunday feria pins M26 shape 2(b)'s known-wrong
walkback deliberately, labelled as such so a future fix fails loudly,
and established two things M26 had not recorded: both parts come from
the displacing feast, not only the Gospel it cites, and the reference
carries a stray trailing period inherited through the bootstrap.
Blast radius: zero. The CLI never calls Validate, no data file changed,
and 20 years spanning 1583-9998 produce 7301 byte-identical lines
between the pre-change binary and HEAD.
Step 4's blind spot is now measured rather than argued. Corrupting the
Common of Non-Virgins I's Epistle leaves layer 3 (16801 days) and layer
4 (730 days) both green, reddening only one unit test and one new pin.
Both step-4 pins matched the scans on the first attempt -- the first
independent confirmation that route has ever had. It narrows the gap
without closing it: two of five Common-routed saints are witnessed,
isidore-of-seville is not, and gregory-the-great and patrick are
unreachable in any year 2005-2050.
Register: section 6.7 addendum. validate.mli documents both new labels;
liturgical_day.mli's "always empty until Plan 4" was outlived by the
lectionary and is corrected.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Comment and citation corrections found by review, plus one finding that
survived an attempt to reconstruct it. No behaviour change; the suite is
unchanged at 358 green.
The ferial rubric "Diebus ferialibus a 2 ad 5 ianuarii Missa dicitur ut
die 1 ianuarii" was attributed to RG 17(a) in three test comments. It is
not RG 17(a). RG 17(a) (LT.txt:843) fixes only WHEN the Holy Name is
kept -- "celebrandum dominica quae occurrit a die 2 ad 5 ianuarii; secus
die 2 ianuarii". The rubric quoted is a Mass-propers rubric printed
under the Sanctissimi Nominis Iesu heading (scan1:6523-6526, corroborated
scan2:7216), governing what the ordinary ferias of that window say when
they are not the feast at all. Two rules sharing a date window. Fix
round 1 had already corrected this on the data side; the generator's own
comment was right throughout. Only the test comments were stale.
test_differential.ml named bootstrap_lectionary.ml's derived_entries as
the source of ef-holy-name's citations. That binding no longer exists --
it was the mechanism that derived them from ef-holy-name-sunday's
wrongly-widened ini translation, removed in Task 9 because the
derivation was itself the error. They are hand-authored from the scans
now, as holy_name_entries.
M26's shape 1 described its 21 rows as votive Masses of the BVM "on an
otherwise-unoccupied feria, not only a Saturday", splitting them into 4
BVM-Saturday rows and 17 "ordinary weekday ferias". Every one of the 21
is a Saturday, each checked individually against date -d. The error was
not merely verbal: it made the gap look broader than a single cause and
pointed away from the one rule that explains all 21. The sub-groups
differ by season, not weekday, and only in what colitur shows in the
votive Mass's place. One gap -- the office is built (RG 91 entry 27),
the seasonal selection among the five Masses is not.
Recorded, not fixed: step 4 of the reading chain has no oracle witness
at all. This began as a note to write up a live-oracle confirmation for
Felicitas and Perpetua; the reconstruction contradicted its premise. The
sweep ran over 2028, where 6 March resolves as a Lenten feria with the
saint admitted only as a commemoration, so her own Mass is never read
that year. The finding underneath is firmer: only five saints route
through a Common, they are the observed office on five days in all of
2005-2050 (isidore-of-seville 2008/2035/2046, frances-rome and
sts-felicitas-perpetua both 2038), two are never observed in 46 years,
and none falls in layer 4's window. Layer 3 does not cover it either --
lectio resolves the literal "-"/"-" sentinel there, which is what C18 is
gated on, so it confirms only that colitur emits a citation where lectio
emits none, never that it is right. Closing it needs the oracle fixture
extended to 2035, 2038 or 2046; that is a fixture-scope decision.
Register: sections 6.6 and 6.7. CLAUDE.md's layer-blindness list gains
the step-4 entry.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A live-missalemeum sweep over a full liturgical year (2028, 364 days)
returned 333 matching and 31 differing days: 17 already-known or pure
notation, 3 new. All three now adjudicated against the photographic
scans of the 1962 Missal.
The Assumption's Epistle reads "Iudith 13, 22-25; 15, 10" (scan1:35357).
Both engines carried "Judith 13:22-25; 13:15; 13:10" -- a chapter
carry-over defect that re-applied chapter 13 to the second citation and
so invented two verses the Missal never cites. The Seven Sorrows reads
"Iudith 13, 22 et 23-25" (scan1:36938, corroborated at 28468); both
carried "Judith 13:22; 13:25", dropping the range. The third, St Monica,
resolves the other way: the Missal gives 1 Timothy (scan2:32679, heading
and incipit both legible; scan1:29524 agrees) where missalemeum serves
the Common of Holy Women's Proverbs -- colitur is right, no change.
Layer 3 could not have found either citation defect. colitur's data is
bootstrapped from lectio, and lectio is the other side of that
differential, so while both engines produced the identical wrong answer
the comparison held it against itself and reported agreement -- the
shared-lineage blindness CLAUDE.md names, now with a second live witness
beside Holy Thursday's colour. Only an external oracle could see it, and
only outside the committed fixture's own window.
Fixed upstream in lectio, re-bootstrapped here (provenance SHA-256 moved
to 54f6bfc5, verified against the source), fixture regenerated.
The upstream fix inverted layer 3 from blind to load-bearing here:
reverting the Assumption citation now reddens both layers, where before
it would have reddened neither. Verified by mutation, both directions.
Allow-lists:
- C22 closed and removed (128 rows -> 0), not re-adjudicated -- the other
engine was fixed to agree, the same shape as the C2-C5/C7/C9-C13/C21
closures. Its own note had already flagged the Lenten Ember dead-key
bug for upstream; lectio now also fails its build on any unreachable
lectionary key rather than serving a plausible wrong Mass.
- M28 opened (3 rows, verdict colitur), pinning both sides' citation
text rather than the date and diff shape alone, so a different
divergence landing on the same date cannot be swallowed.
- M8's gate widened, expected_rows unchanged at 2: 2027-08-15 carries
both its cause and M28's, and one entry is admitted per day. It stays
under M8, which now requires its own commemoration difference to be
present and admits the citation difference only when it satisfies
M28's two-sided predicate. Neither cause absorbs the other.
layer_m_reason's oracle-row parameter, kept named since M2 closed for
exactly such a future reader, is live again.
Register: docs/research/rules-register.md section 6.6.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three Important findings from coordinator review of 8d1eb25, all fixed.
1. C30's own note claimed its 91-row population "is C25's OWN former
population, before this task narrowed it" -- false. Independently
re-derived against the real resolver (pre-fix vs post-fix binary) and
the lectio fixture: only 44 of the 91 rows were ever in C25 (Wed 6/
Thu 19/Fri 19, matching C25's own fix-round-3 breakdown exactly); the
other 47 (all of Monday/Tuesday, plus part of Wednesday/Friday)
previously matched lectio outright -- both engines independently
landing on the same wrong value -- and are newly allow-listed only
because colitur is now right. Rewritten to the same "both engines
silently agreed on the wrong answer" pattern C33/C34 already used.
2. "Diebus ferialibus" is not RG 17(a) -- RG 17(a) states only the
day-identity privilege; the ferial-Mass rubrics are Mass-propers
rubrics, a distinction the register's own §6.3 already drew before
this task's first pass blurred it in code/allow-list comments. A
further slip: C31/C32 called their own rubric "RG 17(a)'s own next/
third sentence" when it is a different rubric block ~230 lines away,
in Holy Family's (not Holy Name's) own feast propers. Fixed the
attribution chain throughout tools/bootstrap_lectionary.ml,
data/ef/expected-divergences.sexp's C29-C34, and (gitignored, outside
this commit) docs/research/rules-register.md -- closed the §6.3 park,
added §6.5 recording all four citations with corrected attribution.
Quotes and line numbers were already right; only the chain of custody
to RG 17(a) was wrong.
3. M26's own 30 rows classify into two nameable shapes, not "three
confirmed shapes" with an unexplained 23-row residue that was in fact
empty: 21 rows are RG 78's own already-documented BVM votive-Mass
gap; 9 rows are a second, distinct gap -- a week whose own Sunday was
displaced by a movable named feast (Trinity->Corpus Christi, "Missa
Dominicae I post Pentecosten"; Christ the King), where the Missal
requires the displaced Sunday's own Mass to keep informing that
week's ferias but step 3 walks back to the displacing feast's citation
instead -- proven directly by colitur's own citation on the Christ-
the-King rows being literally that feast's own Gospel. verdict open
unchanged (not fixed here); the shape is now named instead of buried.
Minors: section count corrected 1458->1456 (four places); "exactly one
candidate, zero ambiguous" corrected to "correctness rests on ordering,
not uniqueness" (2 of 1456 sections genuinely yield two candidates,
Holy Saturday's own Lectio, both years); the stale "nothing in layers
3-5 compares citations" claim corrected (only layer 5 still doesn't);
M18's note corrected (five new entries not six; ~18 of its remaining
352 rows only agree now because of this task's own fixes, not "already"
before it); "five" corrected to "six" otherwise-unoccupied Saturdays in
bootstrap_lectionary.ml; scan2 quotes softened where OCR-garbled.
Also done outside the git-tracked files: the step-4 (Commons) coverage
gap is now recorded in data/ef/expected-divergences-missalemeum.sexp's
own header, not only the commit body and the (gitignored) report.
dune test: 358 tests green. COLITUR_EXHAUSTIVE_SWEEP=1 dune test
--force: 359 tests green, 95.7s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Layer 4 (test_oracle.ml) now extracts and compares the Epistle/Gospel
citation for every one of the 730 days in the 2026-2027 fixture, the
same axis Task 8 already added to layer 3. tools/
extract_missalemeum_oracle.py gained a structural citation-span
detector (the first "*...*"-wrapped span under 40 chars containing a
chapter:verse-shaped digit pair), verified exhaustively over all 1 458
Lectio/Evangelium sections. 728 of 730 days are comparable; the 2 that
are not (Good Friday, both years) have no such section in missalemeum's
own JSON at all -- a structural limit on the oracle's own side (M23),
not a colitur gap. 451 of 730 days differ and are all explained across
19 cited entries; 0 unexplained. Chain step 4 (the Commons) has no
layer-4 witness: confirmed empirically (a scratch sweep, not merely
re-asserted from Task 6's own report) that none of the 15 Commons/proper
saints is ever the observed office in this window.
The comparison surfaced three genuine colitur bugs in
data/ef/lectionary.sexp, all fixed at the source in
tools/bootstrap_lectionary.ml:
1. Holy Name of Jesus (ef-holy-name-sunday/ef-holy-name) carried a
mis-derived citation, borrowed by analogy from an unrelated ini
section ("ef-christmas-sunday-0", the Sunday-within-the-Octave's own
Mass) on the wrong assumption the two share a Mass. Disproved by
missalemeum's own extraction for both real Holy Name Sundays in its
window, confirmed against the Missal itself, twice, both scans
(Acts 4:8-12/Luke 2:21, not Gal 4:1-7/Luke 2:33-40).
2. RG 17(a)'s own "diebus ferialibus" rubric (immediately following the
Holy Name heading, both scans) was never implemented: 2-5 January
ferias repeat 1 January's Mass, 7-12 January ferias before Holy
Family Sunday repeat Epiphany's, and Holy Family's own week has a
named formula ("Missa Dominicae Primae post Epiphaniam"). All three
fell to step 3's undifferentiated Sunday walkback instead. Fixed for
three 5-weekday families, deliberately excluding Saturday from all
three -- RG 78's BVM Saturday Office wins there unconditionally,
confirmed against missalemeum's own title on this window's own such
Saturdays.
3. Corpus Christi and Sacred Heart (both real, never-impeded I-class
movable feasts) had no lectionary entry at all. Both engines
independently fell back to Trinity Sunday's own citation and
silently agreed on the wrong answer -- the "Holy Thursday violet in
both" shape CLAUDE.md names for exactly this reason.
Because lectionary.sexp is shared with layer 3 (the lectio differential,
2005-2050), these fixes changed colitur's output for the whole domain,
not only 2026-2027. data/ef/expected-divergences.sexp and
test_differential.ml needed matching updates: C1 narrowed 361->173 and
C25 narrowed 57->13 (rows that now resolve correctly moved to new
entries), C16's note corrected (count unchanged), and six new entries
added (C29-C34, 397 rows total, all verdict colitur). test_lectionary.ml
and test_lectionary_ef.ml's own pins were updated to match (entry count
131->148; one step-3 regression test re-pointed to a date that still
genuinely exercises that step).
One entry, M26 (30 rows, verdict open), is a genuine unfixed gap: colitur's
step-3 "resume the preceding Sunday" fallback is its only ferial-Mass
mechanism, and missalemeum's own selection is more granular in three
confirmed ways (a rotating votive Mass of the BVM, a named weekly
formula for at least one week, and later Time-after-Pentecost weeks this
task's own investigation did not narrow to a single rule) -- the same
"needs a season-keyed selection mechanism, new behaviour not new data"
shape data/ef/expected-divergences.sexp's own C6/C25 and Task 6's own
residual concern already name, out of this task's own scope to build.
dune test: 358 tests green. COLITUR_EXHAUSTIVE_SWEEP=1 dune test
--force: 359 tests green, 89.5s.
|