Compare commits

...

3 Commits

Author SHA1 Message Date
will 25669dfdae Record the Confessor Matins psalmody categories in TODO.md
Deploy / deploy (push) Successful in 1m4s
Documents the Francis of Paola / Basil the Great live-verification,
the identical-apart-from-versicles finding, the Duplex+ saints checked
for proper-antiphon eligibility, and the M1617-kalendar wrinkle found
along the way (a SanctiM file existing doesn't mean the saint is
actually on the Monastic Tridentinum 1617 calendar).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:09:55 -04:00
will e9134b2117 Add Matins test proofs for the new Confessor category tiers
Two new Common-tier proofs (Francis of Paola, Basil the Great) confirm
the resolver picks up both new categories with verified antiphon text.
The old ferial-fallback proof (St. John Bosco, Common of a Confessor)
is now superseded since that category is authored -- swapped for Ss.
Fabian and Sebastian (Common of Several Martyrs, still unauthored).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:09:48 -04:00
will b944f18433 Author Common-of-a-Confessor(-Bishop) Matins psalmody overrides
Live-verified against Divinum Officium's Monastic Tridentinum 1617 CGI
instance: St. Francis of Paola (2030-04-02, "vide C5") for the plain
Confessor scheme, St. Basil the Great (2035-06-14) for the Bishop
scheme. The two turned out word-for-word identical in psalm numbers
and antiphon text across all three nocturns, differing only in their
three nocturn-closing versicles -- kept as separate full files since
the mechanism has no partial-override support.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:09:43 -04:00
4 changed files with 305 additions and 8 deletions
+55 -4
View File
@@ -43,11 +43,13 @@ across every hour and content type in this app.
`src/data/scripture/` — long-term project, explicitly deferred; Matins scripture `src/data/scripture/` — long-term project, explicitly deferred; Matins scripture
citations resolve to `missing` text until it lands. citations resolve to `missing` text until it lands.
- Matins psalmody overrides for a Duplex+ weekday feast (see "Matins psalmody overrides" - Matins psalmody overrides for a Duplex+ weekday feast (see "Matins psalmody overrides"
below): mechanism refactored to category-keyed (`categories/*.yml` + `saints/*.yml`), two below): mechanism refactored to category-keyed (`categories/*.yml` + `saints/*.yml`), four
categories authored (Common of a Martyr via St. Lawrence, Common of an Apostle via St. categories authored (Common of a Martyr via St. Lawrence, Common of an Apostle via St.
Andrew). Every other Common category (~73 saints across Confessor, Virgin, Doctor, etc.) Andrew, Common of a Confessor via St. Francis of Paola, Common of a Confessor-Bishop via
still falls back to the plain ferial weekday table, redistributed into 3 nocturns, until St. Basil the Great). Every other Common category (Virgin, Doctor, Common of Several
its own category's real psalmody is authored. Martyrs, etc. — the largest remaining population) still falls back to the plain ferial
weekday table, redistributed into 3 nocturns, until its own category's real psalmody is
authored.
- The four "Commemoratio Octavæ" Ant+V/R+collect blocks (Christmas Octave's own - The four "Commemoratio Octavæ" Ant+V/R+collect blocks (Christmas Octave's own
Nativity/Stephen/John/Holy-Innocents commemorations, e.g. on Dec 30) are now done — Nativity/Stephen/John/Holy-Innocents commemorations, e.g. on Dec 30) are now done —
see "Octave content" below. St. Frances of Rome's/ see "Octave content" below. St. Frances of Rome's/
@@ -1866,6 +1868,55 @@ Common of a Confessor — no category authored yet for that Common — 2026-01-3
clean Saturday with no Sunday collision). `npm test` (375 tests), `npm run build`, and clean Saturday with no Sunday collision). `npm test` (375 tests), `npm run build`, and
`tsc --noEmit` all pass. `tsc --noEmit` all pass.
### Matins psalmody overrides — Confessor categories authored, both tiers identical apart from versicles (2026-08)
Two more Common categories authored, following the same live-verify-a-saint-with-no-proper-
antiphon method as Martyr/Apostle:
- **`common-of-a-confessor`** (Confessor Not a Bishop, "vide C5"): from St. Francis of Paola
(2030-04-02, confirmed clean — safely outside Holy Week/Easter octave that year, per his own
saint-file comment). Nocturn 1 = Ps 1,2,4,5,8,10; Nocturn 2 = Ps 14,20,23,95,96,97; Nocturn 3
= one antiphon over 3 OT canticles (Sirach 14/15, Jeremiah 17, Sirach 31 — same refs already
authored for `common-of-a-martyr`, reused as-is).
- **`common-of-a-confessor-bishop`** ("vide C4"): from St. Basil the Great (2035-06-14, already
live-verified clean per his own saint-file's note that his rendered Matins is byte-identical
to this Common despite his own `[Rule]` saying "vide C4a"/Doctor). Live-verified this Common's
psalm numbers and antiphon text are **word-for-word identical** to `common-of-a-confessor` in
all three nocturns — the only real difference is the three nocturn-closing versicles, which
swap plain-confessor imagery for priestly/episcopal imagery (e.g. Nocturn 3's "Tu es sacérdos
in ætérnum * secúndum órdinem Melchísedech"). Not modeled as an `aliases` redirect (unlike
Common-of-a-Martyr-Bishop → Common-of-a-Martyr, a genuine zero-difference redirect) because the
versicles really do differ and the mechanism has no partial-override/diff support — kept as
its own full duplicate file instead.
Checked every Duplex+ saint on the calendar in both Common categories (St. Aloysius Gonzaga,
St. Anthony of Padua, St. Paschal Baylon, St. John Cantius, St. Bernardine of Siena, St. Joseph
Calasanctius, St. Raymond Nonnatus, St. Anthony Mary Zaccaria for `common-of-a-confessor`; St.
Norbert, St. Andrew Corsini for `common-of-a-confessor-bishop`) — none has a proper antiphon of
his own, so (same as Common-of-a-Martyr-Bishop before it) neither category currently has a
saint eligible for its own `saints/` entry; a real, not accidental, gap, matching the existing
policy of only authoring `saints/*.yml` where a genuine proper deviation exists.
Two live saints, St. Aloysius Gonzaga (June 21) and St. Norbert (June 6), turned out **not** to
be on the Monastic Tridentinum 1617 calendar at all despite having a `SanctiM/MM-DD.txt` file —
confirmed via `Tabulae/Kalendaria/M1617.txt` (no entry) and `Tabulae/Kalendaria/M1930.txt` (only
Norbert, added there) — both were canonized well after 1617 (Aloysius 1726, though pre-1617 in
Norbert's case his 1582 canonization still postdates the M1617 calendar's fixed content). Their
`SanctiM` stub files are shared infrastructure for later Monastic versions (1930/1963), not
proof the saint was ever actually prayed under M1617 itself — a live `Pofficium.pl` query on
their own date just falls through to a plain feria. Not a blocker for authoring the Common
category files themselves (the Common content is shared infrastructure regardless of which
Monastic version's kalendar wires a given saint into it), but worth remembering next time a
"vide C" saint's own date doesn't reproduce under `version=Monastic Tridentinum 1617` — check
`Tabulae/Kalendaria/M1617.txt` before assuming the date/year picked was wrong.
`tests/hours/matins.test.ts` gained two new Common-tier proofs (Francis of Paola,
`common-of-a-confessor`; Basil the Great, `common-of-a-confessor-bishop` — same psalm numbers as
the other, checked directly). The old ferial-fallback proof (St. John Bosco, `common-of-a-
confessor`, now superseded) was swapped for Ss. Fabian and Sebastian (`common-of-several-
martyrs`, still unauthored, 2029-01-20, already live-verified clean). `npm test` (377 tests),
`tsc --noEmit` pass.
### Calendar precedence: a tie against an octave's own elevated closing day now favors the octave (2026-08) ### Calendar precedence: a tie against an octave's own elevated closing day now favors the octave (2026-08)
Real bug found and fixed, same session as the Matins psalmody fix above, discovered while Real bug found and fixed, same session as the Matins psalmody fix above, discovered while
@@ -0,0 +1,107 @@
# Generic Common-of-a-Confessor-Bishop Matins psalmody, live-verified
# against Divinum Officium (Monastic Tridentinum 1617, command=
# prayMatutinum, 2035-06-14 -- St. Basil the Great, whose own [Rule]
# says "vide C4a" (Doctor) but whose live-rendered Matins is byte-
# identical to the plain Common template here, per this app's own
# st-basil-the-great.yml note -- so his output *is* this category's
# generic scheme, not a saint-specific deviation).
#
# Same psalm numbers and antiphon text in all three nocturns as
# common-of-a-confessor.yml (Common of a Confessor Not a Bishop) --
# checked side by side, word for word identical. The only real
# difference is the three nocturn-closing versicles, which swap the
# plain-confessor imagery for priestly/episcopal imagery (e.g. "Tu es
# sacérdos in ætérnum * secúndum órdinem Melchísedech" in Nocturn 3).
# Kept as its own full file rather than an alias (unlike
# Common-of-a-Martyr-Bishop -> Common-of-a-Martyr, a genuine total
# redirect with zero differences) because this scheme's own mechanism
# doesn't support "same groups, different versicles" as a partial
# override -- see matins-psalmody-overrides.ts's own doc comment on
# category files being standalone, not diffed/merged.
id: common-of-a-confessor-bishop
nocturn1:
groups:
- psalms: [1]
antiphon:
la: "Beátus vir, * qui in lege Dómini meditátur: volúntas ejus pérmanet die ac nocte, et ómnia quæcúmque fáciet, semper prosperabúntur."
en: "Blessed is the man * that doth meditate in the law of the Lord; his delight is therein day and night, and whatsoever he doeth shall prosper."
- psalms: [2]
antiphon:
la: "Beátus iste Sanctus, * qui confísus est in Dómino, prædicávit præcéptum Dómini, constitútus est in monte sancto ejus."
en: "Blessed and holy is he * that putteth his trust in the Lord, that declareth the decree of the Lord, and is set upon His holy hill."
- psalms: [4]
antiphon:
la: "Invocántem * exaudívit Dóminus Sanctum suum; Dóminus exaudívit eum, et constítuit eum in pace."
en: "When His holy one called, * the Lord heard him, yea, the Lord heard him, and gave him peace."
- psalms: [5]
antiphon:
la: "Læténtur omnes * qui sperant in te, Dómine; quóniam tu benedixísti justo, scuto bonæ voluntátis tuæ coronásti eum."
en: "Let all those that put their trust in thee rejoice, O Lord, for Thou hast blessed the righteous; * Thou hast compassed him with thy favour as with a shield."
- psalms: [8]
antiphon:
la: "Dómine, Dóminus noster, * quam admirábile est nomen tuum in univérsa terra! quia glória et honóre coronásti Sanctum tuum, et constituísti eum super ópera mánuum tuárum."
en: "O Lord, our Ruler, * how excellent is thy Name in all the earth, Who hast crowned thine holy one with glory and honour, and made him to have dominion over the works of thy hands."
- psalms: [10]
antiphon:
la: "Justus Dóminus * et justítiam diléxit: æquitátem vidit vultus ejus."
en: "The Lord is just, * and hath loved justice: his countenance hath beheld righteousness."
versicle:
v:
la: "Amávit eum Dóminus, et ornávit eum."
en: "The Lord loved him and beautified him."
r:
la: "Stolam glóriæ índuit eum."
en: "He clothed him with a robe of glory."
nocturn2:
groups:
- psalms: [14]
antiphon:
la: "Dómine, * iste Sanctus habitábit in tabernáculo tuo, operátus est justítiam, requiéscet in monte sancto tuo."
en: "Lord, this thy Saint * shall dwell in thy tabernacle, and this that hath worked righteousness shall abide upon thy holy hill."
- psalms: [20]
antiphon:
la: "Vitam pétiit * a te, et tribuísti ei, Dómine: glóriam et magnum decórem imposuísti super eum; posuísti in cápite ejus corónam de lápide pretióso."
en: "He asked life of thee, * and Thou, O Lord, gavest it; honour and great majesty hast Thou laid upon him; Thou hast set a crown of precious stones upon his head."
- psalms: [23]
antiphon:
la: "Hic accípiet * benedictiónem a Dómino, et misericórdiam a Deo salutári suo: quia hæc est generátio quæréntium Dóminum."
en: "He shall receive * a blessing from the Lord, and mercy from the God of his salvation, for this is the generation of them that seek the Lord."
- psalms: [95]
antiphon:
la: "Cognóvit eum Dóminus * in benedictiónibus suis, et invénit grátiam coram óculis Dómini."
en: "The Lord knew him * in his blessings, and he found great favour in the eyes of the Lord."
- psalms: [96]
antiphon:
la: "In fide * et lenitáte ípsius Sanctum fecit illum Dóminus, et osténdit illi glóriam suam."
en: "The Lord * sanctified him in his faith, and meekness, and showed him his glory."
- psalms: [97]
antiphon:
la: "Justus * germinábit sicut lílium: et florébit in ætérnum ante Dóminum."
en: "The just man * shall spring as the lily: and will flower forever before the Lord."
versicle:
v:
la: "Elégit eum Dóminus sacerdótem sibi."
en: "The Lord hath chosen him for a Priest unto Himself."
r:
la: "Ad sacrificándum ei hóstiam laudis."
en: "To offer up unto Him the sacrifice of praise."
nocturn3:
antiphon:
la: "Sint lumbi vestri * præcíncti et lucérnæ ardéntes in mánibus vestris."
en: "Let your loins * be girt, and lamps burning in your hands."
canticles:
- refs:
- { book: sir, chapter: 14, verses: "22" }
- { book: sir, chapter: 15, verses: "3-4" }
- { book: sir, chapter: 15, verses: "6" }
- refs:
- { book: jer, chapter: 17, verses: "7-8" }
- refs:
- { book: sir, chapter: 31, verses: "8-11" }
versicle:
v:
la: "Tu es sacérdos in ætérnum."
en: "Thou art a Priest for ever."
r:
la: "Secúndum órdinem Melchísedech."
en: "After the order of Melchisedek."
@@ -0,0 +1,105 @@
# Generic Common-of-a-Confessor (not a Bishop) Matins psalmody,
# live-verified against Divinum Officium (Monastic Tridentinum 1617,
# command=prayMatutinum, 2030-04-02 -- St. Francis of Paola, whose own
# rule file is "vide C5" i.e. entirely from this Common, no proper
# antiphon of his own -- so his output *is* this category's generic
# scheme, not a saint-specific deviation).
#
# Also live-verified (2035-06-14, St. Basil the Great -- see
# common-of-a-confessor-bishop.yml) that Common-of-a-Confessor-Bishop's
# own Matins psalmody uses the *exact same* psalm numbers and antiphon
# text as this file in all three nocturns -- the two Commons only differ
# in their three nocturn-closing versicles (this file's plain-confessor
# imagery vs. the Bishop file's priestly/episcopal imagery, e.g. "Tu es
# sacérdos in ætérnum"). Not modeled as an alias here (unlike
# Common-of-a-Martyr-Bishop -> Common-of-a-Martyr, a genuine full
# redirect) because the versicles are a real, if small, difference —
# `common-of-a-confessor-bishop.yml` is its own full file, duplicating
# these same psalm/antiphon groups with its own versicles.
id: common-of-a-confessor
nocturn1:
groups:
- psalms: [1]
antiphon:
la: "Beátus vir, * qui in lege Dómini meditátur: volúntas ejus pérmanet die ac nocte, et ómnia quæcúmque fáciet, semper prosperabúntur."
en: "Blessed is the man * that doth meditate in the law of the Lord; his delight is therein day and night, and whatsoever he doeth shall prosper."
- psalms: [2]
antiphon:
la: "Beátus iste Sanctus, * qui confísus est in Dómino, prædicávit præcéptum Dómini, constitútus est in monte sancto ejus."
en: "Blessed and holy is he * that putteth his trust in the Lord, that declareth the decree of the Lord, and is set upon His holy hill."
- psalms: [4]
antiphon:
la: "Invocántem * exaudívit Dóminus Sanctum suum; Dóminus exaudívit eum, et constítuit eum in pace."
en: "When His holy one called, * the Lord heard him, yea, the Lord heard him, and gave him peace."
- psalms: [5]
antiphon:
la: "Læténtur omnes * qui sperant in te, Dómine; quóniam tu benedixísti justo, scuto bonæ voluntátis tuæ coronásti eum."
en: "Let all those that put their trust in thee rejoice, O Lord, for Thou hast blessed the righteous; * Thou hast compassed him with thy favour as with a shield."
- psalms: [8]
antiphon:
la: "Dómine, Dóminus noster, * quam admirábile est nomen tuum in univérsa terra! quia glória et honóre coronásti Sanctum tuum, et constituísti eum super ópera mánuum tuárum."
en: "O Lord, our Ruler, * how excellent is thy Name in all the earth, Who hast crowned thine holy one with glory and honour, and made him to have dominion over the works of thy hands."
- psalms: [10]
antiphon:
la: "Justus Dóminus * et justítiam diléxit: æquitátem vidit vultus ejus."
en: "The Lord is just, * and hath loved justice: his countenance hath beheld righteousness."
versicle:
v:
la: "Amávit eum Dóminus, et ornávit eum."
en: "The Lord loved him and beautified him."
r:
la: "Stolam glóriæ índuit eum."
en: "He clothed him with a robe of glory."
nocturn2:
groups:
- psalms: [14]
antiphon:
la: "Dómine, * iste Sanctus habitábit in tabernáculo tuo, operátus est justítiam, requiéscet in monte sancto tuo."
en: "Lord, this thy Saint * shall dwell in thy tabernacle, and this that hath worked righteousness shall abide upon thy holy hill."
- psalms: [20]
antiphon:
la: "Vitam pétiit * a te, et tribuísti ei, Dómine: glóriam et magnum decórem imposuísti super eum; posuísti in cápite ejus corónam de lápide pretióso."
en: "He asked life of thee, * and Thou, O Lord, gavest it; honour and great majesty hast Thou laid upon him; Thou hast set a crown of precious stones upon his head."
- psalms: [23]
antiphon:
la: "Hic accípiet * benedictiónem a Dómino, et misericórdiam a Deo salutári suo: quia hæc est generátio quæréntium Dóminum."
en: "He shall receive * a blessing from the Lord, and mercy from the God of his salvation, for this is the generation of them that seek the Lord."
- psalms: [95]
antiphon:
la: "Cognóvit eum Dóminus * in benedictiónibus suis, et invénit grátiam coram óculis Dómini."
en: "The Lord knew him * in his blessings, and he found great favour in the eyes of the Lord."
- psalms: [96]
antiphon:
la: "In fide * et lenitáte ípsius Sanctum fecit illum Dóminus, et osténdit illi glóriam suam."
en: "The Lord * sanctified him in his faith, and meekness, and showed him his glory."
- psalms: [97]
antiphon:
la: "Justus * germinábit sicut lílium: et florébit in ætérnum ante Dóminum."
en: "The just man * shall spring as the lily: and will flower forever before the Lord."
versicle:
v:
la: "Os justi meditábitur sapiéntiam."
en: "The mouth of the righteous speaketh wisdom."
r:
la: "Et lingua ejus loquétur judícium."
en: "And his tongue talketh judgment."
nocturn3:
antiphon:
la: "Sint lumbi vestri * præcíncti et lucérnæ ardéntes in mánibus vestris."
en: "Let your loins * be girt, and lamps burning in your hands."
canticles:
- refs:
- { book: sir, chapter: 14, verses: "22" }
- { book: sir, chapter: 15, verses: "3-4" }
- { book: sir, chapter: 15, verses: "6" }
- refs:
- { book: jer, chapter: 17, verses: "7-8" }
- refs:
- { book: sir, chapter: 31, verses: "8-11" }
versicle:
v:
la: "Lex Dei ejus in corde ipsíus."
en: "The law of his God is in his heart."
r:
la: "Et non supplantabúntur gressus ejus."
en: "None of his steps shall slide."
+38 -4
View File
@@ -194,14 +194,48 @@ describe('resolveOrdo("matins", ...) Duplex+ weekday-feast (3-nocturn, non-Sunda
} }
}); });
it("falls through to Common-of-a-Confessor's own generic psalmody for a Duplex+ weekday winner with no proper antiphon authored", () => {
// St. Francis of Paola (Duplex, Common of a Confessor Not a Bishop,
// no proper antiphon of his own) -- 2030-04-02, confirmed clean (no
// Sunday collision, safely outside Holy Week/Easter octave that year)
// per his own saint-file comment.
const common = resolveOrdo('matins', '2030-04-02');
const psalms = common.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
expect(psalms).toEqual([3, 94, 1, 2, 4, 5, 8, 10, 14, 20, 23, 95, 96, 97]);
expect(common.parts.some((p) => p.kind === 'te-deum')).toBe(true);
expect(common.parts.some((p) => p.kind === 'canticle')).toBe(true);
const psalmParts = common.parts.filter((p) => p.kind === 'psalm') as { psalmNumber: number; antiphon?: { status: Record<string, string> } }[];
const nocturnPsalms = psalmParts.filter((p) => ![3, 94].includes(p.psalmNumber));
for (const p of nocturnPsalms) {
expect(p.antiphon?.status.la).toBe('verified');
expect(p.antiphon?.status.en).toBe('verified');
}
});
it("falls through to Common-of-a-Confessor-Bishop's own generic psalmody -- same psalm numbers as Common of a Confessor, different Nocturn versicles", () => {
// St. Basil the Great (Duplex, live-verified as byte-identical to
// common-of-a-confessor-bishop.yml despite his own [Rule] saying
// "vide C4a") -- 2035-06-14, confirmed clean per his own saint-file
// comment.
const common = resolveOrdo('matins', '2035-06-14');
const psalms = common.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
// Same psalm numbers in every slot as Common of a Confessor Not a
// Bishop above -- the two Commons only differ in their versicles.
expect(psalms).toEqual([3, 94, 1, 2, 4, 5, 8, 10, 14, 20, 23, 95, 96, 97]);
expect(common.parts.some((p) => p.kind === 'te-deum')).toBe(true);
expect(common.parts.some((p) => p.kind === 'canticle')).toBe(true);
});
it('falls all the way back to the plain ferial weekday table, redistributed into 3 nocturns, when neither a proper nor a Common entry is authored', () => { it('falls all the way back to the plain ferial weekday table, redistributed into 3 nocturns, when neither a proper nor a Common entry is authored', () => {
// St. John Bosco (Duplex, Common of a Confessor -- no // Ss. Fabian and Sebastian (Duplex, Common of Several Martyrs -- no
// matins-psalmody-overrides category authored yet for either tier) // matins-psalmody-overrides category authored yet for either tier)
// -- 2026-01-31, a Saturday, confirmed clean (no Sunday collision). // -- 2029-01-20, a Saturday, confirmed clean (no Sunday collision)
const fallback = resolveOrdo('matins', '2026-01-31'); // per their own saint-file comment.
const fallback = resolveOrdo('matins', '2029-01-20');
const psalms = fallback.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber); const psalms = fallback.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
// Ps 3 + 94, then Saturday's own 9-psalm ferial table // Ps 3 + 94, then Saturday's own 9-psalm ferial table
// (psalter-distribution.yml), not any Common-of-a-Confessor scheme. // (psalter-distribution.yml), not any Common-of-Several-Martyrs scheme.
expect(psalms[0]).toBe(3); expect(psalms[0]).toBe(3);
expect(psalms[1]).toBe(94); expect(psalms[1]).toBe(94);
expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', 'saturday').map((r) => r.number)); expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', 'saturday').map((r) => r.number));