Resolve 15 "unwinnable" stubs' Benedictus antiphons from source directly
Deploy / deploy (push) Successful in 55s

Direct feedback on the previous commit's framing: "unwinnable" was
describing an artifact of Divinum Officium's own single-winner
precedence model, not a real content gap -- this app already renders
commemorated saints on their own terms and doesn't need a saint to win
a live query to have real content. Fixed by reading each of the 15
saints' own already-validated fileref directly (the exact same fileref
their collect was already sourced from during the Kalendaria-table
pull) for its [Ant 1] section, falling back to whatever Common its own
[Rule]/[Rank] "vide"/"ex CXX" reference names when there isn't one --
the same proper-then-Common shape getDayCollect/collectCommon already
uses, just applied to the Benedictus antiphon too.

14 of the 15 had no [Ant 1] of their own at all -- clean Common
fallback, and every one of their C-numbers falls inside a family
already live-query-verified in the previous commit, so
minorHoursCommon/benedictusCommon were wired directly with full
confidence: St. Marcus, St. Felix (Presbyter), St. Hermes, St.
Saturninus, St. Prisca, St. Thecla, St. Catherine of Siena, Ss.
Machabees, Ss. Marius/Martha/Audifax/Abachum, Ss. Twelve Brothers, Ss.
Alexander/Eventius/Theodulus/Juvenal, Ss. Felix/Simplicius/
Faustinus/Beatrix, Ss. Nabor and Felix. St. Felicitas needed one new
category (C7), but it turned out to be a cross-reference resolving to
text byte-identical to the already-verified common-of-a-virgin.

The 1 remaining (Ss. Euphemia, Lucy, and Geminian) does have her own
real [Ant 1] -- but marked draft, not verified, on principle: St.
Alexius's own file also has a genuine [Ant 1] that his actual
live-rendered Benedictus antiphon doesn't use (common-of-an-abbot's
text instead), so a raw [Ant 1] read alone isn't sufficient proof, and
for a saint that can never be live-checked at all that uncertainty
can't be resolved either way -- used the text anyway, honestly flagged
rather than claimed as confirmed.

