summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 15:27:00 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 15:27:00 +0200
commitc655c525ef07deafb113a291489b396c81d7ce4a (patch)
tree5dd82811c100de6daa995bebd115a12434296508 /scripts
parentef171295a5d7b7cc40ad7367e142de4234a4eb54 (diff)
downloadlectio-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.go101
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.