diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 15:27:00 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 15:27:00 +0200 |
| commit | c655c525ef07deafb113a291489b396c81d7ce4a (patch) | |
| tree | 5dd82811c100de6daa995bebd115a12434296508 /scripts | |
| parent | ef171295a5d7b7cc40ad7367e142de4234a4eb54 (diff) | |
| download | lectio-c655c525ef07deafb113a291489b396c81d7ce4a.tar.gz lectio-c655c525ef07deafb113a291489b396c81d7ce4a.zip | |
gen-sanctoral-ef, caldata: fix round 3 -- guard coverage, rank pins, name test, Anastasia
Second external re-review returned Spec pass on round 2 (all ten prior
findings addressed, C1/C2 fixed at the root, mechanism claims held
under mutation) and found two blocking items in the I3 guard itself,
plus three recommended hardenings. All verified independently before
fixing.
BLOCKING 1: refYearExplainsAbsence's own doc comment said the id-rank
exclusion covers "6-31 December"; the code implemented `Day() >= 26`.
A typo (26 -> 6), but the wrong kind to have on the one comment this
whole review chain exists to keep honest -- fixed as part of the
rewrite below, not left as an isolated one-character correction.
BLOCKING 2: the guard covered only 6 of the 13 MM-DD values round 1's
own temporal_ef.go changes actually made refYearExplainsAbsence
couple to. Measured, not just reasoned about: running the function's
body against the branch-point engine versus HEAD over the six
reference years flips its verdict on 50 dates across 12-17 through
12-23 (round 1's late-Advent promotion, RG 91 entry 18, a 140-day
change) AND 12-26 through 12-31 (the Nativity-octave promotion, RG
67/68, already guarded). The late-Advent half was unguarded.
No live bug today -- confirmed by regenerating: the ini is
BYTE-IDENTICAL before and after this fix. The only sanctoral entry in
17-23 December is `thomas` (21 December), reached via harvestDate's
`obs` (observed-office) path, never refYearExplainsAbsence's
commemoration-id-trust path at all. Widened the guard to 17-31
December anyway, on the side of the known-safe default
(RankCommemoration) rather than wait for the first real entry in that
range to repeat I3's exact failure shape. The two sub-ranges are NOT
the same strength of evidence, and the doc comment says so explicitly
now: 26-31 December rests on a positive RG 68(d)/(e) citation naming
Thomas Becket and Silvester directly as bare "Commemoratio, no
class"; 17-23 December is a precautionary exclusion with no equivalent
citation -- RG 91 entry 18 ranks the FERIA, not any commemoration
that might coincide with it, and (per defect 2's own finding) a
genuine class-2 feast there would actually WIN against the ferias's
own privilege, so "the day is class-2" does not reliably explain a
class-2 saint's absence here the way it does for the Nativity octave's
two named cases. Recorded as a real asymmetry, not smoothed over.
RECOMMENDED 3: rank = commemoration is now pinned for all four entries
this task's two review rounds touched -- romanus, eusebius-confessor
(C2), thomas-becket, silvester (I3) -- not just their presence and
date. `git grep` before this commit returned zero hits for
thomas-becket or silvester rank anywhere in *_test.go; a future
regeneration could have rewritten either back to class-4, the exact
I3 failure mode, with the suite green throughout.
RECOMMENDED 4: TestTridentineNamesPreservedAcrossRegeneration
rewritten on both axes the review found loose. It counted name.pl
only -- a hardcoded single language, the identical "whitelist of two"
mistake C1's own generator fix exists to stop repeating, just moved
into the test instead. Now DISCOVERS every name.<lang> key actually
present in the data (so a regeneration dropping name.la, or losing a
future name.de, is caught the same way as name.pl) and asserts the
discovered language set is exactly {en, pl} -- not just each count.
The >= 315 floor had 7 entries of slack (the review proved 6 missing
Polish names still passed it); tightened to the exact, independently
verified true count (322), zero slack.
RECOMMENDED 5: the 25 December Anastasia gap and its cause are now
recorded, not just her absence. Two compounding, independently
checked causes: fetchOnce reads only `data[0]`, missalemeum's FIRST
of Christmas Day's three Masses, so a commemoration attached to a
second or third Mass (the calendarium's own "In secunda Missa:
Commemoratio S. Anastasiae Mart.") is structurally invisible to this
harvester for ANY date, not just this one -- but ALSO, checked live,
missalemeum's own "commemorations" list is empty on all three of the
date's records, not just the first, so a fetchOnce rewritten to merge
all of a date's Masses would still not recover her. Documented at
both fetchOnce's own definition and the PRIMARY-SOURCE NOTE, since
the two facts belong to different questions (a real code limitation
worth fixing someday vs. an upstream data gap this generator cannot
close at all).
Witnesses: TestTridentineCommemorationRanksPinned (new),
TestTridentineRomanusAndEusebiusPresent (extended with rank),
TestTridentineNamesPreservedAcrossRegeneration (rewritten). No pre-fix
failure to report for the two new/tightened tests specifically --
these are regression GUARDS against a bug class already fixed in
round 2, not fixes for a currently-red state; their value is in what
they would now catch, not what they catch today. internal/caldata/
tridentine-calendar.ini is NOT regenerated by this commit -- confirmed
byte-identical before and after re-running the generator, exactly as
"no live bug today" predicts.
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/gen-sanctoral-ef.go | 101 |
1 files changed, 79 insertions, 22 deletions
diff --git a/scripts/gen-sanctoral-ef.go b/scripts/gen-sanctoral-ef.go index 0812472..0a43869 100644 --- a/scripts/gen-sanctoral-ef.go +++ b/scripts/gen-sanctoral-ef.go @@ -233,6 +233,19 @@ type mmDay struct { } `json:"sections"` } +// fetchOnce fetches one date's proper. KNOWN LIMITATION: missalemeum +// returns one array element PER MASS on a date with more than one (25 +// December: three, ids ...m1/...m2/...m3) and this reads only `data[0]`, +// the first -- so a commemoration attached specifically to a second or +// third Mass (the calendarium's own "In secunda Missa: Commemoratio..." +// pattern, e.g. St Anastasia on Christmas Day) is structurally invisible +// to every caller of this function, not just harvestDate's own use of it. +// See the PRIMARY-SOURCE NOTE above harvestDate for the fuller account -- +// in Anastasia's specific case this is moot (missalemeum's own +// "commemorations" list is empty on all three of the date's records, not +// just the first, so fixing this would not by itself recover her), but the +// limitation is real and would matter for any date whose SECOND or third +// Mass genuinely does carry a commemoration missalemeum's API records. func fetchOnce(date string) (*mmDay, error) { req, _ := http.NewRequest("GET", "https://www.missalemeum.com/en/api/v5/proper/"+date, nil) req.Header.Set("User-Agent", ua) @@ -350,6 +363,28 @@ func readingsFrom(d *mmDay) (first, gospel string) { // wins. (14 August's "St. Eusebius, Conf." and 16 December's "St. Eusebius, // Ep. et Mart." are two different people, both genuinely in the calendarium // -- see slugOverride below, not a reason to drop either.) +// +// Of the four confirming examples above, three (Donatus, Romanus, Eusebius) +// are fixed by this generator as of this note. **25 December's Anastasia is +// NOT, and cannot be from this data source alone** -- a different, NOT +// generator-fixable limitation, recorded so the next person does not +// mistake her continued absence for an oversight of the fix above (she was +// also missing at the branch point, so this is not a regression either): +// the photographic scan places her "In secunda Missa: Commemoratio S. +// Anastasiae Mart." -- specifically the SECOND of Christmas Day's three +// Masses (missalemeum's own "2026-12-25" query independently confirmed to +// return exactly three records, ids ...m1/...m2/...m3). Two compounding +// problems, not one: fetchOnce (below) reads only `data[0]`, the FIRST +// Mass, so this harvester cannot structurally see a commemoration attached +// to a date's second or third Mass at all, for ANY date, not just this +// one -- but ALSO, checked directly (not merely inferred from the +// symptom), missalemeum's OWN "commemorations" list is empty on all THREE +// of the 25 December records, not just the first -- so even a fetchOnce +// rewritten to merge all of a date's Masses would still not recover her: +// missalemeum's own data lacks her here, the same "transcription-shaped" +// gap as Donatus/Romanus/Eusebius, just in the live API rather than the +// static PDF this time. Not fixed: no source this generator reads carries +// her. // slugOverride gives a proper, distinct slug to a small number of // commemorations whose title slugifies IDENTICALLY to an unrelated feast on @@ -388,27 +423,48 @@ var slugOverride = map[string]string{ // lower, per the calendarium), but the function does not itself enforce // it, so a future entry could reach it. // - It is a proxy for "why was this saint never observed", not a citation. -// 6-31 December is excluded from ever trusting the id-derived rank -// below, REGARDLESS of what this heuristic would otherwise say, because -// RG 68(d)/(e) settles two specific cases (Thomas Becket, 29 December; -// Silvester, 31 December) directly: the calendarium's own row names -// each a bare "Commemoratio" with NO class of its own, even though the -// DAY they fall on is II class (a day within the Nativity Octave). A -// rank-blind heuristic reading the day's own class here would (and, -// before this exclusion, did) wrongly conclude "the day was strong -// enough to explain the demotion, so the id's rank is trustworthy" -- -// right about the day, wrong about what the rubric actually says about -// THESE two named commemorations specifically. +// 17-31 December is excluded from ever trusting the id-derived rank +// below, REGARDLESS of what this heuristic would otherwise say -- +// covering BOTH of round 1's own late-Advent/Christmas-octave rank +// promotions, on two DIFFERENT strengths of evidence, recorded +// separately because they are not the same case: +// -- 26-31 December (RG 68(d)/(e), a positive citation): "die 29 +// decembris, fit commemoratio S. Thomae Episcopi et Mart.; die 31 +// decembris, fit commemoratio S. Silvestri I Papae et Conf." -- the +// calendarium names each a bare "Commemoratio" with NO class of its +// own, even though the DAY they fall on is II class (within the +// Nativity Octave). Two REAL entries (Thomas Becket, Silvester) are +// live here; see the call site's own comment. +// -- 17-23 December (RG 91 entry 18, a PRECAUTIONARY exclusion, not a +// positive citation): round 1 also promoted these late-Advent +// ferias from class-3 to class-2 (same lineage, same coupling +// shape as 26-31 December's own promotion). No live entry tests +// this range today -- the sole sanctoral entry there, `thomas` +// (21 December), is an OBSERVED class-2 feast reached via the +// harvestDate `obs` path, not this function's id-rank-trust path +// at all -- but unlike 26-31 December, there is no RG citation +// stating that a saint commemorated here has NO independent class; +// RG 91 entry 18 only ranks the FERIA, and (per defect 2's own +// finding) a genuine class-2 FEAST commemorated here would +// actually WIN against it (entry 16 above entry 18), so "the day +// is class-2" does not reliably explain a class-2 saint's absence +// the way it does for 26-31 December's own two named cases. +// Excluded anyway, on the side of the KNOWN-safe default +// (RankCommemoration) rather than risk repeating the identical +// failure shape the day this range's own first real entry arrives. // -// The 6-31 December exclusion is also a warning about a structural hazard, -// not just a one-off fix: this function calls calendar.Compute, i.e. it -// reads the ENGINE's OWN computed temporal ranks to decide what DATA to -// generate. A change to temporal_ef.go's ranking (e.g. promoting 26-31 -// December from class-4 to class-2, RG 67/68, landed in this same lineage) -// can silently flip this function's verdict and rewrite generated data with -// no code change to this file at all. Any future temporal_ef.go rank change -// should re-check this function's own boundary cases, not just its own -// tests. +// This is also a warning about a structural hazard, not just a boundary +// fix: this function calls calendar.Compute, i.e. it reads the ENGINE's OWN +// computed temporal ranks to decide what DATA to generate. A change to +// temporal_ef.go's ranking (e.g. round 1's own 17-23/26-31 December +// promotions, RG 91 entry 18 / RG 67-68) can silently flip this function's +// verdict and rewrite generated data with no code change to this file at +// all -- confirmed by direct measurement, not just reasoned about: running +// this function's body against the branch-point engine versus the current +// one over the six reference years flips the verdict on 50 dates across +// exactly these 13 MM-DD values (12-17 through 12-23, 12-26 through +// 12-31), no others. Any future temporal_ef.go rank change should re-check +// this function's own boundary cases, not just its own tests. // // harvestDate keeps a saint's commemoration id rank ONLY if every reference // year in which it was seen returned true here -- a single false (an @@ -418,8 +474,9 @@ var slugOverride = map[string]string{ // RankCommemoration, proving the id's rank is a candidate, not a verdict -- // see the call site's own comment). func refYearExplainsAbsence(date time.Time) bool { - if date.Month() == time.December && date.Day() >= 26 && date.Day() <= 31 { - // RG 68(d)/(e): "die 29 decembris, fit commemoratio S. Thomae + if date.Month() == time.December && date.Day() >= 17 && date.Day() <= 31 { + // RG 68(d)/(e) (26-31 Dec) and RG 91 entry 18 (17-23 Dec, + // precautionary): "die 29 decembris, fit commemoratio S. Thomae // Episcopi et Mart.; die 31 decembris, fit commemoratio S. // Silvestri I Papae et Conf." -- both named as a bare // "Commemoratio", no class. See this function's own doc comment. |
