summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 16:45:58 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 16:45:58 +0200
commit5d5454208464892e96310efd99ec9db09171f982 (patch)
treeeb6940f3087bf7cb2fd7c92063e867c95f3b0402 /test
parent76298db9b0fb5409bcfd4276b642f69f4da530e9 (diff)
downloadcolitur-5d5454208464892e96310efd99ec9db09171f982.tar.gz
colitur-5d5454208464892e96310efd99ec9db09171f982.zip
fix(rite-ef): band has no table entry for a bare commemoration, ever
Fix round 1 on the commemoration-identity/tie-break work: the "genuine same-band tie" M17 adjudicated as open was not a tie at all. RG 91's table enumerates only "dies liturgici" (real feasts) -- entry 24's own text is "Festa III classis, in calendario Ecclesiae universae inscripta" -- so a Celebration.t.status = Commemoration_only candidate (a saint reduced to a bare commemoration) has no row in the table to begin with. Precedence_ef.band used to read rank alone, so it silently lent such a candidate the same table entry as a genuine Feast of its own rank; the calendarium's own 22 September row confirms the distinction in its own notation ("S. Thomae de Villanova ... III classis. / Commemoratio Ss. Mauritii et Soc. Mm." -- Thomas carries a class number, Maurice carries none). Fixed at the source, not by adding a status tier to compare_precedence: band now checks status first, ahead of every rank-keyed branch, and returns unclassified for any Commemoration_only candidate. M17 deleted (not re-adjudicated) -- 22 September now resolves identically on both sides, with no divergence left to name. Blast radius measured independently (a temporary git worktree at the pre-fix commit): 4451 days change across the whole 1583-9999 domain, exactly 4 verified shapes -- 3576 x maurice-and-companions-martyrs -> thomas-of-villanova and 282 x eleutherius -> philip-neri (admitted set changes), 502 x appollonia/cyril-of-alexandria and 91 x dorothy/titus (order only, independently confirmed against the calendarium's own listing convention). Zero days change who is observed. Further swept the whole domain for a genuine "two different candidates on the identical real table entry" residual -- the shape RG 113 truly gives no instruction for -- and found it empty: of 109201 same-rank ties domain-wide, 83950 now resolve by band alone and the remaining 25251 are exclusively two Commemoration_only candidates tied at unclassified, never two real feasts. The residual admit still breaks alphabetically is real, but narrower than previously documented. The "66 days" figure is reconciled, not merely disputed: 599 is the tie population, 65 (whole-comparator: 67) is the real admitted-set decision count within it (independently reproduced against the pre-Task-B binary), 149 is order-only. Every one of the 65 real decisions traces to the same Commemoration_only/Feast root cause as Maurice/Thomas. Also: validate.ml's admission fixed-point check recovers a commemoration's real origin (by matching its slug against the day's own temporal office) instead of reconstructing every candidate as Sanctoral, since band -- unlike EF's own admit -- does read origin; precedence_ef.ml/.mli's motivating example and blast-radius claims corrected to no longer rest on the fixed Maurice/Thomas case; test_identity_rank_corroboration's own population count corrected now that 22 September (and 21 other Feast-status matches) became reachable; its rank-agreement pin documents exactly what it does and does not prove. Register (§4, §6.1, §6 checklist) and CLAUDE.md corrected in place, including the RG 113 first-sentence gap this exercise surfaced but did not close, recorded as a new open item. 275 -> 276 tests (with the exhaustive sweep), all green.
Diffstat (limited to 'test')
-rw-r--r--test/test_oracle.ml146
-rw-r--r--test/test_precedence_ef.ml21
-rw-r--r--test/test_rite_ef.ml44
3 files changed, 156 insertions, 55 deletions
diff --git a/test/test_oracle.ml b/test/test_oracle.ml
index 07a5634..2624664 100644
--- a/test/test_oracle.ml
+++ b/test/test_oracle.ml
@@ -107,18 +107,25 @@
entries; RG 110's inseparable-Peter/Paul commemoration is unimplemented
code, a real feature this task did not build) -- honestly verdicted
[missalemeum] (colitur is short a feature or a row, not right), never
- silently absorbed as if colitur were correct. THREE entries (M11, M13
- and, since Task B/ef-rg16a, M17) are [verdict open] -- CORRECTED, final
- fix wave, item 7: this comment previously said "one entry (M13)",
- missing M11, whose own verdict was changed from [colitur] to [open] in
- fix round 1 (see M11's own entry below for why) but this summary was
- never updated to match; M17 (the RG 113 same-band tie-break residual,
- register §6.1) is a genuinely NEW open item, not a stale-comment fix.
- ONE entry (M15) carries its own fourth verdict, [unresolvable] -- not a
- rubric dispute or a data gap either engine is wrong about, but a LIMIT
- of this comparator itself (see M15's own entry). All are adjudicated as
- unresolved/unresolvable, not resolved either way -- the brief's own
- explicit permission ("say so as an open item") used for real, not
+ silently absorbed as if colitur were correct. TWO entries (M11 and M13)
+ are [verdict open] -- CORRECTED, final fix wave, item 7: this comment
+ previously said "one entry (M13)", missing M11, whose own verdict was
+ changed from [colitur] to [open] in fix round 1 (see M11's own entry
+ below for why) but this summary was never updated to match. Task B
+ (branch ef-rg16a) briefly added a THIRD, M17 (the same-band tie-break
+ between Maurice and Thomas of Villanova, 22 September) -- CORRECTED,
+ fix round 1 of that same task: M17 was itself wrong. The "tie" was
+ manufactured by {!Precedence_ef.band} lending a [Commemoration_only]
+ candidate the same table entry as a genuine [Feast] of its own rank
+ (RG 91's table has no row for a bare commemoration at all -- fixed in
+ [band] itself, not here); once fixed, 22 September resolves cleanly on
+ both sides and M17 was deleted, not merely re-adjudicated. ONE entry
+ (M15) carries its own fourth verdict, [unresolvable] -- not a rubric
+ dispute or a data gap either engine is wrong about, but a LIMIT of this
+ comparator itself (see M15's own entry). All remaining OPEN entries are
+ adjudicated as unresolved/unresolvable, not resolved either way -- the
+ brief's own explicit permission ("say so as an open item") used for real,
+ not
defaulted past. See the task report for every entry's full
reasoning and primary-source citation. *)
@@ -655,30 +662,43 @@ let m15_dates =
already tracks). Only 2026 shows here -- 2027's Friday of Passion Week
IS 19 March, M13's own date, where the identity axis cannot even be
reached (M13's own rank/colour mismatch already excludes that day from
- count-matched identity comparison). *)
+ count-matched identity comparison).
+
+ NOTE for whoever builds the office (fix round 1, coordinator finding 7):
+ 27 March 2026 is a III-class day, where RG 111(d) admits TWO
+ commemorations, not one -- yet missalemeum admits only the Seven Sorrows
+ and DISPLACES John Damascene entirely (its own "displaced" list carries
+ his title that day), not merely drops him to second place. Implementing
+ the Seven Sorrows candidate naively (as one more ordinary III-class
+ commemoration competing for the day's two slots) will not reproduce
+ this: John Damascene would still win one of the two admitted slots by
+ dignity/band, giving colitur TWO commemorations where missalemeum shows
+ one. Whatever privilege or precedence the Seven Sorrows commemoration
+ carries must itself explain the exclusion, not just the admission --
+ register §6's own open item for this office should carry this caveat
+ forward. *)
let m16_dates = [ "2026-03-27" ]
-(* M17 -- Task B: the SAME-band residual tie-break (docs/research/rules-
- register.md §4's own "RG 113" entry, and §6.1's "RG 113 same-band
- residual" record) made visible for the first time by identity
- comparison. 22 September 2027 (September Ember Wednesday): colitur
- admits "St. Maurice and Companions, Martyrs" (Commemoration_only,
- Class3); missalemeum shows "St. Thomas of Villanova" (Feast, Class3)
- instead -- both land on {!Precedence_ef.band} entry 24 (III-class
- universal feasts), a genuine tie RG 113 gives no further instruction for
- (its own text only reaches "servetur ordo tabellae praecedentiae", the
- TABLE order; nothing in the primary text breaks a tie WITHIN one table
- entry). colitur's own residual tie-break (alphabetical by slug, §6.1's
- own uncited-convention record) picks "maurice..." over "thomas..." purely
- because 'm' < 't' -- no rubrical warrant either way, so this is NOT
- adjudicated colitur or missalemeum: verdict OPEN, the same explicit
- permission the brief gives M11/M13 ("say so as an open item rather than
- absorbing it"), register §6.1. Only 2027 falls in this window with this
- EXACT collision -- 2026's 22 September is an ordinary (non-Ember)
- Tuesday, so Thomas of Villanova simply wins the day outright on both
- sides (colitur: observed; missalemeum: title) with Maurice commemorated
- alongside him identically on both -- no tie to observe that year. *)
-let m17_dates = [ "2027-09-22" ]
+(* M17 was DELETED, fix round 1 (Task B): the "genuine tie" it adjudicated
+ as [open] was itself wrong. 22 September (any year the September Ember
+ Wednesday falls on the 22nd -- 2027 in this window): colitur used to
+ admit "St. Maurice and Companions, Martyrs" (Commemoration_only, Class3)
+ where missalemeum shows "St. Thomas of Villanova" (Feast, Class3) --
+ NOT because RG 113 runs out of instruction between two same-rank
+ candidates (the framing this entry used to carry), but because
+ {!Precedence_ef.band} used to lend a [Commemoration_only] candidate the
+ SAME table entry (24) as a genuine [Feast] of its own rank, manufacturing
+ a tie the primary text never creates: RG 91's own table enumerates only
+ "dies liturgici" (entry 24: "Festa III classis..." -- FEASTS), and the
+ calendarium's own 22 September row confirms it in its own notation --
+ "S. Thomae de Villanova Ep. et Conf., III classis. / Commemoratio Ss.
+ Mauritii et Soc. Mm." -- Thomas carries a class number, Maurice carries
+ none. Fixed at the source ([band] itself now returns [Precedence_ef
+ .unclassified] for any [Commemoration_only] candidate, docs/research/
+ rules-register.md §6.1's own corrected account) rather than here: 22
+ September now resolves identically on both sides with no allow-list
+ entry needed at all -- removed, not re-adjudicated to a different
+ verdict, since there is no longer a divergence to name. *)
let layer_m_reason (c : colitur_row) (o : oracle_row) diffs =
if diffs = [] then None
@@ -704,7 +724,6 @@ let layer_m_reason (c : colitur_row) (o : oracle_row) diffs =
Some "M13"
else if List.mem c.c_date m15_dates && diffs = [ Comm_identity_unresolved ] then Some "M15"
else if List.mem c.c_date m16_dates && diffs = [ Comm_identity_mismatch ] then Some "M16"
- else if List.mem c.c_date m17_dates && diffs = [ Comm_identity_mismatch ] then Some "M17"
else None
(* ---------------------------------------------------------------------- *)
@@ -862,23 +881,36 @@ let test_layer_m_counts_match_citations () =
combination would break it) that stays honest about what remains
unverified.
+ WHAT THIS DOES NOT PIN (coordinator finding 8, fix round 1, honestly
+ named rather than left implicit): the [oracle_rank = colitur_rank]
+ branch accepts ANY genuine agreement, including one this check cannot
+ independently verify is the CORRECT rank -- a regression that silently
+ flipped some entry's [rank] from [Class3] to [Class4] would land in the
+ agreement branch and pass cleanly if missalemeum's own id happened to
+ read 4 for that entry too (data drift on one side coinciding with data
+ drift on the other is not ruled out by this check, only coincidence
+ independent of any real cause is). This test pins "no third pattern
+ appears", not "every individual rank is correct" -- a narrower, still
+ genuinely useful claim (see the [> 50] population guard below, which
+ confirms the pinned shape is actually exercised at scale, not vacuously
+ true over an empty or trivial set), and this comment says so rather than
+ letting the assertion's own name imply more than it checks.
+
[Feast]-status candidates get the ORIGINAL, unrestricted check (any
- disagreement at all is unexpected) -- but this window's own data never
- actually offers a [Feast]-status LOSING candidate whose identity is
- independently clean (checked: 0 in the 2026-2027 fixture -- a [Feast]
- candidate here either wins its own day outright, in which case it is
- never a commemoration at all, or the one day where it does lose,
- 22 September 2027/Thomas of Villanova, is ITSELF the M17 tie-break
- mismatch and so is excluded by the [identity_diff = None] guard before
- ever reaching this check). Kept anyway, not deleted: a real disagreement
- would still be reported the moment one becomes reachable (a wider oracle
- window, or a data change), and this project's own vacuity catalogue
- flags "an assertion true by construction" as a defect shape to avoid,
- not "an assertion whose population happens to be empty in the one
- fixture available" -- the CHECK still does real work when its input is
- non-empty; it is the DATA, not the code, that is currently silent here. *)
+ disagreement at all is unexpected). CORRECTED (fix round 1, coordinator
+ finding 1): this comment previously claimed this branch was unreachable
+ in the 2026-2027 window (checked: 0) because its one candidate, Thomas
+ of Villanova on 22 September, was M17's own mismatch -- WRONG, once
+ traced further: M17 itself was wrong (see {!Precedence_ef.band}'s own
+ fidelity fix, register §6.1), and fixing it made 22 September resolve
+ cleanly on both sides, reachable here after all. Measured, not assumed:
+ 22 Feast-status commemorations are now examined by this branch (not
+ only Thomas of Villanova -- every OTHER genuinely clean Feast-status
+ match in the window reaches it too, which the earlier version of this
+ comment did not check for before asserting "0"). All 22 agree. *)
let test_identity_rank_corroboration () =
let oracle, colitur = compare_streams () in
+ let feast_checked = ref 0 in
let feast_mismatches = ref [] in
let commemoration_only_checked = ref 0 in
let commemoration_only_surprises = ref [] in
@@ -896,6 +928,7 @@ let test_identity_rank_corroboration () =
| Some oracle_rank -> (
match List.find_opt (fun (_, _, _, n) -> n = Some title) c.c_commemorations with
| Some (slug, colitur_rank, Cel.Feast, _) ->
+ incr feast_checked;
if colitur_rank <> oracle_rank then
feast_mismatches :=
Printf.sprintf "%s: %s oracle-id-rank=%d colitur-rank=%d" c.c_date slug
@@ -921,14 +954,21 @@ let test_identity_rank_corroboration () =
o.o_commemorations o.o_commemoration_ids)
oracle colitur;
Alcotest.(check (list string))
- "Feast-status matches: oracle id-rank agrees with colitur's own rank (none reachable in this window, \
- see this test's own comment; the check still runs)"
- [] (List.rev !feast_mismatches);
+ "Feast-status matches: oracle id-rank agrees with colitur's own rank" [] (List.rev !feast_mismatches);
Alcotest.(check (list string))
"Commemoration_only-status matches: every one fits the KNOWN oracle=4/colitur=3 convention gap -- any \
other combination would be a genuine, new surprise"
[] (List.rev !commemoration_only_surprises);
- (* Vacuity guard for the branch that IS populated in this window. *)
+ (* Vacuity guard: BOTH branches must actually run now. CORRECTED (fix
+ round 1, coordinator finding 1): the [Feast] branch used to be
+ unreachable in this window (checked: 0) because its one candidate,
+ Thomas of Villanova on 22 September, was M17's own mismatch (excluded
+ by the [identity_diff = None] guard above). Fixing {!Precedence_ef
+ .band}'s Commemoration_only fidelity (register §6.1) made that day
+ resolve cleanly, so it is reachable here too -- this guard now expects
+ at least 1, not merely documents the branch as dormant (measured: 22
+ Feast-status commemorations now examined, up from 0). *)
+ Alcotest.(check bool) "the Feast-status population actually examined is non-trivial" true (!feast_checked > 0);
Alcotest.(check bool) "the Commemoration_only population actually examined is non-trivial" true
(!commemoration_only_checked > 50)
diff --git a/test/test_precedence_ef.ml b/test/test_precedence_ef.ml
index ca623d2..2935e40 100644
--- a/test/test_precedence_ef.ml
+++ b/test/test_precedence_ef.ml
@@ -267,7 +267,26 @@ let cases =
(* RG 91's own vigil list (§4, "Vigils / octaves / Rogations / Sunday classes") stops at III class --
there is no IV-class vigil for entry 28's ferial catch-all to absorb. *)
( "unclassified: IV-class candidate marked as a vigil", mk 2026 6 20,
- cand ~rank:V.Class4 "ef-second-mystery-vigil", PE.unclassified )
+ cand ~rank:V.Class4 "ef-second-mystery-vigil", PE.unclassified );
+ (* Task B fix round 1 (coordinator finding 1): RG 91's table enumerates
+ only "dies liturgici" -- a [Cel.Commemoration_only] candidate is
+ never a row in the table at all, regardless of what rank it would
+ otherwise band to. [Class1]/universal-layer is deliberate: absent
+ the status guard this would band to entry 11, the FIRST, highest-
+ priority real entry any sanctoral candidate can reach, so this
+ proves the guard is checked BEFORE and overrides every rank-keyed
+ branch, not only the low-priority ones an accidental placement
+ might happen to catch. Real-world witness: "maurice-and-companions-
+ martyrs" (data/ef/sanctoral.sexp, 22 September, Class3,
+ Commemoration_only) used to band to entry 24 alongside the genuine
+ Class3 feast "thomas-of-villanova" -- test_oracle.ml's own former
+ M17 allow-list entry, deleted once this fix made the divergence it
+ named disappear. *)
+ ( "unclassified: a Commemoration_only candidate has no row in RG91's \
+ table at all, even at Class1", mk 2026 5 20,
+ cand ~origin:P.Sanctoral ~status:Cel.Commemoration_only ~layer:PE.universal_layer
+ "ef-some-commemoration-only",
+ PE.unclassified )
]
(* Review finding 1's end-to-end proof: on a real Sunday landing on 2
diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml
index 6fcb25d..2e5d2fd 100644
--- a/test/test_rite_ef.ml
+++ b/test/test_rite_ef.ml
@@ -298,6 +298,45 @@ let test_search_genuinely_enters_the_window () =
"the walked span is at least 8 days -- the octave alone (Easter..Easter+7), not a one-day hop"
true (List.length walked >= 8)
+(* Task B fix round 1 (coordinator finding 1), end to end with REAL data:
+ 22 September 2027 is a September Ember Wednesday -- the OBSERVED day is
+ the Ember office itself ([band] entry 18, RG 91, beats both sanctoral
+ candidates), and RG 111(c) ("other II class: one") admits exactly ONE
+ of the two competing losers, "maurice-and-companions-martyrs"
+ ([Commemoration_only], Class3) and "thomas-of-villanova" ([Feast],
+ Class3). Before the fix, [Precedence_ef.band] gave Maurice the SAME
+ table entry (24) as Thomas, so the residual slug tie-break picked
+ Maurice ('m' < 't') and OMITTED Thomas -- contrary to the calendarium's
+ own 22 September row ("S. Thomae de Villanova Ep. et Conf., III
+ classis. / Commemoratio Ss. Mauritii et Soc. Mm.": Thomas is the one
+ WITH a table entry) and to missalemeum (test/test_oracle.ml's own
+ former M17 allow-list entry -- oracle "commemorations": Thomas,
+ "displaced": Maurice -- deleted once this fix made the divergence
+ disappear). This is the SAME date the oracle harness itself checks,
+ pinned here too so a regression shows up at the OCaml level, not only
+ via the fixture comparison. *)
+let test_maurice_thomas_band_fidelity_end_to_end () =
+ let layer = real_layer () in
+ (* [Calendar.year]'s own liturgical-year convention (Advent-anchored, see
+ calendar.ml's [year_bounds]): the label [y] spans December of civil
+ year [y] through November of civil year [y+1], so 22 September 2027
+ is reached via [Cal.year ... 2026], not 2027 -- the same
+ [y-1]/straddling indexing test_oracle.ml's own header comment already
+ documents for exactly this reason. *)
+ let days = Cal.year Rite_ef.context layer 2026 in
+ let day = Array.to_list days |> List.find (fun d -> Date.compare d.LD.date (mk 2027 9 22) = 0) in
+ Alcotest.(check string) "the September Ember Wednesday office itself is observed (band entry 18)"
+ "ef-september-ember-wed" (slug_of day.LD.observed);
+ Alcotest.(check (list string))
+ "St Thomas of Villanova (band entry 24, a real table row) is the sole commemoration"
+ [ "thomas-of-villanova" ]
+ (List.map (fun (c, _) -> slug_of c) day.LD.commemorations);
+ Alcotest.(check (list string))
+ "St Maurice and Companions (Commemoration_only, unclassified -- no table row) is omitted, not \
+ commemorated"
+ [ "maurice-and-companions-martyrs" ]
+ (List.map (fun (c, _) -> slug_of c) day.LD.omitted)
+
let suite =
( "Rite_ef (real data: overlay-in-effect, domain-ceiling)",
[ Alcotest.test_case "the overlay suppression is observably in effect" `Quick
@@ -311,4 +350,7 @@ let suite =
Alcotest.test_case "transfer_target skips the whole Easter octave from inside Holy Week" `Quick
test_transfer_target_skips_the_whole_easter_octave;
Alcotest.test_case "the search genuinely enters the window (not vacuous)" `Quick
- test_search_genuinely_enters_the_window ] )
+ test_search_genuinely_enters_the_window;
+ Alcotest.test_case
+ "band fidelity end-to-end: Thomas of Villanova observed, Maurice commemorated (2027-09-22)" `Quick
+ test_maurice_thomas_band_fidelity_end_to_end ] )