| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The prior commit (ea6e598) memoised efTransferPlan but left the SAME
occupiedByClass1/Or2 O(len(merged)) scan pattern in a second place:
transferIfImpededEF's own fallback path, called from computeEF once per
day for every class-1 candidate the (now cached) plan does not resolve
-- confirmed by CPU profile, not assumed: computeEF.func1 (the old
occupiedByRank closure) was ~62% of BenchmarkDays7EF's total time,
almost all of it inside buildCelebration, called from BOTH efTransferPlan
internally AND this second, uncached site.
Before optimising, established precisely what "occupied" depends on, per
the coordinator's warning that occupancy might genuinely mutate during
planning (efTransferPlan's own `claimed` map suggested as much). It does
not: occupiedByRank asks only "does some OTHER entry's ORIGINAL,
untransferred date (buildCelebration + celebrationDate, which never
considers a transfer) equal d" -- a pure function of (merged content,
year, sel) alone, identical to what the transfer-plan cache already
keys on. `claimed`, by contrast, genuinely mutates during one planning
pass (it tracks which TARGET days a transfer walk has already assigned)
and is NOT part of occupiedByRank's computation at all -- it remains
computed fresh inside efTransferPlan every call, untouched by this
change. These are independent, not the same thing wearing two names:
efTransferPlan's own forward-walk loop already checks both, plus a
third condition (the target day's own temporal class), as separate
disjuncts.
Given that, an occupancy INDEX -- not a second cache, and not a
"we already know the answer" shortcut derived from plan's absence (which
would have been correct for the transferIfImpededEF fallback's own
control flow ONLY by coincidence: it ignores the unconditional All Souls
Sunday-transfer special case that runs before the class-1 check on ANY
rank, so a shortcut skipping straight past it would misfire the moment a
user overlay retagged All Souls class-1, however unlikely on shipped
data) -- is the safe fix: buildEFOccupancyIndex does the same
merged-scan ONCE, into date -> []{slug, rank}, and .occupied does the
exact O(1)-ish lookup + tiny-list filter occupiedByRank always computed,
just precomputed. transferIfImpededEF's own signature, control flow and
All Souls handling are completely unchanged; only what its two closure
parameters read from changed.
The index shares the transfer-plan cache's existing key (year, Selection,
SHA-256 of merged) rather than adding a new one -- both are pure in
exactly those three inputs, built in the same pass, so one key correctly
covers both. Only the plan is copied per call (clonePlan); the index,
which can hold one entry per merged slug (~330 on shipped data), is
returned uncopied and documented immutable-after-construction -- safe
under Go's concurrent-read guarantee since nothing anywhere writes to a
returned index.
New TestEFOccupancyIndexDetectsSameDateClass1Collision covers a path
no existing test reached: two class-1 SANCTORAL entries sharing one
ORIGINAL date (St Joseph/the Annunciation, covered by the prior commit's
tests, collide via HOLY WEEK's temporal precedence on DIFFERENT dates,
never with each other). A synthetic overlay (Compute's own public API,
same style as the existing tests) puts two class-1 entries on the same
otherwise-ordinary date: alone, either is simply observed; both
together, RG 97/98 transfer both forward and the shared date reverts to
its temporal office. Mutation-proved: dropping the index's exceptSlug
self-exclusion (reverted after) made the test fail immediately -- every
class-1 entry saw itself in the index and wrongly self-impeded, even the
single-entry case. TestEFTransferPlanCacheConcurrentUse gained a third,
disjoint key exercising this same collision path from goroutines
alongside the two existing ones; `go test -race` on the whole package is
clean.
Benchmarked (interleaved before/after, same method throughout this
branch): BenchmarkDays7EF drops from ~46-48ms (the prior commit's own
plan-cache-only state) to ~15-18ms/op (allocs 208639 -> 98727, -53%;
bytes 16.1MB -> 4.2MB, -74%) -- roughly a further 3x, ~4.6x cumulative
against the original ~74ms. BenchmarkDaysWeek (OF, which never touches
any of this) is unaffected: ~4.9-5.9ms/op both before and after, with
byte-for-byte identical allocs/bytes in every run -- the ms-level
wobble is machine noise, not a regression.
A fresh CPU profile confirms the new remaining bottleneck precisely:
writeSortedFields/hashMergedForPlan (the cache key's own SHA-256 of
merged, ~330 entries) is now ~35% of total time, because it still runs
on EVERY day (7x/week) to know whether a call is a cache hit, even
though the work it gates now mostly isn't. Not fixed here: hoisting the
key computation itself up to mobile.Days's batch level (mirroring the
first commit on this branch, b6ee8f0) would need Compute's public
signature to accept a precomputed key, a bigger surface change than this
task's scope, reported rather than taken unilaterally.
Output identity re-verified: the same 492-case sweep (both forms, both
UI languages, all four corpora, the leap day/Triduum/Requiem/season-
boundary dates, and the three Joseph/Annunciation years) is
byte-identical (SHA-256-equal) before and after -- the same SHA-256 as
the prior commit's own sweep, confirming zero output drift across the
whole chain. go test ./... and make ci (both build tags,
oracle/differential suite included) are green; go test -race on the
whole internal/calendar package is clean.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
computeEF calls efTransferPlan once per day, but the plan is pure in
(year, the merged sanctoral content, Selection) and identical for every
day sharing those three -- e.g. every date in one mobile.Days week.
Rebuilding it per day was a real cost: efTransferPlan walks every
merged entry looking for class-1 candidates, and for each one it
considers, occupiedByClass1/Or2 (an occurrence check it also uses)
walks merged AGAIN -- confirmed by CPU profile, not just by reading the
code (github.com/lukaszkasprzak/lectio/internal/calendar.computeEF.func1,
the occupiedByRank closure, at ~62% of BenchmarkDays7EF's total time
before this fix, almost all of it inside buildCelebration).
efTransferPlanCached (transfer_plan_cache.go) wraps efTransferPlan with
a small, bounded, thread-safe LRU (container/list + sync.Mutex, capped
at 64 entries -- gomobile may call in from multiple goroutines, and an
unbounded map keyed by year would grow as a user scrolls through
decades). The cache key is (year, Selection, a SHA-256 of merged's full
content): merged is a map, so it cannot be a map key field itself, and
Go's randomised map iteration order means two calls with identical
content can visit it differently, so the hash sorts slugs and, within
each entry, its Fields/Variant keys before hashing, and covers every
field of every entry -- not just Rank/Date, the ones efTransferPlan's
own read path happens to touch today, because which entries even
qualify as class-1 is itself computed from that data, and
occupiedByClass1/Or2 scan ALL of merged, not just the class-1 subset.
Selection is included even though EF date resolution ignores it today
(resolveDate never reads its sel parameter) -- keying on it costs
nothing (four small strings) and protects a future change from
silently poisoning a cache that never accounted for it. The returned
map is always a fresh copy (clonePlan), never the cached instance, so
sharing it across goroutines needs no further synchronisation.
Verified the key is complete rather than trusted: with the content hash
temporarily dropped from the key (mutation test, not committed),
TestEFTransferPlanCacheInvalidatesOnOverlay failed immediately -- a
plan warmed for the shipped 2008 calendar was wrongly served back for
the same year with a user overlay applied (the Annunciation suppressed,
which changes where the RG 96(a)/97/98 collision sends St Joseph: 31
March instead of 1 April, empirically confirmed against the pre-cache
code before the test was written). TestEFTransferPlanCacheInvalidatesOnYear
is a lighter companion covering the year field. TestEFTransferPlanCacheConcurrentUse
hammers the cache from 12 goroutines across two different keys and
reasserts correctness afterward; clean under `go test -race`.
Benchmarked (interleaved before/after, same method as the readings.Prepare
commit, to control for machine thermal drift): BenchmarkDays7EF drops
from ~48-51ms to ~32-35ms/op (allocs 253810 -> 208639, -18%; bytes
28.1MB -> 16.1MB, -43%), roughly a third faster. BenchmarkDaysWeek (OF,
which never calls efTransferPlan at all) is unaffected, ~3.5-4.1ms/op
both before and after -- within noise, confirming this change is
EF-only as intended.
EF remains well outside OF's range (~32ms vs ~4ms), and a fresh CPU
profile after this fix places the dominant remaining cost precisely: it
is the SAME occupiedByClass1/Or2 pattern, but living OUTSIDE
efTransferPlan -- transferIfImpededEF's own fallback path, called once
per day for every class-1 candidate NOT already resolved by the (now
cached) plan, i.e. the ordinarily-unimpeded ones (~15-20 of them),
each triggering another O(len(merged)) scan. That call site was not
part of what this task named, and memoising it is a materially
different change (it is keyed per-candidate, not once per day), so it
is reported here rather than folded into this commit.
Output identity re-verified: a 492-case sweep of mobile.Day/mobile.Days
(both forms, both UI languages, all four corpora, a leap day, the
Sacred Triduum, a Requiem day, Christmas/Pentecost/Assumption/All Souls
windows, and the three Joseph/Annunciation transfer-collision years
this fix specifically touches -- 2008, 2035, 2046) produced
byte-identical (SHA-256-equal) JSON before and after. go test ./... and
make ci (both build tags, oracle/differential suite included) are
green.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 97/98 require a year's impeded I-class transfers to be resolved together.
transferIfImpededEF resolved each one independently, so when an early Easter
impedes both St Joseph (19 March) and the Annunciation (25 March), both walked
to the Monday after Low Sunday -- and St Joseph, losing pickEF there, was
observed on NO day of 2008, 2035 or 2046 at all. He appeared only as a
commemoration in the Annunciation's tail.
RG 96(a) settles it outright, and it is not a tie-break: the Annunciation
transferred after Easter has a sedes propria -- "quando est transferendum post
Pascha, transfertur, tamquam in sedem propriam, in feriam II post dominicam in
albis". It claims the Monday by law; the rest queue in RG 98's order (table
position, then the office impeded first) and walk around it.
New efTransferPlan resolves the whole year's transfers as a set and hands each
feast its target; transferIfImpededEF keeps the single-candidate walk and All
Souls. The stale doc comment recording this as a known unfixable limitation is
replaced -- its reasoning argued RG 97/98 tie-breaks, when RG 96(a) decides the
only collision the universal calendar actually produces.
2008-04-01, 2035-04-03, 2046-04-03 ef-easter-2-tuesday -> St Joseph
Matches colitur on all three. Mutation-tested: bypassing the plan reddens the
new test on all three years.
fix(ef): 13 January's Baptism is omitted when Holy Family falls on it
The Holy Family Mass propers, verbatim in both photographic scans of the 1962
Missal:
"Si festum S. Familiae occurrerit die 13 ianuarii, Missa dicitur de festo
S. Familiae, sine commemoratione Baptismatis D.N.I.C., et sine
commemoratione dominicae."
Sine commemoratione: the Baptism is omitted outright, not merely outranked, so
the candidate is dropped rather than left in Others. Structurally the same
answer RG 91 entry 14 gives (Festa Domini II classis: primum mobilia, deinde
fixa), but the propers' rubric is the direct authority for the omission.
lectio kept the Baptism as the observed office on all seven such years in
2005-2050 -- 2008, 2013, 2019, 2030, 2036, 2041, 2047 -- and never observed the
Holy Family Mass on 13 January at all. It already had the Holy Family Mass on
every other Sunday after Epiphany, so this was the one date it could not reach.
13 rows of the 16801-day differential fixture change; colitur agrees with all
of them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 17(a): "festum Ss.mi Nominis Iesu, celebrandum dominica quae occurrit a die
2 ad 5 ianuarii, secus die 2 ianuarii." The Most Holy Name is kept on the
Sunday falling 2-5 January and, when no Sunday falls in that window, on 2
January instead. lectio built neither shape, so the day was an ordinary Sunday
within the octave and read Gal 4:1-7.
The FALLBACK matters more than it looks. The window is four days wide, so in
about three years in seven no Sunday lands in it -- in those years there was no
Holy Name office at all, a missing II-class feast rather than a misnamed one.
The test pins both shapes, checks the fallback does NOT also fire in a year
where the Sunday carries the feast, and asserts across 2005-2050 that every
year gets it exactly once by one shape or the other.
Separately, the ferial days of that window get their own Mass. The Missal's
rubric at the feast says it directly: "Diebus ferialibus a 2 ad 5 ianuarii
Missa dicitur ut die 1 ianuarii" -- the Circumcision's Mass, Titus 2:11-15 /
Luke 2:21. They had been falling through to the Sunday within the octave.
Ferial only, and the exclusions are the interesting part: the Sunday carries
the Holy Name and its own Mass, and an unoccupied Saturday carries Our Lady's
office and hers. 3 January 2026 is such a Saturday and correctly reads
Titus 3:4-7, not 2:11-15 -- which works because the BVM check runs first.
All four days of 2-5 January 2026 now match colitur.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
efWeek had no Passiontide case, so both weeks fell through to 0 and every day
of Holy Week took the slug of its Passion-week namesake: ef-passiontide-0-
monday served both Passion Monday and Holy Monday. The two weeks therefore
shared one set of readings and Holy Week could not have its own -- Good Friday
was reading Passion Friday's Mass.
Numbered from Passion Sunday now, so the weeks are 1 and 2 and the slugs
distinguish them.
The six ef-passiontide-0-* sections are replaced by colitur's twelve rather
than renamed, and the reason is worth recording: they were not simply Passion
week's Masses. The Tuesday section carried HOLY Tuesday's (Jer 11:18-20 with
the Passion according to Mark), which was Passion Tuesday's only by the same
collision -- colitur documents that specific mix-up and its own correction of
it. Renaming 0 to 1 would have kept the wrong Mass on Passion Tuesday while
looking like a clean migration.
All twelve are colitur's, verified there against both photographic scans. All
six days of Holy Week now match colitur, and so does Passion Tuesday.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 87: "Litaniae minores seu Rogationes, per se, assignantur feriis II, III et
IV ante festum Ascensionis Domini." Easter+36 and +37 are that Monday and
Tuesday. RG 128(d) gives them violet -- "for the procession and Mass of the
Greater and Lesser Litanies" -- a penitential note inside white Paschaltide.
lectio computed no Rogation days at all and showed a plain paschaltide feria.
The WEDNESDAY is deliberately not built, and the test asserts its absence.
Easter+38 is by construction the Vigil of the Ascension, II class, which wins
the day outright; what the rubric asks for there is a COMMEMORATION, and this
function returns one office per day. colitur reaches it through a movable-date
entry in its sanctoral overlay. Extending the Rogation branch to +38 to
"complete" the rubric would silently displace the Vigil, so the test pins the
Vigil rather than leaving the omission to a comment.
Rank is left as the feria's: these are IV-class days and any III-class saint
displaces them, which is what makes the office rare. In 2026 all three are
impeded and none appears; 2027 has the Monday unimpeded, and lectio and
colitur are byte-identical across 2-5 May that year.
One oracle allow-list entry: missalemeum builds no Rogation days either and
shows a white feria, so it is the outlier here rather than lectio.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 78, Caput IX "De sancta Maria in sabbato": "In sabbatis, in quibus occurrit
Officium de feria IV classis, fit de sancta Maria in sabbato." A Saturday whose
office would otherwise be a IV-class feria keeps Our Lady's office instead, and
it is white -- RG 431(e) classes that Mass as a "Missa votiva IV classis ... de
B. Maria Virg.", RG 121(a) gives a votive Mass the colour of the feast-type it
answers to, and RG 120(b) makes feasts of the BVM white.
Smaller than it sounds, because the protasis is already computed. "A IV-class
feria" is exactly what rank still being RankClass4 means at that point: every
branch above has promoted Advent, Lent, Passiontide, the Ember days, the
privileged ferias and the Christmas octave out of it.
The SLUG IS DELIBERATELY UNCHANGED. colitur, which has had this office since
its v0.3.0, does the same: a bespoke slug would recur many times a year and
break the one-slug-per-liturgical-year invariant, and the readings are
selected by the Mass formulary rather than by the slug. Only the colour moves.
It is also the temporal office only -- a sanctoral feast that outranks the
Saturday still wins and brings its own colour, exactly as before.
Result: lectio and colitur now agree on the colour of EVERY DAY of 2026. Zero
mismatches, where this class alone was nine days that year and 439 rows across
2005-2050 -- the largest single divergence class between the two engines.
Pinned in both directions, because the tempting wrong fix is to whiten every
Saturday: Lent and Advent Saturdays are III-class violet ferias and must stay
violet, and Holy Saturday must too. Mutation-tested -- dropping the IV-class
guard reddens the test on the Lenten Saturday, not on the per-annum ones.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
efWeek anchored the Time-after-Epiphany week number at 6 January, which put
every week of the season one too high -- 14 January 2026 came out as week 2
when the first Sunday after Epiphany was 11 January and the week running from
it is the first. The error persisted from Epiphany until Septuagesima cut the
season short, so it was wrong for most of January and February every year.
The Sunday is the anchor because it carries the Mass the week is named for,
"Dominica I post Epiphaniam", and its ferias follow it. Epiphany itself is a
feast inside Christmas Time (RG 72), not the head of a numbered week -- which
is the same distinction the Christmas Time boundary fix just made, showing up
again one function along.
Week numbers now match colitur day for day through the season.
The test pins the ordinary years AND the edge that a naive fix gets wrong:
when Epiphany itself falls on a Sunday, "the first Sunday AFTER Epiphany" is
the following week, so the helper starts its search on 7 January rather than
the 6th. It finds such a year in 2005..2050 rather than hardcoding one.
Mutation-tested: restoring the 6 January anchor reddens it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 72: "Tempus natalicium decurrit a I Vesperis Nativitatis Domini usque ad
diem 13 ianuarii INCLUSIVE." 6-13 January is the Epiphany section OF Christmas
Time, not the start of Time after Epiphany. RG 119(a) backs the same boundary
from the colour side, white "usque ad expletum tempus Epiphaniae". lectio
ended Christmas Time on 5 January, so eight days a year came out green that
should be white, and Epiphany itself reported the wrong season.
NOT a one-line boundary change, which is why an earlier attempt was reverted
rather than shipped. efSeason both named the slugs and reported the season, so
moving the boundary renamed every slug in the window -- ef-time-after-epiphany-*
became ef-christmas-* -- and orphaned the ef-time-after-epiphany-sunday-1
lectionary key. The two are now separate axes:
efSlugSeason names slugs and numbers weeks; unchanged behaviour
efSeason the liturgical season, which the colour follows; RG 72's
The split is not a new idea in this file. The resumed-Sundays branch already
does exactly this and says so -- "Season stays time-after-pentecost
(calendrical); the Epiphany slug only routes the readings" -- it simply had no
name, so the one place that needed it could not reuse it.
Result, checked against colitur across all 365 days of 2026: SEASON now agrees
on every single day, where it previously differed on eight. Colour differences
drop to nine, every one a Saturday and every one the BVM Saturday Office
(RG 78, colitur C17) which lectio does not build. Slugs and week numbers are
byte-identical to before.
The oracle's season axis now consults the allow-list and excludes excused days
from its threshold. Neither was true before, because no cited divergence had
ever BEEN a season difference -- the January window showed up as a colour one,
since the wrong season produced the wrong colour too. With the season right,
what is left is a season difference against missalemeum, and an axis whose
threshold is a t.Fatalf would have failed regardless of any allow-list.
TestTemporalEFChristmastideBoundary pins the boundary AND the split, mutation-
tested both ways: reverting the boundary reddens it, and re-merging the two
seasons so the slug is built from the liturgical one reddens it differently,
naming the orphaned lectionary key.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
RG 124, "De coloribus paramentorum", section C, both photographic scans word
for word: 124(b) gives red to "Sanctorum Apostolorum et Evangelistarum, in
eorum die natalicio, excepto festo S. Ioannis (27 decembris)", and 124(e) to
"Sanctorum Martyrum, quorum colitur aut martyrium". Red is for Apostles,
Evangelists and Martyrs. A Confessor, Doctor or non-martyr Virgin is white.
missalemeum has twelve of these INVERTED IN BOTH DIRECTIONS: ten confessors,
doctors and virgins in red (Ephrem, Julia of Falconieri, John Gualbert,
Camillus de Lellis, Jerome Emiliani, Martha, Alphonsus Liguori, Augustine,
Rose of Lima, John of San Fecundo), and two actual martyrs in white
(Apollinaris and Josaphat, both Bishop and Martyr). Both directions failing is
why this reads as one rule applied backwards rather than twelve separate
slips, and why they are corrected as a block.
Adjudicated by colitur against the scans -- its C18 carries the per-slug
reasoning and its M21 the oracle side, verdict colitur. Two of its fourteen,
conversion-of-st-paul and chair-of-st-peter, already agreed here.
Found by a full-year sweep comparing clectio's output against colitur day by
day, not by the patch tool that produced the earlier corrections. That tool
reads colitur's sanctoral.sexp directly and so misses everything in
adjustments.sexp, the overlay where all twenty of colitur's colour edits
actually live -- its SECOND structural blind spot, after temporal days.
All twelve go into the generator's missalOverrides table, so regeneration
preserves them, and into TestTridentineMissalOverrides, now sixteen pinned
fields. Mutation-tested in BOTH directions: reverting a martyr to white and a
confessor to red each redden the test with the rubric named. Pinning only one
direction would have let a careless "make them all white" fix pass ten of
twelve.
The oracle allow-list gains one entry covering the twelve dates in both years.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This file's own comment called RG 132's black "a separate, unmodelled gap".
It is modelled now. RG 128(b)'s exception list carries Good Friday in the same
sentence as Holy Thursday's -- "Actione liturgica feria VI in Passione et
Morte Domini usque ad Communionem exclusive" excepts the day from
Passiontide's violet -- and RG 132 assigns black to it. Adopted from colitur,
which closed the same gap against the Missal in its v0.4.0.
The rubric is per-action and this model carries one colour per day, so black
is the day's principal colour, the same acknowledged limit the Palm Sunday
blessing already has. missalemeum's own colour set for the day is "bv", black
first, so the oracle test accepts this without a new allow-list entry.
Found by cross-checking clectio's output against colitur date by date, NOT by
the patch tool that produced the earlier eight corrections. That tool compares
colitur's sanctoral data against this repo's calendar ini, so a temporal day
-- computed in code on both sides, in neither file -- is structurally
invisible to it. Its docstring now says so.
TestTemporalEFHolyThursdayColour asserted Good Friday was violet "(unchanged)",
encoding the gap; renamed to TestTemporalEFTriduumColours and updated. A test
asserting the absence of a feature passes for exactly as long as the feature
is absent, which is not the same as being correct. Holy Saturday is still
pinned violet in the other direction, so a careless "the whole Triduum is
black" change fails there.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
lectio's EF data has been the upstream for colitur, whose data was
bootstrapped from this ini. colitur has since validated that data against the
1962 Missal itself, and these eight fields are where the two now disagree and
the Missal decides against us. Each carries the rubric.
Calendar. St Ubaldus (16 May) and St Didacus (13 November) become III-class
feasts rather than commemorations: the Missal's own universal calendarium
ranks both "III classis" outright with no commemoration rubric, and each has
its own Mass entry in the Proprium taking its readings from a Common with
only its Oratio proper -- which a bare commemoration never has. Found by
auditing all 290 of colitur's fixed-date entries against that calendarium;
these two were the only status defects in the file. The Vigil of the
Assumption becomes violet from white and the Vigil of St Lawrence violet from
red: RG 128 gives violet to vigils of II and III class outside Paschaltide,
and its sole exception is the Ascension's vigil, inside Paschaltide, which is
white and is untouched here.
Lectionary. Both Ember Saturdays carried the wrong saint's Mass entirely --
St Thomas the Apostle's under ef-advent-ember-sat and St Matthew's under
ef-september-ember-sat, in both cases because 21 December and 21 September
fall on a Saturday in the same six years and a bootstrap generalised one
year's coincidence into a template value.
Two oracle allow-list entries, both placed ahead of the BVM-Saturday entry
deliberately. When 16 May or 13 November falls on a Saturday, that entry's
predicate also matches and would absorb the divergence under a reason no
longer true of it -- it says both engines agree the day is unimpeded, and the
point here is that the day IS impeded. The vigil entry excludes Sundays for
the mirror reason: RG 33 omits a vigil falling on one, and without the guard
2026-08-09 is stolen from the RG 33 entry that actually explains it.
Six days of the 730-day oracle window are affected. Readings are untouched by
that test, so the two lectionary corrections cost it nothing.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three lectionary sections were keyed ef-lent-1-{wednesday,friday,saturday}
while temporal_ef.go's efEmberSlug computes ef-lent-ember-{wed,fri,sat}. The
lookup in caldata.Readings takes the observed slug and has no alias table, so
it missed the entries entirely and fell through to the preceding-Sunday
fallback. Every Lenten Ember day since the data was added has served Lent I
Sunday's Mass -- three days a year, in a season that prints a proper Mass daily.
The values were already right; only the keys were wrong, and they were stale
rather than mistaken: they predate Lent being added to efEmberSlug, and
scripts/genlect.go keys off day.Observed.Slug, so re-running it would already
write the correct names. Advent and September were never affected -- both
already use the -ember- form.
Renamed, and verified the whole file: every one of the 119 section names is now
a slug the calendar actually computes, checked by sweeping 2005-2050. Those
three were the only dead keys.
Added TestEFLectionaryKeysAreReachableSlugs to make the class detectable rather
than just this instance. Mutation-tested: reverting the three keys fails it,
naming all three.
Found by differencing against colitur, the sibling OCaml engine, while giving
it a lectionary of its own. Worth recording how nearly it escaped: colitur
inherited the same wrong keys from this file during its bootstrap, so both
engines produced the same wrong Mass and the differential between them was
silent. It only surfaced when colitur's data was checked against the Missal
itself. Two implementations agreeing is not evidence when one was seeded from
the other.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Cheap fixes flagged in review, all confirmed against the committed
snapshot before being applied:
1. An unmapped season phrase skipped the RANK AND COLOUR checks too,
not just the season comparison -- `continue` in the wrong place.
Confirmed live: neither Holy Thursday ("maundy"/"holy week" do
not match the title "Holy Thursday" itself) nor any of the six
September Ember days (no "ember" case exists at all; the Advent
and Lent Ember days only ever passed by an incidental substring
match on "advent"/"lent") had any oracle coverage at all --
reverting the Holy Thursday colour fix left this test green.
Season skip and rank/colour checks are now independent.
2. Colour membership alone cannot catch a Rose regression: violet is
a member of every rose/violet pair by construction (Gaudete/
Laetare), so "got violet, want one of [rose violet]" passed even
with RG 131's Rose support removed entirely. On the day the pair
actually names (oracle rank 1, the Sunday itself, not a weekday
reusing its propers), a colour set containing rose now demands
rose specifically.
3. `has("sexagesima")` never matched missalemeum's own ligatured
"Sexagesimæ" -- confirmed in the committed snapshot: the ligatured
form appears 10 times, the unligatured form only 4, and every
ligatured instance mapped to season "" (skipped from ALL coverage,
not merely a season miss, given finding 1 above). Normalised once,
generally (æ -> ae), not as a single hardcoded word, so any other
ligature this generator's own data may carry is covered too.
Net effect on TestOracleEF: 730 checked (up from 691), 29 skipped
(down from 39) -- the 10 reclaimed by fix 3. Still green: 0
unallow-listed rank or colour mismatches over the full snapshot.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
transferIfImpededEF resolves one candidate's own transfer walk in
isolation and has no way to notice a SECOND, separately-transferred
I-class candidate landing on the same destination day -- St Joseph
(19 March) and the Annunciation (25 March) can both walk to the
Monday after Low Sunday in the same year (2008, 2035, 2046, e.g.
2035-04-02: annunciation-of-the-blessed-virgin-mary +joseph-spouse-
of-the-bl-virgin-mary). RG 95 grants the right of translation
"solummodo festis I classis" to I-class feasts only, so both
candidates genuinely have it, and the collision they land in
together is an ordinary RG 97/98 occurrence question this function
does not resolve: pickEF's plain alphabetical slug tie-break settles
it instead of re-walking the loser. RG 98 itself supplies the
determinism rule this collision needs and does not have: "in
paritate autem Officium prius impeditum praecedit" -- at equal table
position, the office impeded FIRST takes precedence, which is
chronological (Joseph, impeded on the 19th, before the Annunciation's
own walk begins on the 25th) and may favour Joseph over the current
alphabetical fallback.
No functional change -- this collision is left unfixed, defensible
against scope (it needs resolving occurrence between two ALREADY-
TRANSFERRED candidates, not a single one, a bigger shape than this
function currently has). Leaving it unrecorded was not defensible:
nothing in internal/calendar/ named RG 95, 97, 98, or chained
transfers anywhere before this comment.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
External review of the seven-defect fix (round 1) confirmed all seven
correctly and structurally fixed, then found five further problems in
what shipped alongside, two of them critical. All five verified
independently before being fixed, not applied on the review's say-so
alone -- see each item below for the primary-source check performed.
CRITICAL 1: the regeneration destroyed all 322 Polish display names.
main() preserved name.la across a regeneration (missalemeum has no
Latin titles) but had no equivalent for name.pl, and never emitted
one -- name.pl count went 322 -> 0 (confirmed: `git show
2386a45:internal/caldata/tridentine-calendar.ini | grep -c
'^name.pl'` = 322; the pre-round-2 file = 0). naming.CelebrationName's
own name[lang] -> name.en fallback then silently substituted English
for every Polish EF display, reaching mobile.Day(date, "ef", version,
"pl") -- a shipped dlectio entry point -- with no error anywhere in
the chain, and nothing in this repo's test suite asserting any
name.* field at all. Fixed at the root, not by re-adding name.pl
specifically: `entry.la` (one hardcoded language) is replaced by
`entry.otherNames map[string]string`, populated from every
"name.<lang>" field already present except name.en (English always
comes fresh from missalemeum) and re-emitted verbatim, sorted for
deterministic output -- a third or fourth language added later
survives a regeneration without this function changing again.
CRITICAL 2: St Romanus was deleted, and St Eusebius (14 August)
excluded, on a primary-source claim the primary source itself
contradicts. The exclusion cited the calendarium's 9 August row as
reading only "Vigilia, III classis.", no "Com." line -- checked
against ONE of the three local Missal scans,
"1962-06-23,...LT.pdf", an ELECTRONIC TRANSCRIPTION. The other two,
PHOTOGRAPHIC scans of the actual 1962 Missale Romanum, both carry it:
"missale-romanum-1962.pdf" calendarium, 9 August: "XVI d V 9
Vigilia, III classis, Commemoratio S. Romani Mart.", with the
saint's own proper text elsewhere in the same scan ("Et fit
commemoratio S. Romani Mar-") and its own back-of-book index
("Romani Mart., 9 augusti ... 621"). The transcription silently
drops vigil commemorations generally (also missing there, present in
both photographic scans: 7 August Donatus, 25 December Anastasia).
14 August's "St. Eusebius" is the identical shape (calendarium: "XI
b XIX 14 Vigilia, II classis, Commemoratio S. Eusebii Conf.") and
genuinely a DIFFERENT person from 16 December's "St. Eusebius, Ep.
et Mart." (calendarium: "V XVII S. Eusebii Ep. et Mart., III
classis.") -- a Confessor and a Bishop-and-Martyr, not the same
saint duplicated. knownSpuriousComm (both entries) is removed
outright; slugOverride gains "08-14/eusebius" -> "eusebius-confessor"
so the two no longer collide by slug. Going forward: the
photographic scans are the primary source; the electronic
transcription is a convenience index only; where they disagree, the
scan wins -- recorded in the generator's own comments, not just here.
IMPORTANT 3+4: Thomas Becket (29 Dec) and Silvester (31 Dec) were
silently promoted to class-4 by a hidden coupling. RG 68(d)/(e): "die
29 decembris, fit commemoratio S. Thomae Episcopi et Mart.; die 31
decembris, fit commemoratio S. Silvestri I Papae et Conf." -- both a
bare "Commemoratio" with NO class of their own; the DAY they fall on
(within the Nativity Octave) is II class, confirmed in the same
calendarium row. refYearExplainsAbsence calls calendar.Compute and
trusts a commemoration's own id-rank when the temporal day looks
class-1/2-strong; after this task's own earlier fix promoted 26-31
December from class-4 to class-2 (RG 67/68), that trust flipped for
these two from "not explained" to "explained" purely as a side
effect of an unrelated temporal_ef.go change -- the generator's data
inference reads the engine's own computed ranks, so a temporal_ef.go
rank change can silently rewrite generated data. Fixed narrowly (6-31
December excluded from ever trusting the id-rank, citing RG 68(d)/(e)
directly) and the coupling itself documented in
refYearExplainsAbsence's own doc comment as a standing hazard for the
next temporal_ef.go rank change, not just this one instance.
Separately, the SAME function's doc comment overstated its own
guarantee ("ANY class-1..4 saint would lose there") -- false for its
Lent/Passiontide limb, where a III-class privileged feria does not
beat a I- or II-class feast (RG 109(e) is privilege over an
equal-or-lower class only) -- and the "commemoration id names the
saint's TRUE rank" claim at the call site was falsified by its own
worked example (St Blaise's id claims rank 4, and he is still
correctly ruled RankCommemoration). Both rewritten to describe this
as the rank-blind sampling heuristic it actually is, not a rubric
evaluator.
IMPORTANT 5: the Purification's own citation, strengthened. Three
fixes to classOf's doc comment, no data change (the tag stays
"lord", per round 1's own decision): the colitur cross-reference is
removed (colitur bootstraps from lectio and was reading the same
oracle a second time, not independent corroboration, and citing a
sibling project's in-flight branch is not itself an argument); the
rule that actually makes the occurrence pattern diagnostic is now
named -- RG 91 entry 14 ("Festa Domini II classis") above entry 15
("Dominicae II classis") above entry 16 ("Festa II classis... quae
non [sunt Domini]"), each verified directly against the scan, not
paraphrased; RG 120(b) is recorded as genuine primary-text
counter-evidence ("Adhibetur color albus... b) B. Mariae Virg.,
etiam in benedictione et processione candelarum die 2 februarii" --
2 February filed under the white-colour rule's OWN "B. Mariae Virg."
heading, separate from 120(a)'s "Domini" heading), and RG 112(b)
("Officium, Missa aut commemoratio de dominica excludit
commemorationem... de festo vel mysterio Domini, et vicissim") is
cited as independently backing the empty commemoration list. A new
committed fixture test replaces reliance on the report alone, since
the deciding years (2 February on a Sunday) fall outside this repo's
committed 2026-2027 oracle snapshot: TestPurificationBeatsFebruarySunday
(internal/calendar/precedence_ef_repro_test.go), five independently
fetched years. One of the years this reasoning is sometimes quoted
against, 2036, is corrected in passing: 2 February 2036 is in fact a
Saturday, not a Sunday (`date -d 2036-02-02 +%A`) -- checked here
rather than repeated, 2042 used instead.
Witnesses (internal/caldata/caldata_test.go):
TestTridentineNamesPreservedAcrossRegeneration,
TestTridentineRomanusAndEusebiusPresent (replaces
TestTridentineNoSpuriousRomanus, whose own name asserted the
now-corrected wrong claim). Pre-fix failures (captured against the
committed pre-round-2 state, commit 50e3970):
name.pl coverage = 0 entries, want >= 315
assumption-of-the-blessed-virgin-mary: name.pl = "", want the preserved Polish name
romanus missing: the calendarium's photographic scans both carry "Commemoratio S. Romani Mart." on 9 August
missing "eusebius-confessor" (08-14)
internal/caldata/tridentine-calendar.ini regenerated from the ORIGINAL
branch-point data (`git show 2386a45:...`), not from this session's
own already-damaged intermediate file -- regenerating from an
already-corrupted source would have preserved nothing, since the
preservation mechanism can only preserve what is actually on disk
when it runs. Verified directly (not merely re-tested): all 322
name.en/name.pl values byte-identical to the branch point; 15 rank
fields and 2 class fields differ (the round-1 Lenten-rank and
class-tag fixes, unchanged by this round); 5 new slugs added
(agnes-secundo, boniface-martyr, eusebius-confessor, evaristus,
theodore); 0 slugs removed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Found during the mandated 2005-2050 before/after verification (not
one of the seven, but caused by defect 4's own fix, so fixed in the
same pass rather than left as a self-introduced regression). Neither
is a hypothetical: both are live in the fixed calendar and both were
individually confirmed via cmd/lectio-ef-dump before being written up
as tests.
1. RG 91 entry 4 (Immaculate Conception, Assumption BVM) sits ABOVE
entry 6 (Sundays of Advent/Lent/Passiontide) -- unlike an ORDINARY
I-class feast (entry 11, e.g. St Joseph), the Immaculate Conception
(8 December) is not impeded by an Advent Sunday at all. Before
defect 4, Advent Sundays were wrongly II class, so this was
accidentally right (I beats II outright, no tie reached); once
Sundays became I class the tie-break mattered for the first time,
and precedenceEF had no branch for it -- the Sunday wrongly won.
2. RG 91 entry 5 (Vigil & Octave day of the Nativity) sits above entry
6 the same way, and its own gap was worse than a wrong winner: the
Vigil of Christmas (24 December) falling on Advent IV, once
defect 4 made that a genuine I-class tie, sent the Vigil into
transferIfImpededEF's forward walk -- which has no way to re-place
a transfer crossing the Dec 31/Jan 1 boundary (celebrationDate
re-resolves a fixed date using the YEAR OF THE DAY BEING QUERIED,
so a walk landing in the following January can never match the
query that produced it). The Vigil did not move to the wrong day;
it vanished for the whole year, for every year 24 December is a
Sunday (2006, 2017, 2023, 2028, 2034, 2045).
Both fixed the same way as the existing II-class-feast-of-the-Lord
bonus in precedenceEF (a one-line precedence adjustment keyed on
slug), generalised into a single beatsClass1Sunday helper covering
both RG 91 entries.
A separate, unrelated bug surfaced by the SAME verification pass and
fixed alongside it: transferIfImpededEF's destination check reused
precedenceEF's tie-break BAND to decide "is this day I or II class",
but band encodes a different question (which of two EQUAL-class
candidates wins a tie) -- an ordinary, non-Sunday II-class temporal
candidate YIELDS under defect 2's own fix (band 5), even though it is
genuinely II class. A day within the Octave of the Nativity (26-31
Dec, RG 67) is exactly such a day, so band<=3 alone let a transfer
wrongly land inside it (a real reproduction: "vigil-of-christmas"
would have landed on 29 December 2006 instead of vanishing outright,
caught while tracing the entry-5 bug above). Replaced with a direct
class test (isHighClass), which is both correct and simpler -- it no
longer needs the Sunday flag at all for this particular check.
Witnesses (precedence_ef_repro_test.go): TestImmaculateConceptionBeatsAdventSunday,
TestVigilOfChristmasSurvivesAdventSunday. Fail before this commit with:
2013-12-08 observed = "ef-advent-sunday-2" want immaculate-conception-of-the-blessed-virgin-mary (RG 91 entry 4 beats entry 6)
2006-12-24 observed = "ef-advent-sunday-4" want vigil-of-christmas (RG 91 entry 5 beats entry 6; must not vanish)
(full set: 2013/2019/2024 for the first, 2006/2017/2023/2028 for the second)
|
| |
|
|
|
|
|
|
|
| |
Remove the LECTIO_EF_ORACLE_STRICT gate added when the strengthened
rank/colour assertions first landed. All seven defects are fixed as
of the previous four commits; TestOracleEF now passes unconditionally
as part of the normal go test ./... run, with its small, cited
allow-list (four entries, none of them one of the seven) doing the
only remaining filtering.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
precedenceEF's equal-class tie-break only distinguished "ordinary
III/IV-class feria yields" from "everything else wins" -- Sundays,
named feasts, Lent/Passiontide ferias, AND II-class privileged ferias
(the Ember days, the late-Advent 17-23 Dec ferias) were all bucketed
into the same "wins its tie" branch. RG 91 disagrees at class 2:
entry 15 (Sundays) sits ABOVE entry 16 (II-class feasts of the
universal Church), so a Sunday wins -- but entry 16 sits ABOVE entry
18 (II-class ferias, including the Ember days), so those FERIAS
yield instead, the opposite direction from a Sunday. St Matthew (21
September, II class) was losing to the September Ember Wednesday
every time the two coincided; St Thomas (21 December, II class) was
losing to an ordinary late-Advent feria the same way once the
previous commit correctly promoted those ferias to II class.
The fix distinguishes a II-class Sunday from a II-class feria using a
Sunday flag that already existed on `candidate` (used by the OF path)
but was never wired up for EF: temporal_ef.go's efCel always set
Sunday: false, even on an actual Sunday. A new efSunday helper (efCel
plus the flag) replaces the three efCel calls inside temporalEF's
Sunday branch, and computeEF now propagates td.Sunday into the day's
temporal candidate.
precedence_ef_test.go's own pre-existing witness ("at equal class, the
temporal office wins") encoded exactly the bug: a bare class-2
temporal candidate with no Sunday/season information, standing in for
"the temporal office" in general. It is rewritten into two explicit
cases (Sunday wins its tie; a privileged feria yields) plus the
existing Lent/Passiontide-vs-ordinary III/IV-class case restated
explicitly rather than left implicit.
Witness (precedence_ef_repro_test.go): TestMatthewBeatsSeptemberEmberWednesday.
Fails before this commit with:
2016-09-21 observed = "ef-september-ember-wed" want matthew (RG 91
entry 16 beats entry 18)
2022-09-21 observed = "ef-september-ember-wed" want matthew (RG 91
entry 16 beats entry 18)
With this commit, all seven named defects are fixed:
LECTIO_EF_ORACLE_STRICT=1 go test ./internal/calendar/... -run TestOracleEF
passes (0 unallow-listed rank/colour mismatches over 730 days). The
gate stays in place for this commit; a following commit removes it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
transferIfImpededEF's forward walk only skipped days whose temporal
office was I class. RG 96 requires skipping to "the next following
day that is not I or II class" -- II class also blocks. 2011: the
Sacred Heart (Friday after the Corpus Christi octave) falls on 1 July
and impedes the Precious Blood, also fixed on 1 July. The walk landed
the Precious Blood on 2 July, displacing the Visitation (II class,
fixed) outright, and never reached 3 July (an ordinary II-class
Sunday) at all.
Two changes were needed together, not one:
1. The walk's continuation threshold widens from "temporal band <= 2"
to "<= 3", which is what actually captures every II-class temporal
day (an ordinary Sunday, or a II-class named feast) as well as
every I-class one -- band 3 is precedenceEF's own value for any
non-Sunday-privileged II-class temporal candidate, unchanged by
this commit.
2. A FIXED sanctoral II-class feast (the Visitation) has no temporal
band at all -- the walk needs to also check whether some OTHER
fixed celebration, class 1 or 2, already resolves onto the
candidate day. computeEF now builds this check once (occupiedByRank,
parameterised by which ranks count) and passes it in.
Widening (1) alone is wrong on its own: a class-1 feast is NEVER
actually impeded by a mere II-class day (I class always outranks II
class outright, no tie exists) -- naively applying the wide "<=3"
threshold to decide whether the ORIGINAL date is impeded, not just
where to land afterwards, wrongly bumped unimpeded feasts landing on
an ordinary Sunday (caught while testing this: All Saints, 1 Nov 2026,
a Sunday that year, was wrongly pushed to 3 Nov). transferIfImpededEF
now uses two different thresholds for two different questions -- class
1 only to decide IF a candidate is impeded at all, class 1 OR 2 to
decide where an already-impeded one may land -- see its own doc
comment for the full reasoning.
Witness (precedence_ef_repro_test.go): TestTransferSkipsBothIAndIIClass.
Fails before this commit with:
2011-07-02 observed = "precious-blood-of-our-lord-jesus-christ" want
visitation-of-the-blessed-virgin-mary (RG 96: the Precious Blood
must skip past it, not displace it)
2011-07-04 observed = "ef-time-after-pentecost-3-monday" want
precious-blood-of-our-lord-jesus-christ (RG 96: first day that is
neither I nor II class)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Four fixes to internal/calendar/temporal_ef.go, all found by the
strengthened oracle test (previous commit):
1. Sunday ranks (defect 4). The generic Sunday branch assigned
class-2 to every Sunday except Advent I. RG 11-12 / RG 91 entry 6:
every Sunday of Advent and every Sunday of Lent is I class (as are
Passiontide's own two Sundays and Low Sunday, already correct via
their named-feast cases). Fixes St Joseph wrongly taking a Sunday
of Lent (defect 3, precedence_ef_repro_test.go's
TestJosephYieldsToSundayOfLent) as a direct consequence: once Lent
Sundays are I class, Joseph (also I class) no longer wins the tie
outright and correctly transfers to the 20th via the existing RG
96 walk -- no separate code change was needed for defect 3.
2. Rose (defect 5). efColour had no Rose case at all; Gaudete (Advent
III) and Laetare (Lent IV) now get Rose on that Sunday specifically
(RG 131: rose vestments may be used "in Officio et Missa diei
dominici tantum", for that Sunday's Office and Mass only), not the
surrounding Sundays.
3. Holy Thursday's colour (defect 6). RG 128(b) names the Missa in
Cena Domini as a whole-Mass exception to Passiontide's violet; RG
122 states the same fact affirmatively, in the White section
itself. Good Friday and Holy Saturday, either side, are unchanged
(still violet -- their own black/no-colour treatment is a separate,
unmodelled gap, noted in precedence_ef.go's own doc comments).
4. Beyond the seven, found by the same strengthened test and fixed for
the same RG 91 entry 18 reason defect 2 (next commit) relies on:
the Ember days of Lent had no case in efEmberSlug at all (only
September and Advent did), so they fell through to the ordinary
III-class Lenten-feria rank instead of the II class RG 91 entry 18
requires. The late-Advent ferias (17-23 Dec, RG 91 entry 18) and
the days within the Octave of the Nativity (26-31 Dec, RG 67-68)
had no elevation at all, defaulting to III/IV class. All three are
one-line, unambiguous, primary-cited additions to the same rank
logic already being touched here -- left unfixed, the strengthened
oracle test could only reach green by allow-listing them as if they
were defensible divergences, which they are not.
Witnesses (temporal_ef_test.go): TestTemporalEFSundayRanks,
TestTemporalEFRoseSundays, TestTemporalEFHolyThursdayColour,
TestTemporalEFEmberDayRanks. All fail before this commit; see the
report for the exact pre-fix failure messages.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
internal/calendar/oracle_ef_test.go asserted Season only. lectio's own
EF oracle test comment said as much ("rank/colour are reported
informationally"), which is exactly why several precedence/colour
defects in precedence_ef.go and temporal_ef.go shipped without ever
failing a test: the suite was green while the observed office's own
rank and liturgical colour could be wrong.
Rebuild the oracle from the committed missalemeum snapshot
(sources/snapshot.tar.gz, missalemeum/en/YYYY-MM-DD.json, 2026-01-01
.. 2027-12-31, 730 days) instead of a live 2025-2026 fetch, via a
rewritten scripts/build-oracle-ef.sh -- offline, reproducible, and
correctly separating info.rank/info.colors from info.id (whose
embedded rank is the rank of the propers REUSED that day, not the
day's own rank -- e.g. 2026-01-02 is a class-4 feria carrying id
"sancti:01-01:1:w" because it reuses the Circumcision's propers).
info.colors is an array (14 of 730 days carry two values -- Gaudete/
Laetare "pv", Palm Sunday "rv", Good Friday "bv", Holy Saturday
"vw"), so the new Colour assertion is membership, not equality.
The strengthened assertions immediately expose several real defects
(rank mismatches on every Advent/Lent Sunday, the Ember days, the
late-Advent and Christmas-octave ferias, and more) -- that is the
point, this is the regression net subsequent commits fix against.
Since the repo's convention is go test ./... green at every commit,
TestOracleEF is gated behind LECTIO_EF_ORACLE_STRICT=1 for now rather
than landed red; a later commit removes the gate once the fixes are
in. Season alone stays green throughout (unchanged, always was).
A small, cited allow-list (efAllowList) is included from this commit:
genuine, defensible divergences unrelated to the fixes ahead of it --
RG 91 entry 27's un-built BVM Saturday Office, the pre-existing 6-13
January Christmastide/time-after-Epiphany season-boundary divergence,
missalemeum's own RG 33 gap on a Sunday 9 August, and one collision
(St Joseph vs the Friday of Passion Week, 2027) left explicitly
unresolved even by the sibling project's much deeper primary-source
pass. None of the four is one of the seven defects this branch fixes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Ordinary Form's Table of Liturgical Days treats Sunday as its own
category, never a solemnity. temporal.go's sundayDay() already marks these
days distinctly from named solemnities (Class stays unset, unlike solemn()'s
ClassLord); internal/readings/offline.go now reads that existing signal to
relabel the REPORTED rank to "sunday" for ordinary and privileged-season
Sundays alike, leaving Celebration.Rank, ofRankOrder and all precedence
untouched. Named solemnities landing on a Sunday (Easter, Pentecost, ...)
and feasts of the Lord (Holy Family) keep reporting their own rank. The 1962
form is untouched.
Added calendar.RankSunday (display-only, deliberately excluded from
ofRankOrder), the i18n Sunday/niedziela words, and a test pinning the exact
dates from the original bug report.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Add internal/naming, which renders temporal day names and celebration
display names from the calendar engine's slugs in any language. English is
the built-in baseline; a language is data -- an embedded lang/<code>.ini
(pl shipped) and/or a user file at <config dir>/names/<code>.ini that
overrides it key by key. Names compose from a small vocabulary plus
per-language format templates, so word order and grammatical case can
differ (e.g. Polish genitive "3. Niedziela Okresu Zwykłego"); any string a
language omits falls back to English.
- Move day-name generation out of calendar (calendar.HumanizeSlug removed,
calendar stays a pure engine) into naming.DayName; the English golden
cases carry over verbatim.
- naming.CelebrationName unifies the three duplicated resolvers
(cli/readings/calfeed): name.<lang> -> English -> Latin -> humanized slug.
Saint names stay in the calendar data (name.<lang>), overridable via
calendar layers.
- config: ui_language now accepts any code (lower-cased, not clamped to
en/pl) so names/<code>.ini applies; UI chrome still resolves en/pl and
falls back to English. Add NamesDir(); wire naming.SetUserDir (and the
previously unwired bible.SetUserCorporaDir) in the TUI and web mains too,
so external corpora and name files work across all three binaries.
|
| |
|
|
|
|
|
|
|
|
|
| |
In early-Easter years there are more than 24 Sundays after Pentecost. Per the
1960 Rubrics the surplus Sundays resume the Sundays after Epiphany that
Septuagesima cut short (highest-numbered first) and the last Sunday keeps the
24th (Last) Mass. temporalEF now routes those Sundays to the resumed Epiphany
slug for readings while keeping the calendrical time-after-pentecost season;
the mis-harvested tail entries (post-Pentecost sundays 24-28) are corrected to
the true 24th Mass and the surplus removed. Validated: 88/88 tail/resumed
Sunday gospels match missalemeum 2025-2050; EF oracle green.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
EF readings had ~1.8% of days with no readings (major feasts falling back to
the wrong Sunday, feria-repeats broken when the preceding Sunday was a feast).
Now 0 gaps over 9496 days (2025-2050); reading correctness spot-checks match
missalemeum except the resumed-Epiphany-Sunday edge (next commit).
- calendar.TemporalSlug: the green-season weekday-repeats-the-Sunday rule uses
the Sunday's TEMPORAL slug, so a Monday still finds its Sunday Mass when that
Sunday was displaced by a feast (e.g. the Purification on Feb 2).
- Add the missing EF temporal Masses (from missalemeum): the Nativity Day Mass
(Heb 1:1-12 / John 1:1-14), Epiphany (Isa 60:1-6 / Matt 2:1-12), the Octave
Day of Christmas (Titus 2:11-15 / Luke 2:21), the 6th Sunday after Epiphany
(1 Thess 1:2-10 / Matt 13:31-35), and the last Sunday after Pentecost
(Col 1:9-14 / Matt 24:15-35).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fixes found by validating the offline engine per-day vs the LiturgicalCalendar
API (OF) and missalemeum (EF) across 2005-2050. OF calendar is now 0 real
errors in the forward window (season/cycle already perfect).
OF precedence/transfer (calendar):
- Ash Wednesday and Holy Week Mon-Wed are band-2 privileged, so a coinciding
feast (Chair of St Peter on Ash Wednesday) is suppressed, not observed.
- Solemnities transfer out of Holy Week / the Easter octave: the Annunciation
defers to the Monday after the 2nd Sunday of Easter; St Joseph is anticipated
to the Saturday before Palm Sunday; the Nativity of St John the Baptist moves
to Jun 23 when a Lord's solemnity (Sacred Heart / Corpus Christi) falls Jun 24.
- Within a precedence band, a solemnity of the Lord/BVM outranks a saint's
(dignity tiebreak) rather than losing an alphabetical slug tie.
- Perpetua & Felicity corrected optional -> obligatory memorial.
OF readings resolution (bible.OFRef + lectionary data):
- Verse-accurate Hebrew->Vulgate psalm mapping incl. the split psalms
(9/10, 114/115, 116, 147); "+" verse joins and abbreviated ranges ("127-28")
normalized; single-chapter books (2/3 John, Jude) get chapter 1.
- Cleaned harvest artifacts from of-lectionary.ini (descriptive-suffix gospels,
"or Year A" alternates, "*"/"[Vulg.]"/bracket markers, slash abbreviations);
Joel/Malachi/Zechariah/Esther book-versification citations fixed to Vulgate.
- Split a merged Ps 15:10/11 row in vul.tsv. Result: 0 unresolvable OF readings
over 2557 rendered days (cycles A/B/C, varied Easters).
EF: the Octave of Pentecost (Whit Monday-Saturday) is red, not white.
|
| |
|
|
|
|
|
|
|
|
| |
Temporal days with no proper name showed a slug-derived string ('triduum fri',
'ordinary 11 tue', 'advent-dec-17'). Add calendar.HumanizeSlug, used by both
celebrationName fallbacks (cli + calfeed), turning slugs into proper liturgical
names: Good Friday, Ash Wednesday, 'Tuesday of the 11th Week in Ordinary Time',
'11th Sunday in Ordinary Time', 'December 17', 'The Most Holy Trinity', and the
EF forms ('4th Week after Pentecost', 'Passion Week', 'Ember Wednesday of
September'). Adds TestHumanizeSlug.
|
| |
|
|
|
|
|
|
| |
Mary, Mother of the Church and the Immaculate Heart of Mary are computed in the
engine (no fixed-date INI entry), so they fell back to the ferial. Attach their
proper Masses inline: Mary MotC = Gen 3:9-15,20 / Ps 87 / John 19:25-34;
Immaculate Heart = Isa 61:9-11 / 1 Sam 2 canticle / Luke 2:41-51. Verified they
render when observed (Immaculate Heart 2029-06-09). Extends TestOFMovableMemorials.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(date-based)
Completes the seasonal weekday lectionary. The Christmas-season weekdays sit on
the AdventChristmas page in a SHIFTED 6-column layout (Day in c[1], not c[2] like
Advent's 7-column) -- the generator now finds the Day column wherever it is. Adds
date-based slugs to temporal.go: christmas-dec-<29..31> (octave), christmas-jan-<2..7>
(before Epiphany), christmas-after-epiphany-<weekday> (before the Baptism). Fixes
the christmas-octave-fri coverage gap and the by-date/by-week collision.
362/362 seasonal ferial-days render across 2026-2029 with 0 gaps. Extends
TestOFSeasonalFerials. All of Advent/Lent/Easter/Christmas weekdays now CR-sourced;
only the Commons remain (optional -- memorials default to the ferial).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
late Advent
Re-sources the proper weekdays of Advent (weeks 1-3), Lent, and Eastertide from
catholic-resources.org (scripts/genlect-of-season-ferials-cr.py; single-cycle, so
both -I/-II keys get the same readings). The diff caught real niedziela defects:
- Easter weekday first readings contaminated (easter-3-mon had 1 Cor 15 instead
of Acts 6:8-15; easter-4-thu had Rev 12 instead of Acts 13:13-25).
- The Easter octave weekdays (Easter Mon-Sat) had no readings at all.
- Stray psalm-numbering artifacts (147A(146), Ps42(41)) cleared.
Late Advent (Dec 17-24) is proper to the DATE, not the Advent week, so temporal.go
now keys those days 'advent-dec-<day>' (fixing the same by-date/by-week collision
as the OT numbering) and the generator supplies them (Dec 17 = the genealogy
Matt 1:1-17; Dec 24 = the Benedictus).
157 seasonal ferial-days render across 2026-2028 with 1 residual gap
(christmas-octave-fri). Adds TestOFSeasonalFerials, credits the source, bumps to
0.43.0. Christmas-season weekdays (Dec 29-31, Jan 2-5) remain niedziela-sourced
(CR's shifted layout + Epiphany-dependent assignment -- a documented follow-up).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Ordinary Form ferial lectionary had two linked defects, both from harvesting
niedziela.pl by date: (1) post-Pentecost Ordinary-Time ferial WEEK NUMBERS were
one too high (temporal.go counted back from Christ the King using the weekday
itself, not its preceding Sunday, so a ferial took the next week's number), and
(2) ~17 ferial keys held a SAINT's readings (stored when a saint displaced the
ferial on the queried date, e.g. week 22 smeared with a Marian Prov 8/John 2),
plus 16 weekday slots were missing entirely.
- temporal.go: number a span-2 ferial from its preceding Sunday. Verified
388/388 ferial-week agreement with calapi across 2026-2028.
- scripts/genlect-of-cr.py: regenerate the Ordinary-Time ferial lectionary
(first/psalm/gospel, both years, all 34 weeks) from catholic-resources.org
(Fr. Felix Just, S.J.), which is keyed by liturgical position (immune to
by-date contamination) and complete. Citations only; text still rendered from
the public-domain corpora. of-lectionary.ini OT ferials now 408 (was 392).
Fixes ~17 contaminated entries + 16 gaps + the week-numbering audit item. Psalms
already share modern numbering, so no conversion. Adds TestOFOrdinaryTimeFerialWeek.
Credits catholic-resources.org in NOTICE. Bumps to 0.40.0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
transfers)
Ordinary Form:
- Add the movable Marian memorials that have no fixed date: Mary, Mother of the
Church (Monday after Pentecost) and the Immaculate Heart (Saturday after the
Sacred Heart), computed in computeOF.
- General Norms 60: two coinciding obligatory memorials both become optional and
the ferial is observed -- except Mary, Mother of the Church, which keeps
precedence per its 2018 decree.
Extraordinary Form:
- Capture fixed-date vigils in the sanctoral harvest (Christmas, the Nativity of
St John the Baptist, Sts Peter & Paul, St Lawrence, the Assumption, All Saints,
the Immaculate Conception) -- previously filtered out.
- temporalEF: the movable vigils of the Ascension (I class eve) and Pentecost
(Whitsun Eve), and the September/Advent Ember days (II class violet ferias).
- transferIfImpededEF: the Annunciation transfers past Holy Week/the Easter
octave to the Monday after Low Sunday; All Souls moves to Nov 3 when Nov 2 is a
Sunday.
- genlect harvests proper Masses for the new Ember/vigil days.
Adds TestEFCoverage + TestOFMovableMemorials. Bumps to 0.39.0.
Verified per-day: OF 0 real errors vs calapi (2026-2028, 100% correct-observed);
EF 0 real errors vs missalemeum (2025-2027), residual = deep-tail vigil-occurrence
and St Joseph Passiontide-transfer edges (~1-2 days/yr, documented).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Septuagesima ferias are IV class (per annum), not III class, so a III
class saint (e.g. Conversion of St Paul, Jan 25) displaces them.
- Holy Week ferias and the privileged octaves of Easter and Pentecost are
I class: no saint's feast is admitted (temporal office wins).
- Occurrence tie-break is asymmetric by season: an ordinary feria (Advent,
per annum) yields to an equal-class feast; Lent/Passiontide ferias keep
their privilege and win the tie.
- A II class feast of the Lord (Purification, Exaltation of the Cross,
Dedication of the Lateran) displaces a II class Sunday it falls on.
Validated against missalemeum across 2025-2027: ~97.5% of days observe the
liturgically-correct celebration, 100% rank agreement on saint-days.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Populate the OF General Roman Calendar sanctoral and extend the OF readings to
weekdays and sanctoral days.
Sanctoral calendar (roman-calendar.ini: 29 -> 202 celebrations):
- scripts/gen-sanctoral.go generates it from calapi.inadiutorium.cz (authoritative
General Roman Calendar) -- English name/rank/colour from general-en, Latin from
general-la, Polish from niedziela DayInfo (obligatory celebrations). Fixed dates
the temporal engine already computes are excluded. VALIDATED: my engine's
observed rank agrees with calapi on all 86 obligatory sanctoral days of 2026
(0 mismatches).
Precedence fix (precedence.go): an optional memorial no longer displaces the
weekday as the DEFAULT observed celebration (it sorts below the ferial, shown as
an option) -- matching the General Roman Calendar / calapi.
Readings (of-lectionary.ini: 254 -> 1018 entries) via genlect-of.go:
- Weekday (ferial) 2-year cycle I/II, keyed <ferial-slug>-<WeekdayCycle>, harvested
from niedziela over 2020-2025 (multiple years per cycle; retry-on-failure so a
glitch year never poisons a key; only TRUE ferials + optional-memorial days,
where niedziela shows the ferial).
- Obligatory memorials keyed by their OWN slug (niedziela shows their proper, e.g.
Barnabas -> Acts 11, or the ferial for memorials without a proper) -- never
miskeyed to a ferial position.
- caldata.Readings: OF resolves slug+SundayCycle | slug+WeekdayCycle, else a
memorial falls back to the day's ferial readings.
- Source-glitch normalisation (1J->1 J, PnP->Pnp).
books.ini: canonical names "Song of Solomon" and "The Acts" added as their own
[en] forms (ToEnglishRef emits the canonical; it must resolve). Fixes ~32
cross-chapter Song-of-Songs/Acts citations.
Coverage: 359/365 days of 2026 render (98.4%); 6 residual ferial positions never a
true-ferial in 2020-2025. EF sanctoral and OF psalm renumbering still pending.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Latin fallback
Finish the Extraordinary Form (1962) offline Mass readings so every day of
the year resolves in both English and Polish.
Temporal engine (temporal_ef.go):
- Unique ferial slugs "ef-<season>-<week>-<weekday>" so penitential-season
weekdays (Lent, Advent, Passiontide, Easter octave) can key their own
proper Masses; green-season ferias have no entry and fall back to the
preceding Sunday (the 1962 rule).
- Special "ef-lent-after-ashes-<weekday>" case for the days between Ash
Wednesday and Lent I, which have their own propers.
Lectionary (tridentine-lectionary.ini, 55 -> 143 entries) via genlect.go:
- Generator now fetches proper-season weekdays and stores a feria only when
its Mass differs from the preceding Sunday's.
- cleanCite() normalises two missalemeum data glitches: a stray comma
between book and chapter ("4 Kings, 5:1" -> "4 Kings 5:1") and a
period-as-separator ("John 20. 19-31" -> "John 20:19-31"), both guarded so
legitimate multi-chapter citations are untouched.
Citations & corpora:
- books.ini: Douay abbreviations in the [en] dialect (Ex, Ezech, Jonas, and
3/4 Kings -> canonical 1/2 Kings), so the EF Lenten epistles resolve while
the display keeps modern sigla.
- drb.tsv: +205 rows backfilling the deuterocanonical Daniel 13-14 (Susanna,
Bel) and Esther 11-16 (Greek additions) from get.bible douayrheims, which
the base corpus omitted; scripts/gen-deutero.py documents the fetch.
Rendering (liturgy.go):
- readingLine() falls back to the Latin Vulgate (complete) when the
vernacular corpus lacks a passage, with a clear note. Covers the Polish
Wujek deuterocanon gap (no clean public-domain source exists) and any
future vernacular gap; faithful to the EF as a Latin rite.
Result: 730/730 days resolve for both EN (native) and PL (native + Latin
fallback for 3 deuterocanon days). Version 0.32.0 -> 0.33.0.
|
| |
|
|
|
|
|
|
|
| |
Unique temporal-day slugs (season-sunday-week); scripts/genlect.go generates
tridentine-lectionary.ini from missalemeum keyed by slug (55 entries: the
Sundays of the whole EF year). caldata.TemporalReadings(form,slug) lookup;
--liturgy resolves proper-of-feast else the temporal cycle and renders the text.
bible.ParseRef strips abbreviation dots (1 Cor. -> 1 Cor). 52/53 Sundays fully
resolve offline.
|
| |
|
|
|
|
| |
temporalEF validated against Divinum Officium data. Season-mapping handles the
Whit octave (Paschaltide), the Christmas vigil (Advent), and the resumed
Epiphany Sundays (within Time after Pentecost).
|
| | |
|
| |
|
|
| |
temporal-before-sanctoral)
|
| |
|
|
|
|
| |
8 EF seasons by date boundary (Septuagesima, Passiontide, Time after Epiphany/
Pentecost + shared); major feasts of the Lord; Christ the King = last Sun of
October. ParseRank accepts EF ranks.
|
| |
|
|
|
|
|
| |
oracle 0 mismatches)
Adds EF rank constants (class-1..4, commemoration); ofRankOrder() for the two
ordered comparisons; buildCelebration defaults missing rank to ferial.
|
| |
|
|
| |
Also: reject out-of-range MM-DD in resolveDate (strict validation for user data).
|
| |
|
|
| |
superseded ferial from Others
|
| | |
|
| |
|
|
|
|
| |
Caught + fixed an Advent-start bug: anchor on Christmas Eve, not Dec 25, so
years where Dec 25 is a Sunday don't lose the first week of Advent. Rank-class
diffs (324) are informational — the initial sanctoral dataset is partial.
|
| |
|
|
| |
Sunday band before solemnity-rank fallback
|
| |
|
|
|
|
| |
cycles
Also: deterministic pick tiebreak by slug.
|
| | |
|
| | |
|