diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-13 11:19:19 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-13 11:19:19 +0200 |
| commit | ee5c81e70adcd2ce6cecc57da4c416d482c187ca (patch) | |
| tree | 45b5d7951bae89b0720d876a2abfddc82627e78c | |
| parent | b5dcdd8d4048295cfbdf1606f4cafd39b3c433e6 (diff) | |
| download | colitur-ee5c81e70adcd2ce6cecc57da4c416d482c187ca.tar.gz colitur-ee5c81e70adcd2ce6cecc57da4c416d482c187ca.zip | |
precedence(ef): RG 110, the inseparable Peter/Paul commemoration
RG 110 (Caput XIV): "In Officio et Missa S. Petri semper fit
commemoratio S. Pauli, et vicissim... pro unica habeantur" -- in the
Office and Mass of either Peter or Paul, a commemoration of the other
is always made, uncapped by RG 111's own admission count. Both
photographic scans, full text including sub-clause (c) (a separate
trigger this register previously had not transcribed: the same rule
also fires when one Apostle's oration is admitted merely as an
ordinary/privileged commemoration, not only when it is the day's own
office).
Three real pairs in the 1962 calendar, all confirmed on both scans:
25 January (conversion-of-st-paul + peter), 22 February
(chair-of-st-peter + paul), 30 June
(in-commemoratione-sancti-pauli-apostoli + a new
commemoration-of-st-peter). The first two already existed as ordinary
sanctoral candidates but were wired into RG 111's ordinary, capped
admission contest instead of this rule's own uncapped one -- the
defect this closes. 30 June had no companion at all: a genuine gap
in lectio's own source data (confirmed directly against
tridentine-calendar.ini) and in missalemeum's own oracle output for
both fixture years, not only a colitur bootstrap miss, so colitur is
now uniquely correct on this date among the three engines compared.
Added via data/ef/adjustments.sexp's own Add directive, the
architecture's existing extension point for exactly this shape of
gap, since data/ef/sanctoral.sexp is generated and lectio must not be
touched.
Precedence_ef.admit gained rg110_additions/rg110_companion_slug,
layered on after admit's own four ordinarily-capped branches decide
their normal result: checks both the observed candidate and every
already-admitted candidate against the three-pair lookup and prepends
any companion found, uncapped and additional, never competing for
RG 111's own slot.
Measured full blast radius (1583-9999, pre-change binary from git
archive at 59fbda8 vs the fixed binary, 3,074,246 days each side,
14,627 differing days, all four predicted shapes, zero unclassified):
3,619 Holy Name fallback days (previous commit), 3,533 days Paul was
wrongly excluded from Chair of St Peter's own day by a competing
privileged feria, 593 days Chair of St Peter itself was admitted only
as an ordinary commemoration with Paul entirely absent, and 6,882 days
the new 30 June companion is added. The 3,533+593 figure supersedes an
earlier, narrower 852-day estimate that measured only the delta
between two older code commits, not the rule's full scope.
The lectio differential needed no change: it does not compare
commemorations at all, and this fix never touches season, slug, rank,
or colour. The missalemeum oracle allow-list needed three changes:
M12 removed (22 February 2027 no longer diverges in commemoration
count), M15's date list gained 2027-02-22 (now reaching identity
comparison for the first time, where it lands on that entry's own
pre-existing, unrelated limit -- one of the two commemorations is
temporal-origin and unnamed), and a new M19 for the 30 June gap.
Mutation-tested, both halves separately: disabling the admit-level
addition reddens the two new unit tests, the oracle's own M12-shaped
checks, and an existing golden pin (updated to expect the Paul
companion); removing only the 30 June data entry reddens solely the
oracle's new M19 count, confirming the code and data halves are
independently load-bearing.
| -rw-r--r-- | data/ef/adjustments.sexp | 50 | ||||
| -rw-r--r-- | data/ef/expected-divergences-missalemeum.sexp | 33 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.ml | 161 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.mli | 20 | ||||
| -rw-r--r-- | test/test_golden.ml | 16 | ||||
| -rw-r--r-- | test/test_oracle.ml | 81 | ||||
| -rw-r--r-- | test/test_precedence_ef.ml | 68 |
7 files changed, 387 insertions, 42 deletions
diff --git a/data/ef/adjustments.sexp b/data/ef/adjustments.sexp index e33b4eb..1451b40 100644 --- a/data/ef/adjustments.sexp +++ b/data/ef/adjustments.sexp @@ -120,8 +120,56 @@ ; `data/ef/sanctoral.sexp` is generated and the source is upstream. Logged ; as a lectio-side generator/data defect in docs/research/rules-register.md ; for a future generator fix, not merely patched here silently. +; +; `Add commemoration-of-st-peter` -- NEW (ef-holyname-rg110 task, RG 110). +; A genuine DATA GAP in lectio's own source, not merely a colitur bootstrap +; miss: `~/git/projects/lectio/internal/caldata/tridentine-calendar.ini` has +; no 30 June entry for this at all (checked directly, `grep -n '30 June'` +; equivalent), so there is nothing for `tools/bootstrap_sanctoral.ml` to +; have picked up even correctly. RG 110's own text (docs/research/rules- +; register.md §4, Caput XIV): "In Officio et Missa S. Petri semper fit +; commemoratio S. Pauli, ET VICISSIM" -- in the Office and Mass of EITHER +; Peter or Paul, a commemoration of the OTHER is always made -- confirmed a +; THIRD time by the calendarium's own June table, BOTH photographic scans, +; word for word: "In Commemoratione S. Pauli Ap., III classis. / +; Commemoratio S. Petri Ap." -- exactly the SAME pattern already present in +; the base bootstrap for the other two RG 110 pairs (25 January's +; `conversion-of-st-paul` + `peter`; 22 February's `chair-of-st-peter` + +; `paul`), just missing here. `Precedence_ef.admit`'s own RG 110 mechanism +; (precedence_ef.ml) looks this slug up by name whenever +; `in-commemoratione-sancti-pauli-apostoli` is the day's own observed office +; -- a distinct slug from `peter` (25 January), because `peter` is already a +; DIFFERENT dated entry and this codebase's slugs are date-canonical, one +; date per slug (design spec §3.2). Class3/Commemoration_only/Saint, the +; SAME shape as `peter`/`paul`'s own two entries; names copied verbatim from +; `peter`'s own entry (the identical saint, the identical commemoration +; text, on a second date) -- NOT independently sourced from a missalemeum +; title (missalemeum shows no commemoration on this date at all, checked +; directly against its own fixture rows for both 2026 and 2027: neither +; year's `In Commemoratione Sancti Pauli Apostoli` row carries any +; commemoration -- missalemeum has the SAME gap this directive closes, +; verdict colitur in data/ef/expected-divergences-missalemeum.sexp's own +; M19). Colour Red: no primary-source pin for this specific field (a +; "Commemoratio" line's own colour is not independently stated by the +; calendarium the way a full feast's class-and-colour line is -- the same +; honestly-flagged inference shape this file's own `romanus`/`eusebius- +; confessor` history already documents elsewhere in this project), chosen to +; match `paul`'s own entry (RG 117's ordinary apostle/martyr colour) and the +; host day's own Red -- inert either way today (Commemoration_only entries +; are never the OBSERVED celebration, so this colour is never printed by the +; current pipeline, the same note `Edit eusebius-confessor` above makes for +; its own colour field). ((id ef-adjustments) (directives ((Suppress vigil-of-christmas) (Edit most-holy-name-of-mary ((Set_subject Bvm))) - (Edit eusebius-confessor ((Set_colour White)))))) + (Edit eusebius-confessor ((Set_colour White))) + (Add + ((date (Fixed (month 6) (day 30))) + (cel + ((slug commemoration-of-st-peter) + (names + ((en "St. Peter") (pl "\197\155w. Piotra, Aposto\197\130a"))) + (rank Class3) (status Commemoration_only) (colour Red) + (subject Saint) (citations ()) (layer ef-universal)))))))) + diff --git a/data/ef/expected-divergences-missalemeum.sexp b/data/ef/expected-divergences-missalemeum.sexp index 15f9cf2..0584dfa 100644 --- a/data/ef/expected-divergences-missalemeum.sexp +++ b/data/ef/expected-divergences-missalemeum.sexp @@ -48,6 +48,25 @@ ; covers only rows where [Observed_identity_unresolved] is the SOLE ; disagreement. ; +; ef-holyname-rg110 task (RG 110, the inseparable Peter/Paul commemoration -- +; PART of RG 91 entry 27's neighbour list this file's own header names at +; line 15 as unbuilt; RG 91 entry 27 itself, the BVM-on-Saturday office, +; remains unbuilt, still M2 below): [M12] CLOSED, REMOVED (same shape as +; M17's own removal above, not re-adjudicated) -- 22 February 2027 no +; longer diverges on [Comm_count], since colitur now admits Paul +; unconditionally alongside whatever else the day's own privileged +; commemoration is (precedence_ef.ml's own [rg110_additions]). [M15]'s own +; date list gained a 20th entry, 2027-02-22 -- M12's own former date, now +; reaching the comparator's IDENTITY axis for the first time (count +; agrees), and landing on M15's own PRE-EXISTING, unrelated limit (one of +; the two admitted commemorations is temporal-origin and unresolvable) -- +; see M15's own note for the full account. [M19] is NEW: RG 110's other +; direction (Paul's own office commemorating Peter, not Peter's own office +; commemorating Paul), 30 June, both years -- missalemeum has the SAME gap +; this task found in lectio's own source data (checked directly, not +; assumed), so this is verdict colitur, not colitur short a feature the way +; M12 used to be. +; ; FIVE MORE REMOVED, ef-rebootstrap (2026-08-12), same shape as M17's own ; removal above -- not re-adjudicated to a different verdict, just gone, ; because the underlying divergence stopped occurring: M4 (second @@ -142,11 +161,6 @@ (verdict open) (note "This entry previously read \"No RG paragraph found to support this\" and verdict colitur -- WRONG, found by fix-round-1 review: RG 67/109(c) and the calendarium's own \"Com. octavae Nativitatis\" note both confirm a real commemoration is due here. It is ALSO a genuine REGRESSION Task 16's own commemoration-eligibility fix introduced: before that fix, colitur unconditionally commemorated the day's own losing temporal candidate (\"ef-christmas-0-<weekday>\", the generic Christmastide ferial slug 26-28 December falls through to since temporal_ef.ml's [named] does not cover those three dates), which happened to keep commemoration PRESENCE agreeing with missalemeum even though the underlying reasoning was wrong (a Task 16 review-round-1 finding elsewhere, F1/F2 fixed it for the general case) -- and Task 16's fix (and this fix round's RG 23-26 correction of it) both correctly stop commemorating that generic candidate, since [ferial_rank]'s Christmastide catch-all gives it Class4, which RG 26 never commemorates. The catch: RG 67 says days WITHIN the octave are II class, not IV -- the generic ferial construction 26-28 December falls through to has the WRONG rank for this specific window (a rank bug, not merely a missing-candidate one; 29-31 December already get the correct Class2 via [named]'s own \"ef-nativity-octave-day-{5,6,7}\" branch, so this is narrowly a 26-28-December gap). Fixing it properly also has to get RG 69 right (\"De dominica infra octavam Nativitatis Domini... semper fit Officium cum commemoratione FESTI FORTE OCCURRENTIS\" -- when the Sunday falls in this window, the SUNDAY is observed with a commemoration of THE FEAST ITSELF, Stephen/John/the Innocents by name -- NOT the generic octave placeholder), so a naive \"treat the octave commemoration as one more competing candidate\" fix would get the Sunday case backwards (confirmed by working through it: it would make the octave placeholder outrank the named saint for the Sunday's own privileged slot, when RG 69 names the SAINT, not the octave, for that slot). This needs either new architecture (a commemoration that attaches to whichever of Stephen/John/the Innocents is observed, not a competing candidate the way RG 92-95's occurrence framework models everything else) or a more careful RG-69-aware fix than this task attempted -- out of scope for this fix round. Verdict changed from colitur to open: neither side is confidently right without that fix, and the previous entry's \"colitur is correct, nothing to implement\" framing was itself the risk this review flagged. Register §6 open item.") (expected_rows 4)) - ((id M12) - (citation "RG 110: \"In Officio et Missa S. Petri semper fit commemoratio S. Pauli, et vicissim\" -- in the Office and Mass of either Peter or Paul, a commemoration of the other is ALWAYS made (\"the inseparable commemoration\"), uncapped by RG 111's ordinary admission count") - (verdict missalemeum) - (note "Unimplemented in this codebase. 22 February 2027 (Chair of St Peter): colitur commemorates only the privileged Lent feria; missalemeum shows that AND \"St. Paul\". A genuine, primary-cited feature gap -- register §6 open item. Narrow: the only row in this window where it becomes visible against this comparator's axes (see the task report for the other Peter/Paul offices checked and found clean).") - (expected_rows 1)) ((id M13) (citation "OPEN -- not adjudicated. 1962 calendarium's March table: \"Feria VI post dominicam I Passionis: Commemoratio septem Dolorum B. Mariæ Virg.\" (confirmed real, every year) -- but RG 91's plain table (entry 11-13, Joseph, I class, vs entry 22, an ordinary III-class Passiontide feria) gives Joseph the day outright, with no RG 96 collision requiring a transfer, so the primary text found here does not by itself explain missalemeum's result") (verdict open) @@ -155,8 +169,8 @@ ((id M15) (citation "docs/research/rules-register.md §4's own \"THE MAPPING, AND EXACTLY WHAT IT CANNOT RESOLVE\" note (test_oracle.ml's own header) -- not an RG citation, a comparator LIMIT: Rite_ef.Temporal_ef never sets an English [names] field on any candidate it builds (temporal_ef.ml has no \"en ...\" string anywhere in it)") (verdict unresolvable) - (note "19 rows where colitur's own admitted commemoration is TEMPORAL-origin (an impeded Advent or Lent feria) -- there is no English name on colitur's side to compare against missalemeum's title text at all, so identity cannot be attempted, only reported as unresolved. Not a rubric dispute and not a data gap either engine is wrong about. Every oracle title on these 19 rows reads as a plain prose description of the SAME temporal ferial slot colitur independently computes for that date (e.g. 2 Dec 2026: oracle \"Feria IV after I Sunday of Advent\" against colitur's own ef-advent-1-wednesday; 4 Dec 2027: oracle \"Sabbato after I Sunday of Advent\" against ef-advent-1-saturday) -- suggestive corroboration, recorded for a human reader, NOT claimed as a verified match: this comparator has no mechanism to verify English prose against an opaque slug. Fixing this at the root (an English name on every temporal-cycle candidate) is a data/lectionary-bootstrap task, not something this comparator can do for itself -- register §6, neighbouring the RG 113 tie-break section, records it as an open item. The 19 dates: 2026-02-24, 2026-03-19, 2026-03-25, 2026-12-02, 2026-12-03, 2026-12-04, 2026-12-07, 2026-12-11, 2026-12-21, 2027-02-24, 2027-12-02, 2027-12-03, 2027-12-04, 2027-12-06, 2027-12-07, 2027-12-11, 2027-12-13, 2027-12-16, 2027-12-21.") - (expected_rows 19)) + (note "20 rows where at least one of colitur's own admitted commemorations is TEMPORAL-origin -- there is no English name on colitur's side to compare against missalemeum's title text at all, so identity cannot be attempted for the WHOLE row, only reported as unresolved (test_oracle.ml's own [identity_diff]: any unresolved name bails the whole comparison out, regardless of how many others resolve cleanly). Not a rubric dispute and not a data gap either engine is wrong about. 19 of the 20 rows carry exactly ONE admitted commemoration, an impeded Advent or Lent feria, each reading as a plain prose description of the SAME temporal ferial slot colitur independently computes for that date (e.g. 2 Dec 2026: oracle \"Feria IV after I Sunday of Advent\" against colitur's own ef-advent-1-wednesday; 4 Dec 2027: oracle \"Sabbato after I Sunday of Advent\" against ef-advent-1-saturday) -- suggestive corroboration, recorded for a human reader, NOT claimed as a verified match: this comparator has no mechanism to verify English prose against an opaque slug. ADDED, ef-holyname-rg110 task: 2027-02-22 -- the closed M12's own date (immediately above this entry's own former position), now admitting TWO commemorations (RG 110's own [paul], sanctoral-origin and resolvable, AND the pre-existing [ef-lent-2-monday], temporal-origin and not) -- newly reaches identity comparison only because RG 110 fixed the [Comm_count] mismatch that used to mask it, and lands on this SAME pre-existing limit, not a new one. Fixing this at the root (an English name on every temporal-cycle candidate) is a data/lectionary-bootstrap task, not something this comparator can do for itself -- register §6, neighbouring the RG 113 tie-break section, records it as an open item. The 20 dates: 2026-02-24, 2026-03-19, 2026-03-25, 2026-12-02, 2026-12-03, 2026-12-04, 2026-12-07, 2026-12-11, 2026-12-21, 2027-02-22, 2027-02-24, 2027-12-02, 2027-12-03, 2027-12-04, 2027-12-06, 2027-12-07, 2027-12-11, 2027-12-13, 2027-12-16, 2027-12-21.") + (expected_rows 20)) ((id M16) (citation "1962 calendarium's March table (same primary text as M13's own citation): \"Feria VI post dominicam I Passionis: Commemoratio septem Dolorum B. Mariae Virg.\" -- a fixed commemoration of Our Lady's Seven Sorrows for the Friday of Passion Week, every year, confirmed real") (verdict missalemeum) @@ -167,3 +181,8 @@ (verdict unresolvable) (note "373 of the 730 days in this window carry this shape alone (M1/M2/M3/M16 absorb a further 26 where it fires ALONGSIDE their own pre-existing citation -- see each entry's own widened note; 373+26=399 is the axis's own full unresolved population). CORRECTED, fix round 1 (coordinator finding 8) -- the full breakdown, precisely: of 730 days, 331 are RESOLVED (colitur's own observed celebration carries a name) -- 330 resolved-and-MATCHING, 1 resolved-and-MISMATCHED (M13, Joseph vs the Seven Sorrows, already its own entry above) -- and 399 are UNRESOLVED (colitur's own name is [None]), split between this entry's own 373 and the 26 absorbed elsewhere. The BLIND SPOT this axis exists to close, stated precisely: a TEMPORAL-origin observed day silently replaced by a DIFFERENT temporal-origin observed day of the SAME RANK AND COLOUR -- exactly the shape that hid Holy Family from every layer before this task (rank 2/white on both sides, purely coincidental). This count PIN is what stands guard against that population growing silently in either direction: an implementation change that made MORE days temporal-origin-and-unresolvable, or fewer, changes 373 and fails the pin, even though the axis itself cannot say WHICH specific day moved or why. The overwhelming majority of days whose observed celebration is TEMPORAL-origin (an ordinary Sunday, a feria, a movable named feast including Holy Family itself) never carry an English name on colitur's side -- not a rubric dispute and not a data gap either engine is wrong about, a LIMIT of this comparator, honestly counted rather than silently passed, per the brief's own explicit instruction (\"a day whose observed identity cannot be resolved must be a counted, allow-listed outcome, never a silent skip\"). Gated on the diff SHAPE alone (exactly, and only, observed-identity-unresolved), not a literal date list the way every other entry in this file is -- at this population size a date list would itself be the \"pattern that could silently widen\" this file's own header warns against, for the opposite reason a date RANGE is normally risky: the predicate (colitur's own resolved name is [None]) is the precise, falsifiable evidence, the same shape M2's own title-substring predicate already uses instead of enumerating dates, just keyed on presence-of-a-name rather than a title string. Building an English name onto every temporal-cycle candidate (a data/lectionary-bootstrap task, Plan 4, out of this task's scope -- register §6, the same open item M15's own note already tracks) would resolve this entry to Matched for every one of these days it does not instead expose as a REAL divergence (Holy Family's own two dates in this window, 2026-01-11 and 2027-01-10, are counted here, not separately -- the name comparison genuinely cannot distinguish \"Holy Family\" from any other unnamed Sunday, so proving Holy Family's own correctness rests on the golden pins and the differential's own C15, not this axis). Derived directly from the comparator's own failure output (test_layer_m_counts_match_citations), not hand-counted first and cross-checked after: 373.") (expected_rows 373)) + ((id M19) + (citation "RG 110's OTHER direction: \"In Officio et Missa S. Petri semper fit commemoratio S. Pauli, ET VICISSIM\" -- the SAME rule M12 used to cite, applied here to Paul's own office commemorating Peter, not Peter's own office commemorating Paul; confirmed a third time by the calendarium's own June table, both photographic scans, word for word: \"In Commemoratione S. Pauli Ap., III classis. / Commemoratio S. Petri Ap.\"") + (verdict colitur) + (note "30 June, both years in this window (2026-06-30, 2027-06-30): colitur now shows +commemoration-of-st-peter (data/ef/adjustments.sexp's own `Add` directive) where missalemeum shows nothing at all -- not even Peter listed under \"displaced\", meaning missalemeum's own engine never constructs a candidate for this commemoration in the first place, the SAME shape this task found in lectio's own source data (`~/git/projects/lectio/internal/caldata/tridentine-calendar.ini` has no 06-30 companion entry either -- an upstream data gap both reference engines share, not a colitur bootstrap miss). Verdict colitur, the same shape as M1/M3/M8/M10 above (each already \"missalemeum does not implement X\"): the calendarium's own text is unconditional and confirmed on both photographic scans, and colitur's own machinery for the other two RG 110 pairs (25 January, 22 February) already produces the identical shape correctly. Diff shape [Comm_presence] alone: rank/colour/observed-identity all already agreed before this fix (the OBSERVED celebration on both dates is unchanged, `in-commemoratione-sancti-pauli-apostoli`, sanctoral-origin with a resolvable name matching missalemeum's own title exactly on both rows) -- only the new commemoration's PRESENCE is new, so identity comparison is never even reached (this file's own header: identity is only attempted once presence and count already agree). 2, one per year, independently re-derived against the fixture's own two 06-30 rows before being set here, not assumed from the citation's own two-pair symmetry.") + (expected_rows 2)) diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 374e9a4..c488596 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -1039,6 +1039,145 @@ let rec take n = function still the pair, not the triple; only the INPUT carries [band]. *) let drop_band (c, p, (_ : int)) = (c, p) +(* RG 110 (docs/research/rules-register.md §4, Caput XIV, "De dierum + liturgicorum occurrentia perpetua"), full text, both photographic scans, + word for word: "110. In Officio et Missa S. Petri semper fit + commemoratio S. Pauli, et vicissim. Haec commemoratio dicitur + inseparabilis; et duae orationes adeo in unam coalescere censentur ut, in + numero orationum computando, pro unica habeantur. Proinde: a) in Officio + S. Petri aut S. Pauli, oratio alterius Apostoli additur, ad Laudes et ad + Vesperas, sub unica conclusione, orationi diei, absque antiphona et + versu; b) in Missa S. Petri aut S. Pauli, oratio alterius Apostoli + additur, sub unica conclusione, orationi diei; c) quoties vero oratio + unius Apostoli addenda est ad modum commemorationis, huic orationi + additur altera immediate, ante omnes alias commemorationes." -- in the + Office and Mass of St Peter, a commemoration of St Paul is ALWAYS made, + and vice versa. This commemoration is called INSEPARABLE, and the two + orations are held to coalesce into one so much that, in counting the + number of orations [RG 111's own admission cap], they are counted AS + ONE. Accordingly: (a)/(b) in the Office/Mass OF Peter or Paul [i.e. when + one of them is the day's own observed office], the other Apostle's + oration is added, under a single conclusion, to the oration of the day; + (c) whenever the oration of ONE Apostle must be added AS A + COMMEMORATION [i.e. when one of them is not the day's own office but is + itself only being admitted as an ordinary/privileged commemoration + elsewhere], the OTHER is added to it immediately, BEFORE ALL OTHER + COMMEMORATIONS. + + Three real pairs in this codebase's data, all three confirmed on both + photographic scans (register §4): 25 January's [conversion-of-st-paul] + + [peter]; 22 February's [chair-of-st-peter] + [paul]; 30 June's + [in-commemoratione-sancti-pauli-apostoli] + [commemoration-of-st-peter] + (data/ef/adjustments.sexp's own `Add` directive -- lectio has no + equivalent entry at all, a genuine upstream data gap, not merely a + colitur bootstrap miss). 29 June ([sts-peter-paul]) needs none: it is + already the JOINT feast, not one Apostle's office alone. [peter]/[paul] + already existed as ordinary [Commemoration_only] sanctoral candidates + before this task (Task 10's bootstrap) -- part of RG 110's own machinery, + just wired into the ORDINARY, CAPPED RG 111 admission contest instead of + this rule's own UNCAPPED one, which is the defect this closes: measured + (1583-9999, `tools/`'s own throwaway scan, reproduced and discarded per + this task's own report) at 3,533 years where [chair-of-st-peter] is + observed but [paul] loses the day's single non-Sunday-II-class slot to a + competing privileged Advent/Lent/Ember feria (RG 109(e)/(d)), and a + FURTHER 593 years of shape (c) below, where [chair-of-st-peter] itself + loses outright to an ordinary (ordinary, i.e. non-I-class) II-class + Sunday and is admitted only AS a commemoration -- [paul] was entirely + absent in every one of those 593 years too, before this fix. 25 + January/30 June never show shape (c) at all: both trigger candidates are + [Class3], below RG 111(b)'s "de festo II classis" floor, so neither can + ever be admitted as a mere ordinary commemoration on a II-class Sunday + in the first place -- confirmed by the same scan, 0 instances either + way, not assumed absent. + + [rg110_companion_slug] reads ONLY the candidate's own slug -- the same + convention every other not-an-RG-citation lookup in this file uses + ({!universal_layer}, {!vigil_suffix}, {!nativity_octave_prefix}) -- not + [subject]/[rank]/anything else, because "which slug pairs with which" is + RG 110's own closed, three-pair list, not a general property [band]/ + [disposition] could derive structurally the way (say) [is_sunday_slug] + derives Sunday-ness from a naming convention. A rename of any of the six + slugs involved (either side of a pair) has nowhere else to be caught but + here -- flagged the same way {!annunciation_slug} already is. *) +let rg110_companion_slug (slug : string) : string option = + if slug = "chair-of-st-peter" then Some "paul" + else if slug = "conversion-of-st-paul" then Some "peter" + else if slug = "in-commemoratione-sancti-pauli-apostoli" then Some "commemoration-of-st-peter" + else None + +(* Looks [companion_slug] up directly in [comms] -- the SAME full candidate + pool {!admit} itself sorts from, before any cap is applied, so this finds + the companion regardless of whether the NORMAL admission process would + have kept it (it never would: {!band} always ranks a [Commemoration_only] + candidate at {!unclassified}, worse than literally any real table entry, + so a companion competing on its own merits never survives a contested + slot -- RG 110's entire point is that it should not have to). Returns the + ORIGINAL triple's candidate/privilege pair, unrebuilt, for the same + physical-equality reason {!drop_band} and this function's own top comment + already document -- reusing the [privilege] {!disposition}/{!privilege_of} + already computed for it rather than inventing a new tag here. *) +let rg110_find_companion comms companion_slug = + List.find_map + (fun (c, p, (_ : int)) -> + if Slug.to_string c.Precedence.cel.Celebration.slug = companion_slug then Some (c, p) else None) + comms + +(* RG 110's own uncapped addition, applied to [normal] -- the day's ALREADY- + COMPUTED, ORDINARILY-CAPPED admission result (every branch of {!admit} + below, unchanged otherwise). Two triggers, covering RG 110(a)/(b) and + RG 110(c) respectively, in this function's own comment above: + - [observed] itself is one of the three trigger slugs (shape (a)/(b): + Peter's or Paul's own Office/Mass IS today's day) -- the companion is + looked up directly and appended, uncapped. + - one of [normal]'s OWN members is a trigger slug (shape (c): Peter's or + Paul's own office lost the day outright but is itself being admitted + as an ordinary/privileged commemoration of whatever else won) -- same + lookup, same append. + Both can never fire together on one call ({!Precedence.resolve} only + ever calls {!Precedence.rules.admit} once per date, and all three + trigger slugs are distinct FIXED calendar dates -- 25 January, 22 + February, 30 June -- so at most one of the two conditions is ever true + for a given [observed]/[normal] pair; [List.filter_map] over both + sources together is simply the shortest way to express "check every + place a trigger slug could legitimately appear", not evidence they are + expected to co-occur). + + PREPENDED, not appended, per RG 110(c)'s own explicit ordering directive + ("ante omnes alias commemorationes") -- the one clause in this rule that + actually specifies WHERE, restated here for shape (a)/(b) too rather than + drawing an unwarranted distinction between the two: no live data in this + codebase ever has a THIRD, unrelated commemoration alongside a RG 110 + pair to make the ordering choice visible in real output (the companion is + always the only OTHER item, or the only item, in [normal] already), so + this is closer to a defensive convention than a tested outcome -- flagged + honestly rather than claimed as independently verified. + + A companion already present in [normal] on its own merits (structurally + unreachable today -- {!unclassified}'s own comment above -- but not + provably so for every future rite/data shape) is not added a second + time: [List.exists] guards each append. *) +let rg110_additions (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list) + ~(observed : Vocab_ef.rank Precedence.candidate) + (normal : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) : + (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list = + let already_has slug = + List.exists (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug = slug) normal + in + let trigger_slugs = + Slug.to_string observed.Precedence.cel.Celebration.slug + :: List.map (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug) normal + in + let companions = + List.filter_map + (fun trigger_slug -> + match rg110_companion_slug trigger_slug with + | None -> None + | Some companion_slug -> + if already_has companion_slug then None else rg110_find_companion comms companion_slug) + trigger_slugs + in + companions @ normal + let admit ~(observed : Vocab_ef.rank Precedence.candidate) ~(temporal : Vocab_ef.rank Precedence.candidate) (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list) : @@ -1073,8 +1212,16 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) is_sunday_slug (Slug.to_string temporal.Precedence.cel.Celebration.slug) in let open Vocab_ef in - match (observed_rank, day_is_sunday) with - | Class1, _ -> + (* RG 110's own uncapped additions are layered on AFTER this match -- + {!rg110_additions}'s own top comment -- never inside any one branch: + the three trigger slugs can each reach [observed] via any rank/Sunday + combination the table admits (a Class2 fixed feast, here, but nothing + in {!rg110_additions} itself assumes that), so computing it once, + uniformly, over whatever this match already decided is both simpler + and safer than duplicating the same lookup into every branch. *) + let normal = + match (observed_rank, day_is_sunday) with + | Class1, _ -> (* RG 111: "I class: none save one privileged." Ordinary commemorations never get a slot at all on a I-class day, no matter how many are due; at most one privileged one does, the highest-precedence one @@ -1127,10 +1274,12 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) goes to whichever candidate outranks the rest by RG 113's own table-of-precedence order ({!band}), privileged or not. *) (match sorted with [] -> [] | best :: _ -> [ drop_band best ]) - | (Class3 | Class4), _ -> - (* RG 111: "III-IV class: at most two" -- by RG 113's table order, same - as the non-Sunday II-class case, just with room for two. *) - List.map drop_band (take 2 sorted) + | (Class3 | Class4), _ -> + (* RG 111: "III-IV class: at most two" -- by RG 113's table order, same + as the non-Sunday II-class case, just with room for two. *) + List.map drop_band (take 2 sorted) + in + rg110_additions comms ~observed normal (* Task 11: RG 96 -- where an impeded I-class feast lands (docs/research/ rules-register.md §4, "Transfer/translation"). [band] decides who is diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 6a96c4e..765b573 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -243,6 +243,26 @@ val september_ember_prefix : string - [observed] a [Class3] or [Class4] day: at most two, by {!band}'s table order alone. + ADDED, ef-holyname-rg110 task: RG 110 (docs/research/rules-register.md + §4, Caput XIV) -- "In Officio et Missa S. Petri semper fit + commemoratio S. Pauli, et vicissim... pro unica habeantur" -- whenever + [observed], or one of the candidates the FOUR cases above already admit, + is one of RG 110's own three trigger slugs (25 January's + [conversion-of-st-paul], 22 February's [chair-of-st-peter], 30 June's + [in-commemoratione-sancti-pauli-apostoli]), its companion Apostle + ([peter]/[paul]/[commemoration-of-st-peter] respectively) is looked up + directly in [comms] and PREPENDED to whatever the four cases above + already produced -- UNCAPPED, i.e. added on top of, not competing for, + any of those cases' own admission limits ("pro unica habeantur": the + pair is held as ONE for RG 111's own counting purposes, so it never + consumes a second ordinary/privileged slot). This is layered on AFTER + the four cases above decide the day's ordinarily-capped result, uniformly + regardless of which of the four produced it -- see the .ml's own + [rg110_additions] for the full citation, the measured blast radius, and + why the two lookups (observed itself, and each already-admitted + candidate) together cover both of RG 110's own textual triggers, + (a)/(b) and (c). + CORRECTED, Task B (branch ef-rg16a): this used to sort by [Vocab_ef.rank] (RG 8's coarse four-class "dignity") and break same-rank ties alphabetically by slug -- an engineering convention with no diff --git a/test/test_golden.ml b/test/test_golden.ml index c5aa65a..80e2c9d 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -434,11 +434,21 @@ let test_ember_ferias_commemorated_when_impeded () = comms=[ef-advent-ember-fri:privileged] in=- out=[]"; (* 1902-02-22: Lent Ember Saturday (RG91 entry 18, II class) impeded by the Chair of St Peter (II class, universal, entry 16). Same RG24/ - RG109(e) mandate. *) - check ~msg:"1902-02-22: Lent Ember Saturday, impeded, MUST be commemorated (RG24) and is privileged (RG109e)" + RG109(e) mandate. ALSO, ef-holyname-rg110 task: RG 110's own + inseparable Peter/Paul commemoration -- "in Officio ... S. Petri semper + fit commemoratio S. Pauli" -- fires unconditionally whenever + [chair-of-st-peter] is observed, uncapped by RG 111's own admission + count, so `+paul` now rides alongside the Ember feria's own + commemoration rather than being displaced by it (precedence_ef.ml's own + [rg110_additions]). This pin's own [ef-lent-ember-sat:privileged] half + is UNCHANGED from before this task -- only [paul:ordinary] is new, + confirming RG 110 adds a genuinely SEPARATE, uncapped slot rather than + competing for the Ember feria's own privileged one. *) + check ~msg:"1902-02-22: Lent Ember Saturday, impeded, MUST be commemorated (RG24) and is privileged (RG109e) -- \ + AND RG110's Paul companion rides alongside it, uncapped" 1902 2 22 "1902-02-22 saturday season=lent week=1 slug=chair-of-st-peter rank=class-2 colour=white subject=saint \ - comms=[ef-lent-ember-sat:privileged] in=- out=[]" + comms=[ef-lent-ember-sat:privileged,paul:ordinary] in=- out=[]" let test_iv_class_ferias_never_commemorated () = (* 2026-07-22: an ordinary Time-after-Pentecost feria (Class4, RG91 entry diff --git a/test/test_oracle.ml b/test/test_oracle.ml index 35d4501..370eec0 100644 --- a/test/test_oracle.ml +++ b/test/test_oracle.ml @@ -599,23 +599,23 @@ let m10_dates = [ "2026-11-30"; "2027-11-30"; "2026-12-08"; "2027-12-08" ] own full note and the task report. *) let m11_dates = [ "2026-12-26"; "2026-12-28"; "2027-12-27"; "2027-12-28" ] -(* M12 -- RG 110, primary text: "In Officio et Missa S. Petri semper fit - commemoratio S. Pauli, et vicissim" -- in the Office and Mass of EITHER - Peter or Paul, a commemoration of the OTHER is ALWAYS made (the - "inseparable commemoration"), uncapped by RG 111's ordinary admission - count ("in numero orationum computando, pro unica habeantur" -- counted - as one for counting purposes, i.e. it rides alongside whatever RG 111 - already admits). Unimplemented in this codebase -- confirmed by the one - row in this window where it is visible: 22 February 2027 (Chair of - St Peter) shows only the privileged Lent feria commemorated in colitur, - while missalemeum shows that AND "St. Paul". A genuine, primary-cited - feature gap -- verdict missalemeum, register §6 open item. Real but - NARROW: every other Peter/Paul office in this window (29 June, both - years; 1 August M4-adjacent dates were checked and found clean) either - had no room for the extra commemoration to become visible against this - comparator's axes, or fell on a day this predicate does not otherwise - touch -- see the task report for the specific check. *) -let m12_dates = [ "2027-02-22" ] +(* M12 -- CLOSED (ef-holyname-rg110 task): RG 110 is now built + (precedence_ef.ml's own [rg110_additions]/[rg110_companion_slug]), and + this window's one visible instance -- 22 February 2027 -- no longer + diverges from missalemeum on [Comm_count] at all: colitur now shows the + privileged Lent feria's own commemoration AND "St. Paul", same COUNT + (two) as missalemeum. Removed rather than kept as a dead 0-row citation, + the SAME shape M17's own removal already established (this file's own + header): the count pin now enforces that this exact divergence cannot + silently reappear. NOT closed with zero remaining difference, though -- + see M15's own widened date list immediately below: once count agrees, + IDENTITY comparison is reached for the first time on this date, and + trips a SEPARATE, pre-existing, unrelated comparator limit (one of + colitur's two admitted commemorations, [ef-lent-2-monday], is + TEMPORAL-origin and carries no English name) -- a newly-exposed + instance of M15's own gap, not a new one, folded into M15's date list + rather than kept here under RG 110's own citation, which no longer + explains ANY part of this row's remaining diff. *) (* M13 -- OPEN, NOT adjudicated (the brief's own explicit permission, "say so as an open item rather than absorbing it"). 19 March 2027: St @@ -647,13 +647,13 @@ let m13_dates = [ "2027-03-19" ] either engine is wrong about: a genuine LIMIT of this comparator, honestly counted rather than silently passed (the brief's own explicit instruction: "a day whose commemoration cannot be matched must be - visibly counted and reported, not quietly passed"). On each of these 19 + visibly counted and reported, not quietly passed"). On 19 of these 20 rows, colitur's own admitted commemoration is TEMPORAL-origin (an impeded Advent/Lent feria) -- Rite_ef.Temporal_ef's own [build] (temporal_ef.ml) never sets a celebration's [names] field, so there is no English string on colitur's side to compare against missalemeum's title text at all (this file's own header, "THE MAPPING, AND EXACTLY WHAT IT - CANNOT RESOLVE"). Every one of these 19 oracle titles reads as a plain + CANNOT RESOLVE"). Every one of those 19 oracle titles reads as a plain prose description of the SAME temporal ferial slot colitur independently computes for that date (e.g. 2 Dec 2026: oracle "Feria IV after I Sunday of Advent" against colitur's own [ef-advent-1-wednesday]; 4 Dec 2027: @@ -664,12 +664,25 @@ let m13_dates = [ "2027-03-19" ] unresolved. Fixing this at the ROOT (giving every temporal-cycle candidate an English name) is a data/lectionary-bootstrap task, not something this comparator can do for itself -- register §6, "RG 113 - tie-break" section's own neighbour, records it as an open item. *) + tie-break" section's own neighbour, records it as an open item. + + ADDED, ef-holyname-rg110 task: 2027-02-22 -- the closed M12's own date, + above. NOT the same shape as the other 19 (colitur admits TWO + commemorations there, not one -- [paul], sanctoral-origin and + resolvable, and [ef-lent-2-monday], temporal-origin and not): the + comparator's own [identity_diff] bails out to [Comm_identity_unresolved] + the moment ANY admitted commemoration is unresolvable, regardless of how + many others resolve cleanly (test_oracle.ml's own [identity_diff], + "[List.exists] ... name = None"), so this row reaches the identical + diagnosis for the identical structural reason, once RG 110 stopped + [Comm_count] from masking it. Genuinely newly-EXPOSED by this task's own + fix, not newly-CAUSED by it: the underlying gap (no English name for a + temporal-cycle candidate) is exactly M15's own pre-existing one. *) let m15_dates = [ "2026-02-24"; "2026-03-19"; "2026-03-25"; "2026-12-02"; "2026-12-03"; "2026-12-04"; - "2026-12-07"; "2026-12-11"; "2026-12-21"; "2027-02-24"; "2027-12-02"; "2027-12-03"; - "2027-12-04"; "2027-12-06"; "2027-12-07"; "2027-12-11"; "2027-12-13"; "2027-12-16"; - "2027-12-21" ] + "2026-12-07"; "2026-12-11"; "2026-12-21"; "2027-02-22"; "2027-02-24"; "2027-12-02"; + "2027-12-03"; "2027-12-04"; "2027-12-06"; "2027-12-07"; "2027-12-11"; "2027-12-13"; + "2027-12-16"; "2027-12-21" ] (* M16 -- Task B: a genuine IDENTITY mismatch, both sides otherwise agreeing on rank/colour/count/presence. 27 March 2026 (Friday of Passion Week): @@ -766,6 +779,26 @@ let m16_dates = [ "2026-03-27" ] bootstrap task (Plan 4), not something this comparator can do for itself -- register §6 tracks it, the same open item [M15]'s own note already points to. *) +(* M19 -- ef-holyname-rg110 task: RG 110's OTHER direction, 30 June (this + window's two instances -- both years -- of + [in-commemoratione-sancti-pauli-apostoli], data/ef/adjustments.sexp's + own `Add commemoration-of-st-peter` directive; see that file's own + citation for the full scan quote). Unlike 22 February above, + missalemeum's own row for both dates shows an EMPTY commemorations array + ("St. Peter" absent, and not even listed as "displaced" -- nothing + competes for the slot on either side, so there is no candidate for + missalemeum's own engine to have rejected, only one it never + constructed): missalemeum has the SAME gap this task's own scan found in + lectio's source data (`~/git/projects/lectio/internal/caldata/ + tridentine-calendar.ini` has no 06-30 companion entry either) -- a + genuine feature gap, verdict colitur, the SAME shape as M1/M3/M8/M10 + above (each already "missalemeum does not implement X"), not a + citation-count coincidence: RG 110's own text is unconditional ("in + Officio et Missa S. Petri semper fit commemoratio S. Pauli, ET + VICISSIM"), and the calendarium's own June table states the SAME + pattern already built for the other two pairs, word for word. *) +let m19_dates = [ "2026-06-30"; "2027-06-30" ] + let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = if diffs = [] then None (* M1's own subset widened (this task): colitur's observed day on both @@ -795,7 +828,6 @@ let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = else if List.mem c.c_date m8_dates && diffs = [ Comm_presence ] then Some "M8" else if List.mem c.c_date m10_dates && diffs = [ Comm_presence ] then Some "M10" else if List.mem c.c_date m11_dates && diffs = [ Comm_presence ] then Some "M11" - else if List.mem c.c_date m12_dates && diffs = [ Comm_count ] then Some "M12" (* M13's own subset widened AGAIN (this task, following the SAME pattern Task B's own widening comment below records): colitur's observed celebration here is Joseph, SANCTORAL-origin, so its name IS @@ -818,6 +850,7 @@ let layer_m_reason (c : colitur_row) (o : oracle_row) diffs = && subset diffs [ Comm_identity_mismatch; Observed_identity_unresolved ] then Some "M16" else if diffs = [ Observed_identity_unresolved ] then Some "M18" + else if List.mem c.c_date m19_dates && diffs = [ Comm_presence ] then Some "M19" else None (* ---------------------------------------------------------------------- *) diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml index d873e34..af7f65e 100644 --- a/test/test_precedence_ef.ml +++ b/test/test_precedence_ef.ml @@ -1016,6 +1016,33 @@ let sixtus = UNIVERSALLY), so the two values coinciding is the faithful shape, not a simplification that dodges the new parameter. The last two rows are where they genuinely differ -- see their own comment. *) +(* RG 110 (§4, Caput XIV): candidates built with the REAL trigger/companion + slugs {!PE.rg110_companion_slug} matches on -- unlike every other + [admit_cases] candidate above, which is deliberately built with a + SYNTHETIC slug {!PE.band}'s 28 branches (and this file's own RG 110 + lookup) cannot recognise, these three rows need [PE.admit] to recognise + the slug itself, so the real ones are used directly. [~status: + Cel.Commemoration_only] on the companions matches the real data shape + (data/ef/sanctoral.sexp's own [peter]/[paul]; + data/ef/adjustments.sexp's own [commemoration-of-st-peter]) even though + [admit] itself never reads [status] -- only [Precedence.resolve]'s own + partition does, upstream of [admit] -- kept for a reader's sake, not a + test requirement. *) +let chair_of_st_peter_synth = cand ~rank:V.Class2 "chair-of-st-peter" +let paul_companion_synth = cand ~rank:V.Class3 ~status:Cel.Commemoration_only "paul" + +(* Any real Lent/Ember feria's own shape (a Class3, PRIVILEGED, temporal- + origin candidate) -- {!PE.rg110_companion_slug} does not match this slug, + so it is a neutral bystander in [admit]'s own normal (pre-RG110) contest, + exactly like [privileged_lo] elsewhere in this table, just kept + separately named so this block's own rows are self-contained and do not + depend on a binding declared for an unrelated purpose above. Given the + WORSE (higher) precedence int than [paul_companion_synth] would need to + win the day's one "other II class" slot on its own dignity ALONE -- the + whole point of the row below is that RG 110 does not care who wins that + contest; it adds Paul regardless. *) +let lent_feria_synth = (cand ~origin:P.Temporal ~rank:V.Class3 "ef-lent-2-monday-synth", P.Privileged, 180) + let admit_cases = [ (* RG 111 (§4): "I class: none save one privileged." *) ( "I-class day, only an ordinary commemoration due -> none admitted", @@ -1109,7 +1136,46 @@ let admit_cases = admitted", lord_winner, lord_winner, [ po sixtus P.Ordinary ], - [ "pope-sixtus-ii-felicissimus-and-agapitus-martyrs" ] ) + [ "pope-sixtus-ii-felicissimus-and-agapitus-martyrs" ] ); + (* RG 110, shape (a)/(b): [chair-of-st-peter] observed, a competing + PRIVILEGED Lent feria also due for the day's single "other II class" + slot (RG 111(c) itself has no privilege-override clause -- unlike the + Sunday branch above -- so pure precedence order decides that slot on + its own, and [lent_feria_synth]'s own 180 beats + [paul_companion_synth]'s 999). Without RG 110, this is exactly + precedence_ef.ml's own measured defect (3,533 of 8,417 domain-wide + years, this task's own report): Paul loses the slot outright and is + never admitted at all. With RG 110, Paul is added ADDITIONALLY, + PREPENDED ahead of the feria per RG 110(c)'s own "ante omnes alias + commemorationes" -- both admitted, not one displacing the other. *) + ( "RG110 (a)/(b): chair-of-st-peter observed, Paul rides alongside a \ + competing privileged Lent feria, uncapped and prepended", + chair_of_st_peter_synth, chair_of_st_peter_synth, + [ (paul_companion_synth, P.Ordinary, 999); lent_feria_synth ], + [ "paul"; "ef-lent-2-monday-synth" ] ); + (* RG 110, shape (c): [chair-of-st-peter] itself LOSES the day outright + (observed is some unrelated II-class Sunday) but is admitted, on its + OWN Class2 dignity, as RG 111(b)'s single ordinary commemoration -- + exactly the domain-wide 593-year pattern this task's own report + measures. RG 110(c)'s own text ("quoties vero oratio unius Apostoli + addenda est ad modum commemorationis...") governs precisely this: the + companion (Paul, Class3, no standing of its OWN for this Sunday's + rank-floored slot) rides along anyway, uncapped. *) + ( "RG110 (c): chair-of-st-peter admitted merely AS a commemoration \ + (loses the day to an ordinary II-class Sunday), Paul still rides \ + along", + cand ~rank:V.Class2 "ef-some-other-sunday", cand ~rank:V.Class2 "ef-some-other-sunday", + [ po chair_of_st_peter_synth P.Ordinary; (paul_companion_synth, P.Ordinary, 999) ], + [ "paul"; "chair-of-st-peter" ] ); + (* Defensive: a trigger slug is [observed], but its companion is simply + not present among [comms] at all (a data gap, or -- as this exact + shape was for 30 June before this task's own [Add + commemoration-of-st-peter] directive -- an upstream omission). + {!PE.rg110_find_companion} must return [None] gracefully, not raise; + the day's own (empty, here) normal result is unaffected. *) + ( "RG110 defensive: chair-of-st-peter observed, no companion present in \ + comms at all -> no crash, nothing added", + chair_of_st_peter_synth, chair_of_st_peter_synth, [], [] ) ] (* Order independence (brief: "the admitted set must not depend on input |
