Fix privileged-feria-major: nothing is ever commemorated, any rank
Deploy / deploy (push) Successful in 1m32s

The Easter octave cascade built last commit reused the existing
"duplex-majus+ still gets a bare commemoration" branch unchanged,
trusting its own citation (St. Mark within the Easter Octave). That
citation was a misreading. Live-verified: the Annunciation (Duplex I.
classis) transfers with zero commemoration on Holy Thursday itself, and
Chair of St. Peter at Antioch does the same on Ash Wednesday - the
exception was wrong everywhere this category is used, not just Holy
Week. privileged-feria-major now matches privileged-sunday exactly:
unconditional transfer, no exceptions, any rank.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
This commit is contained in:
2026-09-01 14:08:15 -04:00
parent d6c8e8b7a8
commit fc4ac33903
6 changed files with 105 additions and 36 deletions
+29
View File
@@ -4767,3 +4767,32 @@ New tests in `tests/calendar/transfer.test.ts` cover the full chain (Leo/Hermene
landing in strict native-date order past the whole 2033 span) and the Annunciation's own landing in strict native-date order past the whole 2033 span) and the Annunciation's own
zero-commemoration case (2035). `npm test` (823 passed), `tsc --noEmit`, and `npm run build` zero-commemoration case (2035). `npm test` (823 passed), `tsc --noEmit`, and `npm run build`
all pass. all pass.
### Correction, same day: `privileged-feria-major`'s own "duplex-majus+ still gets a nod" rule was wrong
User pushed back immediately: "you dont commemorate -anything- from palm sunday through low
sunday" — and was right. The cascade mechanism above was built reusing `applyIncomingTransfer`'s
existing `privileged-feria-major` no-op unchanged, on the assumption that its sibling behavior
in `decideOccurrence` (a *native* duplex-majus+ candidate there gets a bare commemoration, only
below-majus ranks transfer) was itself correct — it had a citation (St. Mark, Duplex II. classis,
"merely commemorated" within the Easter Octave). That citation turned out to be a misreading,
not just unverified.
Live-checked two more real dates before touching code: the Annunciation (Duplex I. classis, the
highest rank that exists) transfers with **zero** commemoration on Holy Thursday itself in years
it lands there (2027/2032/2043) — not just off a Sunday, off an ordinary Holy Week feria too.
Then checked whether this was Holy-Week-specific or true of every `privileged-feria-major` use:
Chair of St. Peter at Antioch (Duplex majus) transfers the same way off Ash Wednesday (2034).
So the exception was wrong everywhere this category is used, not just the Easter span.
Fixed: `commemorations.ts`'s `privileged-feria-major` case now matches `privileged-sunday`
exactly — transfers every rank unconditionally, no commemoration ever, for anyone. Three
existing tests had the old (wrong) assumption baked into their expected values and needed
updating to real, live-verified outcomes, not just loosened: St. Mark (2030) now transfers
instead of being commemorated, landing May 5 that year (vu's own dense April/May calendar
pushes him well past his native date); the Annunciation's own 2035 landing moved from Apr 4 to
Apr 6, since two *earlier*-native-dated candidates (St. Benedict, also Duplex I. classis, native
Mar 21; St. Gabriel the Archangel, Duplex majus, native Mar 24) are now also correctly caught by
the same span that year and queue ahead of her.
`npm test` (822 passed), `tsc --noEmit`, and `npm run build` all pass.
+16 -5
View File
@@ -143,11 +143,22 @@ export function decideOccurrence(
}; };
case 'privileged-feria-major': case 'privileged-feria-major':
// Ash Wednesday, Holy Week, the Easter Octave, the Vigil of // Ash Wednesday, Holy Week, the Easter Octave, Low Sunday, the
// Christmas — never displaced, same as privileged-sunday. // Vigils of Christmas/Pentecost — never displaced, and never gives
if (isAtLeast(sanctoral.rank, 'duplex-majus')) { // even a bare commemoration to any rank, same as privileged-sunday.
return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] }; // The "duplex-majus+ still gets a nod" branch this case used to
} // have was wrong, not just unverified: the one case that seemed to
// support it (St. Mark, Duplex II. classis, "merely commemorated"
// within the Easter Octave) turns out to have been a misreading —
// live-verified 2026-09-01, direct instruction ("you dont
// commemorate -anything- from palm sunday through low sunday"),
// confirmed with two more dates on top of the Annunciation/Palm-
// Easter-Sunday evidence already in the privileged-sunday case
// below: the Annunciation (Duplex I. classis) transfers with zero
// commemoration on Holy Thursday itself in years it lands there
// (2027/2032/2043), and Chair of St. Peter at Antioch (Duplex
// majus) transfers off Ash Wednesday the same way (2034) — so the
// exception was wrong for Ash Wednesday too, not just Holy Week.
return { return {
winner: temporalWinner, winner: temporalWinner,
commemorations: [], commemorations: [],
+10 -6
View File
@@ -76,12 +76,16 @@ export type FeastClass =
// 1617 engine, which the original single-tier model wrongly forbade) — // 1617 engine, which the original single-tier model wrongly forbade) —
// see calendar/commemorations.ts's rules for `privileged-feria` (behaves // see calendar/commemorations.ts's rules for `privileged-feria` (behaves
// like `ordinary-sunday`: Duplex+ wins outright) vs `privileged-feria-major` // like `ordinary-sunday`: Duplex+ wins outright) vs `privileged-feria-major`
// (behaves like `privileged-sunday`: never displaced at all, confirmed by // (behaves like `privileged-sunday`: never displaced, and — corrected
// checking St. Mark, Duplex II. classis, merely commemorated rather than // 2026-09-01 — never gets even a bare commemoration at any rank either;
// winning within the Easter Octave). See data/calendar/temporal- // an earlier claim that a duplex-majus+ candidate is "merely commemorated"
// categories.yml for exactly which days fall in which tier — some of that // there, citing St. Mark within the Easter Octave, turned out to be a
// split (e.g. whether Pentecost's own Ember days share Lent's lesser tier // misreading — direct instruction confirmed nothing is ever commemorated
// or Easter's major one) is still a reconstructed guess, not verified. // anywhere from Palm Sunday through Low Sunday, and live-checking Ash
// Wednesday found the same). See data/calendar/temporal-categories.yml
// for exactly which days fall in which tier — some of that split (e.g.
// whether Pentecost's own Ember days share Lent's lesser tier or
// Easter's major one) is still a reconstructed guess, not verified.
// `privileged-feria-minor` is a third, weaker tier, added during the // `privileged-feria-minor` is a third, weaker tier, added during the
// December sanctoral pull after finding Advent's own ordinary (non-Ember) // December sanctoral pull after finding Advent's own ordinary (non-Ember)
// ferias have real standing of their own in the live engine — a Simplex // ferias have real standing of their own in the live engine — a Simplex
+23 -6
View File
@@ -18,12 +18,29 @@ describe('April sanctoral pull (first pass)', () => {
expect(getDayCollect(day).status.en).toBe('verified'); expect(getDayCollect(day).status.en).toBe('verified');
}); });
it('St. Mark is only ever commemorated, never wins, within the Easter Octave', () => { it('St. Mark gets nothing at all within the Easter Octave — not even a bare commemoration — and transfers past it instead', () => {
const day = resolveDay('2030-04-25'); // Corrected 2026-09-01: nothing is ever commemorated anywhere from
expect(day.winner.kind).toBe('temporal'); // Palm Sunday through Low Sunday, no exception for any rank (direct
expect(day.commemorations).toEqual([ // instruction; live-verified against the Annunciation on Holy
{ kind: 'sanctoral', id: 'st-mark', name: 'St. Mark, Evangelist', nameLa: 'Sanctus Marcus, Evangelista', rank: 'duplex-2-classis' }, // Thursday and Chair of St. Peter at Antioch on Ash Wednesday, both
]); // Duplex-majus+ and both transferring with zero commemoration). Mark
// (Duplex II. classis) transfers off Apr 25, 2030 the same way; vu's
// own dense calendar that week pushes his actual landing all the way
// to May 5 (every day in between already spoken for).
const native = resolveDay('2030-04-25');
expect(native.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
expect(native.commemorations).toEqual([]);
expect(native.transferredAway?.candidate.id).toBe('st-mark');
const landing = resolveDay('2030-05-05');
expect(landing.winner).toEqual({
kind: 'sanctoral',
id: 'st-mark',
name: 'St. Mark, Evangelist',
nameLa: 'Sanctus Marcus, Evangelista',
rank: 'duplex-2-classis',
transferredFrom: '2030-04-25',
});
}); });
it('St. Robert beats St. Catherine of Siena on their shared monastic-calendar date', () => { it('St. Robert beats St. Catherine of Siena on their shared monastic-calendar date', () => {
+13 -12
View File
@@ -89,21 +89,22 @@ describe('decideOccurrence — privileged-feria', () => {
}); });
describe('decideOccurrence — privileged-feria-major', () => { describe('decideOccurrence — privileged-feria-major', () => {
it('is never displaced, same as privileged-sunday — duplex-majus and up are commemorated', () => { it('is never displaced, and never gives even a bare commemoration to any rank — everything transfers forward', () => {
// Verified: St. Mark, Duplex II. classis, only ever commemorated — // Corrected 2026-09-01, direct instruction: nothing is ever
// never wins outright — within the Easter Octave. // commemorated anywhere from Palm Sunday through Low Sunday — the
for (const rank of ['duplex-majus', 'duplex-2-classis', 'duplex-1-classis'] as const) { // earlier "duplex-majus+ still gets a nod" claim (citing St. Mark,
// Duplex II. classis, within the Easter Octave) was a misreading.
// Live-checked two more dates: the Annunciation (Duplex I. classis)
// transfers with zero commemoration on Holy Thursday itself in years
// it lands there, and Chair of St. Peter at Antioch (Duplex majus)
// does the same on Ash Wednesday — so the fix applies to every use
// of this category, not just Holy Week.
for (const rank of ['duplex-majus', 'duplex-2-classis', 'duplex-1-classis', 'duplex', 'semiduplex', 'simplex'] as const) {
const result = decideOccurrence('privileged-feria-major', 'id', saint(rank)); const result = decideOccurrence('privileged-feria-major', 'id', saint(rank));
expect(result.winner).toEqual({ kind: 'temporal', id: 'id' }); expect(result.winner).toEqual({ kind: 'temporal', id: 'id' });
expect(result.commemorations).toEqual([{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank }]);
}
});
it('yields nothing short of duplex-majus — transfers instead', () => {
const result = decideOccurrence('privileged-feria-major', 'id', saint('duplex'));
expect(result.winner).toEqual({ kind: 'temporal', id: 'id' });
expect(result.commemorations).toEqual([]); expect(result.commemorations).toEqual([]);
expect(result.transfer).toEqual({ candidate: saint('duplex'), direction: 'forward' }); expect(result.transfer).toEqual({ candidate: saint(rank), direction: 'forward' });
}
}); });
}); });
+14 -7
View File
@@ -171,15 +171,17 @@ describe('multi-hop forward transfer through Holy Week/the Easter Octave', () =>
expect(tiburtius?.kind === 'sanctoral' ? tiburtius.transferredFrom : undefined).toBe('2033-04-14'); expect(tiburtius?.kind === 'sanctoral' ? tiburtius.transferredFrom : undefined).toBe('2033-04-14');
}); });
it("2035: the Annunciation (native Easter Sunday itself, Duplex I. classis) transfers past the whole span with zero commemoration anywhere in it — matching the real reference engine's own behavior, not just a native-date-order queue effect", () => { it("2035: the Annunciation (native Easter Sunday itself, Duplex I. classis) transfers past the whole span with zero commemoration anywhere in it, queued behind two earlier-native-dated candidates also caught by the same span", () => {
// Easter 2035 is Mar 25 -- the Annunciation's own fixed date. Live- // Easter 2035 is Mar 25 -- the Annunciation's own fixed date. Live-
// verified 2026-09-01: the real Monastic 1617 engine shows her // verified 2026-09-01: the real Monastic 1617 engine shows her
// transferring off Easter Day with no commemoration anywhere in Holy // transferring off Easter Day with no commemoration anywhere in Holy
// Week/the Octave/Low Sunday (Apr 1 that year), unlike a same-ranked // Week/the Octave/Low Sunday (Apr 1 that year) -- corrected the same
// *native* occurrence there (e.g. St. Mark, Duplex II. classis, // day, direct instruction: nothing is ever commemorated anywhere in
// commemorated when native within some other year's Octave) -- // this span, at any rank, not even duplex-majus+ -- so a *native*
// confirming a transferred-in candidate never gets the "duplex-majus+ // occurrence there (e.g. St. Mark within some other year's Octave)
// still gets a nod" treatment on these days, only a native one does. // gets exactly the same "transfers, no exception" treatment as a
// transferred-in candidate passing through, not the "still gets a
// nod" treatment this test used to assert existed.
const easterSunday = resolveDay('2035-03-25'); const easterSunday = resolveDay('2035-03-25');
expect(easterSunday.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' }); expect(easterSunday.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
expect(easterSunday.transferredAway?.candidate.id).toBe('annunciation'); expect(easterSunday.transferredAway?.candidate.id).toBe('annunciation');
@@ -189,7 +191,12 @@ describe('multi-hop forward transfer through Holy Week/the Easter Octave', () =>
expect(day.commemorations.some((c) => c.kind === 'sanctoral' && c.id === 'annunciation')).toBe(false); expect(day.commemorations.some((c) => c.kind === 'sanctoral' && c.id === 'annunciation')).toBe(false);
} }
const landing = resolveDay('2035-04-04'); // vu's own calendar also has St. Benedict (native Mar 21, also
// Duplex I. classis, also impeded that year) and St. Gabriel the
// Archangel (native Mar 24, Duplex majus) queued *ahead* of the
// Annunciation by native date -- they land Apr 4 and Apr 5
// respectively, pushing her actual landing to Apr 6.
const landing = resolveDay('2035-04-06');
expect(landing.winner).toEqual({ expect(landing.winner).toEqual({
kind: 'sanctoral', kind: 'sanctoral',
id: 'annunciation', id: 'annunciation',