St. Frances of Rome and St. Bibiana (the older, pre-Kalendaria-table
109-saint pass) are good candidates for this same fix but weren't
touched here, since their own fileref was never derived by this
session's work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 09:38:12 -04:00
parent 9faa7168bc
commit ea15e909df
17 changed files with 229 additions and 16 deletions
+83 -7
View File
@@ -425,6 +425,76 @@ P/T/S/N for these 26 not attempted in this pass — a further layer on
top, same as how the original 109 got Benedictus antiphons long before
their own later, separate P/T/S/N pass (duplex-majus+ subset only).
### The 15 "unwinnable" stubs' Benedictus antiphons, resolved from source (2026-08)
Direct feedback on the section above: "unwinnable" was describing an
artifact of Divinum Officium's own single-winner precedence model, not
a real content gap — the app doesn't need a saint to *win* a live query
to have real content, since this app already renders commemorated
saints on their own terms (`day.commemorations`) rather than only ever
showing the day's outright winner. The fix: read each of the 15 saints'
own already-validated fileref directly (`extract_final.py`'s
`load_file`/`resolve`, the exact same fileref their collect was already
sourced from during the Kalendaria-table pull — no live query, no
"does this saint ever win" requirement at all) for its `[Ant 1]`
section, falling back to whatever Common its own `[Rule]`/`[Rank]`
"`vide`/`ex CXX`" reference names when there isn't one — the same
proper-then-Common shape `getDayCollect`/`collectCommon` already uses,
just applied to the Benedictus antiphon too.
**14 of the 15 had no `[Ant 1]` of their own at all** — clean Common
fallback, and every one of their `[Rule]`/`[Rank]` C-numbers (`C2a`,
`C2a-1`, `C3`, `C3a`, `C3a-1`, `C4b`, `C6`, `C6-1`, `C6a`, `C7`) falls
inside a family already live-query-verified in the section above, so
`minorHoursCommon`/`benedictusCommon` were wired directly with full
confidence, no new content needed: St. Marcus (`C4b` — a whole-file
`@Commune/C4` inherit, just the title relabeled "of the Popes" — into
`common-of-a-confessor-bishop`); St. Felix (Presbyter), St. Hermes, St.
Saturninus (`C2a`/`C2a-1` into `common-of-a-martyr`); St. Prisca, St.
Thecla, St. Catherine of Siena (`C6`/`C6-1`/`C6a` into
`common-of-a-virgin`); Ss. Machabees, Ss. Marius/Martha/Audifax/Abachum,
Ss. Twelve Brothers, Ss. Alexander/Eventius/Theodulus/Juvenal, Ss.
Felix/Simplicius/Faustinus/Beatrix, Ss. Nabor and Felix
(`C3`/`C3a`/`C3a-1` into `common-of-several-martyrs`). St. Felicitas is
the one genuine new finding: her file points to `C7` ("Commune Unius
non Virginis Martyris"), which isn't a category this project had
touched yet — but `C7`'s own `[Ant 1]` is itself a cross-reference
(`@Commune/C6:Ant 2`) that resolves to text byte-identical to
`common-of-a-virgin`'s own already-verified Benedictus antiphon, so no
new Common file was needed even there.
**The 1 remaining (Ss. Euphemia, Lucy, and Geminian) does have her own
real `[Ant 1]`** — but marked `status.la: draft`, not `verified`, on
principle: a real counterexample turned up in the *same* pass while
double-checking a winnable saint for comparison — St. Alexius's own
file also has a genuine `[Ant 1]` ("Similábo eum..."), yet his actual
live-rendered Benedictus antiphon (already confirmed in the section
above) uses `common-of-an-abbot`'s text instead, not his own `[Ant 1]`
at all. So a raw `[Ant 1]` read is demonstrably not sufficient proof by
itself that it's what the Benedictus canticle actually uses (Vespers'
Magnificat is a live candidate for what it's *actually* for), even for
a saint that *can* be live-checked — and for one that never can be, that
uncertainty can't be resolved at all. Used the text anyway (better than
nothing, and plausibly correct), just honestly flagged rather than
claimed as confirmed.
All 15 now have real content — none left "honestly missing." St.
Frances of Rome and St. Bibiana (the original, pre-Kalendaria-table
109-saint pass) are still left missing and are good candidates for this
exact same fix — this pass didn't touch them since their own fileref
was never derived (they predate the Kalendaria-table work this session
built), not because they're any less resolvable in principle. Ss.
Ursula and Companions (the section above) is the one case that's
actually different in kind: no other calendar member shares her
`C6b`/plural-Virgins-and-Martyrs category to cross-check a Common
against, so even a clean file read wouldn't have anything solid to
verify itself against.
Two vigils (Vigil of St. John the Baptist, Vigil of St. Lawrence) didn't
resolve via this file-based method at all (their particular file format
wasn't handled by this pass's parser) — no impact, both already had
real content from the live-query batch above.
### Christmas-Octave Sunday content — done (2026-08)
Was: only the Benedictus antiphon existed for `christmas-octave-sunday`
@@ -761,13 +831,19 @@ correction (confirmed Roman-rite sourced), a handful already matched.
(see the section above for method and its own correction).
- **94 `propers: null` saints: done (2026-08)** — see the dedicated
section below for the collect-authoring pass and the era-specific-
calendar discovery it took to do reliably. Benedictus antiphon: done
for all 94 (own dedicated sections below) — 66 via a shared Common; of
the other 26 (own proper collect), 15 are genuinely unwinnable stubs
left honestly missing, 7 share an existing Common, and 4 have real
unique text. P/T/S/N: done for the 66-saint batch; the 26 propers-set
saints' own P/T/S/N not yet attempted, a further layer on top (see
that section's own note).
calendar discovery it took to do reliably. Benedictus antiphon: real
content for all of them except the deliberate `example-confessor` test
placeholder and Ss. Ursula and Companions (left honestly unset, no way
to verify a category of one — see her own file's comment) — 66 via a
shared Common (own dedicated section below); of the other 26 (own
proper collect), 21 share an existing Common (7 live-query-verified
directly, 14 more resolved straight from source once "unwinnable"
stopped being treated as a content blocker — see that section's own
note) and 5 have real unique text (one, Ss. Euphemia/Lucy/Geminian,
honestly marked `draft` rather than `verified` since it can never be
checked against a live rendering). P/T/S/N: done for the 66-saint
batch; the 26 propers-set saints' own P/T/S/N not yet attempted, a
further layer on top (see that section's own note).
### Bug found and fixed while pulling christmas-octave-sunday's antiphon
@@ -12,3 +12,8 @@ name: "Ss. Alexander I, Eventius, and Theodulus, Martyrs, and Juvenal, Bishop an
rank: simplex
common: common-of-several-martyrs
propers: "ss-alexander-eventius-theodulus-and-juvenal"
# No [Ant 1] of its own in the source (05-03r.txt) -- [Rule]/[Rank] point to
# Commune/C3 instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -12,3 +12,8 @@ name: "Ss. Felix, Simplicius, Faustinus, and Beatrix, Martyrs"
rank: simplex
common: common-of-several-martyrs
propers: "ss-felix-simplicius-faustinus-and-beatrix"
# No [Ant 1] of its own in the source (07-29cc.txt) -- [Rule]/[Rank] point to
# Commune/C3a instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -7,3 +7,8 @@ name: "Ss. Machabees, Martyrs"
rank: simplex
common: common-of-several-martyrs
propers: "ss-machabees"
# No [Ant 1] of its own in the source (08-01r.txt) -- [Rule]/[Rank] point to
# Commune/C3 instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -10,3 +10,8 @@ name: "Ss. Marius, Martha, Audifax, and Abachum, Martyrs"
rank: simplex
common: common-of-several-martyrs
propers: "ss-marius-martha-audifax-and-abachum"
# No [Ant 1] of its own in the source (01-19.txt) -- [Rule]/[Rank] point to
# Commune/C3a instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -12,3 +12,8 @@ name: "Ss. Nabor and Felix, Martyrs"
rank: simplex
common: common-of-several-martyrs
propers: "ss-nabor-and-felix"
# No [Ant 1] of its own in the source (07-12o.txt) -- [Rule]/[Rank] point to
# Commune/C3a-1 instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -7,3 +7,8 @@ name: "Ss. Twelve Brothers, Martyrs"
rank: simplex
common: common-of-several-martyrs
propers: "ss-twelve-brothers"
# No [Ant 1] of its own in the source (09-01cc.txt) -- [Rule]/[Rank] point to
# Commune/C3 instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon: common-of-several-martyrs
benedictusCommon: common-of-several-martyrs
@@ -11,3 +11,8 @@ name: "St. Catherine of Siena, Virgin"
rank: simplex
common: common-of-a-virgin
propers: "st-catherine-of-siena"
# No [Ant 1] of its own in the source (04-30.txt) -- [Rule]/[Rank] point to
# Commune/C6a instead, the same C-number family already live-query-verified
# for common-of-a-virgin.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-virgin
benedictusCommon: common-of-a-virgin
@@ -7,3 +7,10 @@ name: "St. Felicitas, Martyr"
rank: simplex
common: common-of-a-martyr
propers: "st-felicitas"
# No [Ant 1] of its own in the source (11-23cc.txt) -- [Rule]/[Rank] point to
# Commune/C7 instead, the same C-number family already live-query-verified
# for common-of-a-virgin.yml (see that section of TODO.md). C7 itself
# resolves via a cross-reference to Commune/C6:Ant 2, confirmed byte-
# identical to common-of-a-virgin's own already-verified text.
minorHoursCommon: common-of-a-virgin
benedictusCommon: common-of-a-virgin
@@ -10,3 +10,8 @@ name: "St. Felix, Priest and Martyr"
rank: simplex
common: common-of-a-martyr
propers: "st-felix-presbyter"
# No [Ant 1] of its own in the source (01-14cc.txt) -- [Rule]/[Rank] point to
# Commune/C2a instead, the same C-number family already live-query-verified
# for common-of-a-martyr.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-martyr
benedictusCommon: common-of-a-martyr
+5
View File
@@ -7,3 +7,8 @@ name: "St. Hermes, Martyr"
rank: simplex
common: common-of-a-martyr
propers: "st-hermes"
# No [Ant 1] of its own in the source (08-28cc.txt) -- [Rule]/[Rank] point to
# Commune/C2a-1 instead, the same C-number family already live-query-verified
# for common-of-a-martyr.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-martyr
benedictusCommon: common-of-a-martyr
+5
View File
@@ -8,3 +8,8 @@ name: "St. Marcus, Pope and Confessor"
rank: simplex
common: common-of-a-confessor-bishop
propers: "st-marcus"
# No [Ant 1] of its own in the source (10-07t.txt) -- [Rule]/[Rank] point to
# Commune/C4b instead, the same C-number family already live-query-verified
# for common-of-a-confessor-bishop.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-confessor-bishop
benedictusCommon: common-of-a-confessor-bishop
+5
View File
@@ -10,3 +10,8 @@ name: "St. Prisca, Virgin and Martyr"
rank: simplex
common: common-of-a-virgin-martyr
propers: "st-prisca"
# No [Ant 1] of its own in the source (01-18r.txt) -- [Rule]/[Rank] point to
# Commune/C6-1 instead, the same C-number family already live-query-verified
# for common-of-a-virgin.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-virgin
benedictusCommon: common-of-a-virgin
@@ -7,3 +7,8 @@ name: "St. Saturninus, Martyr"
rank: simplex
common: common-of-a-martyr
propers: "st-saturninus"
# No [Ant 1] of its own in the source (11-29r.txt) -- [Rule]/[Rank] point to
# Commune/C2a-1 instead, the same C-number family already live-query-verified
# for common-of-a-martyr.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-martyr
benedictusCommon: common-of-a-martyr
+5
View File
@@ -7,3 +7,8 @@ name: "St. Thecla, Virgin and Martyr"
rank: simplex
common: common-of-a-virgin-martyr
propers: "st-thecla"
# No [Ant 1] of its own in the source (09-23cc.txt) -- [Rule]/[Rank] point to
# Commune/C6 instead, the same C-number family already live-query-verified
# for common-of-a-virgin.yml (see that section of TODO.md).
minorHoursCommon: common-of-a-virgin
benedictusCommon: common-of-a-virgin
@@ -0,0 +1,22 @@
# Pulled directly from her own source file's [Ant 1]
# (Sancti/09-16cc.txt, the same fileref her collect was verified against
# -- see her own saint record's comment) -- NOT live-query-verified, and
# marked `draft` rather than `verified` for that reason: she can never
# be the day's own rendered title (always commemorated under Ss.
# Cornelius and Cyprian, see her own file's comment), so there is no
# live Lauds page to confirm this [Ant 1] text is actually what's used
# at the Benedictus specifically, rather than elsewhere (Vespers'
# Magnificat, say) -- a real, demonstrated risk: St. Alexius's own file
# likewise has a real [Ant 1] ("Similábo eum...") that his live-rendered
# Benedictus antiphon does *not* use (it draws from common-of-an-abbot
# instead, confirmed live) -- so a raw [Ant 1] read is not on its own
# sufficient proof for a *winnable* saint either, let alone one that can
# never be checked. English translated (the source's own English page
# had no rendering at all), same `draft` reasoning applies twice over.
id: ss-euphemia-lucia-and-geminianus-antiphon
text:
la: "Gaudent in cælis * ánimæ Sanctórum, qui Christi vestígia sunt secúti: et quia pro ejus amóre sánguinem suum fudérunt, ídeo cum Christo exsúltant sine fine."
en: "The souls of the Saints rejoice in heaven, * who have followed the footsteps of Christ: and because for love of him they shed their blood, therefore they exult with Christ without end."
status:
la: draft
en: draft
+52 -9
View File
@@ -9,6 +9,17 @@ import {
} from '../../src/hours/resolve-common';
import type { LiturgicalDay } from '../../src/calendar/types';
function syntheticSanctoralDay(id: string): LiturgicalDay {
return {
date: '2026-01-01',
weekday: 'thursday',
season: 'trinitytide',
temporalCategory: 'ordinary-feria',
winner: { kind: 'sanctoral', id, name: id, rank: 'simplex' },
commemorations: [],
};
}
describe('getDayCollect', () => {
it('resolves a real, verified collect for an ordinary Sunday', () => {
const day = resolveDay('2026-08-09'); // post-pentecost-11
@@ -30,20 +41,52 @@ describe('getDayCollect', () => {
// Kalendaria-table pull, 2026-08) -- so this exercises the "missing"
// path with a synthetic winner rather than hunting for a real
// example that no longer exists.
const day: LiturgicalDay = {
date: '2026-01-01',
weekday: 'thursday',
season: 'trinitytide',
temporalCategory: 'ordinary-feria',
winner: { kind: 'sanctoral', id: 'example-confessor', name: 'Example Confessor (placeholder)', rank: 'simplex' },
commemorations: [],
};
const collect = getDayCollect(day);
const collect = getDayCollect(syntheticSanctoralDay('example-confessor'));
expect(collect.status.la).toBe('missing');
expect(collect.status.en).toBe('missing');
});
});
describe('genuinely unwinnable stubs still get a Benedictus antiphon (2026-08)', () => {
// These saints are always commemorated, never the day's own rendered
// title (see e.g. st-marcus.yml's own comment) -- so unlike the rest of
// this project's content, their own [Ant 1] (or lack of one) had to be
// pulled directly from source rather than confirmed against a live
// Lauds query, since no such query is ever possible for them. A
// synthetic winner is used here for the same reason: resolveDay would
// never actually pick one of these as day.winner.
it("resolves via its Common category when the saint's own file has no [Ant 1] at all", () => {
// St. Marcus -- [Rule] says "vide C4b" with no override; C4b is a
// whole-file inherit of C4 (just the title relabeled "of the Popes"),
// the same family already live-query-verified for
// common-of-a-confessor-bishop.yml.
const ben = getBenedictusAntiphon(syntheticSanctoralDay('st-marcus'));
expect(ben.status).toEqual({ la: 'verified', en: 'verified' });
});
it('resolves via a cross-referenced Common category (C7 -> Commune/C6:Ant 2)', () => {
// St. Felicitas -- [Rule] says "vide C7", a category this project
// hadn't touched before; C7's own [Ant 1] is itself a reference to
// Commune/C6:Ant 2, which resolves to text byte-identical to
// common-of-a-virgin's own already-verified Benedictus antiphon.
const ben = getBenedictusAntiphon(syntheticSanctoralDay('st-felicitas'));
expect(ben.status).toEqual({ la: 'verified', en: 'verified' });
});
it("resolves its own file's [Ant 1] as draft, not verified, when it can't be checked against a live rendering", () => {
// Ss. Euphemia, Lucy, and Geminian -- her own file *does* have an
// [Ant 1], but St. Alexius (a winnable saint checked earlier in this
// same 2026-08 pass) proved a real [Ant 1] doesn't always end up the
// rendered Benedictus antiphon either (his own live Lauds query uses
// common-of-an-abbot's text instead) -- so a raw-file [Ant 1] read is
// never enough on its own to claim `verified`, and even less so for a
// saint no live query can ever confirm.
const ben = getBenedictusAntiphon(syntheticSanctoralDay('ss-euphemia-lucia-and-geminianus'));
expect(ben.status).toEqual({ la: 'draft', en: 'draft' });
expect(ben.text.la).toContain('Gaudent');
});
});
describe('minorHoursCommon/benedictusCommon fallback (2026-08, the 94 propers:null saints)', () => {
it('a below-duplex-majus saint still gets a real Benedictus antiphon and P/T/S/N content via its Common category', () => {
// St. Anacletus, Semiduplex -- below the duplex-majus+ threshold that